diff --git a/.blueprint/code-workspace/command.mts b/.blueprint/code-workspace/command.mts index 9c897fdf14d1..8c54ddd5f528 100644 --- a/.blueprint/code-workspace/command.mts +++ b/.blueprint/code-workspace/command.mts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { type JHipsterCommandDefinition } from '../../generators/base/api.mjs'; +import { type JHipsterCommandDefinition } from '../../generators/base/api.js'; const command: JHipsterCommandDefinition = { arguments: {}, diff --git a/.blueprint/code-workspace/generator.mjs b/.blueprint/code-workspace/generator.mjs index cab998ae28c8..1254f96df559 100644 --- a/.blueprint/code-workspace/generator.mjs +++ b/.blueprint/code-workspace/generator.mjs @@ -1,7 +1,7 @@ import { join } from 'path'; import * as _ from 'lodash-es'; -import BaseGenerator from '../../generators/base/index.mjs'; -import { getPackageRoot } from '../../lib/index.mjs'; +import BaseGenerator from '../../generators/base/index.js'; +import { getPackageRoot } from '../../lib/index.js'; import command from './command.mjs'; import { defaultSamplesFolder, promptSamplesFolder, samplesFolderConfig } from '../support.mjs'; diff --git a/.blueprint/constants.js b/.blueprint/constants.js index b105a4fc950c..db1c0b7a35ac 100644 --- a/.blueprint/constants.js +++ b/.blueprint/constants.js @@ -1,5 +1,5 @@ import { join } from 'path'; -import { getPackageRoot } from '../lib/index.mjs'; +import { getPackageRoot } from '../lib/index.js'; const packageRoot = getPackageRoot(); export const defaultSamplesFolder = join(packageRoot, '../jhipster-samples'); diff --git a/.blueprint/from-issue/command.mts b/.blueprint/from-issue/command.mts index 8816557e54e3..1eed2feacdba 100644 --- a/.blueprint/from-issue/command.mts +++ b/.blueprint/from-issue/command.mts @@ -16,8 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JHipsterCommandDefinition } from '../../generators/base/api.mjs'; -import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../../generators/generator-list.mjs'; +import { JHipsterCommandDefinition } from '../../generators/base/api.js'; +import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../../generators/generator-list.js'; const command: JHipsterCommandDefinition = { arguments: { diff --git a/.blueprint/from-issue/generator.mjs b/.blueprint/from-issue/generator.mjs index b6153f352d24..05e7d86b140b 100644 --- a/.blueprint/from-issue/generator.mjs +++ b/.blueprint/from-issue/generator.mjs @@ -1,11 +1,11 @@ import { Octokit } from 'octokit'; import { setOutput } from '@actions/core'; -import BaseGenerator from '../../generators/base/index.mjs'; +import BaseGenerator from '../../generators/base/index.js'; import command from './command.mjs'; import { promptSamplesFolder } from '../support.mjs'; import { join } from 'path'; -import { GENERATOR_APP, GENERATOR_JDL } from '../../generators/generator-list.mjs'; -import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.mjs'; +import { GENERATOR_APP, GENERATOR_JDL } from '../../generators/generator-list.js'; +import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.js'; import { CLI_NAME } from '../../cli/utils.mjs'; import EnvironmentBuilder from '../../cli/environment-builder.mjs'; diff --git a/.blueprint/generate-sample/command.mts b/.blueprint/generate-sample/command.mts index 734ed74d5f15..b20e4e9b0711 100644 --- a/.blueprint/generate-sample/command.mts +++ b/.blueprint/generate-sample/command.mts @@ -16,8 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JHipsterCommandDefinition } from '../../generators/base/api.mjs'; -import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../../generators/generator-list.mjs'; +import { JHipsterCommandDefinition } from '../../generators/base/api.js'; +import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../../generators/generator-list.js'; const command: JHipsterCommandDefinition = { arguments: { diff --git a/.blueprint/generate-sample/generator.mjs b/.blueprint/generate-sample/generator.mjs index 128db9bd1354..0de1311b5dff 100644 --- a/.blueprint/generate-sample/generator.mjs +++ b/.blueprint/generate-sample/generator.mjs @@ -1,10 +1,10 @@ import { extname } from 'path'; import { transform } from '@yeoman/transform'; -import BaseGenerator from '../../generators/base/index.mjs'; +import BaseGenerator from '../../generators/base/index.js'; import command from './command.mjs'; import { generateSample } from './support/generate-sample.js'; import { promptSamplesFolder } from '../support.mjs'; -import { GENERATOR_APP, GENERATOR_JDL } from '../../generators/generator-list.mjs'; +import { GENERATOR_APP, GENERATOR_JDL } from '../../generators/generator-list.js'; export default class extends BaseGenerator { sampleName; diff --git a/.blueprint/support.mts b/.blueprint/support.mts index 0af73bded6cb..92abef5f125c 100644 --- a/.blueprint/support.mts +++ b/.blueprint/support.mts @@ -1,4 +1,4 @@ -import CoreGenerator from '../generators/base-core/index.mjs'; +import CoreGenerator from '../generators/base-core/index.js'; import { defaultSamplesFolder } from './constants.js'; export const samplesFolderConfig = 'samplesFolder'; diff --git a/.blueprint/update-vscode/command.mts b/.blueprint/update-vscode/command.mts index 4423e2b12d7e..993b9069623f 100644 --- a/.blueprint/update-vscode/command.mts +++ b/.blueprint/update-vscode/command.mts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { type JHipsterCommandDefinition } from '../../generators/base/api.mjs'; +import { type JHipsterCommandDefinition } from '../../generators/base/api.js'; const command: JHipsterCommandDefinition = { arguments: {}, diff --git a/.blueprint/update-vscode/generator.mjs b/.blueprint/update-vscode/generator.mjs index d505641da925..228ecb72d88c 100644 --- a/.blueprint/update-vscode/generator.mjs +++ b/.blueprint/update-vscode/generator.mjs @@ -1,6 +1,6 @@ import { join } from 'path'; -import BaseGenerator from '../../generators/base/index.mjs'; -import { getPackageRoot } from '../../lib/index.mjs'; +import BaseGenerator from '../../generators/base/index.js'; +import { getPackageRoot } from '../../lib/index.js'; import command from './command.mjs'; import { getWorkflowSamples } from '../generate-sample/support/get-workflow-samples.js'; diff --git a/.eslintrc.json b/.eslintrc.json index dbf42fdbf41a..feb90da03d7f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -34,12 +34,7 @@ "rules": { "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-this-alias": "off" - } - }, - { - "files": ["**/*.mjs"], - "rules": { + "@typescript-eslint/no-this-alias": "off", "import/no-unresolved": "off" } }, diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5054fa4cac83..5e0f13721757 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -29,8 +29,8 @@ Internally, JHipster uses [Yeoman](https://yeoman.io) as the core. JHipster is t ## Lifecycle - [CLI entry point](https://github.com/jhipster/generator-jhipster/blob/main/cli/jhipster.cjs) -- [Basic environment validation](https://github.com/jhipster/generator-jhipster/blob/main/cli/cli.mjs) -- [Cli arguments parsing and Environment bootstrap](https://github.com/jhipster/generator-jhipster/blob/main/cli/program.mjs) +- [Basic environment validation](https://github.com/jhipster/generator-jhipster/blob/main/cli/cli.js) +- [Cli arguments parsing and Environment bootstrap](https://github.com/jhipster/generator-jhipster/blob/main/cli/program.js) - Lookup for generators and blueprints - Build CLI options and arguments definition - Parse options and arguments diff --git a/cli/cli.mjs b/cli/cli.mjs index d49a1c1bd4d2..3d6de0ccd757 100644 --- a/cli/cli.mjs +++ b/cli/cli.mjs @@ -23,7 +23,7 @@ import { existsSync } from 'fs'; import semver from 'semver'; import chalk from 'chalk'; -import { packageJson } from '../lib/index.mjs'; +import { packageJson } from '../lib/index.js'; import { runJHipster } from './program.mjs'; import { done, logger } from './utils.mjs'; diff --git a/cli/cli.spec.mts b/cli/cli.spec.mts index eb5c0924e659..8f191002e469 100644 --- a/cli/cli.spec.mts +++ b/cli/cli.spec.mts @@ -7,7 +7,7 @@ import { expect, mock, resetAllMocks, fn } from 'esmocha'; import { execaCommandSync } from 'execa'; import { BaseEnvironment } from '@yeoman/types'; import { coerce } from 'semver'; -import { defaultHelpers as helpers, createBlueprintFiles } from '../test/support/index.mjs'; +import { defaultHelpers as helpers, createBlueprintFiles } from '../test/support/index.js'; import { getCommand as actualGetCommonand } from './utils.mjs'; import { createProgram } from './program.mjs'; @@ -157,7 +157,7 @@ describe('cli', () => { beforeEach(async () => { getCommand.mockImplementation(actualGetCommonand); - const BaseGenerator = (await import('../generators/base/index.mjs')).default; + const BaseGenerator = (await import('../generators/base/index.js')).default; env = await helpers.createTestEnv(); generator = new (helpers.createDummyGenerator(BaseGenerator))({ env, sharedData: {} }); generator._options = { diff --git a/cli/download.mts b/cli/download.mts index 0624417fdf0e..12a94cf6b964 100644 --- a/cli/download.mts +++ b/cli/download.mts @@ -22,7 +22,7 @@ import path from 'path'; import { inspect } from 'util'; import { logger } from './utils.mjs'; -import { packageJson } from '../lib/index.mjs'; +import { packageJson } from '../lib/index.js'; const downloadFile = (url: string, filename: string): Promise => { return new Promise((resolve, reject) => { diff --git a/cli/environment-builder.mjs b/cli/environment-builder.mjs index 3b7e8ae11dc3..a1e6cd0e345a 100644 --- a/cli/environment-builder.mjs +++ b/cli/environment-builder.mjs @@ -26,8 +26,8 @@ import Environment from 'yeoman-environment'; import { QueuedAdapter } from '@yeoman/adapter'; import { CLI_NAME, logger } from './utils.mjs'; -import { createJHipsterLogger, packageNameToNamespace } from '../generators/base/support/index.mjs'; -import { parseBlueprintInfo, loadBlueprintsFromConfiguration, mergeBlueprints } from '../generators/base/internal/index.mjs'; +import { createJHipsterLogger, packageNameToNamespace } from '../generators/base/support/index.js'; +import { parseBlueprintInfo, loadBlueprintsFromConfiguration, mergeBlueprints } from '../generators/base/internal/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/cli/environment-builder.spec.mts b/cli/environment-builder.spec.mts index 86b4069b08af..7dcead7ed09c 100644 --- a/cli/environment-builder.spec.mts +++ b/cli/environment-builder.spec.mts @@ -22,7 +22,7 @@ import fs from 'fs'; import { expect } from 'chai'; import sinon from 'sinon'; import { jestExpect } from 'esmocha'; -import { defaultHelpers as helpers, createBlueprintFiles } from '../test/support/index.mjs'; +import { defaultHelpers as helpers, createBlueprintFiles } from '../test/support/index.js'; import EnvironmentBuilder from './environment-builder.mjs'; diff --git a/cli/jhipster-command.mjs b/cli/jhipster-command.mjs index d05134082177..63db337eeb10 100644 --- a/cli/jhipster-command.mjs +++ b/cli/jhipster-command.mjs @@ -20,7 +20,7 @@ import chalk from 'chalk'; import { Command, Option } from 'commander'; import lodash from 'lodash'; -import { convertConfigToOption } from '../lib/internal/index.mjs'; +import { convertConfigToOption } from '../lib/internal/index.js'; const { kebabCase } = lodash; diff --git a/cli/logo.mjs b/cli/logo.mjs index 6c97379ca2fd..9144d16b5670 100644 --- a/cli/logo.mjs +++ b/cli/logo.mjs @@ -17,7 +17,7 @@ * limitations under the License. */ import chalk from 'chalk'; -import { packageJson } from '../lib/index.mjs'; +import { packageJson } from '../lib/index.js'; export default `${chalk.green(' ██╗')}${chalk.red(' ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗')} ${chalk.green(' ██║')}${chalk.red(' ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗')} diff --git a/cli/program.mjs b/cli/program.mjs index 3a8f1cd93d8a..986241b0bfe7 100644 --- a/cli/program.mjs +++ b/cli/program.mjs @@ -30,9 +30,9 @@ import EnvironmentBuilder from './environment-builder.mjs'; import SUB_GENERATORS from './commands.mjs'; import JHipsterCommand from './jhipster-command.mjs'; import { CLI_NAME, logger, getCommand, done } from './utils.mjs'; -import { packageJson } from '../lib/index.mjs'; -import { packageNameToNamespace } from '../generators/base/support/index.mjs'; -import command from '../generators/base/command.mjs'; +import { packageJson } from '../lib/index.js'; +import { packageNameToNamespace } from '../generators/base/support/index.js'; +import command from '../generators/base/command.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/cli/program.spec.mts b/cli/program.spec.mts index c8028f27c123..074524e87037 100644 --- a/cli/program.spec.mts +++ b/cli/program.spec.mts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { createProgram } from './program.mjs'; -import { defaultHelpers as helpers } from '../test/support/index.mjs'; +import { defaultHelpers as helpers } from '../test/support/index.js'; describe('cli - program', () => { beforeEach(async () => { diff --git a/cli/utils.mjs b/cli/utils.mjs index d8fc2b67e181..9368f5cc0cc4 100644 --- a/cli/utils.mjs +++ b/cli/utils.mjs @@ -19,7 +19,7 @@ /* eslint-disable no-console */ import chalk from 'chalk'; -import { createJHipsterLogger, CLI_LOGGER } from '../generators/base/support/index.mjs'; +import { createJHipsterLogger, CLI_LOGGER } from '../generators/base/support/index.js'; export const CLI_NAME = 'jhipster'; export const GENERATOR_NAME = 'generator-jhipster'; diff --git a/generators/angular/__snapshots__/generator.spec.mts.snap b/generators/angular/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/angular/__snapshots__/generator.spec.mts.snap rename to generators/angular/__snapshots__/generator.spec.ts.snap diff --git a/generators/angular/cleanup.mts b/generators/angular/cleanup.mts deleted file mode 100644 index e59c1c5898da..000000000000 --- a/generators/angular/cleanup.mts +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Copyright 2013-2023 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 CoreGenerator from '../base-core/index.mjs'; -import { CLIENT_WEBPACK_DIR } from '../generator-constants.mjs'; -import { GeneratorDefinition } from '../base-application/generator.mjs'; - -/** - * Removes files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -// eslint-disable-next-line import/prefer-default-export -export default function cleanupOldFilesTask(this: CoreGenerator, { application }: GeneratorDefinition['writingTaskParam']) { - if (this.isJhipsterVersionLessThan('3.2.0')) { - // removeFile and removeFolder methods should be called here for files and folders to cleanup - this.removeFile(`${application.clientSrcDir}app/components/form/uib-pager.config.js`); - this.removeFile(`${application.clientSrcDir}app/components/form/uib-pagination.config.js`); - } - if (this.isJhipsterVersionLessThan('3.11.0')) { - this.removeFile(`${application.clientSrcDir}app/layouts/navbar/active-link.directive.js`); - } - if (this.isJhipsterVersionLessThan('4.11.1')) { - this.removeFile(`${application.clientSrcDir}app/app.main-aot.ts`); - } - if (this.isJhipsterVersionLessThan('5.0.0')) { - this.removeFile(`${application.clientSrcDir}app//app.route.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/account.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/auth-jwt.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/auth-session.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/csrf.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/state-storage.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/auth/user-route-access-service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/language/language.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/language/language.helper.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/login/login-modal.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/login/login.service.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/model/base-entity.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/model/request-util.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/user/account.model.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/user/user.model.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/user/user.service.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-dialog.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-modal.service.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-modal.service.ts`); - - this.removeFile(`${application.clientTestDir}spec/app/shared/user/user.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-dialog.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/entry.ts`); - this.removeFile(`${application.clientTestDir}karma.conf.js`); - } - if (this.isJhipsterVersionLessThan('5.8.0')) { - this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics-modal.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics-modal.component.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics-modal.component.spec.ts`); - } - if (this.isJhipsterVersionLessThan('6.0.0')) { - this.removeFolder(`${application.clientSrcDir}app/shared/layout/header/menus`); - this.removeFolder(`${application.clientTestDir}spec/app/shared/layout/header/menus`); - } - if (this.isJhipsterVersionLessThan('6.3.0')) { - this.removeFile(`${application.clientSrcDir}app/account/index.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/index.ts`); - this.removeFile(`${application.clientSrcDir}app/core/index.ts`); - this.removeFile(`${application.clientSrcDir}app/home/index.ts`); - this.removeFile(`${application.clientSrcDir}app/layouts/index.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/index.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/shared-common.module.ts`); - } - if (this.isJhipsterVersionLessThan('6.4.0')) { - this.removeFile(`${application.clientSrcDir}app/admin/admin.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/admin.module.ts`); - } - if (this.isJhipsterVersionLessThan('6.6.1')) { - this.removeFile(`${application.clientSrcDir}app/core/language/language.helper.ts`); - } - if (this.isJhipsterVersionLessThan('6.8.0')) { - this.removeFile(`${application.clientSrcDir}app/tsconfig-aot.json`); - } - if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { - this.removeFile(`${application.clientSrcDir}app/account/password/password-strength-bar.component.ts`); - this.removeFile(`${application.clientSrcDir}app/account/password/password-strength-bar.scss`); - this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.scss`); - this.removeFile(`${application.clientSrcDir}app/home/home.scss`); - this.removeFile(`${application.clientSrcDir}app/layouts/navbar/navbar.scss`); - this.removeFile(`${application.clientSrcDir}app/layouts/profiles/page-ribbon.scss`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audit-application.model.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audit.model.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.service.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/health/health-modal.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/health/health-modal.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-delete-dialog.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-delete-dialog.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-detail.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-detail.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.component.html`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-update.component.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-update.component.html`); - this.removeFile(`${application.clientSrcDir}app/entities/entity.module.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/util/datepicker-adapter.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/login/login.component.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/login/login.component.html`); - this.removeFile(`${application.clientSrcDir}app/core/auth/user-route-access-service.ts`); - if (!application.authenticationTypeSession || !(application as any).communicationSpringWebsocket) { - this.removeFile(`${application.clientSrcDir}app/core/auth/csrf.service.ts`); - } - this.removeFolder(`${application.clientSrcDir}app/core/login`); - this.removeFolder(`${application.clientSrcDir}app/blocks`); - this.removeFile(`${application.clientSrcDir}app/core/date/datepicker-adapter.ts`); - this.removeFile(`${application.clientSrcDir}app/core/icons/font-awesome-icons.ts`); - this.removeFile(`${application.clientSrcDir}app/core/language/language.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/constants/authority.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/constants/error.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/constants/input.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/constants/pagination.constants.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/util/request-util.ts`); - this.removeFile(`${application.clientSrcDir}app/core/core.module.ts`); - this.removeFile(`${application.clientSrcDir}app/vendor.ts`); - this.removeFile(`${application.clientSrcDir}app/app.main.ts`); - this.removeFile(`${application.clientSrcDir}app/polyfills.ts`); - this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.common.js`); - this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.dev.js`); - this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.prod.js`); - this.removeFile(`${CLIENT_WEBPACK_DIR}utils.js`); - this.removeFile('tsconfig.base.json'); - this.removeFile('postcss.config.js'); - this.removeFile('proxy.conf.json'); - this.removeFile('tslint.json'); - - // unreleased files and folders cleanup for v7 developers - this.removeFile(`${application.clientSrcDir}app/shared/duration.pipe.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/find-language-from-key.pipe.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/translate.directive.ts`); - this.removeFile(`${application.clientSrcDir}app/core/user/authority.model.ts`); - this.removeFolder(`${application.clientSrcDir}app/core/config`); - this.removeFolder(`${application.clientSrcDir}app/core/event-manager`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/jvm-memory`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/jvm-threads`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-cache`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-datasource`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-endpoints-requests`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-garbagecollector`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-modal-threads`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-request`); - this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-system`); - this.removeFile(`${application.clientSrcDir}app/shared/has-any-authority.directive.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/item-count.component.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/item-count.component.spec.ts`); - - // test files removal from old location - // a) deleted before moving tests next to files they are testing - this.removeFile(`${application.clientTestDir}spec/app/account/password/password-strength-bar.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-delete-dialog.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-detail.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-update.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/login/login-modal.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/login/login-modal.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/user/account.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/audits/audits.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/audits/audits.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/login/login.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/test.module.ts`); - this.removeFile(`${application.clientTestDir}jest.ts`); - this.removeFile(`${application.clientTestDir}jest-global-mocks.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-account.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-active-modal.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-alert.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-event-manager.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-language.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-login-modal.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-login.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-route.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-state-storage.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/mock-tracker.service.ts`); - this.removeFile(`${application.clientTestDir}spec/helpers/spyobject.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/translate.directive.spec.ts`); - this.removeFolder(`${application.clientTestDir}spec/app/core/event-manager`); - // b) deleted while moving tests next to files they are testing - this.removeFile(`${application.clientTestDir}spec/app/account/activate/activate.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/password-reset/init/password-reset-init.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/password/password-strength-bar/password-strength-bar.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/password/password.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/register/register.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/sessions/sessions.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/account/settings/settings.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/configuration/configuration.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/configuration/configuration.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/health/health.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/logs/logs.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/logs/logs.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/delete/user-management-delete-dialog.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/detail/user-management-detail.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/list/user-management.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/update/user-management-update.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/auth/account.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/user/user.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/util/alert.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/util/data-util.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/util/event-manager.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/core/util/parse-links.service.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/home/home.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/layouts/main/main.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/layouts/navbar/navbar.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/login/login.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/alert/alert-error.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/alert/alert.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/date/format-medium-date.pipe.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/date/format-medium-datetime.pipe.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/item-count.component.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/language/translate.directive.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/sort/sort-by.directive.spec.ts`); - this.removeFile(`${application.clientTestDir}spec/app/shared/sort/sort.directive.spec.ts`); - this.removeFile(`${application.clientTestDir}jest.conf.js`); - } - if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { - this.removeFile(`${application.clientSrcDir}app/core/user/account.model.ts`); - this.removeFile(`${application.clientSrcDir}app/core/user/user.model.ts`); - this.removeFile(`${application.clientSrcDir}app/core/user/user.service.ts`); - this.removeFile(`${application.clientSrcDir}app/core/user/user.service.spec.ts`); - } - if (this.isJhipsterVersionLessThan('7.0.0-beta.2')) { - this.removeFile(`${application.clientSrcDir}app/core/config/config.service.ts`); - this.removeFile(`${application.clientSrcDir}app/core/config/config.service.spec.ts`); - this.removeFile('.npmrc'); - } - if (this.isJhipsterVersionLessThan('7.1.1')) { - this.removeFile('.npmrc'); - } - - if (this.isJhipsterVersionLessThan('7.6.1')) { - this.removeFile(`${application.clientSrcDir}content/scss/rtl.scss`); - } - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile('.browserslistrc'); - this.removeFile(`${application.clientSrcDir}polyfills.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/logs/logs.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/health/health.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/gateway/gateway.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/configuration/configuration.module.ts`); - this.removeFile(`${application.clientSrcDir}app/home/home.module.ts`); - this.removeFile(`${application.clientSrcDir}app/home/home.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/configuration/configuration.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/gateway/gateway.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/health/health.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/logs/logs.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics.route.ts`); - this.removeFile(`${application.clientSrcDir}app/layouts/navbar/navbar.route.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/shared-libs.module.ts`); - this.removeFile(`${application.clientSrcDir}app/shared/shared-libs.module.ts`); - this.removeFile(`${application.clientSrcDir}app/login/login.module.ts`); - this.removeFile(`${application.clientSrcDir}app/login/login.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/tracker/tracker.route.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/tracker/tracker.module.ts`); - this.removeFile(`${application.clientSrcDir}app/account/account.module.ts`); - } - if (this.isJhipsterVersionLessThan('8.0.1')) { - this.removeFile(`${application.clientSrcDir}app/layouts/main/main.module.ts`); - this.removeFile(`${application.clientSrcDir}app/admin/admin-routing.module.ts`); - this.removeFile(`${application.clientSrcDir}app/app.module.ts`); - this.removeFile(`${application.clientSrcDir}app/app-routing.module.ts`); - this.removeFile(`${application.clientSrcDir}app/entities/entity-routing.module.ts`); - } -} diff --git a/generators/angular/cleanup.ts b/generators/angular/cleanup.ts new file mode 100644 index 000000000000..a36cb57ef49b --- /dev/null +++ b/generators/angular/cleanup.ts @@ -0,0 +1,291 @@ +/** + * Copyright 2013-2023 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 CoreGenerator from '../base-core/index.js'; +import { CLIENT_WEBPACK_DIR } from '../generator-constants.js'; +import { GeneratorDefinition } from '../base-application/generator.js'; + +/** + * Removes files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +// eslint-disable-next-line import/prefer-default-export +export default function cleanupOldFilesTask(this: CoreGenerator, { application }: GeneratorDefinition['writingTaskParam']) { + if (this.isJhipsterVersionLessThan('3.2.0')) { + // removeFile and removeFolder methods should be called here for files and folders to cleanup + this.removeFile(`${application.clientSrcDir}app/components/form/uib-pager.config.js`); + this.removeFile(`${application.clientSrcDir}app/components/form/uib-pagination.config.js`); + } + if (this.isJhipsterVersionLessThan('3.11.0')) { + this.removeFile(`${application.clientSrcDir}app/layouts/navbar/active-link.directive.js`); + } + if (this.isJhipsterVersionLessThan('4.11.1')) { + this.removeFile(`${application.clientSrcDir}app/app.main-aot.ts`); + } + if (this.isJhipsterVersionLessThan('5.0.0')) { + this.removeFile(`${application.clientSrcDir}app//app.route.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/account.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/auth-jwt.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/auth-session.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/csrf.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/state-storage.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/auth/user-route-access-service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/language/language.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/language/language.helper.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/login/login-modal.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/login/login.service.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/model/base-entity.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/model/request-util.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/user/account.model.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/user/user.model.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/user/user.service.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-dialog.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-modal.service.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-modal.service.ts`); + + this.removeFile(`${application.clientTestDir}spec/app/shared/user/user.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-dialog.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/entry.ts`); + this.removeFile(`${application.clientTestDir}karma.conf.js`); + } + if (this.isJhipsterVersionLessThan('5.8.0')) { + this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics-modal.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics-modal.component.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics-modal.component.spec.ts`); + } + if (this.isJhipsterVersionLessThan('6.0.0')) { + this.removeFolder(`${application.clientSrcDir}app/shared/layout/header/menus`); + this.removeFolder(`${application.clientTestDir}spec/app/shared/layout/header/menus`); + } + if (this.isJhipsterVersionLessThan('6.3.0')) { + this.removeFile(`${application.clientSrcDir}app/account/index.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/index.ts`); + this.removeFile(`${application.clientSrcDir}app/core/index.ts`); + this.removeFile(`${application.clientSrcDir}app/home/index.ts`); + this.removeFile(`${application.clientSrcDir}app/layouts/index.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/index.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/shared-common.module.ts`); + } + if (this.isJhipsterVersionLessThan('6.4.0')) { + this.removeFile(`${application.clientSrcDir}app/admin/admin.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/admin.module.ts`); + } + if (this.isJhipsterVersionLessThan('6.6.1')) { + this.removeFile(`${application.clientSrcDir}app/core/language/language.helper.ts`); + } + if (this.isJhipsterVersionLessThan('6.8.0')) { + this.removeFile(`${application.clientSrcDir}app/tsconfig-aot.json`); + } + if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { + this.removeFile(`${application.clientSrcDir}app/account/password/password-strength-bar.component.ts`); + this.removeFile(`${application.clientSrcDir}app/account/password/password-strength-bar.scss`); + this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.scss`); + this.removeFile(`${application.clientSrcDir}app/home/home.scss`); + this.removeFile(`${application.clientSrcDir}app/layouts/navbar/navbar.scss`); + this.removeFile(`${application.clientSrcDir}app/layouts/profiles/page-ribbon.scss`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audit-application.model.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audit.model.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/audits/audits.service.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/health/health-modal.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/health/health-modal.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-delete-dialog.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-delete-dialog.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-detail.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-detail.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.component.html`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-update.component.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management-update.component.html`); + this.removeFile(`${application.clientSrcDir}app/entities/entity.module.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/util/datepicker-adapter.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/login/login.component.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/login/login.component.html`); + this.removeFile(`${application.clientSrcDir}app/core/auth/user-route-access-service.ts`); + if (!application.authenticationTypeSession || !(application as any).communicationSpringWebsocket) { + this.removeFile(`${application.clientSrcDir}app/core/auth/csrf.service.ts`); + } + this.removeFolder(`${application.clientSrcDir}app/core/login`); + this.removeFolder(`${application.clientSrcDir}app/blocks`); + this.removeFile(`${application.clientSrcDir}app/core/date/datepicker-adapter.ts`); + this.removeFile(`${application.clientSrcDir}app/core/icons/font-awesome-icons.ts`); + this.removeFile(`${application.clientSrcDir}app/core/language/language.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/constants/authority.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/constants/error.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/constants/input.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/constants/pagination.constants.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/util/request-util.ts`); + this.removeFile(`${application.clientSrcDir}app/core/core.module.ts`); + this.removeFile(`${application.clientSrcDir}app/vendor.ts`); + this.removeFile(`${application.clientSrcDir}app/app.main.ts`); + this.removeFile(`${application.clientSrcDir}app/polyfills.ts`); + this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.common.js`); + this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.dev.js`); + this.removeFile(`${CLIENT_WEBPACK_DIR}webpack.prod.js`); + this.removeFile(`${CLIENT_WEBPACK_DIR}utils.js`); + this.removeFile('tsconfig.base.json'); + this.removeFile('postcss.config.js'); + this.removeFile('proxy.conf.json'); + this.removeFile('tslint.json'); + + // unreleased files and folders cleanup for v7 developers + this.removeFile(`${application.clientSrcDir}app/shared/duration.pipe.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/find-language-from-key.pipe.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/translate.directive.ts`); + this.removeFile(`${application.clientSrcDir}app/core/user/authority.model.ts`); + this.removeFolder(`${application.clientSrcDir}app/core/config`); + this.removeFolder(`${application.clientSrcDir}app/core/event-manager`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/jvm-memory`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/jvm-threads`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-cache`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-datasource`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-endpoints-requests`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-garbagecollector`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-modal-threads`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-request`); + this.removeFolder(`${application.clientSrcDir}app/admin/metrics/metrics-system`); + this.removeFile(`${application.clientSrcDir}app/shared/has-any-authority.directive.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/item-count.component.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/item-count.component.spec.ts`); + + // test files removal from old location + // a) deleted before moving tests next to files they are testing + this.removeFile(`${application.clientTestDir}spec/app/account/password/password-strength-bar.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-delete-dialog.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-detail.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/user-management-update.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/login/login-modal.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/login/login-modal.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/user/account.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/audits/audits.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/audits/audits.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/login/login.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/test.module.ts`); + this.removeFile(`${application.clientTestDir}jest.ts`); + this.removeFile(`${application.clientTestDir}jest-global-mocks.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-account.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-active-modal.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-alert.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-event-manager.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-language.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-login-modal.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-login.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-route.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-state-storage.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/mock-tracker.service.ts`); + this.removeFile(`${application.clientTestDir}spec/helpers/spyobject.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/translate.directive.spec.ts`); + this.removeFolder(`${application.clientTestDir}spec/app/core/event-manager`); + // b) deleted while moving tests next to files they are testing + this.removeFile(`${application.clientTestDir}spec/app/account/activate/activate.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/password-reset/init/password-reset-init.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/password/password-strength-bar/password-strength-bar.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/password/password.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/register/register.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/sessions/sessions.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/account/settings/settings.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/configuration/configuration.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/configuration/configuration.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/health/health.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/logs/logs.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/logs/logs.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/metrics/metrics.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/delete/user-management-delete-dialog.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/detail/user-management-detail.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/list/user-management.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/admin/user-management/update/user-management-update.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/auth/account.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/user/user.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/util/alert.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/util/data-util.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/util/event-manager.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/core/util/parse-links.service.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/home/home.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/layouts/main/main.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/layouts/navbar/navbar.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/login/login.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/alert/alert-error.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/alert/alert.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/date/format-medium-date.pipe.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/date/format-medium-datetime.pipe.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/item-count.component.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/language/translate.directive.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/sort/sort-by.directive.spec.ts`); + this.removeFile(`${application.clientTestDir}spec/app/shared/sort/sort.directive.spec.ts`); + this.removeFile(`${application.clientTestDir}jest.conf.js`); + } + if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { + this.removeFile(`${application.clientSrcDir}app/core/user/account.model.ts`); + this.removeFile(`${application.clientSrcDir}app/core/user/user.model.ts`); + this.removeFile(`${application.clientSrcDir}app/core/user/user.service.ts`); + this.removeFile(`${application.clientSrcDir}app/core/user/user.service.spec.ts`); + } + if (this.isJhipsterVersionLessThan('7.0.0-beta.2')) { + this.removeFile(`${application.clientSrcDir}app/core/config/config.service.ts`); + this.removeFile(`${application.clientSrcDir}app/core/config/config.service.spec.ts`); + this.removeFile('.npmrc'); + } + if (this.isJhipsterVersionLessThan('7.1.1')) { + this.removeFile('.npmrc'); + } + + if (this.isJhipsterVersionLessThan('7.6.1')) { + this.removeFile(`${application.clientSrcDir}content/scss/rtl.scss`); + } + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile('.browserslistrc'); + this.removeFile(`${application.clientSrcDir}polyfills.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/user-management/user-management.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/logs/logs.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/health/health.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/gateway/gateway.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/configuration/configuration.module.ts`); + this.removeFile(`${application.clientSrcDir}app/home/home.module.ts`); + this.removeFile(`${application.clientSrcDir}app/home/home.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/configuration/configuration.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/docs/docs.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/gateway/gateway.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/health/health.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/logs/logs.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/metrics/metrics.route.ts`); + this.removeFile(`${application.clientSrcDir}app/layouts/navbar/navbar.route.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/shared-libs.module.ts`); + this.removeFile(`${application.clientSrcDir}app/shared/shared-libs.module.ts`); + this.removeFile(`${application.clientSrcDir}app/login/login.module.ts`); + this.removeFile(`${application.clientSrcDir}app/login/login.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/tracker/tracker.route.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/tracker/tracker.module.ts`); + this.removeFile(`${application.clientSrcDir}app/account/account.module.ts`); + } + if (this.isJhipsterVersionLessThan('8.0.1')) { + this.removeFile(`${application.clientSrcDir}app/layouts/main/main.module.ts`); + this.removeFile(`${application.clientSrcDir}app/admin/admin-routing.module.ts`); + this.removeFile(`${application.clientSrcDir}app/app.module.ts`); + this.removeFile(`${application.clientSrcDir}app/app-routing.module.ts`); + this.removeFile(`${application.clientSrcDir}app/entities/entity-routing.module.ts`); + } +} diff --git a/generators/angular/entity-files-angular.mts b/generators/angular/entity-files-angular.mts deleted file mode 100644 index 15bcbce498b3..000000000000 --- a/generators/angular/entity-files-angular.mts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2013-2023 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 { GeneratorDefinition } from '../base-application/generator.mjs'; -import { clientApplicationTemplatesBlock } from '../client/support/files.mjs'; -import CoreGenerator from '../base-core/index.mjs'; - -export const angularFiles = { - client: [ - { - ...clientApplicationTemplatesBlock(), - templates: ['entities/_entityFolder_/_entityFile_.model.ts', 'entities/_entityFolder_/_entityFile_.test-samples.ts'], - }, - { - condition: generator => !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/_entityFolder_/_entityFile_.routes.ts', - 'entities/_entityFolder_/detail/_entityFile_-detail.component.html', - 'entities/_entityFolder_/detail/_entityFile_-detail.component.ts', - 'entities/_entityFolder_/detail/_entityFile_-detail.component.spec.ts', - 'entities/_entityFolder_/list/_entityFile_.component.html', - 'entities/_entityFolder_/list/_entityFile_.component.ts', - 'entities/_entityFolder_/list/_entityFile_.component.spec.ts', - 'entities/_entityFolder_/route/_entityFile_-routing-resolve.service.ts', - 'entities/_entityFolder_/route/_entityFile_-routing-resolve.service.spec.ts', - 'entities/_entityFolder_/service/_entityFile_.service.ts', - 'entities/_entityFolder_/service/_entityFile_.service.spec.ts', - ], - }, - { - condition: generator => !generator.readOnly && !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/_entityFolder_/update/_entityFile_-form.service.ts', - 'entities/_entityFolder_/update/_entityFile_-form.service.spec.ts', - 'entities/_entityFolder_/update/_entityFile_-update.component.html', - 'entities/_entityFolder_/update/_entityFile_-update.component.spec.ts', - 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.html', - 'entities/_entityFolder_/update/_entityFile_-update.component.ts', - 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.ts', - 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.spec.ts', - ], - }, - ], -}; - -export async function writeEntitiesFiles(this: CoreGenerator, { application, entities }: GeneratorDefinition['writingEntitiesTaskParam']) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - await this.writeFiles({ - sections: angularFiles, - context: { ...application, ...entity }, - }); - } -} - -export async function postWriteEntitiesFiles(this: CoreGenerator, taskParam: GeneratorDefinition['postWritingEntitiesTaskParam']) { - const { source, application } = taskParam; - const entities = taskParam.entities.filter(entity => !entity.skipClient && !entity.builtIn && !entity.embedded); - source.addEntitiesToClient({ application, entities }); -} - -export function cleanupEntitiesFiles(this: CoreGenerator, { application, entities }: GeneratorDefinition['writingEntitiesTaskParam']) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - const { entityFolderName, entityFileName, name: entityName } = entity; - if (this.isJhipsterVersionLessThan('5.0.0')) { - this.removeFile(`${application.clientSrcDir}app/entities/${entityName}/${entityName}.model.ts`); - } - - if (this.isJhipsterVersionLessThan('6.3.0')) { - this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/index.ts`); - } - - if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.route.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.component.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.component.html`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-detail.component.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-detail.component.html`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.html`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-update.component.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-update.component.html`); - this.removeFile(`${application.clientSrcDir}/app/shared/model/${entity.entityModelFileName}.model.ts`); - entity.fields.forEach(field => { - if (field.fieldIsEnum === true) { - const { enumFileName } = field; - this.removeFile(`${application.clientSrcDir}/app/shared/model/enumerations/${enumFileName}.model.ts`); - } - }); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-routing-resolve.service.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-routing.module.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.service.ts`); - this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.component.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-detail.component.spec.ts`); - this.removeFile( - `${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.spec.ts`, - ); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-update.component.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); - } - - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/${entityFileName}.module.ts`); - this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/route/${entityFileName}-routing.module.ts`); - } - } -} diff --git a/generators/angular/entity-files-angular.ts b/generators/angular/entity-files-angular.ts new file mode 100644 index 000000000000..de2a4617c106 --- /dev/null +++ b/generators/angular/entity-files-angular.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2013-2023 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 { GeneratorDefinition } from '../base-application/generator.js'; +import { clientApplicationTemplatesBlock } from '../client/support/files.js'; +import CoreGenerator from '../base-core/index.js'; + +export const angularFiles = { + client: [ + { + ...clientApplicationTemplatesBlock(), + templates: ['entities/_entityFolder_/_entityFile_.model.ts', 'entities/_entityFolder_/_entityFile_.test-samples.ts'], + }, + { + condition: generator => !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/_entityFolder_/_entityFile_.routes.ts', + 'entities/_entityFolder_/detail/_entityFile_-detail.component.html', + 'entities/_entityFolder_/detail/_entityFile_-detail.component.ts', + 'entities/_entityFolder_/detail/_entityFile_-detail.component.spec.ts', + 'entities/_entityFolder_/list/_entityFile_.component.html', + 'entities/_entityFolder_/list/_entityFile_.component.ts', + 'entities/_entityFolder_/list/_entityFile_.component.spec.ts', + 'entities/_entityFolder_/route/_entityFile_-routing-resolve.service.ts', + 'entities/_entityFolder_/route/_entityFile_-routing-resolve.service.spec.ts', + 'entities/_entityFolder_/service/_entityFile_.service.ts', + 'entities/_entityFolder_/service/_entityFile_.service.spec.ts', + ], + }, + { + condition: generator => !generator.readOnly && !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/_entityFolder_/update/_entityFile_-form.service.ts', + 'entities/_entityFolder_/update/_entityFile_-form.service.spec.ts', + 'entities/_entityFolder_/update/_entityFile_-update.component.html', + 'entities/_entityFolder_/update/_entityFile_-update.component.spec.ts', + 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.html', + 'entities/_entityFolder_/update/_entityFile_-update.component.ts', + 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.ts', + 'entities/_entityFolder_/delete/_entityFile_-delete-dialog.component.spec.ts', + ], + }, + ], +}; + +export async function writeEntitiesFiles(this: CoreGenerator, { application, entities }: GeneratorDefinition['writingEntitiesTaskParam']) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + await this.writeFiles({ + sections: angularFiles, + context: { ...application, ...entity }, + }); + } +} + +export async function postWriteEntitiesFiles(this: CoreGenerator, taskParam: GeneratorDefinition['postWritingEntitiesTaskParam']) { + const { source, application } = taskParam; + const entities = taskParam.entities.filter(entity => !entity.skipClient && !entity.builtIn && !entity.embedded); + source.addEntitiesToClient({ application, entities }); +} + +export function cleanupEntitiesFiles(this: CoreGenerator, { application, entities }: GeneratorDefinition['writingEntitiesTaskParam']) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + const { entityFolderName, entityFileName, name: entityName } = entity; + if (this.isJhipsterVersionLessThan('5.0.0')) { + this.removeFile(`${application.clientSrcDir}app/entities/${entityName}/${entityName}.model.ts`); + } + + if (this.isJhipsterVersionLessThan('6.3.0')) { + this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/index.ts`); + } + + if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.route.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.component.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.component.html`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-detail.component.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-detail.component.html`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.html`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-update.component.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-update.component.html`); + this.removeFile(`${application.clientSrcDir}/app/shared/model/${entity.entityModelFileName}.model.ts`); + entity.fields.forEach(field => { + if (field.fieldIsEnum === true) { + const { enumFileName } = field; + this.removeFile(`${application.clientSrcDir}/app/shared/model/enumerations/${enumFileName}.model.ts`); + } + }); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-routing-resolve.service.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}-routing.module.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.service.ts`); + this.removeFile(`${application.clientSrcDir}/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.component.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-detail.component.spec.ts`); + this.removeFile( + `${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-delete-dialog.component.spec.ts`, + ); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-update.component.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); + } + + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/${entityFileName}.module.ts`); + this.removeFile(`${application.clientSrcDir}app/entities/${entityFolderName}/route/${entityFileName}-routing.module.ts`); + } + } +} diff --git a/generators/angular/files-angular.js b/generators/angular/files-angular.js new file mode 100644 index 000000000000..3cfb69cebea8 --- /dev/null +++ b/generators/angular/files-angular.js @@ -0,0 +1,478 @@ +/** + * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.js'; + +export const files = { + jhipsterProject: [ + { + templates: ['README.md.jhi.client.angular'], + }, + ], + common: [ + clientRootTemplatesBlock({ + templates: [ + '.eslintrc.json', + 'angular.json', + 'ngsw-config.json', + 'package.json', + 'tsconfig.json', + 'tsconfig.app.json', + 'tsconfig.spec.json', + 'jest.conf.js', + 'webpack/environment.js', + 'webpack/proxy.conf.js', + 'webpack/webpack.custom.js', + 'webpack/logo-jhipster.png', + ], + }), + ], + sass: [ + { + ...clientSrcTemplatesBlock(), + templates: ['content/scss/_bootstrap-variables.scss', 'content/scss/global.scss', 'content/scss/vendor.scss'], + }, + ], + angularApp: [ + { + ...clientSrcTemplatesBlock(), + templates: ['main.ts', 'bootstrap.ts', 'declarations.d.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + templates: ['app.config.ts', 'app.component.ts', 'app.routes.ts', 'app.constants.ts', 'app-page-title-strategy.ts'], + }, + ], + microfrontend: [ + clientRootTemplatesBlock({ + condition: generator => generator.microfrontend, + templates: ['webpack/webpack.microfrontend.js'], + }), + { + condition: generator => generator.microfrontend && generator.applicationTypeGateway, + ...clientApplicationTemplatesBlock(), + templates: ['core/microfrontend/index.ts'], + }, + ], + angularMain: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + // entities + 'entities/entity-navbar-items.ts', + 'entities/entity.routes.ts', + // home module + 'home/home.component.ts', + 'home/home.component.html', + // layouts + 'layouts/profiles/page-ribbon.component.ts', + 'layouts/profiles/profile.service.ts', + 'layouts/profiles/profile-info.model.ts', + 'layouts/main/main.component.ts', + 'layouts/main/main.component.html', + 'layouts/navbar/navbar-item.model.d.ts', + 'layouts/navbar/navbar.component.ts', + 'layouts/navbar/navbar.component.html', + 'layouts/footer/footer.component.ts', + 'layouts/footer/footer.component.html', + 'layouts/error/error.route.ts', + 'layouts/error/error.component.ts', + 'layouts/error/error.component.html', + // login + 'login/login.service.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['layouts/navbar/active-menu.directive.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + templates: ['layouts/profiles/page-ribbon.component.scss', 'layouts/navbar/navbar.component.scss', 'home/home.component.scss'], + }, + // login + { + ...clientApplicationTemplatesBlock(), + condition: generator => !generator.authenticationTypeOauth2, + templates: ['login/login.component.ts', 'login/login.component.html', 'login/login.model.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + condition: generator => generator.authenticationTypeOauth2, + templates: ['login/logout.model.ts'], + }, + ], + angularAccountModule: [ + { + ...clientApplicationTemplatesBlock(), + condition: generator => generator.generateUserManagement, + templates: [ + 'account/account.route.ts', + 'account/activate/activate.route.ts', + 'account/activate/activate.component.ts', + 'account/activate/activate.component.html', + 'account/activate/activate.service.ts', + 'account/password/password.route.ts', + 'account/password/password-strength-bar/password-strength-bar.component.ts', + 'account/password/password-strength-bar/password-strength-bar.component.html', + 'account/password/password-strength-bar/password-strength-bar.component.scss', + 'account/password/password.component.ts', + 'account/password/password.component.html', + 'account/password/password.service.ts', + 'account/register/register.route.ts', + 'account/register/register.component.ts', + 'account/register/register.component.html', + 'account/register/register.service.ts', + 'account/register/register.model.ts', + 'account/password-reset/init/password-reset-init.route.ts', + 'account/password-reset/init/password-reset-init.component.ts', + 'account/password-reset/init/password-reset-init.component.html', + 'account/password-reset/init/password-reset-init.service.ts', + 'account/password-reset/finish/password-reset-finish.route.ts', + 'account/password-reset/finish/password-reset-finish.component.ts', + 'account/password-reset/finish/password-reset-finish.component.html', + 'account/password-reset/finish/password-reset-finish.service.ts', + 'account/settings/settings.route.ts', + 'account/settings/settings.component.ts', + 'account/settings/settings.component.html', + ], + }, + { + condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'account/sessions/sessions.route.ts', + 'account/sessions/session.model.ts', + 'account/sessions/sessions.component.ts', + 'account/sessions/sessions.component.html', + 'account/sessions/sessions.service.ts', + ], + }, + ], + angularAdminModule: [ + { + condition: generator => !generator.applicationTypeMicroservice, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/admin.routes.ts', + 'admin/docs/docs.component.ts', + 'admin/docs/docs.component.html', + 'admin/docs/docs.component.scss', + ], + }, + { + condition: generator => generator.withAdminUi, + ...clientApplicationTemplatesBlock(), + templates: [ + // admin modules + 'admin/configuration/configuration.component.ts', + 'admin/configuration/configuration.component.html', + 'admin/configuration/configuration.service.ts', + 'admin/configuration/configuration.model.ts', + 'admin/health/health.component.ts', + 'admin/health/health.component.html', + 'admin/health/modal/health-modal.component.ts', + 'admin/health/modal/health-modal.component.html', + 'admin/health/health.service.ts', + 'admin/health/health.model.ts', + 'admin/logs/log.model.ts', + 'admin/logs/logs.component.ts', + 'admin/logs/logs.component.html', + 'admin/logs/logs.service.ts', + 'admin/metrics/metrics.component.ts', + 'admin/metrics/metrics.component.html', + 'admin/metrics/metrics.service.ts', + 'admin/metrics/metrics.model.ts', + 'admin/metrics/blocks/jvm-memory/jvm-memory.component.ts', + 'admin/metrics/blocks/jvm-memory/jvm-memory.component.html', + 'admin/metrics/blocks/jvm-threads/jvm-threads.component.ts', + 'admin/metrics/blocks/jvm-threads/jvm-threads.component.html', + 'admin/metrics/blocks/metrics-cache/metrics-cache.component.ts', + 'admin/metrics/blocks/metrics-cache/metrics-cache.component.html', + 'admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts', + 'admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html', + 'admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts', + 'admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html', + 'admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts', + 'admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html', + 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts', + 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html', + 'admin/metrics/blocks/metrics-request/metrics-request.component.ts', + 'admin/metrics/blocks/metrics-request/metrics-request.component.html', + 'admin/metrics/blocks/metrics-system/metrics-system.component.ts', + 'admin/metrics/blocks/metrics-system/metrics-system.component.html', + ], + }, + { + condition: generator => generator.communicationSpringWebsocket, + ...clientSrcTemplatesBlock(), + templates: ['sockjs-client.polyfill.ts'], + }, + { + condition: generator => generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/tracker/tracker.component.ts', + 'admin/tracker/tracker.component.html', + 'core/tracker/tracker-activity.model.ts', + 'core/tracker/tracker.service.ts', + ], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/user-management/user-management.route.ts', + 'admin/user-management/user-management.model.ts', + 'admin/user-management/list/user-management.component.ts', + 'admin/user-management/list/user-management.component.html', + 'admin/user-management/detail/user-management-detail.component.ts', + 'admin/user-management/detail/user-management-detail.component.html', + 'admin/user-management/update/user-management-update.component.ts', + 'admin/user-management/update/user-management-update.component.html', + 'admin/user-management/delete/user-management-delete-dialog.component.ts', + 'admin/user-management/delete/user-management-delete-dialog.component.html', + 'admin/user-management/service/user-management.service.ts', + ], + }, + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/gateway/gateway-route.model.ts', + 'admin/gateway/gateway.component.ts', + 'admin/gateway/gateway.component.html', + 'admin/gateway/gateway-routes.service.ts', + ], + }, + ], + angularCore: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'core/config/application-config.service.ts', + 'core/config/application-config.service.spec.ts', + + 'core/util/data-util.service.ts', + 'core/util/parse-links.service.ts', + 'core/util/alert.service.ts', + 'core/util/event-manager.service.ts', + 'core/util/operators.spec.ts', + 'core/util/operators.ts', + + // config + 'config/uib-pagination.config.ts', + 'config/dayjs.ts', + 'config/datepicker-adapter.ts', + 'config/font-awesome-icons.ts', + 'config/error.constants.ts', + 'config/input.constants.ts', + 'config/navigation.constants.ts', + 'config/pagination.constants.ts', + 'config/authority.constants.ts', + + // interceptors + 'core/interceptor/error-handler.interceptor.ts', + 'core/interceptor/notification.interceptor.ts', + 'core/interceptor/auth-expired.interceptor.ts', + 'core/interceptor/index.ts', + + // request + 'core/request/request-util.ts', + 'core/request/request.model.ts', + ], + }, + { + condition: generator => generator.authenticationTypeJwt, + ...clientApplicationTemplatesBlock(), + templates: ['core/interceptor/auth.interceptor.ts'], + }, + { + condition: generator => generator.generateBuiltInUserEntity, + ...clientApplicationTemplatesBlock(), + templates: ['entities/user/user.service.ts', 'entities/user/user.service.spec.ts', 'entities/user/user.model.ts'], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['config/language.constants.ts', 'config/translation.config.ts'], + }, + ], + angularShared: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'shared/shared.module.ts', + 'shared/date/index.ts', + 'shared/date/duration.pipe.ts', + 'shared/date/format-medium-date.pipe.ts', + 'shared/date/format-medium-datetime.pipe.ts', + 'shared/sort/index.ts', + 'shared/sort/sort.directive.ts', + 'shared/sort/sort.service.ts', + 'shared/sort/sort-by.directive.ts', + 'shared/pagination/index.ts', + 'shared/pagination/item-count.component.ts', + // alert service code + 'shared/alert/alert.component.ts', + 'shared/alert/alert.component.html', + 'shared/alert/alert-error.component.ts', + 'shared/alert/alert-error.component.html', + 'shared/alert/alert-error.model.ts', + // filtering options + 'shared/filter/index.ts', + 'shared/filter/filter.component.html', + 'shared/filter/filter.component.ts', + 'shared/filter/filter.model.spec.ts', + 'shared/filter/filter.model.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: [ + 'shared/language/index.ts', + 'shared/language/translation.module.ts', + 'shared/language/find-language-from-key.pipe.ts', + 'shared/language/translate.directive.ts', + ], + }, + ], + angularAuthService: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'core/auth/state-storage.service.ts', + 'shared/auth/has-any-authority.directive.ts', + 'core/auth/account.model.ts', + 'core/auth/account.service.ts', + 'core/auth/account.service.spec.ts', + 'core/auth/user-route-access.service.ts', + ], + }, + { + condition: generator => generator.authenticationTypeJwt, + ...clientApplicationTemplatesBlock(), + templates: ['core/auth/auth-jwt.service.ts', 'core/auth/auth-jwt.service.spec.ts'], + }, + { + condition: generator => generator.authenticationUsesCsrf, + ...clientApplicationTemplatesBlock(), + templates: ['core/auth/auth-session.service.ts'], + }, + { + condition: generator => generator.authenticationTypeSession && generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: ['core/auth/csrf.service.ts'], + }, + ], + clientTestFw: [ + { + condition: generator => generator.withAdminUi, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/configuration/configuration.component.spec.ts', + 'admin/configuration/configuration.service.spec.ts', + 'admin/health/modal/health-modal.component.spec.ts', + 'admin/health/health.component.spec.ts', + 'admin/health/health.service.spec.ts', + 'admin/logs/logs.component.spec.ts', + 'admin/logs/logs.service.spec.ts', + 'admin/metrics/metrics.component.spec.ts', + 'admin/metrics/metrics.service.spec.ts', + 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.spec.ts', + ], + }, + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'shared/auth/has-any-authority.directive.spec.ts', + 'core/util/event-manager.service.spec.ts', + 'core/util/data-util.service.spec.ts', + 'core/util/parse-links.service.spec.ts', + 'core/util/alert.service.spec.ts', + 'home/home.component.spec.ts', + 'layouts/main/main.component.spec.ts', + 'layouts/navbar/navbar.component.spec.ts', + 'layouts/profiles/page-ribbon.component.spec.ts', + 'shared/alert/alert.component.spec.ts', + 'shared/alert/alert-error.component.spec.ts', + 'shared/date/format-medium-date.pipe.spec.ts', + 'shared/date/format-medium-datetime.pipe.spec.ts', + 'shared/sort/sort.directive.spec.ts', + 'shared/sort/sort-by.directive.spec.ts', + 'shared/pagination/item-count.component.spec.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['shared/language/translate.directive.spec.ts'], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'account/activate/activate.component.spec.ts', + 'account/activate/activate.service.spec.ts', + 'account/password/password.component.spec.ts', + 'account/password/password.service.spec.ts', + 'account/password/password-strength-bar/password-strength-bar.component.spec.ts', + 'account/password-reset/init/password-reset-init.component.spec.ts', + 'account/password-reset/init/password-reset-init.service.spec.ts', + 'account/password-reset/finish/password-reset-finish.component.spec.ts', + 'account/password-reset/finish/password-reset-finish.service.spec.ts', + 'account/register/register.component.spec.ts', + 'account/register/register.service.spec.ts', + 'account/settings/settings.component.spec.ts', + ], + }, + { + condition: generator => !generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['login/login.component.spec.ts'], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/user-management/list/user-management.component.spec.ts', + 'admin/user-management/detail/user-management-detail.component.spec.ts', + 'admin/user-management/update/user-management-update.component.spec.ts', + 'admin/user-management/delete/user-management-delete-dialog.component.spec.ts', + 'admin/user-management/service/user-management.service.spec.ts', + ], + }, + { + condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: ['account/sessions/sessions.component.spec.ts'], + }, + ], +}; + +export async function writeFiles({ application }) { + if (!application.clientFrameworkAngular) return; + + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/angular/files-angular.mjs b/generators/angular/files-angular.mjs deleted file mode 100644 index 70c5f4248a30..000000000000 --- a/generators/angular/files-angular.mjs +++ /dev/null @@ -1,478 +0,0 @@ -/** - * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.mjs'; - -export const files = { - jhipsterProject: [ - { - templates: ['README.md.jhi.client.angular'], - }, - ], - common: [ - clientRootTemplatesBlock({ - templates: [ - '.eslintrc.json', - 'angular.json', - 'ngsw-config.json', - 'package.json', - 'tsconfig.json', - 'tsconfig.app.json', - 'tsconfig.spec.json', - 'jest.conf.js', - 'webpack/environment.js', - 'webpack/proxy.conf.js', - 'webpack/webpack.custom.js', - 'webpack/logo-jhipster.png', - ], - }), - ], - sass: [ - { - ...clientSrcTemplatesBlock(), - templates: ['content/scss/_bootstrap-variables.scss', 'content/scss/global.scss', 'content/scss/vendor.scss'], - }, - ], - angularApp: [ - { - ...clientSrcTemplatesBlock(), - templates: ['main.ts', 'bootstrap.ts', 'declarations.d.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - templates: ['app.config.ts', 'app.component.ts', 'app.routes.ts', 'app.constants.ts', 'app-page-title-strategy.ts'], - }, - ], - microfrontend: [ - clientRootTemplatesBlock({ - condition: generator => generator.microfrontend, - templates: ['webpack/webpack.microfrontend.js'], - }), - { - condition: generator => generator.microfrontend && generator.applicationTypeGateway, - ...clientApplicationTemplatesBlock(), - templates: ['core/microfrontend/index.ts'], - }, - ], - angularMain: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - // entities - 'entities/entity-navbar-items.ts', - 'entities/entity.routes.ts', - // home module - 'home/home.component.ts', - 'home/home.component.html', - // layouts - 'layouts/profiles/page-ribbon.component.ts', - 'layouts/profiles/profile.service.ts', - 'layouts/profiles/profile-info.model.ts', - 'layouts/main/main.component.ts', - 'layouts/main/main.component.html', - 'layouts/navbar/navbar-item.model.d.ts', - 'layouts/navbar/navbar.component.ts', - 'layouts/navbar/navbar.component.html', - 'layouts/footer/footer.component.ts', - 'layouts/footer/footer.component.html', - 'layouts/error/error.route.ts', - 'layouts/error/error.component.ts', - 'layouts/error/error.component.html', - // login - 'login/login.service.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['layouts/navbar/active-menu.directive.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - templates: ['layouts/profiles/page-ribbon.component.scss', 'layouts/navbar/navbar.component.scss', 'home/home.component.scss'], - }, - // login - { - ...clientApplicationTemplatesBlock(), - condition: generator => !generator.authenticationTypeOauth2, - templates: ['login/login.component.ts', 'login/login.component.html', 'login/login.model.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - condition: generator => generator.authenticationTypeOauth2, - templates: ['login/logout.model.ts'], - }, - ], - angularAccountModule: [ - { - ...clientApplicationTemplatesBlock(), - condition: generator => generator.generateUserManagement, - templates: [ - 'account/account.route.ts', - 'account/activate/activate.route.ts', - 'account/activate/activate.component.ts', - 'account/activate/activate.component.html', - 'account/activate/activate.service.ts', - 'account/password/password.route.ts', - 'account/password/password-strength-bar/password-strength-bar.component.ts', - 'account/password/password-strength-bar/password-strength-bar.component.html', - 'account/password/password-strength-bar/password-strength-bar.component.scss', - 'account/password/password.component.ts', - 'account/password/password.component.html', - 'account/password/password.service.ts', - 'account/register/register.route.ts', - 'account/register/register.component.ts', - 'account/register/register.component.html', - 'account/register/register.service.ts', - 'account/register/register.model.ts', - 'account/password-reset/init/password-reset-init.route.ts', - 'account/password-reset/init/password-reset-init.component.ts', - 'account/password-reset/init/password-reset-init.component.html', - 'account/password-reset/init/password-reset-init.service.ts', - 'account/password-reset/finish/password-reset-finish.route.ts', - 'account/password-reset/finish/password-reset-finish.component.ts', - 'account/password-reset/finish/password-reset-finish.component.html', - 'account/password-reset/finish/password-reset-finish.service.ts', - 'account/settings/settings.route.ts', - 'account/settings/settings.component.ts', - 'account/settings/settings.component.html', - ], - }, - { - condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'account/sessions/sessions.route.ts', - 'account/sessions/session.model.ts', - 'account/sessions/sessions.component.ts', - 'account/sessions/sessions.component.html', - 'account/sessions/sessions.service.ts', - ], - }, - ], - angularAdminModule: [ - { - condition: generator => !generator.applicationTypeMicroservice, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/admin.routes.ts', - 'admin/docs/docs.component.ts', - 'admin/docs/docs.component.html', - 'admin/docs/docs.component.scss', - ], - }, - { - condition: generator => generator.withAdminUi, - ...clientApplicationTemplatesBlock(), - templates: [ - // admin modules - 'admin/configuration/configuration.component.ts', - 'admin/configuration/configuration.component.html', - 'admin/configuration/configuration.service.ts', - 'admin/configuration/configuration.model.ts', - 'admin/health/health.component.ts', - 'admin/health/health.component.html', - 'admin/health/modal/health-modal.component.ts', - 'admin/health/modal/health-modal.component.html', - 'admin/health/health.service.ts', - 'admin/health/health.model.ts', - 'admin/logs/log.model.ts', - 'admin/logs/logs.component.ts', - 'admin/logs/logs.component.html', - 'admin/logs/logs.service.ts', - 'admin/metrics/metrics.component.ts', - 'admin/metrics/metrics.component.html', - 'admin/metrics/metrics.service.ts', - 'admin/metrics/metrics.model.ts', - 'admin/metrics/blocks/jvm-memory/jvm-memory.component.ts', - 'admin/metrics/blocks/jvm-memory/jvm-memory.component.html', - 'admin/metrics/blocks/jvm-threads/jvm-threads.component.ts', - 'admin/metrics/blocks/jvm-threads/jvm-threads.component.html', - 'admin/metrics/blocks/metrics-cache/metrics-cache.component.ts', - 'admin/metrics/blocks/metrics-cache/metrics-cache.component.html', - 'admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts', - 'admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html', - 'admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts', - 'admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html', - 'admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts', - 'admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html', - 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts', - 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html', - 'admin/metrics/blocks/metrics-request/metrics-request.component.ts', - 'admin/metrics/blocks/metrics-request/metrics-request.component.html', - 'admin/metrics/blocks/metrics-system/metrics-system.component.ts', - 'admin/metrics/blocks/metrics-system/metrics-system.component.html', - ], - }, - { - condition: generator => generator.communicationSpringWebsocket, - ...clientSrcTemplatesBlock(), - templates: ['sockjs-client.polyfill.ts'], - }, - { - condition: generator => generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/tracker/tracker.component.ts', - 'admin/tracker/tracker.component.html', - 'core/tracker/tracker-activity.model.ts', - 'core/tracker/tracker.service.ts', - ], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/user-management/user-management.route.ts', - 'admin/user-management/user-management.model.ts', - 'admin/user-management/list/user-management.component.ts', - 'admin/user-management/list/user-management.component.html', - 'admin/user-management/detail/user-management-detail.component.ts', - 'admin/user-management/detail/user-management-detail.component.html', - 'admin/user-management/update/user-management-update.component.ts', - 'admin/user-management/update/user-management-update.component.html', - 'admin/user-management/delete/user-management-delete-dialog.component.ts', - 'admin/user-management/delete/user-management-delete-dialog.component.html', - 'admin/user-management/service/user-management.service.ts', - ], - }, - { - condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/gateway/gateway-route.model.ts', - 'admin/gateway/gateway.component.ts', - 'admin/gateway/gateway.component.html', - 'admin/gateway/gateway-routes.service.ts', - ], - }, - ], - angularCore: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'core/config/application-config.service.ts', - 'core/config/application-config.service.spec.ts', - - 'core/util/data-util.service.ts', - 'core/util/parse-links.service.ts', - 'core/util/alert.service.ts', - 'core/util/event-manager.service.ts', - 'core/util/operators.spec.ts', - 'core/util/operators.ts', - - // config - 'config/uib-pagination.config.ts', - 'config/dayjs.ts', - 'config/datepicker-adapter.ts', - 'config/font-awesome-icons.ts', - 'config/error.constants.ts', - 'config/input.constants.ts', - 'config/navigation.constants.ts', - 'config/pagination.constants.ts', - 'config/authority.constants.ts', - - // interceptors - 'core/interceptor/error-handler.interceptor.ts', - 'core/interceptor/notification.interceptor.ts', - 'core/interceptor/auth-expired.interceptor.ts', - 'core/interceptor/index.ts', - - // request - 'core/request/request-util.ts', - 'core/request/request.model.ts', - ], - }, - { - condition: generator => generator.authenticationTypeJwt, - ...clientApplicationTemplatesBlock(), - templates: ['core/interceptor/auth.interceptor.ts'], - }, - { - condition: generator => generator.generateBuiltInUserEntity, - ...clientApplicationTemplatesBlock(), - templates: ['entities/user/user.service.ts', 'entities/user/user.service.spec.ts', 'entities/user/user.model.ts'], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['config/language.constants.ts', 'config/translation.config.ts'], - }, - ], - angularShared: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'shared/shared.module.ts', - 'shared/date/index.ts', - 'shared/date/duration.pipe.ts', - 'shared/date/format-medium-date.pipe.ts', - 'shared/date/format-medium-datetime.pipe.ts', - 'shared/sort/index.ts', - 'shared/sort/sort.directive.ts', - 'shared/sort/sort.service.ts', - 'shared/sort/sort-by.directive.ts', - 'shared/pagination/index.ts', - 'shared/pagination/item-count.component.ts', - // alert service code - 'shared/alert/alert.component.ts', - 'shared/alert/alert.component.html', - 'shared/alert/alert-error.component.ts', - 'shared/alert/alert-error.component.html', - 'shared/alert/alert-error.model.ts', - // filtering options - 'shared/filter/index.ts', - 'shared/filter/filter.component.html', - 'shared/filter/filter.component.ts', - 'shared/filter/filter.model.spec.ts', - 'shared/filter/filter.model.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: [ - 'shared/language/index.ts', - 'shared/language/translation.module.ts', - 'shared/language/find-language-from-key.pipe.ts', - 'shared/language/translate.directive.ts', - ], - }, - ], - angularAuthService: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'core/auth/state-storage.service.ts', - 'shared/auth/has-any-authority.directive.ts', - 'core/auth/account.model.ts', - 'core/auth/account.service.ts', - 'core/auth/account.service.spec.ts', - 'core/auth/user-route-access.service.ts', - ], - }, - { - condition: generator => generator.authenticationTypeJwt, - ...clientApplicationTemplatesBlock(), - templates: ['core/auth/auth-jwt.service.ts', 'core/auth/auth-jwt.service.spec.ts'], - }, - { - condition: generator => generator.authenticationUsesCsrf, - ...clientApplicationTemplatesBlock(), - templates: ['core/auth/auth-session.service.ts'], - }, - { - condition: generator => generator.authenticationTypeSession && generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: ['core/auth/csrf.service.ts'], - }, - ], - clientTestFw: [ - { - condition: generator => generator.withAdminUi, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/configuration/configuration.component.spec.ts', - 'admin/configuration/configuration.service.spec.ts', - 'admin/health/modal/health-modal.component.spec.ts', - 'admin/health/health.component.spec.ts', - 'admin/health/health.service.spec.ts', - 'admin/logs/logs.component.spec.ts', - 'admin/logs/logs.service.spec.ts', - 'admin/metrics/metrics.component.spec.ts', - 'admin/metrics/metrics.service.spec.ts', - 'admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.spec.ts', - ], - }, - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'shared/auth/has-any-authority.directive.spec.ts', - 'core/util/event-manager.service.spec.ts', - 'core/util/data-util.service.spec.ts', - 'core/util/parse-links.service.spec.ts', - 'core/util/alert.service.spec.ts', - 'home/home.component.spec.ts', - 'layouts/main/main.component.spec.ts', - 'layouts/navbar/navbar.component.spec.ts', - 'layouts/profiles/page-ribbon.component.spec.ts', - 'shared/alert/alert.component.spec.ts', - 'shared/alert/alert-error.component.spec.ts', - 'shared/date/format-medium-date.pipe.spec.ts', - 'shared/date/format-medium-datetime.pipe.spec.ts', - 'shared/sort/sort.directive.spec.ts', - 'shared/sort/sort-by.directive.spec.ts', - 'shared/pagination/item-count.component.spec.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['shared/language/translate.directive.spec.ts'], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'account/activate/activate.component.spec.ts', - 'account/activate/activate.service.spec.ts', - 'account/password/password.component.spec.ts', - 'account/password/password.service.spec.ts', - 'account/password/password-strength-bar/password-strength-bar.component.spec.ts', - 'account/password-reset/init/password-reset-init.component.spec.ts', - 'account/password-reset/init/password-reset-init.service.spec.ts', - 'account/password-reset/finish/password-reset-finish.component.spec.ts', - 'account/password-reset/finish/password-reset-finish.service.spec.ts', - 'account/register/register.component.spec.ts', - 'account/register/register.service.spec.ts', - 'account/settings/settings.component.spec.ts', - ], - }, - { - condition: generator => !generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['login/login.component.spec.ts'], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/user-management/list/user-management.component.spec.ts', - 'admin/user-management/detail/user-management-detail.component.spec.ts', - 'admin/user-management/update/user-management-update.component.spec.ts', - 'admin/user-management/delete/user-management-delete-dialog.component.spec.ts', - 'admin/user-management/service/user-management.service.spec.ts', - ], - }, - { - condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: ['account/sessions/sessions.component.spec.ts'], - }, - ], -}; - -export async function writeFiles({ application }) { - if (!application.clientFrameworkAngular) return; - - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/angular/generator.mts b/generators/angular/generator.mts deleted file mode 100644 index d032a7c397ff..000000000000 --- a/generators/angular/generator.mts +++ /dev/null @@ -1,408 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import chalk from 'chalk'; -import { isFileStateModified } from 'mem-fs-editor/state'; - -import BaseApplicationGenerator, { type Entity } from '../base-application/index.mjs'; -import { GENERATOR_ANGULAR, GENERATOR_CLIENT, GENERATOR_LANGUAGES } from '../generator-list.mjs'; -import { defaultLanguage } from '../languages/support/index.mjs'; -import { writeEntitiesFiles, postWriteEntitiesFiles, cleanupEntitiesFiles } from './entity-files-angular.mjs'; -import { writeFiles } from './files-angular.mjs'; -import cleanupOldFilesTask from './cleanup.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { - buildAngularFormPath as angularFormPath, - addEntitiesRoute, - addToEntitiesMenu, - translateAngularFilesTransform, - isTranslatedAngularFile, - addRoute, - addItemToMenu, - addItemToAdminMenu, - addIconImport, -} from './support/index.mjs'; -import { - generateEntityClientEnumImports as getClientEnumImportsFormat, - getTypescriptKeyType as getTSKeyType, - generateTestEntityId as getTestEntityId, - generateTestEntityPrimaryKey as getTestEntityPrimaryKey, - generateTypescriptTestEntity as generateTestEntity, -} from '../client/support/index.mjs'; -import type { CommonClientServerApplication } from '../base-application/types.mjs'; -import { createNeedleCallback } from '../base/support/index.mjs'; - -const { ANGULAR } = clientFrameworkTypes; - -export default class AngularGenerator extends BaseApplicationGenerator { - localEntities?: any[]; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_ANGULAR); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_CLIENT); - await this.dependsOnJHipster(GENERATOR_LANGUAGES); - } - } - - get loading() { - return this.asLoadingTaskGroup({ - loadPackageJson({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_ANGULAR, 'resources', 'package.json'), - ); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.asLoadingTaskGroup(this.delegateTasksToBlueprint(() => this.loading)); - } - - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application }) { - application.webappEnumerationsDir = `${application.clientSrcDir}app/entities/enumerations/`; - application.angularLocaleId = application.nativeLanguageDefinition.angularLocale ?? defaultLanguage.angularLocale!; - }, - addNeedles({ source, application }) { - source.addEntitiesToClient = param => { - const { application, entities } = param; - this.addEntitiesToModule({ application, entities }); - this.addEntitiesToMenu({ application, entities }); - }; - - source.addAdminRoute = (args: Omit[0], 'needle'>) => - this.editFile( - `${application.srcMainWebapp}app/admin/admin.routes.ts`, - addRoute({ - needle: 'add-admin-route', - ...args, - }), - ); - - source.addItemToAdminMenu = (args: Omit[0], 'needle' | 'enableTranslation' | 'jhiPrefix'>) => { - this.editFile( - `${application.srcMainWebapp}app/layouts/navbar/navbar.component.html`, - addItemToAdminMenu({ - enableTranslation: application.enableTranslation, - jhiPrefix: application.jhiPrefix, - ...args, - }), - ); - if (args.icon) { - source.addIconImport!({ icon: args.icon }); - } - }; - - source.addIconImport = args => { - const iconsPath = `${application.srcMainWebapp}app/config/font-awesome-icons.ts`; - const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Icon imports not updated with icon'; - this.editFile(iconsPath, { ignoreNonExisting }, addIconImport(args)); - }; - - source.addWebpackConfig = args => { - const webpackPath = `${application.clientRootDir}webpack/webpack.custom.js`; - const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; - this.editFile( - webpackPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-webpack-config', - contentToAdd: `,${args.config}`, - }), - ); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); - } - - get default() { - return this.asDefaultTaskGroup({ - loadEntities() { - const entities = this.sharedData.getEntities().map(({ entity }) => entity); - this.localEntities = entities.filter(entity => !entity.builtIn && !entity.skipClient); - }, - queueTranslateTransform({ control, application }) { - this.queueTransformStream( - { - name: 'translating angular application', - filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedAngularFile(file), - refresh: false, - }, - translateAngularFilesTransform(control.getWebappTranslation, application.enableTranslation), - ); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupOldFilesTask, - writeFiles, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - cleanupEntitiesFiles, - writeEntitiesFiles, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - postWriteEntitiesFiles, - }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.postWritingEntities); - } - - get end() { - return this.asEndTaskGroup({ - end({ application }) { - this.log.ok('Angular application generated successfully.'); - this.log.log( - chalk.green(` Start your Webpack development server with: - ${chalk.yellow.bold(`${application.nodePackageManager} start`)} -`), - ); - }, - }); - } - - get [BaseApplicationGenerator.END]() { - return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); - } - - /** - * @private - * Add new scss style to the angular application in "vendor.scss". - * - * @param {string} style - scss to add in the file - * @param {string} comment - comment to add before css code - * - * example: - * - * style = '.success {\n @extend .message;\n border-color: green;\n}' - * comment = 'Message' - * - * * ========================================================================== - * Message - * ========================================================================== * - * .success { - * @extend .message; - * border-color: green; - * } - * - */ - addVendorSCSSStyle(style, comment) { - this.needleApi.clientAngular.addVendorSCSSStyle(style, comment); - } - - /** - * @private - * Add a new lazy loaded module to admin routing file. - * - * @param {string} route - The route for the module. For example 'entity-audit'. - * @param {string} modulePath - The path to the module file. For example './entity-audit/entity-audit.module'. - * @param {string} moduleName - The name of the module. For example 'EntityAuditModule'. - * @param {string} pageTitle - The translation key if i18n is enabled or the text if i18n is disabled for the page title in the browser. - * For example 'entityAudit.home.title' for i18n enabled or 'Entity audit' for i18n disabled. - * If undefined then application global page title is used in the browser title bar. - */ - addAdminRoute(route, modulePath, moduleName, pageTitle) { - this.needleApi.clientAngular.addAdminRoute(route, modulePath, moduleName, pageTitle); - } - - /** - * @private - * Add a new module in the TS modules file. - * - * @param {string} appName - Angular2 application name. - * @param {string} angularName - The name of the new admin item. - * @param {string} folderName - The name of the folder. - * @param {string} fileName - The name of the file. - * @param {boolean} enableTranslation - If translations are enabled or not. - * @param {string} clientFramework - The name of the client framework. - */ - addAngularModule(appName, angularName, folderName, fileName, enableTranslation) { - this.needleApi.clientAngular.addModule(appName, angularName, folderName, fileName, enableTranslation); - } - - /** - * @private - * Add a new icon to icon imports. - * - * @param {string} iconName - The name of the Font Awesome icon. - */ - addIcon(iconName) { - this.needleApi.clientAngular.addIcon(iconName); - } - - /** - * Add a new menu element to the admin menu. - * - * @param {string} routerName - The name of the Angular router that is added to the admin menu. - * @param {string} iconName - The name of the Font Awesome icon that will be displayed. - * @param {boolean} enableTranslation - If translations are enabled or not - * @param {string} translationKeyMenu - i18n key for entry in the admin menu - */ - addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu = _.camelCase(routerName), jhiPrefix?) { - this.needleApi.clientAngular.addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu, jhiPrefix); - } - - addEntitiesToMenu({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { - const filePath = `${application.clientSrcDir}app/layouts/navbar/navbar.component.html`; - const ignoreNonExisting = chalk.yellow('Reference to entities not added to menu.'); - const editCallback = addToEntitiesMenu({ application, entities }); - - this.editFile(filePath, { ignoreNonExisting }, editCallback); - } - - addEntitiesToModule({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { - const filePath = `${application.clientSrcDir}app/entities/entity.routes.ts`; - const ignoreNonExisting = chalk.yellow(`Route(s) not added to ${filePath}.`); - const addRouteCallback = addEntitiesRoute({ application, entities }); - this.editFile(filePath, { ignoreNonExisting }, addRouteCallback); - } - - /** - * @private - * Add new scss style to the angular application in "global.scss - * - * @param {string} style - css to add in the file - * @param {string} comment - comment to add before css code - * - * example: - * - * style = '.jhipster {\n color: #baa186;\n}' - * comment = 'New JHipster color' - * - * * ========================================================================== - * New JHipster color - * ========================================================================== * - * .jhipster { - * color: #baa186; - * } - * - */ - addMainSCSSStyle(style, comment) { - this.needleApi.clientAngular.addGlobalSCSSStyle(style, comment); - } - - /** - * Returns the typescript import section of enums referenced by all fields of the entity. - * @param fields returns the import of enums that are referenced by the fields - * @returns {typeImports:Map} the fields that potentially contains some enum types - */ - generateEntityClientEnumImports(fields) { - return getClientEnumImportsFormat(fields, ANGULAR); - } - - /** - * Get the typescript type of a non-composite primary key - * @param primaryKey the primary key of the entity - * @returns {string} the typescript type. - */ - getTypescriptKeyType(primaryKey) { - return getTSKeyType(primaryKey); - } - - /** - * generates a value for a primary key type - * @param primaryKey the primary key attribute (or its type) of the entity - * @param index an index to add salt to the value - * @param wrapped if the value should be within quotes - * @returns {string|number|string} - */ - generateTestEntityId(primaryKey, index = 0, wrapped = true) { - return getTestEntityId(primaryKey, index, wrapped); - } - - /** - * @private - * Generate a test entity, for the PK references (when the PK is a composite, derived key) - * - * @param {any} primaryKey - primary key definition. - * @param {number} [index] - index of the primary key sample, pass undefined for a random key. - */ - generateTestEntityPrimaryKey(primaryKey, index) { - return getTestEntityPrimaryKey(primaryKey, index); - } - - /** - * @private - * Generate a test entity instance with faked values. - * - * @param {any} references - references to other entities. - * @param {any} additionalFields - additional fields to add to the entity or with default values that overrides generated values. - */ - generateTypescriptTestEntity(references, additionalFields) { - return generateTestEntity(references, additionalFields); - } - - /** - * @private - * Create a angular form path getter method of reference. - * - * @param {object} reference - * @param {string[]} prefix - * @return {string} - */ - buildAngularFormPath(reference, prefix = []) { - return angularFormPath(reference, prefix); - } - - /** - * @private - * Add a new menu element, at the root of the menu. - * - * @param {string} routerName - The name of the router that is added to the menu. - * @param {string} iconName - The name of the Font Awesome icon that will be displayed. - * @param {boolean} enableTranslation - If translations are enabled or not - * @param {string} clientFramework - The name of the client framework - * @param {string} translationKeyMenu - i18n key for entry in the menu - */ - addElementToMenu(routerName, iconName, enableTranslation, clientFramework, translationKeyMenu = _.camelCase(routerName)) { - this.needleApi.clientAngular.addElementToMenu(routerName, iconName, enableTranslation, translationKeyMenu); - } -} diff --git a/generators/angular/generator.spec.mts b/generators/angular/generator.spec.mts deleted file mode 100644 index b6388c78f626..000000000000 --- a/generators/angular/generator.spec.mts +++ /dev/null @@ -1,167 +0,0 @@ -import { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildClientSamples, - checkEnforcements, - entitiesClientSamples as entities, - defaultHelpers as helpers, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { GENERATOR_ANGULAR } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const { ANGULAR: clientFramework } = clientFrameworkTypes; -const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const testSamples = buildClientSamples(commonConfig); - -const clientAdminFiles = clientSrcDir => [ - `${clientSrcDir}app/admin/configuration/configuration.component.html`, - `${clientSrcDir}app/admin/configuration/configuration.component.ts`, - `${clientSrcDir}app/admin/configuration/configuration.service.ts`, - `${clientSrcDir}app/admin/configuration/configuration.model.ts`, - `${clientSrcDir}app/admin/health/modal/health-modal.component.html`, - `${clientSrcDir}app/admin/health/modal/health-modal.component.ts`, - `${clientSrcDir}app/admin/health/health.component.html`, - `${clientSrcDir}app/admin/health/health.component.ts`, - `${clientSrcDir}app/admin/health/health.service.ts`, - `${clientSrcDir}app/admin/health/health.model.ts`, - `${clientSrcDir}app/admin/logs/log.model.ts`, - `${clientSrcDir}app/admin/logs/logs.component.html`, - `${clientSrcDir}app/admin/logs/logs.component.ts`, - `${clientSrcDir}app/admin/logs/logs.service.ts`, - `${clientSrcDir}app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/jvm-threads/jvm-threads.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/jvm-threads/jvm-threads.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-cache/metrics-cache.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-request/metrics-request.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-request/metrics-request.component.html`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-system/metrics-system.component.ts`, - `${clientSrcDir}app/admin/metrics/blocks/metrics-system/metrics-system.component.html`, - `${clientSrcDir}app/admin/metrics/metrics.component.html`, - `${clientSrcDir}app/admin/metrics/metrics.component.ts`, - `${clientSrcDir}app/admin/metrics/metrics.service.ts`, - `${clientSrcDir}app/admin/metrics/metrics.model.ts`, - `${clientSrcDir}app/admin/configuration/configuration.component.spec.ts`, - `${clientSrcDir}app/admin/configuration/configuration.service.spec.ts`, - `${clientSrcDir}app/admin/health/modal/health-modal.component.spec.ts`, - `${clientSrcDir}app/admin/health/health.component.spec.ts`, - `${clientSrcDir}app/admin/health/health.service.spec.ts`, - `${clientSrcDir}app/admin/logs/logs.component.spec.ts`, - `${clientSrcDir}app/admin/logs/logs.service.spec.ts`, - `${clientSrcDir}app/admin/metrics/metrics.component.spec.ts`, - `${clientSrcDir}app/admin/metrics/metrics.service.spec.ts`, -]; - -describe(`generator - ${clientFramework}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - checkEnforcements({ client: true }, GENERATOR_ANGULAR); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { clientRootDir = '' } = sampleConfig; - - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(sampleConfig, entities) - .withControl({ getWebappTranslation: () => 'translations' }) - .withMockedGenerators(['jhipster:common', 'jhipster:languages']); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct clientFramework', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); - }); - it('should not contain version placeholders at package.json', () => { - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); - }); - - describe('withAdminUi', () => { - const { applicationType, withAdminUi, clientRootDir = '' } = sampleConfig; - const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; - const generateAdminUi = applicationType !== 'microservice' && withAdminUi; - const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; - - it(adminUiComponents, () => { - const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); - assertion(clientAdminFiles(clientSrcDir)); - }); - - if (applicationType !== 'microservice') { - const adminUiRoutingTitle = generateAdminUi ? 'should generate admin routing' : 'should not generate admin routing'; - it(adminUiRoutingTitle, () => { - const assertion = (...args) => - generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args); - assertion( - `${clientSrcDir}app/admin/admin.routes.ts`, - ` - { - path: 'configuration', - loadComponent: () => import('./configuration/configuration.component'), - title: 'configuration.title', - }, - { - path: 'health', - loadComponent: () => import('./health/health.component'), - title: 'health.title', - }, - { - path: 'logs', - loadComponent: () => import('./logs/logs.component'), - title: 'logs.title', - }, - { - path: 'metrics', - loadComponent: () => import('./metrics/metrics.component'), - title: 'metrics.title', - }, -`, - ); - }); - } - }); - }); - }); -}); diff --git a/generators/angular/generator.spec.ts b/generators/angular/generator.spec.ts new file mode 100644 index 000000000000..3ec2ba8de379 --- /dev/null +++ b/generators/angular/generator.spec.ts @@ -0,0 +1,167 @@ +import { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildClientSamples, + checkEnforcements, + entitiesClientSamples as entities, + defaultHelpers as helpers, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import { GENERATOR_ANGULAR } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const { ANGULAR: clientFramework } = clientFrameworkTypes; +const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const testSamples = buildClientSamples(commonConfig); + +const clientAdminFiles = clientSrcDir => [ + `${clientSrcDir}app/admin/configuration/configuration.component.html`, + `${clientSrcDir}app/admin/configuration/configuration.component.ts`, + `${clientSrcDir}app/admin/configuration/configuration.service.ts`, + `${clientSrcDir}app/admin/configuration/configuration.model.ts`, + `${clientSrcDir}app/admin/health/modal/health-modal.component.html`, + `${clientSrcDir}app/admin/health/modal/health-modal.component.ts`, + `${clientSrcDir}app/admin/health/health.component.html`, + `${clientSrcDir}app/admin/health/health.component.ts`, + `${clientSrcDir}app/admin/health/health.service.ts`, + `${clientSrcDir}app/admin/health/health.model.ts`, + `${clientSrcDir}app/admin/logs/log.model.ts`, + `${clientSrcDir}app/admin/logs/logs.component.html`, + `${clientSrcDir}app/admin/logs/logs.component.ts`, + `${clientSrcDir}app/admin/logs/logs.service.ts`, + `${clientSrcDir}app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/jvm-threads/jvm-threads.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/jvm-threads/jvm-threads.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-cache/metrics-cache.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-request/metrics-request.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-request/metrics-request.component.html`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-system/metrics-system.component.ts`, + `${clientSrcDir}app/admin/metrics/blocks/metrics-system/metrics-system.component.html`, + `${clientSrcDir}app/admin/metrics/metrics.component.html`, + `${clientSrcDir}app/admin/metrics/metrics.component.ts`, + `${clientSrcDir}app/admin/metrics/metrics.service.ts`, + `${clientSrcDir}app/admin/metrics/metrics.model.ts`, + `${clientSrcDir}app/admin/configuration/configuration.component.spec.ts`, + `${clientSrcDir}app/admin/configuration/configuration.service.spec.ts`, + `${clientSrcDir}app/admin/health/modal/health-modal.component.spec.ts`, + `${clientSrcDir}app/admin/health/health.component.spec.ts`, + `${clientSrcDir}app/admin/health/health.service.spec.ts`, + `${clientSrcDir}app/admin/logs/logs.component.spec.ts`, + `${clientSrcDir}app/admin/logs/logs.service.spec.ts`, + `${clientSrcDir}app/admin/metrics/metrics.component.spec.ts`, + `${clientSrcDir}app/admin/metrics/metrics.service.spec.ts`, +]; + +describe(`generator - ${clientFramework}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + checkEnforcements({ client: true }, GENERATOR_ANGULAR); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { clientRootDir = '' } = sampleConfig; + + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(sampleConfig, entities) + .withControl({ getWebappTranslation: () => 'translations' }) + .withMockedGenerators(['jhipster:common', 'jhipster:languages']); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct clientFramework', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); + }); + it('should not contain version placeholders at package.json', () => { + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); + }); + + describe('withAdminUi', () => { + const { applicationType, withAdminUi, clientRootDir = '' } = sampleConfig; + const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; + const generateAdminUi = applicationType !== 'microservice' && withAdminUi; + const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; + + it(adminUiComponents, () => { + const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); + assertion(clientAdminFiles(clientSrcDir)); + }); + + if (applicationType !== 'microservice') { + const adminUiRoutingTitle = generateAdminUi ? 'should generate admin routing' : 'should not generate admin routing'; + it(adminUiRoutingTitle, () => { + const assertion = (...args) => + generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args); + assertion( + `${clientSrcDir}app/admin/admin.routes.ts`, + ` + { + path: 'configuration', + loadComponent: () => import('./configuration/configuration.component'), + title: 'configuration.title', + }, + { + path: 'health', + loadComponent: () => import('./health/health.component'), + title: 'health.title', + }, + { + path: 'logs', + loadComponent: () => import('./logs/logs.component'), + title: 'logs.title', + }, + { + path: 'metrics', + loadComponent: () => import('./metrics/metrics.component'), + title: 'metrics.title', + }, +`, + ); + }); + } + }); + }); + }); +}); diff --git a/generators/angular/generator.ts b/generators/angular/generator.ts new file mode 100644 index 000000000000..3681543f0351 --- /dev/null +++ b/generators/angular/generator.ts @@ -0,0 +1,408 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import chalk from 'chalk'; +import { isFileStateModified } from 'mem-fs-editor/state'; + +import BaseApplicationGenerator, { type Entity } from '../base-application/index.js'; +import { GENERATOR_ANGULAR, GENERATOR_CLIENT, GENERATOR_LANGUAGES } from '../generator-list.js'; +import { defaultLanguage } from '../languages/support/index.js'; +import { writeEntitiesFiles, postWriteEntitiesFiles, cleanupEntitiesFiles } from './entity-files-angular.js'; +import { writeFiles } from './files-angular.js'; +import cleanupOldFilesTask from './cleanup.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { + buildAngularFormPath as angularFormPath, + addEntitiesRoute, + addToEntitiesMenu, + translateAngularFilesTransform, + isTranslatedAngularFile, + addRoute, + addItemToMenu, + addItemToAdminMenu, + addIconImport, +} from './support/index.js'; +import { + generateEntityClientEnumImports as getClientEnumImportsFormat, + getTypescriptKeyType as getTSKeyType, + generateTestEntityId as getTestEntityId, + generateTestEntityPrimaryKey as getTestEntityPrimaryKey, + generateTypescriptTestEntity as generateTestEntity, +} from '../client/support/index.js'; +import type { CommonClientServerApplication } from '../base-application/types.js'; +import { createNeedleCallback } from '../base/support/index.js'; + +const { ANGULAR } = clientFrameworkTypes; + +export default class AngularGenerator extends BaseApplicationGenerator { + localEntities?: any[]; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_ANGULAR); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_CLIENT); + await this.dependsOnJHipster(GENERATOR_LANGUAGES); + } + } + + get loading() { + return this.asLoadingTaskGroup({ + loadPackageJson({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster(GENERATOR_ANGULAR, 'resources', 'package.json'), + ); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.asLoadingTaskGroup(this.delegateTasksToBlueprint(() => this.loading)); + } + + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application }) { + application.webappEnumerationsDir = `${application.clientSrcDir}app/entities/enumerations/`; + application.angularLocaleId = application.nativeLanguageDefinition.angularLocale ?? defaultLanguage.angularLocale!; + }, + addNeedles({ source, application }) { + source.addEntitiesToClient = param => { + const { application, entities } = param; + this.addEntitiesToModule({ application, entities }); + this.addEntitiesToMenu({ application, entities }); + }; + + source.addAdminRoute = (args: Omit[0], 'needle'>) => + this.editFile( + `${application.srcMainWebapp}app/admin/admin.routes.ts`, + addRoute({ + needle: 'add-admin-route', + ...args, + }), + ); + + source.addItemToAdminMenu = (args: Omit[0], 'needle' | 'enableTranslation' | 'jhiPrefix'>) => { + this.editFile( + `${application.srcMainWebapp}app/layouts/navbar/navbar.component.html`, + addItemToAdminMenu({ + enableTranslation: application.enableTranslation, + jhiPrefix: application.jhiPrefix, + ...args, + }), + ); + if (args.icon) { + source.addIconImport!({ icon: args.icon }); + } + }; + + source.addIconImport = args => { + const iconsPath = `${application.srcMainWebapp}app/config/font-awesome-icons.ts`; + const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Icon imports not updated with icon'; + this.editFile(iconsPath, { ignoreNonExisting }, addIconImport(args)); + }; + + source.addWebpackConfig = args => { + const webpackPath = `${application.clientRootDir}webpack/webpack.custom.js`; + const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; + this.editFile( + webpackPath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-webpack-config', + contentToAdd: `,${args.config}`, + }), + ); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); + } + + get default() { + return this.asDefaultTaskGroup({ + loadEntities() { + const entities = this.sharedData.getEntities().map(({ entity }) => entity); + this.localEntities = entities.filter(entity => !entity.builtIn && !entity.skipClient); + }, + queueTranslateTransform({ control, application }) { + this.queueTransformStream( + { + name: 'translating angular application', + filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedAngularFile(file), + refresh: false, + }, + translateAngularFilesTransform(control.getWebappTranslation, application.enableTranslation), + ); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupOldFilesTask, + writeFiles, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + cleanupEntitiesFiles, + writeEntitiesFiles, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + postWriteEntitiesFiles, + }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.postWritingEntities); + } + + get end() { + return this.asEndTaskGroup({ + end({ application }) { + this.log.ok('Angular application generated successfully.'); + this.log.log( + chalk.green(` Start your Webpack development server with: + ${chalk.yellow.bold(`${application.nodePackageManager} start`)} +`), + ); + }, + }); + } + + get [BaseApplicationGenerator.END]() { + return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); + } + + /** + * @private + * Add new scss style to the angular application in "vendor.scss". + * + * @param {string} style - scss to add in the file + * @param {string} comment - comment to add before css code + * + * example: + * + * style = '.success {\n @extend .message;\n border-color: green;\n}' + * comment = 'Message' + * + * * ========================================================================== + * Message + * ========================================================================== * + * .success { + * @extend .message; + * border-color: green; + * } + * + */ + addVendorSCSSStyle(style, comment) { + this.needleApi.clientAngular.addVendorSCSSStyle(style, comment); + } + + /** + * @private + * Add a new lazy loaded module to admin routing file. + * + * @param {string} route - The route for the module. For example 'entity-audit'. + * @param {string} modulePath - The path to the module file. For example './entity-audit/entity-audit.module'. + * @param {string} moduleName - The name of the module. For example 'EntityAuditModule'. + * @param {string} pageTitle - The translation key if i18n is enabled or the text if i18n is disabled for the page title in the browser. + * For example 'entityAudit.home.title' for i18n enabled or 'Entity audit' for i18n disabled. + * If undefined then application global page title is used in the browser title bar. + */ + addAdminRoute(route, modulePath, moduleName, pageTitle) { + this.needleApi.clientAngular.addAdminRoute(route, modulePath, moduleName, pageTitle); + } + + /** + * @private + * Add a new module in the TS modules file. + * + * @param {string} appName - Angular2 application name. + * @param {string} angularName - The name of the new admin item. + * @param {string} folderName - The name of the folder. + * @param {string} fileName - The name of the file. + * @param {boolean} enableTranslation - If translations are enabled or not. + * @param {string} clientFramework - The name of the client framework. + */ + addAngularModule(appName, angularName, folderName, fileName, enableTranslation) { + this.needleApi.clientAngular.addModule(appName, angularName, folderName, fileName, enableTranslation); + } + + /** + * @private + * Add a new icon to icon imports. + * + * @param {string} iconName - The name of the Font Awesome icon. + */ + addIcon(iconName) { + this.needleApi.clientAngular.addIcon(iconName); + } + + /** + * Add a new menu element to the admin menu. + * + * @param {string} routerName - The name of the Angular router that is added to the admin menu. + * @param {string} iconName - The name of the Font Awesome icon that will be displayed. + * @param {boolean} enableTranslation - If translations are enabled or not + * @param {string} translationKeyMenu - i18n key for entry in the admin menu + */ + addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu = _.camelCase(routerName), jhiPrefix?) { + this.needleApi.clientAngular.addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu, jhiPrefix); + } + + addEntitiesToMenu({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { + const filePath = `${application.clientSrcDir}app/layouts/navbar/navbar.component.html`; + const ignoreNonExisting = chalk.yellow('Reference to entities not added to menu.'); + const editCallback = addToEntitiesMenu({ application, entities }); + + this.editFile(filePath, { ignoreNonExisting }, editCallback); + } + + addEntitiesToModule({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { + const filePath = `${application.clientSrcDir}app/entities/entity.routes.ts`; + const ignoreNonExisting = chalk.yellow(`Route(s) not added to ${filePath}.`); + const addRouteCallback = addEntitiesRoute({ application, entities }); + this.editFile(filePath, { ignoreNonExisting }, addRouteCallback); + } + + /** + * @private + * Add new scss style to the angular application in "global.scss + * + * @param {string} style - css to add in the file + * @param {string} comment - comment to add before css code + * + * example: + * + * style = '.jhipster {\n color: #baa186;\n}' + * comment = 'New JHipster color' + * + * * ========================================================================== + * New JHipster color + * ========================================================================== * + * .jhipster { + * color: #baa186; + * } + * + */ + addMainSCSSStyle(style, comment) { + this.needleApi.clientAngular.addGlobalSCSSStyle(style, comment); + } + + /** + * Returns the typescript import section of enums referenced by all fields of the entity. + * @param fields returns the import of enums that are referenced by the fields + * @returns {typeImports:Map} the fields that potentially contains some enum types + */ + generateEntityClientEnumImports(fields) { + return getClientEnumImportsFormat(fields, ANGULAR); + } + + /** + * Get the typescript type of a non-composite primary key + * @param primaryKey the primary key of the entity + * @returns {string} the typescript type. + */ + getTypescriptKeyType(primaryKey) { + return getTSKeyType(primaryKey); + } + + /** + * generates a value for a primary key type + * @param primaryKey the primary key attribute (or its type) of the entity + * @param index an index to add salt to the value + * @param wrapped if the value should be within quotes + * @returns {string|number|string} + */ + generateTestEntityId(primaryKey, index = 0, wrapped = true) { + return getTestEntityId(primaryKey, index, wrapped); + } + + /** + * @private + * Generate a test entity, for the PK references (when the PK is a composite, derived key) + * + * @param {any} primaryKey - primary key definition. + * @param {number} [index] - index of the primary key sample, pass undefined for a random key. + */ + generateTestEntityPrimaryKey(primaryKey, index) { + return getTestEntityPrimaryKey(primaryKey, index); + } + + /** + * @private + * Generate a test entity instance with faked values. + * + * @param {any} references - references to other entities. + * @param {any} additionalFields - additional fields to add to the entity or with default values that overrides generated values. + */ + generateTypescriptTestEntity(references, additionalFields) { + return generateTestEntity(references, additionalFields); + } + + /** + * @private + * Create a angular form path getter method of reference. + * + * @param {object} reference + * @param {string[]} prefix + * @return {string} + */ + buildAngularFormPath(reference, prefix = []) { + return angularFormPath(reference, prefix); + } + + /** + * @private + * Add a new menu element, at the root of the menu. + * + * @param {string} routerName - The name of the router that is added to the menu. + * @param {string} iconName - The name of the Font Awesome icon that will be displayed. + * @param {boolean} enableTranslation - If translations are enabled or not + * @param {string} clientFramework - The name of the client framework + * @param {string} translationKeyMenu - i18n key for entry in the menu + */ + addElementToMenu(routerName, iconName, enableTranslation, clientFramework, translationKeyMenu = _.camelCase(routerName)) { + this.needleApi.clientAngular.addElementToMenu(routerName, iconName, enableTranslation, translationKeyMenu); + } +} diff --git a/generators/angular/index.mts b/generators/angular/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/angular/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/angular/index.ts b/generators/angular/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/angular/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/angular/needle-api/needle-client-angular.mts b/generators/angular/needle-api/needle-client-angular.mts deleted file mode 100644 index 8535814ea1d9..000000000000 --- a/generators/angular/needle-api/needle-client-angular.mts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import needleClientBase from '../../client/needle-api/needle-client.mjs'; -import { LINE_LENGTH } from '../../generator-constants.mjs'; -import { stripMargin, upperFirstCamelCase } from '../../base/support/index.mjs'; -import { clientFrameworkTypes } from '../../../jdl/jhipster/index.mjs'; -import { createNeedleCallback } from '../../base/support/needles.mjs'; - -const { ANGULAR } = clientFrameworkTypes; -export default class extends needleClientBase { - addGlobalSCSSStyle(style, comment) { - const filePath = `${this.clientSrcDir}content/scss/global.scss`; - this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main'); - } - - addVendorSCSSStyle(style, comment) { - const filePath = `${this.clientSrcDir}content/scss/vendor.scss`; - super.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-vendor'); - } - - addModule(appName, angularName, folderName, fileName, enableTranslation) { - const modulePath = `${this.clientSrcDir}app/app.config.ts`; - const importNeedle = 'jhipster-needle-angular-add-module-import'; - const moduleNeedle = 'jhipster-needle-angular-add-module'; - - this._genericAddModule(appName, angularName, folderName, fileName, enableTranslation, ANGULAR, modulePath, importNeedle, moduleNeedle); - } - - _genericAddModule( - appName, - angularName, - folderName, - fileName, - enableTranslation, - clientFramework, - modulePath, - importNeedle, - moduleNeedle, - ) { - const errorMessage = `${ - chalk.yellow('Reference to ') + angularName + folderName + fileName + enableTranslation + clientFramework - } ${chalk.yellow(`not added to ${modulePath}.\n`)}`; - - const importRewriteFileModel = this._generateRewriteFileModelWithImportStatement( - appName, - angularName, - folderName, - fileName, - modulePath, - importNeedle, - ); - importRewriteFileModel.prettierAware = true; - this.addBlockContentToFile(importRewriteFileModel, errorMessage); - - const moduleRewriteFileModel = this._generateRewriteFileModelAddModule(appName, angularName, modulePath, moduleNeedle); - this.addBlockContentToFile(moduleRewriteFileModel, errorMessage); - } - - _generateRewriteFileModelWithImportStatement(appName, angularName, folderName, fileName, modulePath, needle) { - const importStatement = this._generateImportStatement(appName, angularName, folderName, fileName); - - return this.generateFileModel(modulePath, needle, stripMargin(importStatement)); - } - - _generateImportStatement(appName, angularName, folderName, fileName) { - let importStatement = `|import { ${appName}${angularName}Module } from './${folderName}/${fileName}.module';`; - if (importStatement.length > LINE_LENGTH) { - // prettier-ignore - importStatement = `|import { - | ${appName}${angularName}Module - |} from './${folderName}/${fileName}.module';`; - } - - return importStatement; - } - - _generateRewriteFileModelAddModule(appName, angularName, modulePath, needle) { - return this.generateFileModel(modulePath, needle, stripMargin(`|${appName}${angularName}Module,`)); - } - - addIcon(iconName) { - const iconsPath = `${this.clientSrcDir}app/config/font-awesome-icons.ts`; - const ignoreNonExisting = this.generator.sharedData.getControl().ignoreNeedlesError && 'Icon imports not updated with icon'; - const iconImport = `fa${upperFirstCamelCase(iconName)}`; - this.generator.editFile( - iconsPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-icon-import', - contentToCheck: new RegExp(`\\b${iconImport}\\b`), - contentToAdd: (content, { indentPrefix }) => - content.replace( - /(\r?\n)(\s*)\/\/ jhipster-needle-add-icon-import/g, - `\n${indentPrefix}${iconImport},\n${indentPrefix}// jhipster-needle-add-icon-import`, - ), - }), - ); - } - - addElementToMenu(routerName, iconName, enableTranslation, translationKeyMenu = routerName, jhiPrefix = 'jhi') { - const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; - const entityMenuPath = `${this.clientSrcDir}app/layouts/navbar/navbar.component.html`; - const routerLink = `routerLink="/${routerName}"`; - // prettier-ignore - const entityEntry = ``; - const rewriteFileModel = this.generateFileModel(entityMenuPath, 'jhipster-needle-add-element-to-menu', entityEntry); - rewriteFileModel.regexp = routerLink; - - this.addBlockContentToFile(rewriteFileModel, errorMessage); - this.addIcon(iconName); - } - - addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu = routerName, jhiPrefix = 'jhi') { - const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to admin menu.\n')}`; - const navbarAdminPath = `${this.clientSrcDir}app/layouts/navbar/navbar.component.html`; - const routerLink = `routerLink="/${routerName}"`; - // prettier-ignore - const entityEntry = `
  • - - - ${_.startCase(routerName)} - -
  • `; - const rewriteFileModel = this.generateFileModel(navbarAdminPath, 'jhipster-needle-add-element-to-admin-menu', entityEntry); - rewriteFileModel.regexp = routerLink; - - this.addBlockContentToFile(rewriteFileModel, errorMessage); - this.addIcon(iconName); - } - - _addRoute(route, modulePath, moduleName, needleName, filePath, pageTitle?, { contentToCheck }: { contentToCheck?: string } = {}) { - const ignoreNonExisting = `${chalk.yellow('Route ') + route + chalk.yellow(` not added to ${filePath}.\n`)}`; - // prettier-ignore - const routingEntry = ` - { - path: '${route}',${pageTitle ? ` - data: { pageTitle: '${pageTitle}' },` : ''} - loadChildren: () => import('${modulePath}')${moduleName ? `.then(m => m.${moduleName})` : ''}, - },`; - this.generator.editFile( - filePath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: needleName, - contentToAdd: routingEntry, - ignoreWhitespaces: true, - contentToCheck: contentToCheck ?? `path: '${route}'`, - autoIndent: true, - }), - ); - } - - addAdminRoute(route, modulePath, moduleName, pageTitle) { - const adminModulePath = `${this.clientSrcDir}app/admin/admin.routes.ts`; - this._addRoute(route, modulePath, moduleName, 'jhipster-needle-add-admin-route', adminModulePath, pageTitle); - } -} diff --git a/generators/angular/needle-api/needle-client-angular.ts b/generators/angular/needle-api/needle-client-angular.ts new file mode 100644 index 000000000000..7787b3a3f3d4 --- /dev/null +++ b/generators/angular/needle-api/needle-client-angular.ts @@ -0,0 +1,181 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import needleClientBase from '../../client/needle-api/needle-client.js'; +import { LINE_LENGTH } from '../../generator-constants.js'; +import { stripMargin, upperFirstCamelCase } from '../../base/support/index.js'; +import { clientFrameworkTypes } from '../../../jdl/jhipster/index.js'; +import { createNeedleCallback } from '../../base/support/needles.js'; + +const { ANGULAR } = clientFrameworkTypes; +export default class extends needleClientBase { + addGlobalSCSSStyle(style, comment) { + const filePath = `${this.clientSrcDir}content/scss/global.scss`; + this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main'); + } + + addVendorSCSSStyle(style, comment) { + const filePath = `${this.clientSrcDir}content/scss/vendor.scss`; + super.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-vendor'); + } + + addModule(appName, angularName, folderName, fileName, enableTranslation) { + const modulePath = `${this.clientSrcDir}app/app.config.ts`; + const importNeedle = 'jhipster-needle-angular-add-module-import'; + const moduleNeedle = 'jhipster-needle-angular-add-module'; + + this._genericAddModule(appName, angularName, folderName, fileName, enableTranslation, ANGULAR, modulePath, importNeedle, moduleNeedle); + } + + _genericAddModule( + appName, + angularName, + folderName, + fileName, + enableTranslation, + clientFramework, + modulePath, + importNeedle, + moduleNeedle, + ) { + const errorMessage = `${ + chalk.yellow('Reference to ') + angularName + folderName + fileName + enableTranslation + clientFramework + } ${chalk.yellow(`not added to ${modulePath}.\n`)}`; + + const importRewriteFileModel = this._generateRewriteFileModelWithImportStatement( + appName, + angularName, + folderName, + fileName, + modulePath, + importNeedle, + ); + importRewriteFileModel.prettierAware = true; + this.addBlockContentToFile(importRewriteFileModel, errorMessage); + + const moduleRewriteFileModel = this._generateRewriteFileModelAddModule(appName, angularName, modulePath, moduleNeedle); + this.addBlockContentToFile(moduleRewriteFileModel, errorMessage); + } + + _generateRewriteFileModelWithImportStatement(appName, angularName, folderName, fileName, modulePath, needle) { + const importStatement = this._generateImportStatement(appName, angularName, folderName, fileName); + + return this.generateFileModel(modulePath, needle, stripMargin(importStatement)); + } + + _generateImportStatement(appName, angularName, folderName, fileName) { + let importStatement = `|import { ${appName}${angularName}Module } from './${folderName}/${fileName}.module';`; + if (importStatement.length > LINE_LENGTH) { + // prettier-ignore + importStatement = `|import { + | ${appName}${angularName}Module + |} from './${folderName}/${fileName}.module';`; + } + + return importStatement; + } + + _generateRewriteFileModelAddModule(appName, angularName, modulePath, needle) { + return this.generateFileModel(modulePath, needle, stripMargin(`|${appName}${angularName}Module,`)); + } + + addIcon(iconName) { + const iconsPath = `${this.clientSrcDir}app/config/font-awesome-icons.ts`; + const ignoreNonExisting = this.generator.sharedData.getControl().ignoreNeedlesError && 'Icon imports not updated with icon'; + const iconImport = `fa${upperFirstCamelCase(iconName)}`; + this.generator.editFile( + iconsPath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-icon-import', + contentToCheck: new RegExp(`\\b${iconImport}\\b`), + contentToAdd: (content, { indentPrefix }) => + content.replace( + /(\r?\n)(\s*)\/\/ jhipster-needle-add-icon-import/g, + `\n${indentPrefix}${iconImport},\n${indentPrefix}// jhipster-needle-add-icon-import`, + ), + }), + ); + } + + addElementToMenu(routerName, iconName, enableTranslation, translationKeyMenu = routerName, jhiPrefix = 'jhi') { + const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; + const entityMenuPath = `${this.clientSrcDir}app/layouts/navbar/navbar.component.html`; + const routerLink = `routerLink="/${routerName}"`; + // prettier-ignore + const entityEntry = ``; + const rewriteFileModel = this.generateFileModel(entityMenuPath, 'jhipster-needle-add-element-to-menu', entityEntry); + rewriteFileModel.regexp = routerLink; + + this.addBlockContentToFile(rewriteFileModel, errorMessage); + this.addIcon(iconName); + } + + addElementToAdminMenu(routerName, iconName, enableTranslation, translationKeyMenu = routerName, jhiPrefix = 'jhi') { + const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to admin menu.\n')}`; + const navbarAdminPath = `${this.clientSrcDir}app/layouts/navbar/navbar.component.html`; + const routerLink = `routerLink="/${routerName}"`; + // prettier-ignore + const entityEntry = `
  • + + + ${_.startCase(routerName)} + +
  • `; + const rewriteFileModel = this.generateFileModel(navbarAdminPath, 'jhipster-needle-add-element-to-admin-menu', entityEntry); + rewriteFileModel.regexp = routerLink; + + this.addBlockContentToFile(rewriteFileModel, errorMessage); + this.addIcon(iconName); + } + + _addRoute(route, modulePath, moduleName, needleName, filePath, pageTitle?, { contentToCheck }: { contentToCheck?: string } = {}) { + const ignoreNonExisting = `${chalk.yellow('Route ') + route + chalk.yellow(` not added to ${filePath}.\n`)}`; + // prettier-ignore + const routingEntry = ` + { + path: '${route}',${pageTitle ? ` + data: { pageTitle: '${pageTitle}' },` : ''} + loadChildren: () => import('${modulePath}')${moduleName ? `.then(m => m.${moduleName})` : ''}, + },`; + this.generator.editFile( + filePath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: needleName, + contentToAdd: routingEntry, + ignoreWhitespaces: true, + contentToCheck: contentToCheck ?? `path: '${route}'`, + autoIndent: true, + }), + ); + } + + addAdminRoute(route, modulePath, moduleName, pageTitle) { + const adminModulePath = `${this.clientSrcDir}app/admin/admin.routes.ts`; + this._addRoute(route, modulePath, moduleName, 'jhipster-needle-add-admin-route', adminModulePath, pageTitle); + } +} diff --git a/generators/angular/support/index.mts b/generators/angular/support/index.mts deleted file mode 100644 index bffdde88cd2f..000000000000 --- a/generators/angular/support/index.mts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2013-2023 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 './path-utils.mjs'; -export * from './needles.mjs'; -export * from './translate-angular.mjs'; -export { default as translateAngularFilesTransform } from './translate-angular.mjs'; -export { default as updateLanguagesTask } from './update-languages.mjs'; diff --git a/generators/angular/support/index.ts b/generators/angular/support/index.ts new file mode 100644 index 000000000000..6c220a721daa --- /dev/null +++ b/generators/angular/support/index.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2013-2023 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 './path-utils.js'; +export * from './needles.js'; +export * from './translate-angular.js'; +export { default as translateAngularFilesTransform } from './translate-angular.js'; +export { default as updateLanguagesTask } from './update-languages.js'; diff --git a/generators/angular/support/needles.mts b/generators/angular/support/needles.mts deleted file mode 100644 index c6a1831d8ab1..000000000000 --- a/generators/angular/support/needles.mts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright 2013-2023 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 { Entity } from '../../base-application/index.mjs'; -import type { BaseApplication, CommonClientServerApplication } from '../../base-application/types.mjs'; -import { createNeedleCallback } from '../../base/support/needles.mjs'; -import { upperFirstCamelCase } from '../../base/support/string.mjs'; -import { joinCallbacks } from '../../base/support/write-files.mjs'; - -export function addRoute({ - needle, - route, - pageTitle, - title, - modulePath, - moduleName, - component, -}: { - needle: string; - route: string; - modulePath: string; - pageTitle?: string; - title?: string; - moduleName?: string; - component?: boolean; -}) { - const routePath = `path: '${route}',`; - // prettier-ignore - const contentToAdd = ` - { - ${routePath}${pageTitle ? ` - data: { pageTitle: '${pageTitle}' },` : ''}${title ? ` - title: '${title}',` : ''} - load${component ? 'Component' : 'Children'}: () => import('${modulePath}')${moduleName ? `.then(m => m.${moduleName})` : ''}, - },`; - return createNeedleCallback({ - needle, - contentToAdd, - contentToCheck: routePath, - }); -} - -export function addEntitiesRoute({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { - const { enableTranslation } = application; - return joinCallbacks( - ...entities.map(entity => { - const { i18nKeyPrefix, entityClassPlural, entityFolderName, entityFileName, entityUrl } = entity; - - const pageTitle = enableTranslation ? `${i18nKeyPrefix}.home.title` : entityClassPlural; - const modulePath = `./${entityFolderName}/${entityFileName}.routes`; - - return addRoute({ - needle: 'jhipster-needle-add-entity-route', - route: entityUrl, - modulePath, - pageTitle, - }); - }), - ); -} - -type MenuItem = { - jhiPrefix: string; - enableTranslation?: boolean; - route: string; - translationKey?: string; - icon?: string; - name?: string; -}; - -export function addItemToMenu({ - needle, - enableTranslation, - jhiPrefix, - icon = 'asterisk', - route, - translationKey, - name = '', -}: MenuItem & { needle: string }) { - const routerLink = `routerLink="/${route}"`; - const contentToAdd = ` -
  • - - - ${name} - -
  • `; - return createNeedleCallback({ - needle, - contentToAdd, - contentToCheck: routerLink, - }); -} - -export const addItemToAdminMenu = (menu: MenuItem) => - addItemToMenu({ - needle: 'add-element-to-admin-menu', - ...menu, - }); - -export const addIconImport = ({ icon }: { icon: string }) => { - const iconImport = `fa${upperFirstCamelCase(icon)}`; - return createNeedleCallback({ - needle: 'jhipster-needle-add-icon-import', - contentToCheck: new RegExp(`\\b${iconImport}\\b`), - contentToAdd: (content, { indentPrefix }) => - content.replace( - /(\r?\n)(\s*)\/\/ jhipster-needle-add-icon-import/g, - `\n${indentPrefix}${iconImport},\n${indentPrefix}// jhipster-needle-add-icon-import`, - ), - }); -}; - -export function addToEntitiesMenu({ application, entities }: { application: BaseApplication; entities: Entity[] }) { - const { enableTranslation, jhiPrefix } = application; - return joinCallbacks( - ...entities.map(entity => { - const { entityPage, entityTranslationKeyMenu, entityClassHumanized } = entity; - const routerLink = `routerLink="/${entityPage}"`; - - // prettier-ignore - const contentToAdd =` -
  • - - - ${entityClassHumanized} - -
  • `; - - return createNeedleCallback({ - needle: 'jhipster-needle-add-entity-to-menu', - contentToAdd, - contentToCheck: routerLink, - }); - }), - ); -} diff --git a/generators/angular/support/needles.ts b/generators/angular/support/needles.ts new file mode 100644 index 000000000000..1b4162da3fbc --- /dev/null +++ b/generators/angular/support/needles.ts @@ -0,0 +1,152 @@ +/** + * Copyright 2013-2023 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 { Entity } from '../../base-application/index.js'; +import type { BaseApplication, CommonClientServerApplication } from '../../base-application/types.js'; +import { createNeedleCallback } from '../../base/support/needles.js'; +import { upperFirstCamelCase } from '../../base/support/string.js'; +import { joinCallbacks } from '../../base/support/write-files.js'; + +export function addRoute({ + needle, + route, + pageTitle, + title, + modulePath, + moduleName, + component, +}: { + needle: string; + route: string; + modulePath: string; + pageTitle?: string; + title?: string; + moduleName?: string; + component?: boolean; +}) { + const routePath = `path: '${route}',`; + // prettier-ignore + const contentToAdd = ` + { + ${routePath}${pageTitle ? ` + data: { pageTitle: '${pageTitle}' },` : ''}${title ? ` + title: '${title}',` : ''} + load${component ? 'Component' : 'Children'}: () => import('${modulePath}')${moduleName ? `.then(m => m.${moduleName})` : ''}, + },`; + return createNeedleCallback({ + needle, + contentToAdd, + contentToCheck: routePath, + }); +} + +export function addEntitiesRoute({ application, entities }: { application: CommonClientServerApplication; entities: Entity[] }) { + const { enableTranslation } = application; + return joinCallbacks( + ...entities.map(entity => { + const { i18nKeyPrefix, entityClassPlural, entityFolderName, entityFileName, entityUrl } = entity; + + const pageTitle = enableTranslation ? `${i18nKeyPrefix}.home.title` : entityClassPlural; + const modulePath = `./${entityFolderName}/${entityFileName}.routes`; + + return addRoute({ + needle: 'jhipster-needle-add-entity-route', + route: entityUrl, + modulePath, + pageTitle, + }); + }), + ); +} + +type MenuItem = { + jhiPrefix: string; + enableTranslation?: boolean; + route: string; + translationKey?: string; + icon?: string; + name?: string; +}; + +export function addItemToMenu({ + needle, + enableTranslation, + jhiPrefix, + icon = 'asterisk', + route, + translationKey, + name = '', +}: MenuItem & { needle: string }) { + const routerLink = `routerLink="/${route}"`; + const contentToAdd = ` +
  • + + + ${name} + +
  • `; + return createNeedleCallback({ + needle, + contentToAdd, + contentToCheck: routerLink, + }); +} + +export const addItemToAdminMenu = (menu: MenuItem) => + addItemToMenu({ + needle: 'add-element-to-admin-menu', + ...menu, + }); + +export const addIconImport = ({ icon }: { icon: string }) => { + const iconImport = `fa${upperFirstCamelCase(icon)}`; + return createNeedleCallback({ + needle: 'jhipster-needle-add-icon-import', + contentToCheck: new RegExp(`\\b${iconImport}\\b`), + contentToAdd: (content, { indentPrefix }) => + content.replace( + /(\r?\n)(\s*)\/\/ jhipster-needle-add-icon-import/g, + `\n${indentPrefix}${iconImport},\n${indentPrefix}// jhipster-needle-add-icon-import`, + ), + }); +}; + +export function addToEntitiesMenu({ application, entities }: { application: BaseApplication; entities: Entity[] }) { + const { enableTranslation, jhiPrefix } = application; + return joinCallbacks( + ...entities.map(entity => { + const { entityPage, entityTranslationKeyMenu, entityClassHumanized } = entity; + const routerLink = `routerLink="/${entityPage}"`; + + // prettier-ignore + const contentToAdd =` +
  • + + + ${entityClassHumanized} + +
  • `; + + return createNeedleCallback({ + needle: 'jhipster-needle-add-entity-to-menu', + contentToAdd, + contentToCheck: routerLink, + }); + }), + ); +} diff --git a/generators/angular/support/path-utils.mjs b/generators/angular/support/path-utils.js similarity index 100% rename from generators/angular/support/path-utils.mjs rename to generators/angular/support/path-utils.js diff --git a/generators/angular/support/translate-angular.js b/generators/angular/support/translate-angular.js new file mode 100644 index 000000000000..f33c2f48954b --- /dev/null +++ b/generators/angular/support/translate-angular.js @@ -0,0 +1,148 @@ +/** + * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; +import { Minimatch } from 'minimatch'; + +import { createJhiTransformTranslateReplacer, createJhiTransformTranslateStringifyReplacer } from '../../languages/support/index.js'; + +const PLACEHOLDER_REGEX = /(?:placeholder|title)=['|"](\{\{\s?['|"]([a-zA-Z0-9.\-_]+)['|"]\s?\|\s?translate\s?\}\})['|"]/.source; + +const JHI_TRANSLATE_REGEX = /(\n?\s*[a-z][a-zA-Z]*Translate="[a-zA-Z0-9 +{}'_!?.]+")/.source; +const TRANSLATE_VALUES_REGEX = /(\n?\s*\[translateValues\]="\{(?:(?!\}").)*?\}")/.source; +const TRANSLATE_REGEX = [JHI_TRANSLATE_REGEX, TRANSLATE_VALUES_REGEX].join('|'); + +function getTranslationValue(getWebappTranslation, key, data) { + return getWebappTranslation(key, data) || undefined; +} + +/** + * Replace translation key with translation values + * + * @param {import('../generator-base.js')} generator + * @param {string} content + * @param {string} regexSource regular expression to find keys + * @param {object} [options] + * @param {number} [options.keyIndex] + * @param {number} [options.replacementIndex] + * @returns {string} + */ +function replaceTranslationKeysWithText(getWebappTranslation, content, regexSource, { keyIndex = 1, replacementIndex = 1, escape } = {}) { + const regex = new RegExp(regexSource, 'g'); + const allMatches = content.matchAll(regex); + for (const match of allMatches) { + // match is now the next match, in array form and our key is at index 1, index 1 is replace target. + const key = match[keyIndex]; + const target = match[replacementIndex]; + let translation = getTranslationValue(getWebappTranslation, key); + if (escape) { + translation = escape(translation, match); + } + content = content.replace(target, translation); + } + return content; +} + +/** + * + * @param {import('../generator-base.js')} generator reference to the generator + * @param {string} content html content + * @param {string} jsKey + * @returns string with jsKey value replaced + */ +function replaceJSTranslation(getWebappTranslation, content, jsKey) { + return replaceTranslationKeysWithText( + getWebappTranslation, + content, + `${jsKey}\\s?:\\s?['|"]([a-zA-Z0-9.\\-_]+\\.[a-zA-Z0-9.\\-_]+)['|"]`, + { + escape: (translation, match) => translation.replaceAll(match[0].slice(-1), `\\${match[0].slice(-1)}`), + }, + ); +} + +/** + * + * @param {import('../generator-base.js')} generator reference to the generator + * @param {string} content html content + * @returns string with pageTitle replaced + */ +function replacePageTitles(getWebappTranslation, content) { + return replaceJSTranslation(getWebappTranslation, content, 'title'); +} + +/** + * @type {function(import('../generator-base.js'), string): string} + */ +function replacePlaceholders(getWebappTranslation, content) { + return replaceTranslationKeysWithText(getWebappTranslation, content, PLACEHOLDER_REGEX, { keyIndex: 2 }); +} + +/** + * Replace error code translation key with translated message + * + * @type {function(import('../generator-base.js'), string): string} + */ +function replaceErrorMessage(getWebappTranslation, content) { + return replaceJSTranslation(getWebappTranslation, content, 'errorMessage'); +} + +/** + * Replace and cleanup translations. + * + * @type {import('../generator-base.js').EditFileCallback} + * @this {import('../generator-base.js')} + */ +export const createTranslationReplacer = (getWebappTranslation, enableTranslation) => { + const htmlJhiTranslateReplacer = createJhiTransformTranslateReplacer(getWebappTranslation, { escapeHtml: true }); + const htmlJhiTranslateStringifyReplacer = createJhiTransformTranslateStringifyReplacer(getWebappTranslation, { escapeHtml: true }); + return function replaceAngularTranslations(content, filePath) { + if (/\.html$/.test(filePath)) { + if (!enableTranslation) { + content = content.replace(new RegExp(TRANSLATE_REGEX, 'g'), ''); + content = replacePlaceholders(getWebappTranslation, content); + } + } + // Translate html files and inline templates. + if (/(:?\.html|component\.ts)$/.test(filePath)) { + content = htmlJhiTranslateReplacer(content); + content = htmlJhiTranslateStringifyReplacer(content); + } + if (!enableTranslation) { + if (/(:?route|module)\.ts$/.test(filePath)) { + content = replacePageTitles(getWebappTranslation, content); + } + if (/error\.route\.ts$/.test(filePath)) { + content = replaceErrorMessage(getWebappTranslation, content); + } + } + return content; + }; +}; + +const minimatch = new Minimatch('**/*{.html,.component.ts,.route.ts,.module.ts}'); +export const isTranslatedAngularFile = file => minimatch.match(file.path); + +const translateAngularFilesTransform = (getWebappTranslation, enableTranslation) => { + const translate = createTranslationReplacer(getWebappTranslation, enableTranslation); + return passthrough(file => { + file.contents = Buffer.from(translate(file.contents.toString(), file.path)); + }); +}; + +export default translateAngularFilesTransform; diff --git a/generators/angular/support/translate-angular.mjs b/generators/angular/support/translate-angular.mjs deleted file mode 100644 index c394e664d263..000000000000 --- a/generators/angular/support/translate-angular.mjs +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; -import { Minimatch } from 'minimatch'; - -import { createJhiTransformTranslateReplacer, createJhiTransformTranslateStringifyReplacer } from '../../languages/support/index.mjs'; - -const PLACEHOLDER_REGEX = /(?:placeholder|title)=['|"](\{\{\s?['|"]([a-zA-Z0-9.\-_]+)['|"]\s?\|\s?translate\s?\}\})['|"]/.source; - -const JHI_TRANSLATE_REGEX = /(\n?\s*[a-z][a-zA-Z]*Translate="[a-zA-Z0-9 +{}'_!?.]+")/.source; -const TRANSLATE_VALUES_REGEX = /(\n?\s*\[translateValues\]="\{(?:(?!\}").)*?\}")/.source; -const TRANSLATE_REGEX = [JHI_TRANSLATE_REGEX, TRANSLATE_VALUES_REGEX].join('|'); - -function getTranslationValue(getWebappTranslation, key, data) { - return getWebappTranslation(key, data) || undefined; -} - -/** - * Replace translation key with translation values - * - * @param {import('../generator-base.js')} generator - * @param {string} content - * @param {string} regexSource regular expression to find keys - * @param {object} [options] - * @param {number} [options.keyIndex] - * @param {number} [options.replacementIndex] - * @returns {string} - */ -function replaceTranslationKeysWithText(getWebappTranslation, content, regexSource, { keyIndex = 1, replacementIndex = 1, escape } = {}) { - const regex = new RegExp(regexSource, 'g'); - const allMatches = content.matchAll(regex); - for (const match of allMatches) { - // match is now the next match, in array form and our key is at index 1, index 1 is replace target. - const key = match[keyIndex]; - const target = match[replacementIndex]; - let translation = getTranslationValue(getWebappTranslation, key); - if (escape) { - translation = escape(translation, match); - } - content = content.replace(target, translation); - } - return content; -} - -/** - * - * @param {import('../generator-base.js')} generator reference to the generator - * @param {string} content html content - * @param {string} jsKey - * @returns string with jsKey value replaced - */ -function replaceJSTranslation(getWebappTranslation, content, jsKey) { - return replaceTranslationKeysWithText( - getWebappTranslation, - content, - `${jsKey}\\s?:\\s?['|"]([a-zA-Z0-9.\\-_]+\\.[a-zA-Z0-9.\\-_]+)['|"]`, - { - escape: (translation, match) => translation.replaceAll(match[0].slice(-1), `\\${match[0].slice(-1)}`), - }, - ); -} - -/** - * - * @param {import('../generator-base.js')} generator reference to the generator - * @param {string} content html content - * @returns string with pageTitle replaced - */ -function replacePageTitles(getWebappTranslation, content) { - return replaceJSTranslation(getWebappTranslation, content, 'title'); -} - -/** - * @type {function(import('../generator-base.js'), string): string} - */ -function replacePlaceholders(getWebappTranslation, content) { - return replaceTranslationKeysWithText(getWebappTranslation, content, PLACEHOLDER_REGEX, { keyIndex: 2 }); -} - -/** - * Replace error code translation key with translated message - * - * @type {function(import('../generator-base.js'), string): string} - */ -function replaceErrorMessage(getWebappTranslation, content) { - return replaceJSTranslation(getWebappTranslation, content, 'errorMessage'); -} - -/** - * Replace and cleanup translations. - * - * @type {import('../generator-base.js').EditFileCallback} - * @this {import('../generator-base.js')} - */ -export const createTranslationReplacer = (getWebappTranslation, enableTranslation) => { - const htmlJhiTranslateReplacer = createJhiTransformTranslateReplacer(getWebappTranslation, { escapeHtml: true }); - const htmlJhiTranslateStringifyReplacer = createJhiTransformTranslateStringifyReplacer(getWebappTranslation, { escapeHtml: true }); - return function replaceAngularTranslations(content, filePath) { - if (/\.html$/.test(filePath)) { - if (!enableTranslation) { - content = content.replace(new RegExp(TRANSLATE_REGEX, 'g'), ''); - content = replacePlaceholders(getWebappTranslation, content); - } - } - // Translate html files and inline templates. - if (/(:?\.html|component\.ts)$/.test(filePath)) { - content = htmlJhiTranslateReplacer(content); - content = htmlJhiTranslateStringifyReplacer(content); - } - if (!enableTranslation) { - if (/(:?route|module)\.ts$/.test(filePath)) { - content = replacePageTitles(getWebappTranslation, content); - } - if (/error\.route\.ts$/.test(filePath)) { - content = replaceErrorMessage(getWebappTranslation, content); - } - } - return content; - }; -}; - -const minimatch = new Minimatch('**/*{.html,.component.ts,.route.ts,.module.ts}'); -export const isTranslatedAngularFile = file => minimatch.match(file.path); - -const translateAngularFilesTransform = (getWebappTranslation, enableTranslation) => { - const translate = createTranslationReplacer(getWebappTranslation, enableTranslation); - return passthrough(file => { - file.contents = Buffer.from(translate(file.contents.toString(), file.path)); - }); -}; - -export default translateAngularFilesTransform; diff --git a/generators/angular/support/translate-angular.spec.mts b/generators/angular/support/translate-angular.spec.mts deleted file mode 100644 index 1ff0ad7fba5f..000000000000 --- a/generators/angular/support/translate-angular.spec.mts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; - -import { createTranslationReplacer } from './translate-angular.mjs'; - -describe('generator - angular - transform', () => { - describe('replaceAngularTranslations', () => { - let replaceAngularTranslations; - - beforeEach(() => { - let value = 0; - replaceAngularTranslations = createTranslationReplacer(esmocha.fn().mockImplementation(key => `translated-value-${key}-${value++}`)); - }); - - describe('with translation disabled', () => { - describe('.html files', () => { - const extension = '.html'; - - it('should translate __jhiTransformTranslate__ function', () => { - const body = ` -

    __jhiTransformTranslate__('activate.title1')

    -

    __jhiTransformTranslate__('activate.title2')

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    translated-value-activate.title1-0

    -

    translated-value-activate.title2-1

    -" -`); - }); - - it('should remove jhiTranslate attribute', () => { - const body = ` -

    activate.title1

    -

    activate.title2

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    activate.title1

    -

    activate.title2

    -" -`); - }); - - it('should remove [translateValues] attribute', () => { - const body = ` -

    translate-values1

    -

    translate-values2

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    translate-values1

    -

    translate-values2

    -" -`); - }); - - it('should remove neasted [translateValues] attribute', () => { - const body = ` -

    translate-values1

    -

    translate-values2

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    translate-values1

    -

    translate-values2

    -" -`); - }); - - it('should remove [translateValues] attribute with any character', () => { - const body = ` -

    translate-values1

    -

    translate-values2

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    translate-values1

    -

    translate-values2

    -" -`); - }); - - it('should remove neasted [translateValues] attribute', () => { - const body = ` -

    translate-values1

    -

    translate-values2

    -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -

    translate-values1

    -

    translate-values2

    -" -`); - }); - - it('should remove placeholder attribute value with translated value', () => { - const body = ` - - -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" - - -" -`); - }); - - it('should remove title attribute value with translated value', () => { - const body = ` - - -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" - - -" -`); - }); - }); - - describe('.route.ts files', () => { - const extension = '.route.ts'; - - it('should translate title fields with translation values', () => { - const body = ` -title: 'activate.title1', -title: 'activate.title2', -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -title: 'translated-value-activate.title1-0', -title: 'translated-value-activate.title2-1', -" -`); - }); - }); - - describe('.module.ts files', () => { - const extension = '.module.ts'; - - it('should translate title fields with translation values', () => { - const body = ` -title: 'activate.title1', -title: 'activate.title2', -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -title: 'translated-value-activate.title1-0', -title: 'translated-value-activate.title2-1', -" -`); - }); - }); - - describe('.error.route.ts files', () => { - const extension = '.error.route.ts'; - - it('should translate pageTitle fields with translation values', () => { - const body = ` -errorMessage: 'activate.title1', -errorMessage: 'activate.title2', -`; - expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` -" -errorMessage: 'translated-value-activate.title1-0', -errorMessage: 'translated-value-activate.title2-1', -" -`); - }); - }); - }); - }); -}); diff --git a/generators/angular/support/translate-angular.spec.ts b/generators/angular/support/translate-angular.spec.ts new file mode 100644 index 000000000000..b9cf8c0b3312 --- /dev/null +++ b/generators/angular/support/translate-angular.spec.ts @@ -0,0 +1,193 @@ +/** + * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; + +import { createTranslationReplacer } from './translate-angular.js'; + +describe('generator - angular - transform', () => { + describe('replaceAngularTranslations', () => { + let replaceAngularTranslations; + + beforeEach(() => { + let value = 0; + replaceAngularTranslations = createTranslationReplacer(esmocha.fn().mockImplementation(key => `translated-value-${key}-${value++}`)); + }); + + describe('with translation disabled', () => { + describe('.html files', () => { + const extension = '.html'; + + it('should translate __jhiTransformTranslate__ function', () => { + const body = ` +

    __jhiTransformTranslate__('activate.title1')

    +

    __jhiTransformTranslate__('activate.title2')

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    translated-value-activate.title1-0

    +

    translated-value-activate.title2-1

    +" +`); + }); + + it('should remove jhiTranslate attribute', () => { + const body = ` +

    activate.title1

    +

    activate.title2

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    activate.title1

    +

    activate.title2

    +" +`); + }); + + it('should remove [translateValues] attribute', () => { + const body = ` +

    translate-values1

    +

    translate-values2

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    translate-values1

    +

    translate-values2

    +" +`); + }); + + it('should remove neasted [translateValues] attribute', () => { + const body = ` +

    translate-values1

    +

    translate-values2

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    translate-values1

    +

    translate-values2

    +" +`); + }); + + it('should remove [translateValues] attribute with any character', () => { + const body = ` +

    translate-values1

    +

    translate-values2

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    translate-values1

    +

    translate-values2

    +" +`); + }); + + it('should remove neasted [translateValues] attribute', () => { + const body = ` +

    translate-values1

    +

    translate-values2

    +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +

    translate-values1

    +

    translate-values2

    +" +`); + }); + + it('should remove placeholder attribute value with translated value', () => { + const body = ` + + +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" + + +" +`); + }); + + it('should remove title attribute value with translated value', () => { + const body = ` + + +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" + + +" +`); + }); + }); + + describe('.route.ts files', () => { + const extension = '.route.ts'; + + it('should translate title fields with translation values', () => { + const body = ` +title: 'activate.title1', +title: 'activate.title2', +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +title: 'translated-value-activate.title1-0', +title: 'translated-value-activate.title2-1', +" +`); + }); + }); + + describe('.module.ts files', () => { + const extension = '.module.ts'; + + it('should translate title fields with translation values', () => { + const body = ` +title: 'activate.title1', +title: 'activate.title2', +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +title: 'translated-value-activate.title1-0', +title: 'translated-value-activate.title2-1', +" +`); + }); + }); + + describe('.error.route.ts files', () => { + const extension = '.error.route.ts'; + + it('should translate pageTitle fields with translation values', () => { + const body = ` +errorMessage: 'activate.title1', +errorMessage: 'activate.title2', +`; + expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(` +" +errorMessage: 'translated-value-activate.title1-0', +errorMessage: 'translated-value-activate.title2-1', +" +`); + }); + }); + }); + }); +}); diff --git a/generators/angular/support/update-languages.mts b/generators/angular/support/update-languages.mts deleted file mode 100644 index a8ac51a6c8ca..000000000000 --- a/generators/angular/support/update-languages.mts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../../base/index.mjs'; -import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/index.mjs'; -import { generateLanguagesWebappOptions } from '../../languages/support/index.mjs'; - -function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languagesDefinition = [] } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - const newContent = `{ - ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')} - // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object - }; -`; - this.editFile(`${clientSrcDir}app/shared/language/find-language-from-key.pipe.ts`, { ignoreNonExisting }, content => - content.replace(/{\s*('[a-z-]*':)?([^=]*jhipster-needle-i18n-language-key-pipe[^;]*)\};/g, newContent), - ); -} - -function updateLanguagesInConstantsTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'export const LANGUAGES: string[] = [\n'; - languages?.forEach(lang => { - newContent += ` '${lang}',\n`; - }); - newContent += ' // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array\n];'; - - this.editFile(`${clientSrcDir}app/config/language.constants.ts`, { ignoreNonExisting }, content => - content.replace(/export.*LANGUAGES.*\[([^\]]*jhipster-needle-i18n-language-constant[^\]]*)\];/g, newContent), - ); -} - -function updateLanguagesInWebpackTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, clientRootDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'groupBy: [\n'; - const srcRelativePath = this.relativeDir(clientRootDir, clientSrcDir); - languages?.forEach(language => { - newContent += ` { pattern: "./${srcRelativePath}i18n/${language}/*.json", fileName: "./i18n/${language}.json" },\n`; - }); - newContent += - ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n' + - ' ]'; - - this.editFile(`${clientRootDir}webpack/webpack.custom.js`, { ignoreNonExisting }, content => - content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), - ); -} - -export default function updateLanguagesTask(this: BaseGenerator, param: UpdateClientLanguagesTaskParam) { - updateLanguagesInPipeTask.call(this, param); - updateLanguagesInConstantsTask.call(this, param); - updateLanguagesInWebpackTask.call(this, param); - updateLanguagesInDayjsConfigurationTask.call(this, param, { configurationFile: `${param.application.clientSrcDir}app/config/dayjs.ts` }); -} diff --git a/generators/angular/support/update-languages.ts b/generators/angular/support/update-languages.ts new file mode 100644 index 000000000000..c9fec6357dd6 --- /dev/null +++ b/generators/angular/support/update-languages.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../../base/index.js'; +import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/index.js'; +import { generateLanguagesWebappOptions } from '../../languages/support/index.js'; + +function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languagesDefinition = [] } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + const newContent = `{ + ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')} + // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object + }; +`; + this.editFile(`${clientSrcDir}app/shared/language/find-language-from-key.pipe.ts`, { ignoreNonExisting }, content => + content.replace(/{\s*('[a-z-]*':)?([^=]*jhipster-needle-i18n-language-key-pipe[^;]*)\};/g, newContent), + ); +} + +function updateLanguagesInConstantsTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languages } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + let newContent = 'export const LANGUAGES: string[] = [\n'; + languages?.forEach(lang => { + newContent += ` '${lang}',\n`; + }); + newContent += ' // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array\n];'; + + this.editFile(`${clientSrcDir}app/config/language.constants.ts`, { ignoreNonExisting }, content => + content.replace(/export.*LANGUAGES.*\[([^\]]*jhipster-needle-i18n-language-constant[^\]]*)\];/g, newContent), + ); +} + +function updateLanguagesInWebpackTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, clientRootDir, languages } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + let newContent = 'groupBy: [\n'; + const srcRelativePath = this.relativeDir(clientRootDir, clientSrcDir); + languages?.forEach(language => { + newContent += ` { pattern: "./${srcRelativePath}i18n/${language}/*.json", fileName: "./i18n/${language}.json" },\n`; + }); + newContent += + ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n' + + ' ]'; + + this.editFile(`${clientRootDir}webpack/webpack.custom.js`, { ignoreNonExisting }, content => + content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), + ); +} + +export default function updateLanguagesTask(this: BaseGenerator, param: UpdateClientLanguagesTaskParam) { + updateLanguagesInPipeTask.call(this, param); + updateLanguagesInConstantsTask.call(this, param); + updateLanguagesInWebpackTask.call(this, param); + updateLanguagesInDayjsConfigurationTask.call(this, param, { configurationFile: `${param.application.clientSrcDir}app/config/dayjs.ts` }); +} diff --git a/generators/angular/types.d.mts b/generators/angular/types.d.ts similarity index 100% rename from generators/angular/types.d.mts rename to generators/angular/types.d.ts diff --git a/generators/app/__snapshots__/generator.spec.mts.snap b/generators/app/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/app/__snapshots__/generator.spec.mts.snap rename to generators/app/__snapshots__/generator.spec.ts.snap diff --git a/generators/app/cleanup.mjs b/generators/app/cleanup.js similarity index 100% rename from generators/app/cleanup.mjs rename to generators/app/cleanup.js diff --git a/generators/app/command.mts b/generators/app/command.mts deleted file mode 100644 index 7a9f583d711f..000000000000 --- a/generators/app/command.mts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; -import { - GENERATOR_BOOTSTRAP, - GENERATOR_BOOTSTRAP_APPLICATION_BASE, - GENERATOR_CLIENT, - GENERATOR_COMMON, - GENERATOR_CYPRESS, - GENERATOR_GIT, - GENERATOR_LANGUAGES, - GENERATOR_SERVER, -} from '../generator-list.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - defaults: { - description: 'Execute jhipster with default config', - type: Boolean, - }, - skipClient: { - description: 'Skip the client-side application generation', - type: Boolean, - scope: 'storage', - }, - skipServer: { - description: 'Skip the server-side application generation', - type: Boolean, - scope: 'storage', - }, - jhiPrefix: { - description: 'Add prefix before services, controllers and states name', - type: String, - scope: 'storage', - }, - entitySuffix: { - description: 'Add suffix after entities name', - type: String, - scope: 'storage', - }, - dtoSuffix: { - description: 'Add suffix after dtos name', - type: String, - scope: 'storage', - }, - blueprint: { - description: 'DEPRECATED: Specify a generator blueprint to use for the sub generators', - type: Array, - }, - blueprints: { - description: - 'A comma separated list of one or more generator blueprints to use for the sub generators, e.g. --blueprints kotlin,vuejs', - type: String, - }, - ignoreErrors: { - description: "Don't fail on prettier errors.", - type: Boolean, - }, - pkType: { - description: 'Default primary key type (beta)', - type: String, - scope: 'storage', - }, - clientPackageManager: { - description: 'Force an unsupported client package manager', - type: String, - scope: 'storage', - }, - testFrameworks: { - description: 'Test frameworks to be generated', - type: Array, - }, - }, - import: [ - GENERATOR_BOOTSTRAP, - GENERATOR_BOOTSTRAP_APPLICATION_BASE, - GENERATOR_COMMON, - GENERATOR_SERVER, - GENERATOR_CLIENT, - GENERATOR_GIT, - GENERATOR_CYPRESS, - GENERATOR_LANGUAGES, - ], -}; - -export default command; diff --git a/generators/app/command.ts b/generators/app/command.ts new file mode 100644 index 000000000000..57a802358250 --- /dev/null +++ b/generators/app/command.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; +import { + GENERATOR_BOOTSTRAP, + GENERATOR_BOOTSTRAP_APPLICATION_BASE, + GENERATOR_CLIENT, + GENERATOR_COMMON, + GENERATOR_CYPRESS, + GENERATOR_GIT, + GENERATOR_LANGUAGES, + GENERATOR_SERVER, +} from '../generator-list.js'; + +const command: JHipsterCommandDefinition = { + options: { + defaults: { + description: 'Execute jhipster with default config', + type: Boolean, + }, + skipClient: { + description: 'Skip the client-side application generation', + type: Boolean, + scope: 'storage', + }, + skipServer: { + description: 'Skip the server-side application generation', + type: Boolean, + scope: 'storage', + }, + jhiPrefix: { + description: 'Add prefix before services, controllers and states name', + type: String, + scope: 'storage', + }, + entitySuffix: { + description: 'Add suffix after entities name', + type: String, + scope: 'storage', + }, + dtoSuffix: { + description: 'Add suffix after dtos name', + type: String, + scope: 'storage', + }, + blueprint: { + description: 'DEPRECATED: Specify a generator blueprint to use for the sub generators', + type: Array, + }, + blueprints: { + description: + 'A comma separated list of one or more generator blueprints to use for the sub generators, e.g. --blueprints kotlin,vuejs', + type: String, + }, + ignoreErrors: { + description: "Don't fail on prettier errors.", + type: Boolean, + }, + pkType: { + description: 'Default primary key type (beta)', + type: String, + scope: 'storage', + }, + clientPackageManager: { + description: 'Force an unsupported client package manager', + type: String, + scope: 'storage', + }, + testFrameworks: { + description: 'Test frameworks to be generated', + type: Array, + }, + }, + import: [ + GENERATOR_BOOTSTRAP, + GENERATOR_BOOTSTRAP_APPLICATION_BASE, + GENERATOR_COMMON, + GENERATOR_SERVER, + GENERATOR_CLIENT, + GENERATOR_GIT, + GENERATOR_CYPRESS, + GENERATOR_LANGUAGES, + ], +}; + +export default command; diff --git a/generators/app/composing.spec.mts b/generators/app/composing.spec.mts deleted file mode 100644 index e7f6f49499bb..000000000000 --- a/generators/app/composing.spec.mts +++ /dev/null @@ -1,144 +0,0 @@ -import assert from 'assert'; - -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { GENERATOR_APP } from '../generator-list.mjs'; - -const allMockedComposedGenerators = [ - 'jhipster:common', - 'jhipster:languages', - 'jhipster:entities', - 'jhipster:entity', - 'jhipster:database-changelog', - 'jhipster:bootstrap', - 'jhipster:git', - 'jhipster:server', - 'jhipster:client', -]; - -describe('generator - app - composing', () => { - describe('when mocking all generators', () => { - describe('with default options', () => { - let runResult; - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_APP).withJHipsterConfig().withMockedGenerators(allMockedComposedGenerators); - }); - - it('should compose with bootstrap generator', () => { - assert(runResult.mockedGenerators['jhipster:bootstrap'].called); - }); - it('should compose with common generator', () => { - const CommonGenerator = runResult.mockedGenerators['jhipster:common']; - assert(CommonGenerator.calledOnce); - }); - it('should compose with server generator', () => { - const ServerGenerator = runResult.mockedGenerators['jhipster:server']; - assert(ServerGenerator.calledOnce); - }); - it('should compose with client generator', () => { - const ClientGenerator = runResult.mockedGenerators['jhipster:client']; - assert(ClientGenerator.calledOnce); - }); - it('should not compose with languages generator', () => { - const LanguagesGenerator = runResult.mockedGenerators['jhipster:languages']; - assert.equal(LanguagesGenerator.callCount, 0); - }); - it('should not compose with entities generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:entities']; - assert.equal(MockedGenerator.callCount, 0); - }); - it('should not compose with entity generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; - assert.equal(MockedGenerator.callCount, 0); - }); - it('should not compose with database-changelog generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; - assert.equal(MockedGenerator.callCount, 0); - }); - }); - - describe('with --skip-client', () => { - let runResult; - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_APP) - .withJHipsterConfig({ - skipClient: true, - }) - .withMockedGenerators(allMockedComposedGenerators); - }); - - it('should compose with bootstrap generator', () => { - const BootstrapGenerator = runResult.mockedGenerators['jhipster:bootstrap']; - assert(BootstrapGenerator.called); - }); - it('should compose with common generator', () => { - const CommonGenerator = runResult.mockedGenerators['jhipster:common']; - assert(CommonGenerator.calledOnce); - }); - it('should compose with server generator', () => { - const ServerGenerator = runResult.mockedGenerators['jhipster:server']; - assert(ServerGenerator.calledOnce); - }); - it('should not compose with client generator', () => { - const ClientGenerator = runResult.mockedGenerators['jhipster:client']; - assert.equal(ClientGenerator.callCount, 0); - }); - it('should not compose with languages generator', () => { - const LanguagesGenerator = runResult.mockedGenerators['jhipster:languages']; - assert.equal(LanguagesGenerator.callCount, 0); - }); - it('should not compose with entities generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:entities']; - assert.equal(MockedGenerator.callCount, 0); - }); - it('should not compose with entity generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; - assert.equal(MockedGenerator.callCount, 0); - }); - it('should not compose with database-changelog generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; - assert.equal(MockedGenerator.callCount, 0); - }); - }); - - describe('with --skip-server', () => { - let runResult; - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_APP) - .withJHipsterConfig({ - skipServer: true, - }) - .withMockedGenerators(allMockedComposedGenerators); - }); - - it('should compose with bootstrap generator', () => { - assert(runResult.mockedGenerators['jhipster:bootstrap'].called); - }); - it('should compose with common generator', () => { - const CommonGenerator = runResult.mockedGenerators['jhipster:common']; - assert(CommonGenerator.calledOnce); - }); - it('should not compose with server generator', () => { - const ServerGenerator = runResult.mockedGenerators['jhipster:server']; - assert(ServerGenerator.callCount === 0); - }); - it('should compose with client generator', () => { - const ClientGenerator = runResult.mockedGenerators['jhipster:client']; - assert(ClientGenerator.calledOnce); - }); - it('should not compose with entities generator', () => { - const EntityGenerator = runResult.mockedGenerators['jhipster:entities']; - assert.equal(EntityGenerator.callCount, 0); - }); - it('should not compose with entity generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; - assert.equal(MockedGenerator.callCount, 0); - }); - it('should not compose with database-changelog generator', () => { - const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; - assert.equal(MockedGenerator.callCount, 0); - }); - }); - }); -}); diff --git a/generators/app/composing.spec.ts b/generators/app/composing.spec.ts new file mode 100644 index 000000000000..e717dcd91ef6 --- /dev/null +++ b/generators/app/composing.spec.ts @@ -0,0 +1,144 @@ +import assert from 'assert'; + +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { GENERATOR_APP } from '../generator-list.js'; + +const allMockedComposedGenerators = [ + 'jhipster:common', + 'jhipster:languages', + 'jhipster:entities', + 'jhipster:entity', + 'jhipster:database-changelog', + 'jhipster:bootstrap', + 'jhipster:git', + 'jhipster:server', + 'jhipster:client', +]; + +describe('generator - app - composing', () => { + describe('when mocking all generators', () => { + describe('with default options', () => { + let runResult; + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_APP).withJHipsterConfig().withMockedGenerators(allMockedComposedGenerators); + }); + + it('should compose with bootstrap generator', () => { + assert(runResult.mockedGenerators['jhipster:bootstrap'].called); + }); + it('should compose with common generator', () => { + const CommonGenerator = runResult.mockedGenerators['jhipster:common']; + assert(CommonGenerator.calledOnce); + }); + it('should compose with server generator', () => { + const ServerGenerator = runResult.mockedGenerators['jhipster:server']; + assert(ServerGenerator.calledOnce); + }); + it('should compose with client generator', () => { + const ClientGenerator = runResult.mockedGenerators['jhipster:client']; + assert(ClientGenerator.calledOnce); + }); + it('should not compose with languages generator', () => { + const LanguagesGenerator = runResult.mockedGenerators['jhipster:languages']; + assert.equal(LanguagesGenerator.callCount, 0); + }); + it('should not compose with entities generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:entities']; + assert.equal(MockedGenerator.callCount, 0); + }); + it('should not compose with entity generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; + assert.equal(MockedGenerator.callCount, 0); + }); + it('should not compose with database-changelog generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; + assert.equal(MockedGenerator.callCount, 0); + }); + }); + + describe('with --skip-client', () => { + let runResult; + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_APP) + .withJHipsterConfig({ + skipClient: true, + }) + .withMockedGenerators(allMockedComposedGenerators); + }); + + it('should compose with bootstrap generator', () => { + const BootstrapGenerator = runResult.mockedGenerators['jhipster:bootstrap']; + assert(BootstrapGenerator.called); + }); + it('should compose with common generator', () => { + const CommonGenerator = runResult.mockedGenerators['jhipster:common']; + assert(CommonGenerator.calledOnce); + }); + it('should compose with server generator', () => { + const ServerGenerator = runResult.mockedGenerators['jhipster:server']; + assert(ServerGenerator.calledOnce); + }); + it('should not compose with client generator', () => { + const ClientGenerator = runResult.mockedGenerators['jhipster:client']; + assert.equal(ClientGenerator.callCount, 0); + }); + it('should not compose with languages generator', () => { + const LanguagesGenerator = runResult.mockedGenerators['jhipster:languages']; + assert.equal(LanguagesGenerator.callCount, 0); + }); + it('should not compose with entities generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:entities']; + assert.equal(MockedGenerator.callCount, 0); + }); + it('should not compose with entity generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; + assert.equal(MockedGenerator.callCount, 0); + }); + it('should not compose with database-changelog generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; + assert.equal(MockedGenerator.callCount, 0); + }); + }); + + describe('with --skip-server', () => { + let runResult; + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_APP) + .withJHipsterConfig({ + skipServer: true, + }) + .withMockedGenerators(allMockedComposedGenerators); + }); + + it('should compose with bootstrap generator', () => { + assert(runResult.mockedGenerators['jhipster:bootstrap'].called); + }); + it('should compose with common generator', () => { + const CommonGenerator = runResult.mockedGenerators['jhipster:common']; + assert(CommonGenerator.calledOnce); + }); + it('should not compose with server generator', () => { + const ServerGenerator = runResult.mockedGenerators['jhipster:server']; + assert(ServerGenerator.callCount === 0); + }); + it('should compose with client generator', () => { + const ClientGenerator = runResult.mockedGenerators['jhipster:client']; + assert(ClientGenerator.calledOnce); + }); + it('should not compose with entities generator', () => { + const EntityGenerator = runResult.mockedGenerators['jhipster:entities']; + assert.equal(EntityGenerator.callCount, 0); + }); + it('should not compose with entity generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:entity']; + assert.equal(MockedGenerator.callCount, 0); + }); + it('should not compose with database-changelog generator', () => { + const MockedGenerator = runResult.mockedGenerators['jhipster:database-changelog']; + assert.equal(MockedGenerator.callCount, 0); + }); + }); + }); +}); diff --git a/generators/app/generator.js b/generators/app/generator.js new file mode 100644 index 000000000000..3e65fe0983f9 --- /dev/null +++ b/generators/app/generator.js @@ -0,0 +1,199 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return, import/no-named-as-default-member */ +import chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import { checkNode, loadStoredAppOptions } from './support/index.js'; +import cleanupOldFilesTask from './cleanup.js'; +import { askForInsightOptIn } from './prompts.js'; +import statistics from '../statistics.js'; +import { + GENERATOR_APP, + GENERATOR_COMMON, + GENERATOR_CLIENT, + GENERATOR_SERVER, + GENERATOR_BOOTSTRAP_APPLICATION_BASE, +} from '../generator-list.js'; +import { getDefaultAppName } from '../project-name/support/index.js'; +import { packageJson } from '../../lib/index.js'; + +import { applicationTypes, applicationOptions } from '../../jdl/jhipster/index.js'; +import command from './command.js'; + +const { MICROSERVICE } = applicationTypes; +const { JHI_PREFIX, BASE_NAME, JWT_SECRET_KEY, PACKAGE_NAME, PACKAGE_FOLDER, REMEMBER_ME_KEY } = applicationOptions.OptionNames; + +export default class JHipsterAppGenerator extends BaseApplicationGenerator { + command = command; + + async beforeQueue() { + loadStoredAppOptions.call(this); + + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_APP); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_BASE); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + validateNode() { + if (this.skipChecks) { + return; + } + checkNode(this.logger); + }, + + async checkForNewJHVersion() { + if (!this.skipChecks) { + await this.checkForNewVersion(); + } + }, + loadOptions() { + this.parseJHipsterCommand(this.command); + }, + + validate() { + if (!this.skipChecks && this.jhipsterConfig.skipServer && this.jhipsterConfig.skipClient) { + throw new Error(`You can not pass both ${chalk.yellow('--skip-client')} and ${chalk.yellow('--skip-server')} together`); + } + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return this.asPromptingTaskGroup({ + askForInsightOptIn, + async prompting({ control }) { + if (control.existingProject && this.options.askAnswered !== true) return; + await this.prompt(this.prepareQuestions(this.command.configs)); + }, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get configuring() { + return { + setup() { + if (!this.options.reproducibleTests) { + this.jhipsterConfig.jhipsterVersion = packageJson.version; + } + + if (this.jhipsterConfig.applicationType === MICROSERVICE) { + this.jhipsterConfig.skipUserManagement = true; + } + }, + fixConfig() { + if (this.jhipsterConfig.jhiPrefix) { + this.jhipsterConfig.jhiPrefix = _.camelCase(this.jhipsterConfig.jhiPrefix); + } + }, + defaults() { + if (!this.options.reproducible) { + this.config.defaults({ + baseName: getDefaultAppName(this), + creationTimestamp: new Date().getTime(), + }); + } + }, + }; + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get composing() { + return this.asComposingTaskGroup({ + /** + * Composing with others generators, must be executed after `configuring` priority to let others + * generators `configuring` priority to run. + * + * Composing in different tasks the result would be: + * - composeCommon (app) -> initializing (common) -> prompting (common) -> ... -> composeServer (app) -> initializing (server) -> ... + * + * This behaviour allows a more consistent blueprint support. + */ + async composeCommon() { + await this.composeWithJHipster(GENERATOR_COMMON); + }, + async composeServer() { + if (!this.jhipsterConfigWithDefaults.skipServer) { + await this.composeWithJHipster(GENERATOR_SERVER); + } + }, + async composeClient() { + if (!this.jhipsterConfigWithDefaults.skipClient) { + await this.composeWithJHipster(GENERATOR_CLIENT); + } + }, + /** + * At this point every other generator should already be configured, so, enforce defaults fallback. + */ + saveConfigWithDefaults() { + const config = this.jhipsterConfigWithDefaults; + if (config.entitySuffix === config.dtoSuffix) { + throw new Error('Entities cannot be generated as the entity suffix and DTO suffix are equals !'); + } + }, + }); + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get default() { + return this.asDefaultTaskGroup({ + insight({ control }) { + const yorc = { + ..._.omit(this.jhipsterConfig, [JHI_PREFIX, BASE_NAME, JWT_SECRET_KEY, PACKAGE_NAME, PACKAGE_FOLDER, REMEMBER_ME_KEY]), + }; + yorc.applicationType = this.jhipsterConfig.applicationType; + statistics.sendYoRc(yorc, control.existingProject, this.jhipsterConfig.jhipsterVersion); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupOldFilesTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } +} diff --git a/generators/app/generator.mjs b/generators/app/generator.mjs deleted file mode 100644 index 336348d03fdd..000000000000 --- a/generators/app/generator.mjs +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return, import/no-named-as-default-member */ -import chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { checkNode, loadStoredAppOptions } from './support/index.mjs'; -import cleanupOldFilesTask from './cleanup.mjs'; -import { askForInsightOptIn } from './prompts.mjs'; -import statistics from '../statistics.mjs'; -import { - GENERATOR_APP, - GENERATOR_COMMON, - GENERATOR_CLIENT, - GENERATOR_SERVER, - GENERATOR_BOOTSTRAP_APPLICATION_BASE, -} from '../generator-list.mjs'; -import { getDefaultAppName } from '../project-name/support/index.mjs'; -import { packageJson } from '../../lib/index.mjs'; - -import { applicationTypes, applicationOptions } from '../../jdl/jhipster/index.mjs'; -import command from './command.mjs'; - -const { MICROSERVICE } = applicationTypes; -const { JHI_PREFIX, BASE_NAME, JWT_SECRET_KEY, PACKAGE_NAME, PACKAGE_FOLDER, REMEMBER_ME_KEY } = applicationOptions.OptionNames; - -export default class JHipsterAppGenerator extends BaseApplicationGenerator { - command = command; - - async beforeQueue() { - loadStoredAppOptions.call(this); - - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_APP); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_BASE); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - validateNode() { - if (this.skipChecks) { - return; - } - checkNode(this.logger); - }, - - async checkForNewJHVersion() { - if (!this.skipChecks) { - await this.checkForNewVersion(); - } - }, - loadOptions() { - this.parseJHipsterCommand(this.command); - }, - - validate() { - if (!this.skipChecks && this.jhipsterConfig.skipServer && this.jhipsterConfig.skipClient) { - throw new Error(`You can not pass both ${chalk.yellow('--skip-client')} and ${chalk.yellow('--skip-server')} together`); - } - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return this.asPromptingTaskGroup({ - askForInsightOptIn, - async prompting({ control }) { - if (control.existingProject && this.options.askAnswered !== true) return; - await this.prompt(this.prepareQuestions(this.command.configs)); - }, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get configuring() { - return { - setup() { - if (!this.options.reproducibleTests) { - this.jhipsterConfig.jhipsterVersion = packageJson.version; - } - - if (this.jhipsterConfig.applicationType === MICROSERVICE) { - this.jhipsterConfig.skipUserManagement = true; - } - }, - fixConfig() { - if (this.jhipsterConfig.jhiPrefix) { - this.jhipsterConfig.jhiPrefix = _.camelCase(this.jhipsterConfig.jhiPrefix); - } - }, - defaults() { - if (!this.options.reproducible) { - this.config.defaults({ - baseName: getDefaultAppName(this), - creationTimestamp: new Date().getTime(), - }); - } - }, - }; - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get composing() { - return this.asComposingTaskGroup({ - /** - * Composing with others generators, must be executed after `configuring` priority to let others - * generators `configuring` priority to run. - * - * Composing in different tasks the result would be: - * - composeCommon (app) -> initializing (common) -> prompting (common) -> ... -> composeServer (app) -> initializing (server) -> ... - * - * This behaviour allows a more consistent blueprint support. - */ - async composeCommon() { - await this.composeWithJHipster(GENERATOR_COMMON); - }, - async composeServer() { - if (!this.jhipsterConfigWithDefaults.skipServer) { - await this.composeWithJHipster(GENERATOR_SERVER); - } - }, - async composeClient() { - if (!this.jhipsterConfigWithDefaults.skipClient) { - await this.composeWithJHipster(GENERATOR_CLIENT); - } - }, - /** - * At this point every other generator should already be configured, so, enforce defaults fallback. - */ - saveConfigWithDefaults() { - const config = this.jhipsterConfigWithDefaults; - if (config.entitySuffix === config.dtoSuffix) { - throw new Error('Entities cannot be generated as the entity suffix and DTO suffix are equals !'); - } - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get default() { - return this.asDefaultTaskGroup({ - insight({ control }) { - const yorc = { - ..._.omit(this.jhipsterConfig, [JHI_PREFIX, BASE_NAME, JWT_SECRET_KEY, PACKAGE_NAME, PACKAGE_FOLDER, REMEMBER_ME_KEY]), - }; - yorc.applicationType = this.jhipsterConfig.applicationType; - statistics.sendYoRc(yorc, control.existingProject, this.jhipsterConfig.jhipsterVersion); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupOldFilesTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } -} diff --git a/generators/app/generator.spec.mts b/generators/app/generator.spec.mts deleted file mode 100644 index 4c952a0975f7..000000000000 --- a/generators/app/generator.spec.mts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { getCommandHelpOutput, shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import Generator from './index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorPath = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('help', () => { - it('should print expected information', async () => { - expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); - }); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('with', () => { - describe('default config', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withJHipsterConfig().withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ - user: expect.any(Object), - jhipsterPackageJson: expect.any(Object), - }); - }); - }); - - describe('gateway', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - applicationType: 'gateway', - }) - .withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ - user: expect.any(Object), - jhipsterPackageJson: expect.any(Object), - jwtSecretKey: expect.any(String), - }); - }); - }); - - describe('microservice', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - applicationType: 'microservice', - }) - .withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ - jhipsterPackageJson: expect.any(Object), - jwtSecretKey: expect.any(String), - }); - }); - }); - }); - - describe('jdlStore', () => { - describe('with application', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({ - jdlStore: 'app.jdl', - skipServer: true, - skipClient: true, - }) - .withOptions({ refreshOnCommit: true }) - .withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - describe('with application and entities', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig( - { - jdlStore: 'app.jdl', - skipServer: true, - skipClient: true, - }, - [{ name: 'Foo' }, { name: 'Bar' }], - ) - .withOptions({ refreshOnCommit: true }) - .withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - - describe('with incremental changelog application and entities', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig( - { - jdlStore: 'app.jdl', - skipServer: true, - skipClient: true, - incrementalChangelog: true, - }, - [{ name: 'Foo' }, { name: 'Bar' }], - ) - .withOptions({ refreshOnCommit: true }) - .withSkipWritingPriorities(); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/app/generator.spec.ts b/generators/app/generator.spec.ts new file mode 100644 index 000000000000..047231780eab --- /dev/null +++ b/generators/app/generator.spec.ts @@ -0,0 +1,163 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { getCommandHelpOutput, shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers, runResult } from '../../test/support/index.js'; +import Generator from './index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorPath = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('help', () => { + it('should print expected information', async () => { + expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); + }); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('with', () => { + describe('default config', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withJHipsterConfig().withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ + user: expect.any(Object), + jhipsterPackageJson: expect.any(Object), + }); + }); + }); + + describe('gateway', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + applicationType: 'gateway', + }) + .withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ + user: expect.any(Object), + jhipsterPackageJson: expect.any(Object), + jwtSecretKey: expect.any(String), + }); + }); + }); + + describe('microservice', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + applicationType: 'microservice', + }) + .withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.generator.sharedData.getApplication()).toMatchSnapshot({ + jhipsterPackageJson: expect.any(Object), + jwtSecretKey: expect.any(String), + }); + }); + }); + }); + + describe('jdlStore', () => { + describe('with application', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({ + jdlStore: 'app.jdl', + skipServer: true, + skipClient: true, + }) + .withOptions({ refreshOnCommit: true }) + .withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + describe('with application and entities', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig( + { + jdlStore: 'app.jdl', + skipServer: true, + skipClient: true, + }, + [{ name: 'Foo' }, { name: 'Bar' }], + ) + .withOptions({ refreshOnCommit: true }) + .withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + + describe('with incremental changelog application and entities', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig( + { + jdlStore: 'app.jdl', + skipServer: true, + skipClient: true, + incrementalChangelog: true, + }, + [{ name: 'Foo' }, { name: 'Bar' }], + ) + .withOptions({ refreshOnCommit: true }) + .withSkipWritingPriorities(); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/app/index.mts b/generators/app/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/app/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/app/index.ts b/generators/app/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/app/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/app/jdl/application-options.mts b/generators/app/jdl/application-options.mts deleted file mode 100644 index 25c15ef7a229..000000000000 --- a/generators/app/jdl/application-options.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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.mjs'; diff --git a/generators/app/jdl/application-options.ts b/generators/app/jdl/application-options.ts new file mode 100644 index 000000000000..8d06956ee210 --- /dev/null +++ b/generators/app/jdl/application-options.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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.mts b/generators/app/jdl/index.mts deleted file mode 100644 index bd3e38f352f7..000000000000 --- a/generators/app/jdl/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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.mjs'; diff --git a/generators/app/jdl/index.ts b/generators/app/jdl/index.ts new file mode 100644 index 000000000000..2bc59256987e --- /dev/null +++ b/generators/app/jdl/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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/app/prompts.js b/generators/app/prompts.js new file mode 100644 index 000000000000..5a042d05f327 --- /dev/null +++ b/generators/app/prompts.js @@ -0,0 +1,33 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import statistics from '../statistics.js'; + +export async function askForInsightOptIn() { + if (!statistics.shouldWeAskForOptIn()) return; + const answers = await this.prompt({ + type: 'confirm', + name: 'insight', + message: `May ${chalk.cyan('JHipster')} anonymously report usage statistics to improve the tool over time?`, + default: true, + }); + if (answers.insight !== undefined) { + statistics.setOptOutStatus(!answers.insight); + } +} diff --git a/generators/app/prompts.mjs b/generators/app/prompts.mjs deleted file mode 100644 index 6fe9731fc123..000000000000 --- a/generators/app/prompts.mjs +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import statistics from '../statistics.mjs'; - -export async function askForInsightOptIn() { - if (!statistics.shouldWeAskForOptIn()) return; - const answers = await this.prompt({ - type: 'confirm', - name: 'insight', - message: `May ${chalk.cyan('JHipster')} anonymously report usage statistics to improve the tool over time?`, - default: true, - }); - if (answers.insight !== undefined) { - statistics.setOptOutStatus(!answers.insight); - } -} diff --git a/generators/app/support/check-node.js b/generators/app/support/check-node.js new file mode 100644 index 000000000000..e666c10ac34a --- /dev/null +++ b/generators/app/support/check-node.js @@ -0,0 +1,67 @@ +/** + * Copyright 2013-2023 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 semver from 'semver'; +import chalk from 'chalk'; + +import { packageJson } from '../../../lib/index.js'; + +const isNodeVersionCompliantWithRequirement = (gatheredFromEnvironment, requiredVersion) => { + return !semver.satisfies(gatheredFromEnvironment, requiredVersion); +}; + +const getNodeReleaseFromCurrentProcess = () => { + return process.release || {}; +}; + +const isNodeLTS = release => { + return release.lts; +}; +const getNodeVersionFromCurrentProcess = () => { + return process.version; +}; +const requiredEngineFromPackageJson = () => { + return packageJson.engines.node; +}; + +/** + * @private + * Check if Node is installed, up to date, and in LTS version. + * Will emit a warning if the current node version is too old compared to the required one or if it is not in LTS. + * @param {any} logger - the logging adapter + * @param {string} requiredNodeVersion - the version needed to run the generator (defaulted to the one mentionned in package.json) + * @param {string} currentNodeVersion - the version of Node installed on the machine (defaulted to the one running the generator) + */ +const checkNode = ( + logger, + requiredNodeVersion = requiredEngineFromPackageJson(), + currentNodeVersion = getNodeVersionFromCurrentProcess(), +) => { + if (isNodeVersionCompliantWithRequirement(currentNodeVersion, requiredNodeVersion)) { + logger.warn( + `Your NodeJS version is too old (${currentNodeVersion}). You should use at least NodeJS ${chalk.bold(requiredNodeVersion)}`, + ); + } + if (!isNodeLTS(getNodeReleaseFromCurrentProcess())) { + logger.warn( + 'Your Node version is not LTS (Long Term Support), use it at your own risk! JHipster does not support non-LTS releases, so if you encounter a bug, please use a LTS version first.', + ); + } +}; + +export default checkNode; diff --git a/generators/app/support/check-node.mjs b/generators/app/support/check-node.mjs deleted file mode 100644 index aae2234c8b30..000000000000 --- a/generators/app/support/check-node.mjs +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2013-2023 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 semver from 'semver'; -import chalk from 'chalk'; - -import { packageJson } from '../../../lib/index.mjs'; - -const isNodeVersionCompliantWithRequirement = (gatheredFromEnvironment, requiredVersion) => { - return !semver.satisfies(gatheredFromEnvironment, requiredVersion); -}; - -const getNodeReleaseFromCurrentProcess = () => { - return process.release || {}; -}; - -const isNodeLTS = release => { - return release.lts; -}; -const getNodeVersionFromCurrentProcess = () => { - return process.version; -}; -const requiredEngineFromPackageJson = () => { - return packageJson.engines.node; -}; - -/** - * @private - * Check if Node is installed, up to date, and in LTS version. - * Will emit a warning if the current node version is too old compared to the required one or if it is not in LTS. - * @param {any} logger - the logging adapter - * @param {string} requiredNodeVersion - the version needed to run the generator (defaulted to the one mentionned in package.json) - * @param {string} currentNodeVersion - the version of Node installed on the machine (defaulted to the one running the generator) - */ -const checkNode = ( - logger, - requiredNodeVersion = requiredEngineFromPackageJson(), - currentNodeVersion = getNodeVersionFromCurrentProcess(), -) => { - if (isNodeVersionCompliantWithRequirement(currentNodeVersion, requiredNodeVersion)) { - logger.warn( - `Your NodeJS version is too old (${currentNodeVersion}). You should use at least NodeJS ${chalk.bold(requiredNodeVersion)}`, - ); - } - if (!isNodeLTS(getNodeReleaseFromCurrentProcess())) { - logger.warn( - 'Your Node version is not LTS (Long Term Support), use it at your own risk! JHipster does not support non-LTS releases, so if you encounter a bug, please use a LTS version first.', - ); - } -}; - -export default checkNode; diff --git a/generators/app/support/config.mts b/generators/app/support/config.mts deleted file mode 100644 index 8f0c81209695..000000000000 --- a/generators/app/support/config.mts +++ /dev/null @@ -1,192 +0,0 @@ -import { camelCase, kebabCase, startCase, upperFirst } from 'lodash-es'; -import { NODE_VERSION } from '../../generator-constants.mjs'; -import { applicationTypes, authenticationTypes, databaseTypes, testFrameworkTypes } from '../../../jdl/index.js'; -import { getHipster, mutateData, pickFields, upperFirstCamelCase } from '../../base/support/index.mjs'; -import { getDBTypeFromDBValue } from '../../server/support/index.mjs'; -import detectLanguage from '../../languages/support/detect-language.mjs'; -import { loadConfig, loadDerivedConfig } from '../../../lib/internal/index.mjs'; -import serverCommand from '../../server/command.mjs'; -import { packageJson } from '../../../lib/index.mjs'; - -const { GATLING, CUCUMBER, CYPRESS } = testFrameworkTypes; -const { GATEWAY, MONOLITH } = applicationTypes; -const { JWT, OAUTH2, SESSION } = authenticationTypes; -const { CASSANDRA, NO: NO_DATABASE } = databaseTypes; - -/** - * Load common options to be stored. - * @deprecated - */ -export function loadStoredAppOptions(this: any, { options = this.options, jhipsterConfig = this.jhipsterConfig, log = this.log } = {}) { - // Parse options only once. - if (this.sharedData.getControl().optionsParsed) return; - this.sharedData.getControl().optionsParsed = true; - - if (options.db) { - const databaseType = getDBTypeFromDBValue(options.db); - if (databaseType) { - jhipsterConfig.databaseType = databaseType; - } else if (!jhipsterConfig.databaseType) { - throw new Error(`Could not detect databaseType for database ${options.db}`); - } - jhipsterConfig.devDatabaseType = options.db; - jhipsterConfig.prodDatabaseType = options.db; - } - if (options.testFrameworks) { - jhipsterConfig.testFrameworks = [...new Set([...(jhipsterConfig.testFrameworks || []), ...options.testFrameworks])]; - } - if (options.language) { - // workaround double options parsing, remove once generator supports skipping parse options - const languages = options.language.flat(); - if (languages.length === 1 && languages[0] === 'false') { - jhipsterConfig.enableTranslation = false; - } else { - jhipsterConfig.languages = [...(jhipsterConfig.languages || []), ...languages]; - } - } - if (options.nativeLanguage) { - if (typeof options.nativeLanguage === 'string') { - jhipsterConfig.nativeLanguage = options.nativeLanguage; - if (!jhipsterConfig.languages) { - jhipsterConfig.languages = [options.nativeLanguage]; - } - } else if (options.nativeLanguage === true) { - jhipsterConfig.nativeLanguage = detectLanguage(); - } - } - - if (jhipsterConfig.clientPackageManager) { - const usingNpm = jhipsterConfig.clientPackageManager === 'npm'; - if (!usingNpm) { - log?.warn(`Using unsupported package manager: ${jhipsterConfig.clientPackageManager}. Install will not be executed.`); - options.skipInstall = true; - } - } -} - -/** - * Load app configs into application. - * all variables should be set to dest, - * all variables should be referred from config, - * @param {any} config - config to load config from - * @param {any} dest - destination context to use default is context - */ -export const loadAppConfig = ({ - config, - application, - useVersionPlaceholders, -}: { - config: any; - application: any; - useVersionPlaceholders?: boolean; -}) => { - loadConfig(serverCommand.configs, { config, application }); - - mutateData( - application, - { - nodeVersion: useVersionPlaceholders ? 'NODE_VERSION' : NODE_VERSION, - jhipsterVersion: useVersionPlaceholders ? 'JHIPSTER_VERSION' : undefined, - }, - pickFields(config, [ - 'jhipsterVersion', - 'baseName', - 'reactive', - 'jhiPrefix', - 'skipFakeData', - 'entitySuffix', - 'dtoSuffix', - 'skipCheckLengthOfIdentifier', - 'microfrontend', - 'microfrontends', - 'skipServer', - 'skipCommitHook', - 'skipClient', - 'prettierJava', - 'pages', - 'skipJhipsterDependencies', - 'withAdminUi', - 'gatewayServerPort', - 'capitalizedBaseName', - 'dasherizedBaseName', - 'humanizedBaseName', - 'projectDescription', - 'authenticationType', - 'rememberMeKey', - 'jwtSecretKey', - 'fakerSeed', - 'skipUserManagement', - 'blueprints', - 'testFrameworks', - ]), - { - jhipsterVersion: packageJson.version, - blueprints: [], - testFrameworks: [], - }, - ); -}; - -/** - * @param {Object} dest - destination context to use default is context - */ -export const loadDerivedAppConfig = ({ application }: { application: any }) => { - loadDerivedConfig(serverCommand.configs, { application }); - - mutateData(application, { - jhiPrefixCapitalized: ({ jhiPrefix }) => upperFirst(jhiPrefix), - jhiPrefixDashed: ({ jhiPrefix }) => kebabCase(jhiPrefix), - - camelizedBaseName: ({ baseName }) => camelCase(baseName), - hipster: ({ baseName }) => getHipster(baseName), - capitalizedBaseName: ({ baseName }) => upperFirst(baseName), - dasherizedBaseName: ({ baseName }) => kebabCase(baseName), - lowercaseBaseName: ({ baseName }) => baseName?.toLowerCase(), - upperFirstCamelCaseBaseName: ({ baseName }) => upperFirstCamelCase(baseName), - humanizedBaseName: ({ baseName }) => (baseName.toLowerCase() === 'jhipster' ? 'JHipster' : startCase(baseName)), - - gatlingTests: ({ testFrameworks }) => testFrameworks?.includes(GATLING), - cucumberTests: ({ testFrameworks }) => testFrameworks?.includes(CUCUMBER), - cypressTests: ({ testFrameworks }) => testFrameworks?.includes(CYPRESS), - - projectDescription: ({ projectDescription, baseName }) => projectDescription ?? `Description for ${baseName}`, - endpointPrefix: ({ applicationType, lowercaseBaseName }) => (applicationType === 'microservice' ? `services/${lowercaseBaseName}` : ''), - }); - - if (application.microfrontends && application.microfrontends.length > 0) { - application.microfrontends.forEach(microfrontend => { - const { baseName } = microfrontend; - microfrontend.lowercaseBaseName = baseName.toLowerCase(); - microfrontend.capitalizedBaseName = upperFirst(baseName); - microfrontend.endpointPrefix = `services/${microfrontend.lowercaseBaseName}`; - }); - } else if (application.microfrontend) { - application.microfrontends = []; - } - application.microfrontend = - application.microfrontend || - (application.applicationTypeMicroservice && !application.skipClient) || - (application.applicationTypeGateway && application.microfrontends && application.microfrontends.length > 0); - - if (application.microfrontend && application.applicationTypeMicroservice && !application.gatewayServerPort) { - application.gatewayServerPort = 8080; - } - - application.authenticationTypeSession = application.authenticationType === SESSION; - application.authenticationTypeJwt = application.authenticationType === JWT; - application.authenticationTypeOauth2 = application.authenticationType === OAUTH2; - - application.generateAuthenticationApi = application.applicationType === MONOLITH || application.applicationType === GATEWAY; - const authenticationApiWithUserManagement = application.authenticationType !== OAUTH2 && application.generateAuthenticationApi; - application.generateUserManagement = - !application.skipUserManagement && application.databaseType !== NO_DATABASE && authenticationApiWithUserManagement; - application.generateInMemoryUserCredentials = !application.generateUserManagement && authenticationApiWithUserManagement; - - // TODO make UserEntity optional on relationships for microservices and oauth2 - // TODO check if we support syncWithIdp using jwt authentication - // Used for relationships and syncWithIdp - const usesSyncWithIdp = application.authenticationType === OAUTH2 && application.databaseType !== NO_DATABASE; - application.generateBuiltInUserEntity = application.generateUserManagement || usesSyncWithIdp; - - application.generateBuiltInAuthorityEntity = application.generateBuiltInUserEntity && application.databaseType !== CASSANDRA; -}; diff --git a/generators/app/support/config.ts b/generators/app/support/config.ts new file mode 100644 index 000000000000..e71f16e8ddca --- /dev/null +++ b/generators/app/support/config.ts @@ -0,0 +1,192 @@ +import { camelCase, kebabCase, startCase, upperFirst } from 'lodash-es'; +import { NODE_VERSION } from '../../generator-constants.js'; +import { applicationTypes, authenticationTypes, databaseTypes, testFrameworkTypes } from '../../../jdl/index.js'; +import { getHipster, mutateData, pickFields, upperFirstCamelCase } from '../../base/support/index.js'; +import { getDBTypeFromDBValue } from '../../server/support/index.js'; +import detectLanguage from '../../languages/support/detect-language.js'; +import { loadConfig, loadDerivedConfig } from '../../../lib/internal/index.js'; +import serverCommand from '../../server/command.js'; +import { packageJson } from '../../../lib/index.js'; + +const { GATLING, CUCUMBER, CYPRESS } = testFrameworkTypes; +const { GATEWAY, MONOLITH } = applicationTypes; +const { JWT, OAUTH2, SESSION } = authenticationTypes; +const { CASSANDRA, NO: NO_DATABASE } = databaseTypes; + +/** + * Load common options to be stored. + * @deprecated + */ +export function loadStoredAppOptions(this: any, { options = this.options, jhipsterConfig = this.jhipsterConfig, log = this.log } = {}) { + // Parse options only once. + if (this.sharedData.getControl().optionsParsed) return; + this.sharedData.getControl().optionsParsed = true; + + if (options.db) { + const databaseType = getDBTypeFromDBValue(options.db); + if (databaseType) { + jhipsterConfig.databaseType = databaseType; + } else if (!jhipsterConfig.databaseType) { + throw new Error(`Could not detect databaseType for database ${options.db}`); + } + jhipsterConfig.devDatabaseType = options.db; + jhipsterConfig.prodDatabaseType = options.db; + } + if (options.testFrameworks) { + jhipsterConfig.testFrameworks = [...new Set([...(jhipsterConfig.testFrameworks || []), ...options.testFrameworks])]; + } + if (options.language) { + // workaround double options parsing, remove once generator supports skipping parse options + const languages = options.language.flat(); + if (languages.length === 1 && languages[0] === 'false') { + jhipsterConfig.enableTranslation = false; + } else { + jhipsterConfig.languages = [...(jhipsterConfig.languages || []), ...languages]; + } + } + if (options.nativeLanguage) { + if (typeof options.nativeLanguage === 'string') { + jhipsterConfig.nativeLanguage = options.nativeLanguage; + if (!jhipsterConfig.languages) { + jhipsterConfig.languages = [options.nativeLanguage]; + } + } else if (options.nativeLanguage === true) { + jhipsterConfig.nativeLanguage = detectLanguage(); + } + } + + if (jhipsterConfig.clientPackageManager) { + const usingNpm = jhipsterConfig.clientPackageManager === 'npm'; + if (!usingNpm) { + log?.warn(`Using unsupported package manager: ${jhipsterConfig.clientPackageManager}. Install will not be executed.`); + options.skipInstall = true; + } + } +} + +/** + * Load app configs into application. + * all variables should be set to dest, + * all variables should be referred from config, + * @param {any} config - config to load config from + * @param {any} dest - destination context to use default is context + */ +export const loadAppConfig = ({ + config, + application, + useVersionPlaceholders, +}: { + config: any; + application: any; + useVersionPlaceholders?: boolean; +}) => { + loadConfig(serverCommand.configs, { config, application }); + + mutateData( + application, + { + nodeVersion: useVersionPlaceholders ? 'NODE_VERSION' : NODE_VERSION, + jhipsterVersion: useVersionPlaceholders ? 'JHIPSTER_VERSION' : undefined, + }, + pickFields(config, [ + 'jhipsterVersion', + 'baseName', + 'reactive', + 'jhiPrefix', + 'skipFakeData', + 'entitySuffix', + 'dtoSuffix', + 'skipCheckLengthOfIdentifier', + 'microfrontend', + 'microfrontends', + 'skipServer', + 'skipCommitHook', + 'skipClient', + 'prettierJava', + 'pages', + 'skipJhipsterDependencies', + 'withAdminUi', + 'gatewayServerPort', + 'capitalizedBaseName', + 'dasherizedBaseName', + 'humanizedBaseName', + 'projectDescription', + 'authenticationType', + 'rememberMeKey', + 'jwtSecretKey', + 'fakerSeed', + 'skipUserManagement', + 'blueprints', + 'testFrameworks', + ]), + { + jhipsterVersion: packageJson.version, + blueprints: [], + testFrameworks: [], + }, + ); +}; + +/** + * @param {Object} dest - destination context to use default is context + */ +export const loadDerivedAppConfig = ({ application }: { application: any }) => { + loadDerivedConfig(serverCommand.configs, { application }); + + mutateData(application, { + jhiPrefixCapitalized: ({ jhiPrefix }) => upperFirst(jhiPrefix), + jhiPrefixDashed: ({ jhiPrefix }) => kebabCase(jhiPrefix), + + camelizedBaseName: ({ baseName }) => camelCase(baseName), + hipster: ({ baseName }) => getHipster(baseName), + capitalizedBaseName: ({ baseName }) => upperFirst(baseName), + dasherizedBaseName: ({ baseName }) => kebabCase(baseName), + lowercaseBaseName: ({ baseName }) => baseName?.toLowerCase(), + upperFirstCamelCaseBaseName: ({ baseName }) => upperFirstCamelCase(baseName), + humanizedBaseName: ({ baseName }) => (baseName.toLowerCase() === 'jhipster' ? 'JHipster' : startCase(baseName)), + + gatlingTests: ({ testFrameworks }) => testFrameworks?.includes(GATLING), + cucumberTests: ({ testFrameworks }) => testFrameworks?.includes(CUCUMBER), + cypressTests: ({ testFrameworks }) => testFrameworks?.includes(CYPRESS), + + projectDescription: ({ projectDescription, baseName }) => projectDescription ?? `Description for ${baseName}`, + endpointPrefix: ({ applicationType, lowercaseBaseName }) => (applicationType === 'microservice' ? `services/${lowercaseBaseName}` : ''), + }); + + if (application.microfrontends && application.microfrontends.length > 0) { + application.microfrontends.forEach(microfrontend => { + const { baseName } = microfrontend; + microfrontend.lowercaseBaseName = baseName.toLowerCase(); + microfrontend.capitalizedBaseName = upperFirst(baseName); + microfrontend.endpointPrefix = `services/${microfrontend.lowercaseBaseName}`; + }); + } else if (application.microfrontend) { + application.microfrontends = []; + } + application.microfrontend = + application.microfrontend || + (application.applicationTypeMicroservice && !application.skipClient) || + (application.applicationTypeGateway && application.microfrontends && application.microfrontends.length > 0); + + if (application.microfrontend && application.applicationTypeMicroservice && !application.gatewayServerPort) { + application.gatewayServerPort = 8080; + } + + application.authenticationTypeSession = application.authenticationType === SESSION; + application.authenticationTypeJwt = application.authenticationType === JWT; + application.authenticationTypeOauth2 = application.authenticationType === OAUTH2; + + application.generateAuthenticationApi = application.applicationType === MONOLITH || application.applicationType === GATEWAY; + const authenticationApiWithUserManagement = application.authenticationType !== OAUTH2 && application.generateAuthenticationApi; + application.generateUserManagement = + !application.skipUserManagement && application.databaseType !== NO_DATABASE && authenticationApiWithUserManagement; + application.generateInMemoryUserCredentials = !application.generateUserManagement && authenticationApiWithUserManagement; + + // TODO make UserEntity optional on relationships for microservices and oauth2 + // TODO check if we support syncWithIdp using jwt authentication + // Used for relationships and syncWithIdp + const usesSyncWithIdp = application.authenticationType === OAUTH2 && application.databaseType !== NO_DATABASE; + application.generateBuiltInUserEntity = application.generateUserManagement || usesSyncWithIdp; + + application.generateBuiltInAuthorityEntity = application.generateBuiltInUserEntity && application.databaseType !== CASSANDRA; +}; diff --git a/generators/app/support/index.mts b/generators/app/support/index.mts deleted file mode 100644 index 62ce87fa36b4..000000000000 --- a/generators/app/support/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './config.mjs'; -export { default as checkNode } from './check-node.mjs'; diff --git a/generators/app/support/index.ts b/generators/app/support/index.ts new file mode 100644 index 000000000000..d6f32f29a8bf --- /dev/null +++ b/generators/app/support/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './config.js'; +export { default as checkNode } from './check-node.js'; diff --git a/generators/base-application/application-options.d.mts b/generators/base-application/application-options.d.ts similarity index 100% rename from generators/base-application/application-options.d.mts rename to generators/base-application/application-options.d.ts diff --git a/generators/base-application/generator.mts b/generators/base-application/generator.mts deleted file mode 100644 index 036501cf3b0a..000000000000 --- a/generators/base-application/generator.mts +++ /dev/null @@ -1,670 +0,0 @@ -/** - * Copyright 2013-2021 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 { upperFirst } from 'lodash-es'; -import type { Storage } from 'yeoman-generator'; - -import BaseGenerator from '../base/index.mjs'; -import { CUSTOM_PRIORITIES, PRIORITY_NAMES, QUEUES } from './priorities.mjs'; -import { JHIPSTER_CONFIG_DIR } from '../generator-constants.mjs'; -import type { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from './tasks.mjs'; -import { GenericTaskGroup, GenericSourceTypeDefinition } from '../base/tasks.mjs'; -import type { BaseApplication, CommonClientServerApplication } from './types.mjs'; -import { getEntitiesFromDir } from './support/index.mjs'; -import { SpringBootSourceType } from '../server/types.mjs'; -import { ClientSourceType } from '../client/types.mjs'; -import { LanguageSourceType } from '../languages/types.js'; -import { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from '../base/api.mjs'; -import { mutateData } from '../base/support/config.mjs'; - -const { - LOADING, - PREPARING, - POST_PREPARING, - CONFIGURING_EACH_ENTITY, - LOADING_ENTITIES, - PREPARING_EACH_ENTITY, - PREPARING_EACH_ENTITY_FIELD, - PREPARING_EACH_ENTITY_RELATIONSHIP, - POST_PREPARING_EACH_ENTITY, - DEFAULT, - WRITING, - POST_WRITING, - WRITING_ENTITIES, - POST_WRITING_ENTITIES, - PRE_CONFLICTS, - INSTALL, - END, -} = PRIORITY_NAMES; - -const { - CONFIGURING_EACH_ENTITY_QUEUE, - LOADING_ENTITIES_QUEUE, - PREPARING_EACH_ENTITY_QUEUE, - PREPARING_EACH_ENTITY_FIELD_QUEUE, - PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, - POST_PREPARING_EACH_ENTITY_QUEUE, - WRITING_ENTITIES_QUEUE, - POST_WRITING_ENTITIES_QUEUE, -} = QUEUES; - -const asPriority = BaseGenerator.asPriority; - -export type BaseApplicationSource = Record any> & SpringBootSourceType & ClientSourceType & LanguageSourceType; - -export type JHipsterApplication = BaseApplication & Partial; - -export type GeneratorDefinition = BaseApplicationGeneratorDefinition< - GenericApplicationDefinition & GenericSourceTypeDefinition ->; - -/** - * This is the base class for a generator that generates entities. - */ -export default class BaseApplicationGenerator< - Definition extends BaseApplicationGeneratorDefinition<{ - applicationType: any; - entityType: any; - sourceType: any; - }> = GeneratorDefinition, -> extends BaseGenerator { - static CONFIGURING_EACH_ENTITY = asPriority(CONFIGURING_EACH_ENTITY); - - static LOADING_ENTITIES = asPriority(LOADING_ENTITIES); - - static PREPARING_EACH_ENTITY = asPriority(PREPARING_EACH_ENTITY); - - static PREPARING_EACH_ENTITY_FIELD = asPriority(PREPARING_EACH_ENTITY_FIELD); - - static PREPARING_EACH_ENTITY_RELATIONSHIP = asPriority(PREPARING_EACH_ENTITY_RELATIONSHIP); - - static POST_PREPARING_EACH_ENTITY = asPriority(POST_PREPARING_EACH_ENTITY); - - static WRITING_ENTITIES = asPriority(WRITING_ENTITIES); - - static POST_WRITING_ENTITIES = asPriority(POST_WRITING_ENTITIES); - - constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { - super(args, options, features); - - if (this.options.help) { - return; - } - - this.registerPriorities(CUSTOM_PRIORITIES); - - /* Add tasks allowing entities priorities to match normal priorities pattern */ - this.on('queueOwnTasks', () => { - this.log.debug('Queueing entity tasks'); - this.queueEntityTasks(); - }); - - if (this.options.applicationWithEntities) { - this.log.warn('applicationWithEntities option is deprecated'); - // Write new definitions to memfs - this.config.set({ - ...this.config.getAll(), - ...this.options.applicationWithEntities.config, - }); - if (this.options.applicationWithEntities.entities) { - const entities = this.options.applicationWithEntities.entities.map(entity => { - const entityName = upperFirst(entity.name); - const file = this.getEntityConfigPath(entityName); - this.fs.writeJSON(file, { ...this.fs.readJSON(file), ...entity }); - return entityName; - }); - this.jhipsterConfig.entities = [...new Set((this.jhipsterConfig.entities || []).concat(entities))]; - } - delete this.options.applicationWithEntities; - } - } - - /** - * Get Entities configuration path - * @returns - */ - getEntitiesConfigPath(...args) { - return this.destinationPath(JHIPSTER_CONFIG_DIR, ...args); - } - - /** - * Get Entity configuration path - * @param entityName Entity name - * @returns - */ - getEntityConfigPath(entityName: string) { - return this.getEntitiesConfigPath(`${upperFirst(entityName)}.json`); - } - - /** - * Get all the generator configuration from the .yo-rc.json file - * @param entityName - Name of the entity to load. - * @param create - Create storage if doesn't exists. - */ - getEntityConfig(entityName: string, create = false): Storage | undefined { - const entityPath = this.getEntityConfigPath(entityName); - if (!create && !this.fs.exists(entityPath)) return undefined; - return this.createStorage(entityPath); - } - - /** - * get sorted list of entity names according to changelog date (i.e. the order in which they were added) - */ - getExistingEntityNames(): string[] { - return this.getExistingEntities().map(entity => entity.name); - } - - /** - * get sorted list of entities according to changelog date (i.e. the order in which they were added) - */ - getExistingEntities(): { name: string; definition: Record }[] { - function isBefore(e1, e2) { - return (e1.definition.annotations?.changelogDate ?? 0) - (e2.definition.annotations?.changelogDate ?? 0); - } - - const configDir = this.getEntitiesConfigPath(); - - const entities: { name: string; definition: Record }[] = []; - for (const entityName of [...new Set(((this.jhipsterConfig.entities as string[]) || []).concat(getEntitiesFromDir(configDir)))]) { - const definition = this.getEntityConfig(entityName)?.getAll(); - if (definition) { - entities.push({ name: entityName, definition }); - } - } - entities.sort(isBefore); - this.jhipsterConfig.entities = entities.map(({ name }) => name); - return entities; - } - - /** - * Priority API stub for blueprints. - * - * Configuring each entity should configure entities. - */ - get configuringEachEntity(): GenericTaskGroup { - return this.asConfiguringEachEntityTaskGroup({}); - } - - get preparingEachEntity(): GenericTaskGroup { - return this.asPreparingEachEntityTaskGroup({}); - } - - /** - * Priority API stub for blueprints. - */ - get preparingEachEntityField(): GenericTaskGroup { - return this.asPreparingEachEntityFieldTaskGroup({}); - } - - /** - * Priority API stub for blueprints. - */ - get preparingEachEntityRelationship(): GenericTaskGroup { - return this.asPreparingEachEntityRelationshipTaskGroup({}); - } - - /** - * Priority API stub for blueprints. - */ - get postPreparingEachEntity(): GenericTaskGroup { - return this.asPostPreparingEachEntityTaskGroup({}); - } - - /** - * Priority API stub for blueprints. - */ - get writingEntities(): GenericTaskGroup { - return this.asWritingEntitiesTaskGroup({}); - } - - /** - * Priority API stub for blueprints. - */ - get postWritingEntities(): GenericTaskGroup { - return this.asPostWritingEntitiesTaskGroup({}); - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asConfiguringEachEntityTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asLoadingEntitiesTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPreparingEachEntityTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPreparingEachEntityFieldTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPreparingEachEntityRelationshipTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPostPreparingEachEntityTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asWritingEntitiesTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPostWritingEntitiesTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Reset entities fake data seed. - * @param {string} seed - */ - resetEntitiesFakeData(seed) { - seed = `${this.sharedData.getApplication().baseName}-${seed}`; - this.log.debug(`Resetting entities seed with '${seed}'`); - this.sharedData.getEntities().forEach(({ entity }) => { - entity.resetFakerSeed(seed); - }); - } - - getArgsForPriority(priorityName): any[] { - const args = super.getArgsForPriority(priorityName); - let firstArg = this.getTaskFirstArgForPriority(priorityName); - if (args.length > 0) { - firstArg = { ...args[0], ...firstArg }; - } - return [firstArg]; - } - - /** - * @protected - */ - protected getTaskFirstArgForPriority(priorityName): any { - if ( - ![ - LOADING, - PREPARING, - POST_PREPARING, - - CONFIGURING_EACH_ENTITY, - LOADING_ENTITIES, - PREPARING_EACH_ENTITY, - PREPARING_EACH_ENTITY_FIELD, - PREPARING_EACH_ENTITY_RELATIONSHIP, - POST_PREPARING_EACH_ENTITY, - - DEFAULT, - WRITING, - WRITING_ENTITIES, - POST_WRITING, - POST_WRITING_ENTITIES, - PRE_CONFLICTS, - INSTALL, - END, - ].includes(priorityName) - ) { - return {}; - } - if (!this.jhipsterConfig.baseName) { - throw new Error(`BaseName (${this.jhipsterConfig.baseName}) application not available for priority ${priorityName}`); - } - const application = this.sharedData.getApplication(); - - if ([PREPARING, LOADING].includes(priorityName)) { - return { - application, - applicationDefaults: data => mutateData(application, data), - }; - } - if (LOADING_ENTITIES === priorityName) { - return { - application, - entitiesToLoad: this.getEntitiesDataToLoad(), - }; - } - if ([DEFAULT].includes(priorityName)) { - return { - application, - ...this.getEntitiesDataForPriorities(), - }; - } - if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { - const applicationAndEntities = { - application, - ...this.getEntitiesDataToWrite(), - }; - if (priorityName === WRITING_ENTITIES) { - return applicationAndEntities; - } - return { - ...applicationAndEntities, - source: this.sharedData.getSource(), - }; - } - - return { application }; - } - - /** - * @private - * Get entities to configure. - * This method doesn't filter entities. An filtered config can be changed at this priority. - * @returns {string[]} - */ - getEntitiesDataToConfigure() { - return this.getExistingEntityNames().map(entityName => { - const entityStorage = this.getEntityConfig(entityName, true); - return { entityName, entityStorage, entityConfig: entityStorage!.createProxy() }; - }); - } - - /** - * @private - * Get entities to load. - * This method doesn't filter entities. An filtered config can be changed at this priority. - * @returns {string[]} - */ - getEntitiesDataToLoad() { - return this.getExistingEntityNames().map(entityName => ({ entityName, entityStorage: this.getEntityConfig(entityName, true) })); - } - - /** - * @private - * Get entities to prepare. - * @returns {object[]} - */ - getEntitiesDataToPrepare() { - return this.sharedData.getEntities().map(({ entityName, ...data }) => ({ - description: entityName, - entityName, - ...data, - })); - } - - /** - * @private - * Get entities and fields to prepare. - * @returns {object[]} - */ - getEntitiesFieldsDataToPrepare() { - return this.getEntitiesDataToPrepare() - .map(({ entity, entityName, ...data }) => { - if (!entity.fields) return []; - - return entity.fields.map(field => ({ - entity, - entityName, - ...data, - field, - fieldName: field.fieldName, - description: `${entityName}#${field.fieldName}`, - })); - }) - .flat(); - } - - /** - * @private - * Get entities and relationships to prepare. - * @returns {object[]} - */ - getEntitiesRelationshipsDataToPrepare() { - return this.getEntitiesDataToPrepare() - .map(({ entity, entityName, ...data }) => { - if (!entity.relationships) return []; - - return entity.relationships.map(relationship => ({ - entity, - entityName, - ...data, - relationship, - relationshipName: relationship.relationshipName, - description: `${entityName}#${relationship.relationshipName}`, - })); - }) - .flat(); - } - - /** - * @private - * Get entities to post prepare. - * @returns {object[]} - */ - getEntitiesDataToPostPrepare() { - return this.getEntitiesDataToPrepare(); - } - - /** - * @private - * Get entities to write. - * @returns {object[]} - */ - getEntitiesDataForPriorities() { - const entitiesDefinitions = this.sharedData.getEntities(); - return { entities: entitiesDefinitions.map(({ entity }) => entity) }; - } - - /** - * @private - * Get entities to write. - * @returns {object[]} - */ - getEntitiesDataToWrite() { - const { entities = [] } = this.options; - const data = this.getEntitiesDataForPriorities(); - if (entities.length === 0) return data; - const filteredEntities = data.entities.filter(entity => entities.includes(entity.name)); - return { ...data, entities: filteredEntities }; - } - - /** - * @private - * Queue entity tasks. - */ - queueEntityTasks() { - this.queueTask({ - queueName: CONFIGURING_EACH_ENTITY_QUEUE, - taskName: 'queueConfiguringEachEntity', - cancellable: true, - method: () => { - this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY}`); - const tasks = this.extractTasksFromPriority(CONFIGURING_EACH_ENTITY, { skip: false }); - this.getEntitiesDataToConfigure().forEach(({ entityName, entityStorage, entityConfig }) => { - this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY} for ${entityName}`); - const args = this.getArgsForPriority(CONFIGURING_EACH_ENTITY); - tasks.forEach(task => { - this.queueTask({ - ...task, - args: [{ ...args[0], entityName, entityStorage, entityConfig }], - }); - }); - }); - }, - } as any); - - this.queueTask({ - queueName: LOADING_ENTITIES_QUEUE, - taskName: 'queueLoadingEntities', - cancellable: true, - method: () => { - this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`); - const tasks = this.extractTasksFromPriority(LOADING_ENTITIES, { skip: false }); - this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`); - const args = this.getArgsForPriority(LOADING_ENTITIES); - tasks.forEach(task => { - this.queueTask({ - ...task, - args, - }); - }); - }, - } as any); - - this.queueTask({ - queueName: PREPARING_EACH_ENTITY_QUEUE, - taskName: 'queuePreparingEachEntity', - cancellable: true, - method: () => { - this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY}`); - const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY, { skip: false }); - this.getEntitiesDataToPrepare().forEach(({ description, ...data }) => { - this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY} for ${description}`); - const args = this.getArgsForPriority(PREPARING_EACH_ENTITY); - tasks.forEach(task => { - this.queueTask({ - ...task, - args: [{ ...args[0], description, ...data }], - }); - }); - }); - }, - } as any); - - this.queueTask({ - queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE, - taskName: 'queuePreparingEachEntityField', - cancellable: true, - method: () => { - const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_FIELD, { skip: false }); - this.getEntitiesFieldsDataToPrepare().forEach(({ description, ...data }) => { - this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_FIELD} for ${description}`); - const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_FIELD); - tasks.forEach(task => { - this.queueTask({ - ...task, - args: [{ ...args[0], description, ...data }], - }); - }); - }); - }, - } as any); - - this.queueTask({ - queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, - taskName: 'queuePreparingEachEntityRelationship', - cancellable: true, - method: () => { - const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_RELATIONSHIP, { skip: false }); - this.getEntitiesRelationshipsDataToPrepare().forEach(({ description, ...data }) => { - this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_RELATIONSHIP} for ${description}`); - const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_RELATIONSHIP); - tasks.forEach(task => { - this.queueTask({ - ...task, - args: [{ ...args[0], description, ...data }], - }); - }); - }); - }, - } as any); - - this.queueTask({ - queueName: POST_PREPARING_EACH_ENTITY_QUEUE, - taskName: 'queuePostPreparingEachEntity', - cancellable: true, - method: () => { - const tasks = this.extractTasksFromPriority(POST_PREPARING_EACH_ENTITY, { skip: false }); - this.getEntitiesDataToPostPrepare().forEach(({ description, ...data }) => { - this.log.debug(`Queueing entity tasks ${POST_PREPARING_EACH_ENTITY} for ${description}`); - const args = this.getArgsForPriority(POST_PREPARING_EACH_ENTITY); - tasks.forEach(task => { - this.queueTask({ - ...task, - args: [{ ...args[0], description, ...data }], - }); - }); - }); - }, - } as any); - - this.queueTask({ - queueName: WRITING_ENTITIES_QUEUE, - taskName: 'queueWritingEachEntity', - cancellable: true, - method: () => { - if (this.options.skipWriting) return; - const tasks = this.extractTasksFromPriority(WRITING_ENTITIES, { skip: false }); - const args = this.getArgsForPriority(WRITING_ENTITIES); - tasks.forEach(task => { - this.queueTask({ - ...task, - args, - }); - }); - }, - } as any); - - this.queueTask({ - queueName: POST_WRITING_ENTITIES_QUEUE, - taskName: 'queuePostWritingEachEntity', - cancellable: true, - method: () => { - if (this.options.skipWriting) return; - const tasks = this.extractTasksFromPriority(POST_WRITING_ENTITIES, { skip: false }); - const args = this.getArgsForPriority(POST_WRITING_ENTITIES); - tasks.forEach(task => { - this.queueTask({ - ...task, - args, - }); - }); - }, - } as any); - } -} diff --git a/generators/base-application/generator.spec.mts b/generators/base-application/generator.spec.mts deleted file mode 100644 index 86d8b99ba175..000000000000 --- a/generators/base-application/generator.spec.mts +++ /dev/null @@ -1,535 +0,0 @@ -/* eslint-disable max-classes-per-file */ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect, esmocha } from 'esmocha'; -import lodash from 'lodash'; - -import EnvironmentBuilder from '../../cli/environment-builder.mjs'; -import Generator from './index.mjs'; -import type { BaseApplication } from '../base-application/types.js'; -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - - // TODO test is broken due to @esbuild-kit/esm-loader - describe.skip('EnvironmentBuilder', () => { - let envBuilder; - before(() => { - envBuilder = EnvironmentBuilder.createDefaultBuilder(); - }); - it(`should be registered as jhipster:${generator} at yeoman-environment`, async () => { - expect(await envBuilder.getEnvironment().get(`jhipster:${generator}`)).toBe(Generator); - }); - }); - - describe('custom priorities tasks', () => { - // no args - const initializing = esmocha.fn(); - const prompting = esmocha.fn(); - const configuring = esmocha.fn(); - const composing = esmocha.fn(); - - // application arg - const loading = esmocha.fn(); - const preparing = esmocha.fn(); - const postPreparing = esmocha.fn(); - const writing = esmocha.fn(); - const postWriting = esmocha.fn(); - const install = esmocha.fn(); - const end = esmocha.fn(); - - // entities args - const configuringEachEntity = esmocha.fn(); - const preparingEachEntity = esmocha.fn(); - const preparingEachEntityField = esmocha.fn(); - const preparingEachEntityRelationship = esmocha.fn(); - const postPreparingEachEntity = esmocha.fn(); - const defaultTask = esmocha.fn(); - const writingEntities = esmocha.fn(); - const postWritingEntities = esmocha.fn(); - - class CustomGenerator extends Generator { - async beforeQueue() { - await this.dependsOnJHipster('bootstrap-application'); - } - - get [Generator.INITIALIZING]() { - return { initializing }; - } - - get [Generator.PROMPTING]() { - return { prompting }; - } - - get [Generator.CONFIGURING]() { - return { configuring }; - } - - get [Generator.COMPOSING]() { - return { composing }; - } - - get [Generator.LOADING]() { - return { loading }; - } - - get [Generator.PREPARING]() { - return { preparing }; - } - - get [Generator.POST_PREPARING]() { - return { postPreparing }; - } - - get [Generator.CONFIGURING_EACH_ENTITY]() { - return { configuringEachEntity }; - } - - get [Generator.PREPARING_EACH_ENTITY]() { - return { preparingEachEntity }; - } - - get [Generator.PREPARING_EACH_ENTITY_FIELD]() { - return { preparingEachEntityField }; - } - - get [Generator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { - return { preparingEachEntityRelationship }; - } - - get [Generator.POST_PREPARING_EACH_ENTITY]() { - return { postPreparingEachEntity }; - } - - get [Generator.DEFAULT]() { - return { defaultTask }; - } - - get [Generator.WRITING]() { - return { writing }; - } - - get [Generator.WRITING_ENTITIES]() { - return { writingEntities }; - } - - get [Generator.POST_WRITING]() { - return { postWriting }; - } - - get [Generator.POST_WRITING_ENTITIES]() { - return { postWritingEntities }; - } - - get [Generator.INSTALL]() { - return { install }; - } - - get [Generator.END]() { - return { end }; - } - } - - before(async () => { - await helpers.run(CustomGenerator).withJHipsterConfig({}, [ - { - name: 'One', - fields: [{ fieldName: 'id', fieldType: 'Long' }], - relationships: [{ relationshipName: 'two', otherEntityName: 'Two', relationshipType: 'many-to-one' }], - }, - { - name: 'Two', - fields: [ - { fieldName: 'id', fieldType: 'Long' }, - { fieldName: 'name', fieldType: 'String' }, - ], - relationships: [ - { relationshipName: 'one', otherEntityName: 'One', relationshipType: 'many-to-one' }, - { relationshipName: 'three', otherEntityName: 'Three', relationshipType: 'many-to-one' }, - ], - }, - { - name: 'Three', - }, - ]); - }); - - it('should call priorities with correct arguments', async () => { - const controlArg = { - control: expect.any(Object), - }; - - const applicationArg = { - ...controlArg, - application: expect.any(Object), - }; - - const applicationSourceArg = { - ...applicationArg, - source: expect.any(Object), - }; - - const applicationDefaultsArg = { - ...applicationArg, - applicationDefaults: expect.any(Function), - }; - - const entityConfiguringArg = { - ...applicationArg, - entityStorage: expect.any(Object), - entityConfig: expect.any(Object), - }; - - const entityArg = { - ...applicationArg, - entity: expect.any(Object), - entityName: expect.any(String), - description: expect.any(String), - }; - - const fieldArg = { - ...entityArg, - fieldName: expect.any(String), - field: expect.any(Object), - }; - - const relationshipArg = { - ...entityArg, - entityName: expect.any(String), - relationshipName: expect.any(String), - relationship: expect.any(Object), - }; - - const entitiesArg = { - ...controlArg, - ...applicationArg, - entities: [expect.any(Object), expect.any(Object), expect.any(Object), expect.any(Object)], - }; - - expect(initializing).toBeCalledWith(controlArg); - expect(prompting).toBeCalledWith(controlArg); - expect(configuring).toBeCalledWith(controlArg); - expect(composing).toBeCalledWith(controlArg); - expect(loading).toBeCalledWith(applicationDefaultsArg); - expect(postPreparing).toBeCalledWith(applicationSourceArg); - - expect(configuringEachEntity).toBeCalledTimes(3); - expect(configuringEachEntity).toHaveBeenNthCalledWith(1, { ...entityConfiguringArg, entityName: 'One' }); - expect(configuringEachEntity).toHaveBeenNthCalledWith(2, { ...entityConfiguringArg, entityName: 'Two' }); - expect(configuringEachEntity).toHaveBeenNthCalledWith(3, { ...entityConfiguringArg, entityName: 'Three' }); - - expect(preparingEachEntity).toBeCalledTimes(4); - expect(preparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); - - expect(preparingEachEntityField).toBeCalledTimes(8); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(1, { ...fieldArg, description: 'User#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(2, { ...fieldArg, description: 'User#login' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(3, { ...fieldArg, description: 'User#firstName' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(4, { ...fieldArg, description: 'User#lastName' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(5, { ...fieldArg, description: 'One#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(6, { ...fieldArg, description: 'Two#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(7, { ...fieldArg, description: 'Two#name' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(8, { ...fieldArg, description: 'Three#id' }); - - expect(preparingEachEntityRelationship).toBeCalledTimes(3); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(1, { ...relationshipArg, description: 'One#two' }); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(2, { ...relationshipArg, description: 'Two#one' }); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(3, { ...relationshipArg, description: 'Two#three' }); - - expect(postPreparingEachEntity).toBeCalledTimes(4); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); - - expect(defaultTask).toBeCalledWith(entitiesArg); - expect(writingEntities).toBeCalledWith(entitiesArg); - expect(postWritingEntities).toBeCalledWith({ ...entitiesArg, source: expect.any(Object) }); - - expect(writing).toBeCalledWith(applicationArg); - expect(install).toBeCalledWith(applicationArg); - expect(end).toBeCalledWith(applicationArg); - - expect(preparing).toBeCalledWith({ ...applicationSourceArg, ...applicationDefaultsArg }); - expect(postWriting).toBeCalledWith(applicationSourceArg); - }); - }); - - describe('entities option', () => { - // no args - const initializing = esmocha.fn(); - const prompting = esmocha.fn(); - const configuring = esmocha.fn(); - const composing = esmocha.fn(); - - // application arg - const loading = esmocha.fn(); - const preparing = esmocha.fn(); - const writing = esmocha.fn(); - const postWriting = esmocha.fn(); - const install = esmocha.fn(); - const end = esmocha.fn(); - - // entities args - const configuringEachEntity = esmocha.fn(); - const preparingEachEntity = esmocha.fn(); - const preparingEachEntityField = esmocha.fn(); - const preparingEachEntityRelationship = esmocha.fn(); - const postPreparingEachEntity = esmocha.fn(); - const defaultTask = esmocha.fn(); - const writingEntities = esmocha.fn(); - const postWritingEntities = esmocha.fn(); - - class CustomGenerator extends Generator { - async beforeQueue() { - await this.dependsOnJHipster('bootstrap-application'); - } - - get [Generator.INITIALIZING]() { - return { initializing }; - } - - get [Generator.PROMPTING]() { - return { prompting }; - } - - get [Generator.CONFIGURING]() { - return { configuring }; - } - - get [Generator.COMPOSING]() { - return { composing }; - } - - get [Generator.LOADING]() { - return { loading }; - } - - get [Generator.PREPARING]() { - return { preparing }; - } - - get [Generator.CONFIGURING_EACH_ENTITY]() { - return { configuringEachEntity }; - } - - get [Generator.PREPARING_EACH_ENTITY]() { - return { preparingEachEntity }; - } - - get [Generator.PREPARING_EACH_ENTITY_FIELD]() { - return { preparingEachEntityField }; - } - - get [Generator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { - return { preparingEachEntityRelationship }; - } - - get [Generator.POST_PREPARING_EACH_ENTITY]() { - return { postPreparingEachEntity }; - } - - get [Generator.DEFAULT]() { - return { defaultTask }; - } - - get [Generator.WRITING]() { - return { writing }; - } - - get [Generator.WRITING_ENTITIES]() { - return { writingEntities }; - } - - get [Generator.POST_WRITING]() { - return { postWriting }; - } - - get [Generator.POST_WRITING_ENTITIES]() { - return { postWritingEntities }; - } - - get [Generator.INSTALL]() { - return { install }; - } - - get [Generator.END]() { - return { end }; - } - } - - before(async () => { - await helpers - .run(CustomGenerator) - .withJHipsterConfig({}, [ - { - name: 'One', - fields: [{ fieldName: 'id', fieldType: 'Long' }], - relationships: [{ relationshipName: 'two', otherEntityName: 'Two', relationshipType: 'many-to-one' }], - }, - { - name: 'Two', - fields: [ - { fieldName: 'id', fieldType: 'Long' }, - { fieldName: 'name', fieldType: 'String' }, - ], - relationships: [ - { relationshipName: 'one', otherEntityName: 'One', relationshipType: 'many-to-one' }, - { relationshipName: 'three', otherEntityName: 'Three', relationshipType: 'many-to-one' }, - ], - }, - { - name: 'Three', - }, - ]) - .withOptions({ - entities: ['One', 'Two'], - }); - }); - - it('should call writingEntities and postWriting priorities with filtered entities', async () => { - const controlArg = { - control: expect.any(Object), - }; - - const applicationArg = { - ...controlArg, - application: expect.any(Object), - }; - - const applicationSourceArg = { - ...applicationArg, - source: expect.any(Object), - }; - - const applicationDefaultsArg = { - ...applicationArg, - applicationDefaults: expect.any(Function), - }; - - const entityConfiguringArg = { - ...applicationArg, - entityStorage: expect.any(Object), - entityConfig: expect.any(Object), - }; - - const entityArg = { - ...applicationArg, - entity: expect.any(Object), - entityName: expect.any(String), - description: expect.any(String), - }; - - const fieldArg = { - ...entityArg, - fieldName: expect.any(String), - field: expect.any(Object), - }; - - const relationshipArg = { - ...entityArg, - entityName: expect.any(String), - relationshipName: expect.any(String), - relationship: expect.any(Object), - }; - - const entitiesArg = { - ...applicationArg, - entities: [expect.any(Object), expect.any(Object), expect.any(Object), expect.any(Object)], - }; - - const writingEntitiesArg = { - ...applicationArg, - entities: [expect.any(Object), expect.any(Object)], - }; - - const postWritingEntitiesArg = { - ...writingEntitiesArg, - source: expect.any(Object), - }; - - expect(initializing).toBeCalledWith(controlArg); - expect(prompting).toBeCalledWith(controlArg); - expect(configuring).toBeCalledWith(controlArg); - expect(composing).toBeCalledWith(controlArg); - expect(loading).toBeCalledWith(applicationDefaultsArg); - - expect(configuringEachEntity).toBeCalledTimes(3); - expect(configuringEachEntity).toHaveBeenNthCalledWith(1, { ...entityConfiguringArg, entityName: 'One' }); - expect(configuringEachEntity).toHaveBeenNthCalledWith(2, { ...entityConfiguringArg, entityName: 'Two' }); - expect(configuringEachEntity).toHaveBeenNthCalledWith(3, { ...entityConfiguringArg, entityName: 'Three' }); - - expect(preparingEachEntity).toBeCalledTimes(4); - expect(preparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); - expect(preparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); - - expect(preparingEachEntityField).toBeCalledTimes(8); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(1, { ...fieldArg, description: 'User#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(2, { ...fieldArg, description: 'User#login' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(3, { ...fieldArg, description: 'User#firstName' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(4, { ...fieldArg, description: 'User#lastName' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(5, { ...fieldArg, description: 'One#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(6, { ...fieldArg, description: 'Two#id' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(7, { ...fieldArg, description: 'Two#name' }); - expect(preparingEachEntityField).toHaveBeenNthCalledWith(8, { ...fieldArg, description: 'Three#id' }); - - expect(preparingEachEntityRelationship).toBeCalledTimes(3); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(1, { ...relationshipArg, description: 'One#two' }); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(2, { ...relationshipArg, description: 'Two#one' }); - expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(3, { ...relationshipArg, description: 'Two#three' }); - - expect(postPreparingEachEntity).toBeCalledTimes(4); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); - expect(postPreparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); - - expect(defaultTask).toBeCalledWith(entitiesArg); - - expect(writingEntities).toBeCalledWith(writingEntitiesArg); - expect(postWritingEntities).toBeCalledWith(postWritingEntitiesArg); - - expect(writing).toBeCalledWith(applicationArg); - expect(install).toBeCalledWith(applicationArg); - expect(end).toBeCalledWith(applicationArg); - - expect(preparing).toBeCalledWith({ ...applicationSourceArg, ...applicationDefaultsArg }); - expect(postWriting).toBeCalledWith(applicationSourceArg); - }); - }); -}); diff --git a/generators/base-application/generator.spec.ts b/generators/base-application/generator.spec.ts new file mode 100644 index 000000000000..c6d0a6ab7909 --- /dev/null +++ b/generators/base-application/generator.spec.ts @@ -0,0 +1,535 @@ +/* eslint-disable max-classes-per-file */ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect, esmocha } from 'esmocha'; +import lodash from 'lodash'; + +import EnvironmentBuilder from '../../cli/environment-builder.mjs'; +import Generator from './index.js'; +import type { BaseApplication } from '../base-application/types.js'; +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + + // TODO test is broken due to @esbuild-kit/esm-loader + describe.skip('EnvironmentBuilder', () => { + let envBuilder; + before(() => { + envBuilder = EnvironmentBuilder.createDefaultBuilder(); + }); + it(`should be registered as jhipster:${generator} at yeoman-environment`, async () => { + expect(await envBuilder.getEnvironment().get(`jhipster:${generator}`)).toBe(Generator); + }); + }); + + describe('custom priorities tasks', () => { + // no args + const initializing = esmocha.fn(); + const prompting = esmocha.fn(); + const configuring = esmocha.fn(); + const composing = esmocha.fn(); + + // application arg + const loading = esmocha.fn(); + const preparing = esmocha.fn(); + const postPreparing = esmocha.fn(); + const writing = esmocha.fn(); + const postWriting = esmocha.fn(); + const install = esmocha.fn(); + const end = esmocha.fn(); + + // entities args + const configuringEachEntity = esmocha.fn(); + const preparingEachEntity = esmocha.fn(); + const preparingEachEntityField = esmocha.fn(); + const preparingEachEntityRelationship = esmocha.fn(); + const postPreparingEachEntity = esmocha.fn(); + const defaultTask = esmocha.fn(); + const writingEntities = esmocha.fn(); + const postWritingEntities = esmocha.fn(); + + class CustomGenerator extends Generator { + async beforeQueue() { + await this.dependsOnJHipster('bootstrap-application'); + } + + get [Generator.INITIALIZING]() { + return { initializing }; + } + + get [Generator.PROMPTING]() { + return { prompting }; + } + + get [Generator.CONFIGURING]() { + return { configuring }; + } + + get [Generator.COMPOSING]() { + return { composing }; + } + + get [Generator.LOADING]() { + return { loading }; + } + + get [Generator.PREPARING]() { + return { preparing }; + } + + get [Generator.POST_PREPARING]() { + return { postPreparing }; + } + + get [Generator.CONFIGURING_EACH_ENTITY]() { + return { configuringEachEntity }; + } + + get [Generator.PREPARING_EACH_ENTITY]() { + return { preparingEachEntity }; + } + + get [Generator.PREPARING_EACH_ENTITY_FIELD]() { + return { preparingEachEntityField }; + } + + get [Generator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { + return { preparingEachEntityRelationship }; + } + + get [Generator.POST_PREPARING_EACH_ENTITY]() { + return { postPreparingEachEntity }; + } + + get [Generator.DEFAULT]() { + return { defaultTask }; + } + + get [Generator.WRITING]() { + return { writing }; + } + + get [Generator.WRITING_ENTITIES]() { + return { writingEntities }; + } + + get [Generator.POST_WRITING]() { + return { postWriting }; + } + + get [Generator.POST_WRITING_ENTITIES]() { + return { postWritingEntities }; + } + + get [Generator.INSTALL]() { + return { install }; + } + + get [Generator.END]() { + return { end }; + } + } + + before(async () => { + await helpers.run(CustomGenerator).withJHipsterConfig({}, [ + { + name: 'One', + fields: [{ fieldName: 'id', fieldType: 'Long' }], + relationships: [{ relationshipName: 'two', otherEntityName: 'Two', relationshipType: 'many-to-one' }], + }, + { + name: 'Two', + fields: [ + { fieldName: 'id', fieldType: 'Long' }, + { fieldName: 'name', fieldType: 'String' }, + ], + relationships: [ + { relationshipName: 'one', otherEntityName: 'One', relationshipType: 'many-to-one' }, + { relationshipName: 'three', otherEntityName: 'Three', relationshipType: 'many-to-one' }, + ], + }, + { + name: 'Three', + }, + ]); + }); + + it('should call priorities with correct arguments', async () => { + const controlArg = { + control: expect.any(Object), + }; + + const applicationArg = { + ...controlArg, + application: expect.any(Object), + }; + + const applicationSourceArg = { + ...applicationArg, + source: expect.any(Object), + }; + + const applicationDefaultsArg = { + ...applicationArg, + applicationDefaults: expect.any(Function), + }; + + const entityConfiguringArg = { + ...applicationArg, + entityStorage: expect.any(Object), + entityConfig: expect.any(Object), + }; + + const entityArg = { + ...applicationArg, + entity: expect.any(Object), + entityName: expect.any(String), + description: expect.any(String), + }; + + const fieldArg = { + ...entityArg, + fieldName: expect.any(String), + field: expect.any(Object), + }; + + const relationshipArg = { + ...entityArg, + entityName: expect.any(String), + relationshipName: expect.any(String), + relationship: expect.any(Object), + }; + + const entitiesArg = { + ...controlArg, + ...applicationArg, + entities: [expect.any(Object), expect.any(Object), expect.any(Object), expect.any(Object)], + }; + + expect(initializing).toBeCalledWith(controlArg); + expect(prompting).toBeCalledWith(controlArg); + expect(configuring).toBeCalledWith(controlArg); + expect(composing).toBeCalledWith(controlArg); + expect(loading).toBeCalledWith(applicationDefaultsArg); + expect(postPreparing).toBeCalledWith(applicationSourceArg); + + expect(configuringEachEntity).toBeCalledTimes(3); + expect(configuringEachEntity).toHaveBeenNthCalledWith(1, { ...entityConfiguringArg, entityName: 'One' }); + expect(configuringEachEntity).toHaveBeenNthCalledWith(2, { ...entityConfiguringArg, entityName: 'Two' }); + expect(configuringEachEntity).toHaveBeenNthCalledWith(3, { ...entityConfiguringArg, entityName: 'Three' }); + + expect(preparingEachEntity).toBeCalledTimes(4); + expect(preparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); + + expect(preparingEachEntityField).toBeCalledTimes(8); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(1, { ...fieldArg, description: 'User#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(2, { ...fieldArg, description: 'User#login' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(3, { ...fieldArg, description: 'User#firstName' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(4, { ...fieldArg, description: 'User#lastName' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(5, { ...fieldArg, description: 'One#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(6, { ...fieldArg, description: 'Two#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(7, { ...fieldArg, description: 'Two#name' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(8, { ...fieldArg, description: 'Three#id' }); + + expect(preparingEachEntityRelationship).toBeCalledTimes(3); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(1, { ...relationshipArg, description: 'One#two' }); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(2, { ...relationshipArg, description: 'Two#one' }); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(3, { ...relationshipArg, description: 'Two#three' }); + + expect(postPreparingEachEntity).toBeCalledTimes(4); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); + + expect(defaultTask).toBeCalledWith(entitiesArg); + expect(writingEntities).toBeCalledWith(entitiesArg); + expect(postWritingEntities).toBeCalledWith({ ...entitiesArg, source: expect.any(Object) }); + + expect(writing).toBeCalledWith(applicationArg); + expect(install).toBeCalledWith(applicationArg); + expect(end).toBeCalledWith(applicationArg); + + expect(preparing).toBeCalledWith({ ...applicationSourceArg, ...applicationDefaultsArg }); + expect(postWriting).toBeCalledWith(applicationSourceArg); + }); + }); + + describe('entities option', () => { + // no args + const initializing = esmocha.fn(); + const prompting = esmocha.fn(); + const configuring = esmocha.fn(); + const composing = esmocha.fn(); + + // application arg + const loading = esmocha.fn(); + const preparing = esmocha.fn(); + const writing = esmocha.fn(); + const postWriting = esmocha.fn(); + const install = esmocha.fn(); + const end = esmocha.fn(); + + // entities args + const configuringEachEntity = esmocha.fn(); + const preparingEachEntity = esmocha.fn(); + const preparingEachEntityField = esmocha.fn(); + const preparingEachEntityRelationship = esmocha.fn(); + const postPreparingEachEntity = esmocha.fn(); + const defaultTask = esmocha.fn(); + const writingEntities = esmocha.fn(); + const postWritingEntities = esmocha.fn(); + + class CustomGenerator extends Generator { + async beforeQueue() { + await this.dependsOnJHipster('bootstrap-application'); + } + + get [Generator.INITIALIZING]() { + return { initializing }; + } + + get [Generator.PROMPTING]() { + return { prompting }; + } + + get [Generator.CONFIGURING]() { + return { configuring }; + } + + get [Generator.COMPOSING]() { + return { composing }; + } + + get [Generator.LOADING]() { + return { loading }; + } + + get [Generator.PREPARING]() { + return { preparing }; + } + + get [Generator.CONFIGURING_EACH_ENTITY]() { + return { configuringEachEntity }; + } + + get [Generator.PREPARING_EACH_ENTITY]() { + return { preparingEachEntity }; + } + + get [Generator.PREPARING_EACH_ENTITY_FIELD]() { + return { preparingEachEntityField }; + } + + get [Generator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { + return { preparingEachEntityRelationship }; + } + + get [Generator.POST_PREPARING_EACH_ENTITY]() { + return { postPreparingEachEntity }; + } + + get [Generator.DEFAULT]() { + return { defaultTask }; + } + + get [Generator.WRITING]() { + return { writing }; + } + + get [Generator.WRITING_ENTITIES]() { + return { writingEntities }; + } + + get [Generator.POST_WRITING]() { + return { postWriting }; + } + + get [Generator.POST_WRITING_ENTITIES]() { + return { postWritingEntities }; + } + + get [Generator.INSTALL]() { + return { install }; + } + + get [Generator.END]() { + return { end }; + } + } + + before(async () => { + await helpers + .run(CustomGenerator) + .withJHipsterConfig({}, [ + { + name: 'One', + fields: [{ fieldName: 'id', fieldType: 'Long' }], + relationships: [{ relationshipName: 'two', otherEntityName: 'Two', relationshipType: 'many-to-one' }], + }, + { + name: 'Two', + fields: [ + { fieldName: 'id', fieldType: 'Long' }, + { fieldName: 'name', fieldType: 'String' }, + ], + relationships: [ + { relationshipName: 'one', otherEntityName: 'One', relationshipType: 'many-to-one' }, + { relationshipName: 'three', otherEntityName: 'Three', relationshipType: 'many-to-one' }, + ], + }, + { + name: 'Three', + }, + ]) + .withOptions({ + entities: ['One', 'Two'], + }); + }); + + it('should call writingEntities and postWriting priorities with filtered entities', async () => { + const controlArg = { + control: expect.any(Object), + }; + + const applicationArg = { + ...controlArg, + application: expect.any(Object), + }; + + const applicationSourceArg = { + ...applicationArg, + source: expect.any(Object), + }; + + const applicationDefaultsArg = { + ...applicationArg, + applicationDefaults: expect.any(Function), + }; + + const entityConfiguringArg = { + ...applicationArg, + entityStorage: expect.any(Object), + entityConfig: expect.any(Object), + }; + + const entityArg = { + ...applicationArg, + entity: expect.any(Object), + entityName: expect.any(String), + description: expect.any(String), + }; + + const fieldArg = { + ...entityArg, + fieldName: expect.any(String), + field: expect.any(Object), + }; + + const relationshipArg = { + ...entityArg, + entityName: expect.any(String), + relationshipName: expect.any(String), + relationship: expect.any(Object), + }; + + const entitiesArg = { + ...applicationArg, + entities: [expect.any(Object), expect.any(Object), expect.any(Object), expect.any(Object)], + }; + + const writingEntitiesArg = { + ...applicationArg, + entities: [expect.any(Object), expect.any(Object)], + }; + + const postWritingEntitiesArg = { + ...writingEntitiesArg, + source: expect.any(Object), + }; + + expect(initializing).toBeCalledWith(controlArg); + expect(prompting).toBeCalledWith(controlArg); + expect(configuring).toBeCalledWith(controlArg); + expect(composing).toBeCalledWith(controlArg); + expect(loading).toBeCalledWith(applicationDefaultsArg); + + expect(configuringEachEntity).toBeCalledTimes(3); + expect(configuringEachEntity).toHaveBeenNthCalledWith(1, { ...entityConfiguringArg, entityName: 'One' }); + expect(configuringEachEntity).toHaveBeenNthCalledWith(2, { ...entityConfiguringArg, entityName: 'Two' }); + expect(configuringEachEntity).toHaveBeenNthCalledWith(3, { ...entityConfiguringArg, entityName: 'Three' }); + + expect(preparingEachEntity).toBeCalledTimes(4); + expect(preparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); + expect(preparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); + + expect(preparingEachEntityField).toBeCalledTimes(8); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(1, { ...fieldArg, description: 'User#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(2, { ...fieldArg, description: 'User#login' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(3, { ...fieldArg, description: 'User#firstName' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(4, { ...fieldArg, description: 'User#lastName' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(5, { ...fieldArg, description: 'One#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(6, { ...fieldArg, description: 'Two#id' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(7, { ...fieldArg, description: 'Two#name' }); + expect(preparingEachEntityField).toHaveBeenNthCalledWith(8, { ...fieldArg, description: 'Three#id' }); + + expect(preparingEachEntityRelationship).toBeCalledTimes(3); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(1, { ...relationshipArg, description: 'One#two' }); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(2, { ...relationshipArg, description: 'Two#one' }); + expect(preparingEachEntityRelationship).toHaveBeenNthCalledWith(3, { ...relationshipArg, description: 'Two#three' }); + + expect(postPreparingEachEntity).toBeCalledTimes(4); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(1, { ...entityArg, entityName: 'User' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(2, { ...entityArg, entityName: 'One' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(3, { ...entityArg, entityName: 'Two' }); + expect(postPreparingEachEntity).toHaveBeenNthCalledWith(4, { ...entityArg, entityName: 'Three' }); + + expect(defaultTask).toBeCalledWith(entitiesArg); + + expect(writingEntities).toBeCalledWith(writingEntitiesArg); + expect(postWritingEntities).toBeCalledWith(postWritingEntitiesArg); + + expect(writing).toBeCalledWith(applicationArg); + expect(install).toBeCalledWith(applicationArg); + expect(end).toBeCalledWith(applicationArg); + + expect(preparing).toBeCalledWith({ ...applicationSourceArg, ...applicationDefaultsArg }); + expect(postWriting).toBeCalledWith(applicationSourceArg); + }); + }); +}); diff --git a/generators/base-application/generator.ts b/generators/base-application/generator.ts new file mode 100644 index 000000000000..cb294c34962a --- /dev/null +++ b/generators/base-application/generator.ts @@ -0,0 +1,670 @@ +/** + * Copyright 2013-2021 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 { upperFirst } from 'lodash-es'; +import type { Storage } from 'yeoman-generator'; + +import BaseGenerator from '../base/index.js'; +import { CUSTOM_PRIORITIES, PRIORITY_NAMES, QUEUES } from './priorities.js'; +import { JHIPSTER_CONFIG_DIR } from '../generator-constants.js'; +import type { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from './tasks.js'; +import { GenericTaskGroup, GenericSourceTypeDefinition } from '../base/tasks.js'; +import type { BaseApplication, CommonClientServerApplication } from './types.js'; +import { getEntitiesFromDir } from './support/index.js'; +import { SpringBootSourceType } from '../server/types.js'; +import { ClientSourceType } from '../client/types.js'; +import { I18nApplication } from '../languages/types.js'; +import { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from '../base/api.js'; +import { mutateData } from '../base/support/config.js'; + +const { + LOADING, + PREPARING, + POST_PREPARING, + CONFIGURING_EACH_ENTITY, + LOADING_ENTITIES, + PREPARING_EACH_ENTITY, + PREPARING_EACH_ENTITY_FIELD, + PREPARING_EACH_ENTITY_RELATIONSHIP, + POST_PREPARING_EACH_ENTITY, + DEFAULT, + WRITING, + POST_WRITING, + WRITING_ENTITIES, + POST_WRITING_ENTITIES, + PRE_CONFLICTS, + INSTALL, + END, +} = PRIORITY_NAMES; + +const { + CONFIGURING_EACH_ENTITY_QUEUE, + LOADING_ENTITIES_QUEUE, + PREPARING_EACH_ENTITY_QUEUE, + PREPARING_EACH_ENTITY_FIELD_QUEUE, + PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, + POST_PREPARING_EACH_ENTITY_QUEUE, + WRITING_ENTITIES_QUEUE, + POST_WRITING_ENTITIES_QUEUE, +} = QUEUES; + +const asPriority = BaseGenerator.asPriority; + +export type BaseApplicationSource = Record any> & SpringBootSourceType & ClientSourceType & I18nApplication; + +export type JHipsterApplication = BaseApplication & Partial; + +export type GeneratorDefinition = BaseApplicationGeneratorDefinition< + GenericApplicationDefinition & GenericSourceTypeDefinition +>; + +/** + * This is the base class for a generator that generates entities. + */ +export default class BaseApplicationGenerator< + Definition extends BaseApplicationGeneratorDefinition<{ + applicationType: any; + entityType: any; + sourceType: any; + }> = GeneratorDefinition, +> extends BaseGenerator { + static CONFIGURING_EACH_ENTITY = asPriority(CONFIGURING_EACH_ENTITY); + + static LOADING_ENTITIES = asPriority(LOADING_ENTITIES); + + static PREPARING_EACH_ENTITY = asPriority(PREPARING_EACH_ENTITY); + + static PREPARING_EACH_ENTITY_FIELD = asPriority(PREPARING_EACH_ENTITY_FIELD); + + static PREPARING_EACH_ENTITY_RELATIONSHIP = asPriority(PREPARING_EACH_ENTITY_RELATIONSHIP); + + static POST_PREPARING_EACH_ENTITY = asPriority(POST_PREPARING_EACH_ENTITY); + + static WRITING_ENTITIES = asPriority(WRITING_ENTITIES); + + static POST_WRITING_ENTITIES = asPriority(POST_WRITING_ENTITIES); + + constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { + super(args, options, features); + + if (this.options.help) { + return; + } + + this.registerPriorities(CUSTOM_PRIORITIES); + + /* Add tasks allowing entities priorities to match normal priorities pattern */ + this.on('queueOwnTasks', () => { + this.log.debug('Queueing entity tasks'); + this.queueEntityTasks(); + }); + + if (this.options.applicationWithEntities) { + this.log.warn('applicationWithEntities option is deprecated'); + // Write new definitions to memfs + this.config.set({ + ...this.config.getAll(), + ...this.options.applicationWithEntities.config, + }); + if (this.options.applicationWithEntities.entities) { + const entities = this.options.applicationWithEntities.entities.map(entity => { + const entityName = upperFirst(entity.name); + const file = this.getEntityConfigPath(entityName); + this.fs.writeJSON(file, { ...this.fs.readJSON(file), ...entity }); + return entityName; + }); + this.jhipsterConfig.entities = [...new Set((this.jhipsterConfig.entities || []).concat(entities))]; + } + delete this.options.applicationWithEntities; + } + } + + /** + * Get Entities configuration path + * @returns + */ + getEntitiesConfigPath(...args) { + return this.destinationPath(JHIPSTER_CONFIG_DIR, ...args); + } + + /** + * Get Entity configuration path + * @param entityName Entity name + * @returns + */ + getEntityConfigPath(entityName: string) { + return this.getEntitiesConfigPath(`${upperFirst(entityName)}.json`); + } + + /** + * Get all the generator configuration from the .yo-rc.json file + * @param entityName - Name of the entity to load. + * @param create - Create storage if doesn't exists. + */ + getEntityConfig(entityName: string, create = false): Storage | undefined { + const entityPath = this.getEntityConfigPath(entityName); + if (!create && !this.fs.exists(entityPath)) return undefined; + return this.createStorage(entityPath); + } + + /** + * get sorted list of entity names according to changelog date (i.e. the order in which they were added) + */ + getExistingEntityNames(): string[] { + return this.getExistingEntities().map(entity => entity.name); + } + + /** + * get sorted list of entities according to changelog date (i.e. the order in which they were added) + */ + getExistingEntities(): { name: string; definition: Record }[] { + function isBefore(e1, e2) { + return (e1.definition.annotations?.changelogDate ?? 0) - (e2.definition.annotations?.changelogDate ?? 0); + } + + const configDir = this.getEntitiesConfigPath(); + + const entities: { name: string; definition: Record }[] = []; + for (const entityName of [...new Set(((this.jhipsterConfig.entities as string[]) || []).concat(getEntitiesFromDir(configDir)))]) { + const definition = this.getEntityConfig(entityName)?.getAll(); + if (definition) { + entities.push({ name: entityName, definition }); + } + } + entities.sort(isBefore); + this.jhipsterConfig.entities = entities.map(({ name }) => name); + return entities; + } + + /** + * Priority API stub for blueprints. + * + * Configuring each entity should configure entities. + */ + get configuringEachEntity(): GenericTaskGroup { + return this.asConfiguringEachEntityTaskGroup({}); + } + + get preparingEachEntity(): GenericTaskGroup { + return this.asPreparingEachEntityTaskGroup({}); + } + + /** + * Priority API stub for blueprints. + */ + get preparingEachEntityField(): GenericTaskGroup { + return this.asPreparingEachEntityFieldTaskGroup({}); + } + + /** + * Priority API stub for blueprints. + */ + get preparingEachEntityRelationship(): GenericTaskGroup { + return this.asPreparingEachEntityRelationshipTaskGroup({}); + } + + /** + * Priority API stub for blueprints. + */ + get postPreparingEachEntity(): GenericTaskGroup { + return this.asPostPreparingEachEntityTaskGroup({}); + } + + /** + * Priority API stub for blueprints. + */ + get writingEntities(): GenericTaskGroup { + return this.asWritingEntitiesTaskGroup({}); + } + + /** + * Priority API stub for blueprints. + */ + get postWritingEntities(): GenericTaskGroup { + return this.asPostWritingEntitiesTaskGroup({}); + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asConfiguringEachEntityTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asLoadingEntitiesTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPreparingEachEntityTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPreparingEachEntityFieldTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPreparingEachEntityRelationshipTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPostPreparingEachEntityTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asWritingEntitiesTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPostWritingEntitiesTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Reset entities fake data seed. + * @param {string} seed + */ + resetEntitiesFakeData(seed) { + seed = `${this.sharedData.getApplication().baseName}-${seed}`; + this.log.debug(`Resetting entities seed with '${seed}'`); + this.sharedData.getEntities().forEach(({ entity }) => { + entity.resetFakerSeed(seed); + }); + } + + getArgsForPriority(priorityName): any[] { + const args = super.getArgsForPriority(priorityName); + let firstArg = this.getTaskFirstArgForPriority(priorityName); + if (args.length > 0) { + firstArg = { ...args[0], ...firstArg }; + } + return [firstArg]; + } + + /** + * @protected + */ + protected getTaskFirstArgForPriority(priorityName): any { + if ( + ![ + LOADING, + PREPARING, + POST_PREPARING, + + CONFIGURING_EACH_ENTITY, + LOADING_ENTITIES, + PREPARING_EACH_ENTITY, + PREPARING_EACH_ENTITY_FIELD, + PREPARING_EACH_ENTITY_RELATIONSHIP, + POST_PREPARING_EACH_ENTITY, + + DEFAULT, + WRITING, + WRITING_ENTITIES, + POST_WRITING, + POST_WRITING_ENTITIES, + PRE_CONFLICTS, + INSTALL, + END, + ].includes(priorityName) + ) { + return {}; + } + if (!this.jhipsterConfig.baseName) { + throw new Error(`BaseName (${this.jhipsterConfig.baseName}) application not available for priority ${priorityName}`); + } + const application = this.sharedData.getApplication(); + + if ([PREPARING, LOADING].includes(priorityName)) { + return { + application, + applicationDefaults: data => mutateData(application, data), + }; + } + if (LOADING_ENTITIES === priorityName) { + return { + application, + entitiesToLoad: this.getEntitiesDataToLoad(), + }; + } + if ([DEFAULT].includes(priorityName)) { + return { + application, + ...this.getEntitiesDataForPriorities(), + }; + } + if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { + const applicationAndEntities = { + application, + ...this.getEntitiesDataToWrite(), + }; + if (priorityName === WRITING_ENTITIES) { + return applicationAndEntities; + } + return { + ...applicationAndEntities, + source: this.sharedData.getSource(), + }; + } + + return { application }; + } + + /** + * @private + * Get entities to configure. + * This method doesn't filter entities. An filtered config can be changed at this priority. + * @returns {string[]} + */ + getEntitiesDataToConfigure() { + return this.getExistingEntityNames().map(entityName => { + const entityStorage = this.getEntityConfig(entityName, true); + return { entityName, entityStorage, entityConfig: entityStorage!.createProxy() }; + }); + } + + /** + * @private + * Get entities to load. + * This method doesn't filter entities. An filtered config can be changed at this priority. + * @returns {string[]} + */ + getEntitiesDataToLoad() { + return this.getExistingEntityNames().map(entityName => ({ entityName, entityStorage: this.getEntityConfig(entityName, true) })); + } + + /** + * @private + * Get entities to prepare. + * @returns {object[]} + */ + getEntitiesDataToPrepare() { + return this.sharedData.getEntities().map(({ entityName, ...data }) => ({ + description: entityName, + entityName, + ...data, + })); + } + + /** + * @private + * Get entities and fields to prepare. + * @returns {object[]} + */ + getEntitiesFieldsDataToPrepare() { + return this.getEntitiesDataToPrepare() + .map(({ entity, entityName, ...data }) => { + if (!entity.fields) return []; + + return entity.fields.map(field => ({ + entity, + entityName, + ...data, + field, + fieldName: field.fieldName, + description: `${entityName}#${field.fieldName}`, + })); + }) + .flat(); + } + + /** + * @private + * Get entities and relationships to prepare. + * @returns {object[]} + */ + getEntitiesRelationshipsDataToPrepare() { + return this.getEntitiesDataToPrepare() + .map(({ entity, entityName, ...data }) => { + if (!entity.relationships) return []; + + return entity.relationships.map(relationship => ({ + entity, + entityName, + ...data, + relationship, + relationshipName: relationship.relationshipName, + description: `${entityName}#${relationship.relationshipName}`, + })); + }) + .flat(); + } + + /** + * @private + * Get entities to post prepare. + * @returns {object[]} + */ + getEntitiesDataToPostPrepare() { + return this.getEntitiesDataToPrepare(); + } + + /** + * @private + * Get entities to write. + * @returns {object[]} + */ + getEntitiesDataForPriorities() { + const entitiesDefinitions = this.sharedData.getEntities(); + return { entities: entitiesDefinitions.map(({ entity }) => entity) }; + } + + /** + * @private + * Get entities to write. + * @returns {object[]} + */ + getEntitiesDataToWrite() { + const { entities = [] } = this.options; + const data = this.getEntitiesDataForPriorities(); + if (entities.length === 0) return data; + const filteredEntities = data.entities.filter(entity => entities.includes(entity.name)); + return { ...data, entities: filteredEntities }; + } + + /** + * @private + * Queue entity tasks. + */ + queueEntityTasks() { + this.queueTask({ + queueName: CONFIGURING_EACH_ENTITY_QUEUE, + taskName: 'queueConfiguringEachEntity', + cancellable: true, + method: () => { + this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY}`); + const tasks = this.extractTasksFromPriority(CONFIGURING_EACH_ENTITY, { skip: false }); + this.getEntitiesDataToConfigure().forEach(({ entityName, entityStorage, entityConfig }) => { + this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY} for ${entityName}`); + const args = this.getArgsForPriority(CONFIGURING_EACH_ENTITY); + tasks.forEach(task => { + this.queueTask({ + ...task, + args: [{ ...args[0], entityName, entityStorage, entityConfig }], + }); + }); + }); + }, + } as any); + + this.queueTask({ + queueName: LOADING_ENTITIES_QUEUE, + taskName: 'queueLoadingEntities', + cancellable: true, + method: () => { + this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`); + const tasks = this.extractTasksFromPriority(LOADING_ENTITIES, { skip: false }); + this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`); + const args = this.getArgsForPriority(LOADING_ENTITIES); + tasks.forEach(task => { + this.queueTask({ + ...task, + args, + }); + }); + }, + } as any); + + this.queueTask({ + queueName: PREPARING_EACH_ENTITY_QUEUE, + taskName: 'queuePreparingEachEntity', + cancellable: true, + method: () => { + this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY}`); + const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY, { skip: false }); + this.getEntitiesDataToPrepare().forEach(({ description, ...data }) => { + this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY} for ${description}`); + const args = this.getArgsForPriority(PREPARING_EACH_ENTITY); + tasks.forEach(task => { + this.queueTask({ + ...task, + args: [{ ...args[0], description, ...data }], + }); + }); + }); + }, + } as any); + + this.queueTask({ + queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE, + taskName: 'queuePreparingEachEntityField', + cancellable: true, + method: () => { + const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_FIELD, { skip: false }); + this.getEntitiesFieldsDataToPrepare().forEach(({ description, ...data }) => { + this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_FIELD} for ${description}`); + const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_FIELD); + tasks.forEach(task => { + this.queueTask({ + ...task, + args: [{ ...args[0], description, ...data }], + }); + }); + }); + }, + } as any); + + this.queueTask({ + queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, + taskName: 'queuePreparingEachEntityRelationship', + cancellable: true, + method: () => { + const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_RELATIONSHIP, { skip: false }); + this.getEntitiesRelationshipsDataToPrepare().forEach(({ description, ...data }) => { + this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_RELATIONSHIP} for ${description}`); + const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_RELATIONSHIP); + tasks.forEach(task => { + this.queueTask({ + ...task, + args: [{ ...args[0], description, ...data }], + }); + }); + }); + }, + } as any); + + this.queueTask({ + queueName: POST_PREPARING_EACH_ENTITY_QUEUE, + taskName: 'queuePostPreparingEachEntity', + cancellable: true, + method: () => { + const tasks = this.extractTasksFromPriority(POST_PREPARING_EACH_ENTITY, { skip: false }); + this.getEntitiesDataToPostPrepare().forEach(({ description, ...data }) => { + this.log.debug(`Queueing entity tasks ${POST_PREPARING_EACH_ENTITY} for ${description}`); + const args = this.getArgsForPriority(POST_PREPARING_EACH_ENTITY); + tasks.forEach(task => { + this.queueTask({ + ...task, + args: [{ ...args[0], description, ...data }], + }); + }); + }); + }, + } as any); + + this.queueTask({ + queueName: WRITING_ENTITIES_QUEUE, + taskName: 'queueWritingEachEntity', + cancellable: true, + method: () => { + if (this.options.skipWriting) return; + const tasks = this.extractTasksFromPriority(WRITING_ENTITIES, { skip: false }); + const args = this.getArgsForPriority(WRITING_ENTITIES); + tasks.forEach(task => { + this.queueTask({ + ...task, + args, + }); + }); + }, + } as any); + + this.queueTask({ + queueName: POST_WRITING_ENTITIES_QUEUE, + taskName: 'queuePostWritingEachEntity', + cancellable: true, + method: () => { + if (this.options.skipWriting) return; + const tasks = this.extractTasksFromPriority(POST_WRITING_ENTITIES, { skip: false }); + const args = this.getArgsForPriority(POST_WRITING_ENTITIES); + tasks.forEach(task => { + this.queueTask({ + ...task, + args, + }); + }); + }, + } as any); + } +} diff --git a/generators/base-application/index.mts b/generators/base-application/index.mts deleted file mode 100644 index be8069ec41c7..000000000000 --- a/generators/base-application/index.mts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ - -/* - * Register generator-base at yeoman-environment - */ -export { default } from './generator.mjs'; -export type { BaseEntity, Entity, Field, Relationship } from './types/index.mjs'; diff --git a/generators/base-application/index.ts b/generators/base-application/index.ts new file mode 100644 index 000000000000..b5a900abdc02 --- /dev/null +++ b/generators/base-application/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2013-2023 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. + */ + +/* + * Register generator-base at yeoman-environment + */ +export { default } from './generator.js'; +export type { BaseEntity, Entity, Field, Relationship } from './types/index.js'; diff --git a/generators/base-application/priorities.js b/generators/base-application/priorities.js new file mode 100644 index 000000000000..95a2cd3fb29e --- /dev/null +++ b/generators/base-application/priorities.js @@ -0,0 +1,163 @@ +/** + * Copyright 2013-2023 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 { QUEUE_PREFIX, PRIORITY_NAMES as PRIORITY_NAMES_BASE, QUEUES as QUEUES_BASE } from '../base/priorities.js'; + +const { DEFAULT, TRANSFORM, MULTISTEP_TRANSFORM } = PRIORITY_NAMES_BASE; + +const CONFIGURING_EACH_ENTITY = 'configuringEachEntity'; +const CONFIGURING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${CONFIGURING_EACH_ENTITY}`; + +const LOADING_ENTITIES = 'loadingEntities'; +const LOADING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${LOADING_ENTITIES}`; + +const PREPARING_EACH_ENTITY = 'preparingEachEntity'; +const PREPARING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY}`; + +const PREPARING_EACH_ENTITY_FIELD = 'preparingEachEntityField'; +const PREPARING_EACH_ENTITY_FIELD_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY_FIELD}`; + +const PREPARING_EACH_ENTITY_RELATIONSHIP = 'preparingEachEntityRelationship'; +const PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY_RELATIONSHIP}`; + +const POST_PREPARING_EACH_ENTITY = 'postPreparingEachEntity'; +const POST_PREPARING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${POST_PREPARING_EACH_ENTITY}`; + +const WRITING_ENTITIES = 'writingEntities'; +const WRITING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${WRITING_ENTITIES}`; + +const POST_WRITING_ENTITIES = 'postWritingEntities'; +const POST_WRITING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${POST_WRITING_ENTITIES}`; + +const LOADING_TRANSLATIONS = 'loadingTranslations'; +const LOADING_TRANSLATIONS_QUEUE = `${QUEUE_PREFIX}${LOADING_TRANSLATIONS}`; + +export const CUSTOM_PRIORITIES = [ + { + priorityName: CONFIGURING_EACH_ENTITY, + queueName: CONFIGURING_EACH_ENTITY_QUEUE, + before: LOADING_ENTITIES, + skip: true, + }, + { + priorityName: LOADING_ENTITIES, + queueName: LOADING_ENTITIES_QUEUE, + before: PREPARING_EACH_ENTITY, + skip: true, + }, + { + priorityName: PREPARING_EACH_ENTITY, + queueName: PREPARING_EACH_ENTITY_QUEUE, + before: PREPARING_EACH_ENTITY_FIELD, + skip: true, + }, + { + priorityName: PREPARING_EACH_ENTITY_FIELD, + queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE, + before: PREPARING_EACH_ENTITY_RELATIONSHIP, + skip: true, + }, + { + priorityName: PREPARING_EACH_ENTITY_RELATIONSHIP, + queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, + before: POST_PREPARING_EACH_ENTITY, + skip: true, + }, + { + priorityName: POST_PREPARING_EACH_ENTITY, + queueName: POST_PREPARING_EACH_ENTITY_QUEUE, + before: DEFAULT, + skip: true, + }, + { + priorityName: WRITING_ENTITIES, + queueName: WRITING_ENTITIES_QUEUE, + before: MULTISTEP_TRANSFORM, + skip: true, + }, + { + priorityName: POST_WRITING_ENTITIES, + queueName: POST_WRITING_ENTITIES_QUEUE, + before: LOADING_TRANSLATIONS, + skip: true, + }, + { + priorityName: LOADING_TRANSLATIONS, + queueName: LOADING_TRANSLATIONS_QUEUE, + before: TRANSFORM, + skip: true, + }, +].reverse(); + +const ENTITY_QUEUES = { + CONFIGURING_EACH_ENTITY_QUEUE, + LOADING_ENTITIES_QUEUE, + PREPARING_EACH_ENTITY_QUEUE, + PREPARING_EACH_ENTITY_FIELD_QUEUE, + PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, + POST_PREPARING_EACH_ENTITY_QUEUE, + WRITING_ENTITIES_QUEUE, + POST_WRITING_ENTITIES_QUEUE, + LOADING_TRANSLATIONS_QUEUE, +}; + +export const ENTITY_PRIORITY_NAMES = { + CONFIGURING_EACH_ENTITY, + LOADING_ENTITIES, + PREPARING_EACH_ENTITY, + PREPARING_EACH_ENTITY_FIELD, + PREPARING_EACH_ENTITY_RELATIONSHIP, + POST_PREPARING_EACH_ENTITY, + WRITING_ENTITIES, + POST_WRITING_ENTITIES, +}; + +export const PRIORITY_NAMES = { + ...PRIORITY_NAMES_BASE, + ...ENTITY_PRIORITY_NAMES, +}; + +export const QUEUES = { + ...QUEUES_BASE, + ...ENTITY_QUEUES, +}; + +export const PRIORITY_NAMES_LIST = [ + PRIORITY_NAMES.INITIALIZING, + PRIORITY_NAMES.PROMPTING, + PRIORITY_NAMES.CONFIGURING, + PRIORITY_NAMES.COMPOSING, + PRIORITY_NAMES.LOADING, + PRIORITY_NAMES.PREPARING, + CONFIGURING_EACH_ENTITY, + LOADING_ENTITIES, + PREPARING_EACH_ENTITY, + PREPARING_EACH_ENTITY_FIELD, + PREPARING_EACH_ENTITY_RELATIONSHIP, + POST_PREPARING_EACH_ENTITY, + DEFAULT, + PRIORITY_NAMES.WRITING, + WRITING_ENTITIES, + PRIORITY_NAMES.POST_WRITING, + POST_WRITING_ENTITIES, + LOADING_TRANSLATIONS, + PRIORITY_NAMES_BASE.INSTALL, + PRIORITY_NAMES_BASE.POST_INSTALL, + PRIORITY_NAMES_BASE.END, +]; diff --git a/generators/base-application/priorities.mjs b/generators/base-application/priorities.mjs deleted file mode 100644 index 6da721d4708e..000000000000 --- a/generators/base-application/priorities.mjs +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright 2013-2023 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 { QUEUE_PREFIX, PRIORITY_NAMES as PRIORITY_NAMES_BASE, QUEUES as QUEUES_BASE } from '../base/priorities.mjs'; - -const { DEFAULT, TRANSFORM, MULTISTEP_TRANSFORM } = PRIORITY_NAMES_BASE; - -const CONFIGURING_EACH_ENTITY = 'configuringEachEntity'; -const CONFIGURING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${CONFIGURING_EACH_ENTITY}`; - -const LOADING_ENTITIES = 'loadingEntities'; -const LOADING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${LOADING_ENTITIES}`; - -const PREPARING_EACH_ENTITY = 'preparingEachEntity'; -const PREPARING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY}`; - -const PREPARING_EACH_ENTITY_FIELD = 'preparingEachEntityField'; -const PREPARING_EACH_ENTITY_FIELD_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY_FIELD}`; - -const PREPARING_EACH_ENTITY_RELATIONSHIP = 'preparingEachEntityRelationship'; -const PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE = `${QUEUE_PREFIX}${PREPARING_EACH_ENTITY_RELATIONSHIP}`; - -const POST_PREPARING_EACH_ENTITY = 'postPreparingEachEntity'; -const POST_PREPARING_EACH_ENTITY_QUEUE = `${QUEUE_PREFIX}${POST_PREPARING_EACH_ENTITY}`; - -const WRITING_ENTITIES = 'writingEntities'; -const WRITING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${WRITING_ENTITIES}`; - -const POST_WRITING_ENTITIES = 'postWritingEntities'; -const POST_WRITING_ENTITIES_QUEUE = `${QUEUE_PREFIX}${POST_WRITING_ENTITIES}`; - -const LOADING_TRANSLATIONS = 'loadingTranslations'; -const LOADING_TRANSLATIONS_QUEUE = `${QUEUE_PREFIX}${LOADING_TRANSLATIONS}`; - -export const CUSTOM_PRIORITIES = [ - { - priorityName: CONFIGURING_EACH_ENTITY, - queueName: CONFIGURING_EACH_ENTITY_QUEUE, - before: LOADING_ENTITIES, - skip: true, - }, - { - priorityName: LOADING_ENTITIES, - queueName: LOADING_ENTITIES_QUEUE, - before: PREPARING_EACH_ENTITY, - skip: true, - }, - { - priorityName: PREPARING_EACH_ENTITY, - queueName: PREPARING_EACH_ENTITY_QUEUE, - before: PREPARING_EACH_ENTITY_FIELD, - skip: true, - }, - { - priorityName: PREPARING_EACH_ENTITY_FIELD, - queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE, - before: PREPARING_EACH_ENTITY_RELATIONSHIP, - skip: true, - }, - { - priorityName: PREPARING_EACH_ENTITY_RELATIONSHIP, - queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, - before: POST_PREPARING_EACH_ENTITY, - skip: true, - }, - { - priorityName: POST_PREPARING_EACH_ENTITY, - queueName: POST_PREPARING_EACH_ENTITY_QUEUE, - before: DEFAULT, - skip: true, - }, - { - priorityName: WRITING_ENTITIES, - queueName: WRITING_ENTITIES_QUEUE, - before: MULTISTEP_TRANSFORM, - skip: true, - }, - { - priorityName: POST_WRITING_ENTITIES, - queueName: POST_WRITING_ENTITIES_QUEUE, - before: LOADING_TRANSLATIONS, - skip: true, - }, - { - priorityName: LOADING_TRANSLATIONS, - queueName: LOADING_TRANSLATIONS_QUEUE, - before: TRANSFORM, - skip: true, - }, -].reverse(); - -const ENTITY_QUEUES = { - CONFIGURING_EACH_ENTITY_QUEUE, - LOADING_ENTITIES_QUEUE, - PREPARING_EACH_ENTITY_QUEUE, - PREPARING_EACH_ENTITY_FIELD_QUEUE, - PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, - POST_PREPARING_EACH_ENTITY_QUEUE, - WRITING_ENTITIES_QUEUE, - POST_WRITING_ENTITIES_QUEUE, - LOADING_TRANSLATIONS_QUEUE, -}; - -export const ENTITY_PRIORITY_NAMES = { - CONFIGURING_EACH_ENTITY, - LOADING_ENTITIES, - PREPARING_EACH_ENTITY, - PREPARING_EACH_ENTITY_FIELD, - PREPARING_EACH_ENTITY_RELATIONSHIP, - POST_PREPARING_EACH_ENTITY, - WRITING_ENTITIES, - POST_WRITING_ENTITIES, -}; - -export const PRIORITY_NAMES = { - ...PRIORITY_NAMES_BASE, - ...ENTITY_PRIORITY_NAMES, -}; - -export const QUEUES = { - ...QUEUES_BASE, - ...ENTITY_QUEUES, -}; - -export const PRIORITY_NAMES_LIST = [ - PRIORITY_NAMES.INITIALIZING, - PRIORITY_NAMES.PROMPTING, - PRIORITY_NAMES.CONFIGURING, - PRIORITY_NAMES.COMPOSING, - PRIORITY_NAMES.LOADING, - PRIORITY_NAMES.PREPARING, - CONFIGURING_EACH_ENTITY, - LOADING_ENTITIES, - PREPARING_EACH_ENTITY, - PREPARING_EACH_ENTITY_FIELD, - PREPARING_EACH_ENTITY_RELATIONSHIP, - POST_PREPARING_EACH_ENTITY, - DEFAULT, - PRIORITY_NAMES.WRITING, - WRITING_ENTITIES, - PRIORITY_NAMES.POST_WRITING, - POST_WRITING_ENTITIES, - LOADING_TRANSLATIONS, - PRIORITY_NAMES_BASE.INSTALL, - PRIORITY_NAMES_BASE.POST_INSTALL, - PRIORITY_NAMES_BASE.END, -]; diff --git a/generators/base-application/support/debug.mts b/generators/base-application/support/debug.ts similarity index 100% rename from generators/base-application/support/debug.mts rename to generators/base-application/support/debug.ts diff --git a/generators/base-application/support/doc.mts b/generators/base-application/support/doc.ts similarity index 100% rename from generators/base-application/support/doc.mts rename to generators/base-application/support/doc.ts diff --git a/generators/base-application/support/entities.mts b/generators/base-application/support/entities.ts similarity index 100% rename from generators/base-application/support/entities.mts rename to generators/base-application/support/entities.ts diff --git a/generators/base-application/support/entity.mts b/generators/base-application/support/entity.ts similarity index 100% rename from generators/base-application/support/entity.mts rename to generators/base-application/support/entity.ts diff --git a/generators/base-application/support/enum.js b/generators/base-application/support/enum.js new file mode 100644 index 000000000000..9cd376d03045 --- /dev/null +++ b/generators/base-application/support/enum.js @@ -0,0 +1,100 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { formatDocAsJavaDoc } from '../../server/support/doc.js'; + +const doesTheEnumValueHaveACustomValue = enumValue => { + return enumValue.includes('('); +}; + +const getCustomValuesState = enumValues => { + const state = { + withoutCustomValue: 0, + withCustomValue: 0, + }; + enumValues.forEach(enumValue => { + if (doesTheEnumValueHaveACustomValue(enumValue)) { + state.withCustomValue++; + } else { + state.withoutCustomValue++; + } + }); + return { + withoutCustomValues: state.withCustomValue === 0, + withSomeCustomValues: state.withCustomValue !== 0 && state.withoutCustomValue !== 0, + withCustomValues: state.withoutCustomValue === 0, + }; +}; + +const getEnums = (enums, customValuesState, comments) => { + if (customValuesState.withoutCustomValues) { + return enums.map(enumValue => ({ + name: enumValue, + value: enumValue, + comment: comments && comments[enumValue] && formatDocAsJavaDoc(comments[enumValue], 4), + })); + } + return enums.map(enumValue => { + if (!doesTheEnumValueHaveACustomValue(enumValue)) { + return { + name: enumValue.trim(), + value: enumValue.trim(), + comment: comments && comments[enumValue] && formatDocAsJavaDoc(comments[enumValue], 4), + }; + } + // eslint-disable-next-line no-unused-vars + const matched = /\s*(.+?)\s*\((.+?)\)/.exec(enumValue); + return { + name: matched[1], + value: matched[2], + comment: comments && comments[matched[1]] && formatDocAsJavaDoc(comments[matched[1]], 4), + }; + }); +}; + +const extractEnumInstance = field => { + const fieldType = field.fieldType; + return _.lowerFirst(fieldType); +}; + +const extractEnumEntries = field => { + return field.fieldValues.split(',').map(fieldValue => fieldValue.trim()); +}; + +/** + * Build an enum object + * @param {Object} field - entity field + * @param {String} [clientRootFolder] - the client's root folder + * @return {Object} the enum info. + */ +// eslint-disable-next-line import/prefer-default-export +export const getEnumInfo = (field, clientRootFolder) => { + field.enumInstance = extractEnumInstance(field); // TODO remove side effect + const enums = extractEnumEntries(field); + const customValuesState = getCustomValuesState(enums); + return { + enumName: field.fieldType, + enumJavadoc: field.fieldTypeDocumentation && formatDocAsJavaDoc(field.fieldTypeDocumentation), + enumInstance: field.enumInstance, + enums, + ...customValuesState, + enumValues: getEnums(enums, customValuesState, field.fieldValuesJavadocs), + clientRootFolder: clientRootFolder ? `${clientRootFolder}-` : '', + }; +}; diff --git a/generators/base-application/support/enum.mjs b/generators/base-application/support/enum.mjs deleted file mode 100644 index 1b0b5ef551cb..000000000000 --- a/generators/base-application/support/enum.mjs +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { formatDocAsJavaDoc } from '../../server/support/doc.mjs'; - -const doesTheEnumValueHaveACustomValue = enumValue => { - return enumValue.includes('('); -}; - -const getCustomValuesState = enumValues => { - const state = { - withoutCustomValue: 0, - withCustomValue: 0, - }; - enumValues.forEach(enumValue => { - if (doesTheEnumValueHaveACustomValue(enumValue)) { - state.withCustomValue++; - } else { - state.withoutCustomValue++; - } - }); - return { - withoutCustomValues: state.withCustomValue === 0, - withSomeCustomValues: state.withCustomValue !== 0 && state.withoutCustomValue !== 0, - withCustomValues: state.withoutCustomValue === 0, - }; -}; - -const getEnums = (enums, customValuesState, comments) => { - if (customValuesState.withoutCustomValues) { - return enums.map(enumValue => ({ - name: enumValue, - value: enumValue, - comment: comments && comments[enumValue] && formatDocAsJavaDoc(comments[enumValue], 4), - })); - } - return enums.map(enumValue => { - if (!doesTheEnumValueHaveACustomValue(enumValue)) { - return { - name: enumValue.trim(), - value: enumValue.trim(), - comment: comments && comments[enumValue] && formatDocAsJavaDoc(comments[enumValue], 4), - }; - } - // eslint-disable-next-line no-unused-vars - const matched = /\s*(.+?)\s*\((.+?)\)/.exec(enumValue); - return { - name: matched[1], - value: matched[2], - comment: comments && comments[matched[1]] && formatDocAsJavaDoc(comments[matched[1]], 4), - }; - }); -}; - -const extractEnumInstance = field => { - const fieldType = field.fieldType; - return _.lowerFirst(fieldType); -}; - -const extractEnumEntries = field => { - return field.fieldValues.split(',').map(fieldValue => fieldValue.trim()); -}; - -/** - * Build an enum object - * @param {Object} field - entity field - * @param {String} [clientRootFolder] - the client's root folder - * @return {Object} the enum info. - */ -// eslint-disable-next-line import/prefer-default-export -export const getEnumInfo = (field, clientRootFolder) => { - field.enumInstance = extractEnumInstance(field); // TODO remove side effect - const enums = extractEnumEntries(field); - const customValuesState = getCustomValuesState(enums); - return { - enumName: field.fieldType, - enumJavadoc: field.fieldTypeDocumentation && formatDocAsJavaDoc(field.fieldTypeDocumentation), - enumInstance: field.enumInstance, - enums, - ...customValuesState, - enumValues: getEnums(enums, customValuesState, field.fieldValuesJavadocs), - clientRootFolder: clientRootFolder ? `${clientRootFolder}-` : '', - }; -}; diff --git a/generators/base-application/support/enum.spec.mts b/generators/base-application/support/enum.spec.mts deleted file mode 100644 index 8b8b5768cec1..000000000000 --- a/generators/base-application/support/enum.spec.mts +++ /dev/null @@ -1,184 +0,0 @@ -import assert from 'assert'; -import { getEnumInfo } from './enum.mjs'; - -describe('base-application - support - enum', () => { - describe('::getEnumInfo', () => { - describe('when passing field data', () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { enumName: 'fieldName', fieldType: 'BigLetters', fieldValues: 'AAA, BBB', fieldTypeDocumentation: 'enum comment' }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it("returns the enum's name", () => { - assert.strictEqual(enumInfo.enumName, 'BigLetters'); - }); - it("returns the enum's instance", () => { - assert.strictEqual(enumInfo.enumInstance, 'bigLetters'); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enums, ['AAA', 'BBB']); - }); - it('returns the enums comment', () => { - assert.deepStrictEqual(enumInfo.enumJavadoc, '/**\n * enum comment\n */'); - }); - }); - describe("when the enums don't have custom values", () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns whether there are custom enums', () => { - assert.strictEqual(enumInfo.withoutCustomValues, true); - assert.strictEqual(enumInfo.withSomeCustomValues, false); - assert.strictEqual(enumInfo.withCustomValues, false); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enumValues, [ - { name: 'AAA', value: 'AAA', comment: undefined }, - { name: 'BBB', value: 'BBB', comment: undefined }, - ]); - }); - }); - describe('when some enums have custom values', () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB' }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns whether there are custom enums', () => { - assert.strictEqual(enumInfo.withoutCustomValues, false); - assert.strictEqual(enumInfo.withSomeCustomValues, true); - assert.strictEqual(enumInfo.withCustomValues, false); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enumValues, [ - { - name: 'AAA', - value: 'aaa', - comment: undefined, - }, - { name: 'BBB', value: 'BBB', comment: undefined }, - ]); - }); - }); - describe('when all the enums have custom values', () => { - describe('without spaces inside them', () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB(bbb)' }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns whether there are custom enums', () => { - assert.strictEqual(enumInfo.withoutCustomValues, false); - assert.strictEqual(enumInfo.withSomeCustomValues, false); - assert.strictEqual(enumInfo.withCustomValues, true); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enumValues, [ - { - name: 'AAA', - value: 'aaa', - comment: undefined, - }, - { name: 'BBB', value: 'bbb', comment: undefined }, - ]); - }); - }); - describe('with spaces inside them', () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB(bbb and b)' }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns whether there are custom enums', () => { - assert.strictEqual(enumInfo.withoutCustomValues, false); - assert.strictEqual(enumInfo.withSomeCustomValues, false); - assert.strictEqual(enumInfo.withCustomValues, true); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enumValues, [ - { - name: 'AAA', - value: 'aaa', - comment: undefined, - }, - { name: 'BBB', value: 'bbb and b', comment: undefined }, - ]); - }); - }); - describe('with comments over them', () => { - let enumInfo; - - before(() => { - const clientRootFolder = 'root'; - const field = { - enumName: 'fieldName', - fieldValues: 'AAA(aaa), BBB(bbb and b)', - fieldValuesJavadocs: { - AAA: 'first comment', - BBB: 'second comment', - }, - }; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns whether there are custom enums', () => { - assert.strictEqual(enumInfo.withoutCustomValues, false); - assert.strictEqual(enumInfo.withSomeCustomValues, false); - assert.strictEqual(enumInfo.withCustomValues, true); - }); - it('returns the enums values', () => { - assert.deepStrictEqual(enumInfo.enumValues, [ - { - name: 'AAA', - value: 'aaa', - comment: ' /**\n * first comment\n */', - }, - { name: 'BBB', value: 'bbb and b', comment: ' /**\n * second comment\n */' }, - ]); - }); - }); - }); - describe('when not passing a client root folder', () => { - let enumInfo; - - before(() => { - const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; - enumInfo = getEnumInfo(field); - }); - - it('returns an empty string for the clientRootFolder property', () => { - assert.strictEqual(enumInfo.clientRootFolder, ''); - }); - }); - describe('when passing a client root folder', () => { - let enumInfo; - - before(() => { - const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; - const clientRootFolder = 'root'; - enumInfo = getEnumInfo(field, clientRootFolder); - }); - - it('returns the clientRootFolder property suffixed by a dash', () => { - assert.strictEqual(enumInfo.clientRootFolder, 'root-'); - }); - }); - }); -}); diff --git a/generators/base-application/support/enum.spec.ts b/generators/base-application/support/enum.spec.ts new file mode 100644 index 000000000000..033609eb3902 --- /dev/null +++ b/generators/base-application/support/enum.spec.ts @@ -0,0 +1,184 @@ +import assert from 'assert'; +import { getEnumInfo } from './enum.js'; + +describe('base-application - support - enum', () => { + describe('::getEnumInfo', () => { + describe('when passing field data', () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { enumName: 'fieldName', fieldType: 'BigLetters', fieldValues: 'AAA, BBB', fieldTypeDocumentation: 'enum comment' }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it("returns the enum's name", () => { + assert.strictEqual(enumInfo.enumName, 'BigLetters'); + }); + it("returns the enum's instance", () => { + assert.strictEqual(enumInfo.enumInstance, 'bigLetters'); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enums, ['AAA', 'BBB']); + }); + it('returns the enums comment', () => { + assert.deepStrictEqual(enumInfo.enumJavadoc, '/**\n * enum comment\n */'); + }); + }); + describe("when the enums don't have custom values", () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns whether there are custom enums', () => { + assert.strictEqual(enumInfo.withoutCustomValues, true); + assert.strictEqual(enumInfo.withSomeCustomValues, false); + assert.strictEqual(enumInfo.withCustomValues, false); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enumValues, [ + { name: 'AAA', value: 'AAA', comment: undefined }, + { name: 'BBB', value: 'BBB', comment: undefined }, + ]); + }); + }); + describe('when some enums have custom values', () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB' }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns whether there are custom enums', () => { + assert.strictEqual(enumInfo.withoutCustomValues, false); + assert.strictEqual(enumInfo.withSomeCustomValues, true); + assert.strictEqual(enumInfo.withCustomValues, false); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enumValues, [ + { + name: 'AAA', + value: 'aaa', + comment: undefined, + }, + { name: 'BBB', value: 'BBB', comment: undefined }, + ]); + }); + }); + describe('when all the enums have custom values', () => { + describe('without spaces inside them', () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB(bbb)' }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns whether there are custom enums', () => { + assert.strictEqual(enumInfo.withoutCustomValues, false); + assert.strictEqual(enumInfo.withSomeCustomValues, false); + assert.strictEqual(enumInfo.withCustomValues, true); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enumValues, [ + { + name: 'AAA', + value: 'aaa', + comment: undefined, + }, + { name: 'BBB', value: 'bbb', comment: undefined }, + ]); + }); + }); + describe('with spaces inside them', () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { enumName: 'fieldName', fieldValues: 'AAA(aaa), BBB(bbb and b)' }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns whether there are custom enums', () => { + assert.strictEqual(enumInfo.withoutCustomValues, false); + assert.strictEqual(enumInfo.withSomeCustomValues, false); + assert.strictEqual(enumInfo.withCustomValues, true); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enumValues, [ + { + name: 'AAA', + value: 'aaa', + comment: undefined, + }, + { name: 'BBB', value: 'bbb and b', comment: undefined }, + ]); + }); + }); + describe('with comments over them', () => { + let enumInfo; + + before(() => { + const clientRootFolder = 'root'; + const field = { + enumName: 'fieldName', + fieldValues: 'AAA(aaa), BBB(bbb and b)', + fieldValuesJavadocs: { + AAA: 'first comment', + BBB: 'second comment', + }, + }; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns whether there are custom enums', () => { + assert.strictEqual(enumInfo.withoutCustomValues, false); + assert.strictEqual(enumInfo.withSomeCustomValues, false); + assert.strictEqual(enumInfo.withCustomValues, true); + }); + it('returns the enums values', () => { + assert.deepStrictEqual(enumInfo.enumValues, [ + { + name: 'AAA', + value: 'aaa', + comment: ' /**\n * first comment\n */', + }, + { name: 'BBB', value: 'bbb and b', comment: ' /**\n * second comment\n */' }, + ]); + }); + }); + }); + describe('when not passing a client root folder', () => { + let enumInfo; + + before(() => { + const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; + enumInfo = getEnumInfo(field); + }); + + it('returns an empty string for the clientRootFolder property', () => { + assert.strictEqual(enumInfo.clientRootFolder, ''); + }); + }); + describe('when passing a client root folder', () => { + let enumInfo; + + before(() => { + const field = { enumName: 'fieldName', fieldValues: 'AAA, BBB' }; + const clientRootFolder = 'root'; + enumInfo = getEnumInfo(field, clientRootFolder); + }); + + it('returns the clientRootFolder property suffixed by a dash', () => { + assert.strictEqual(enumInfo.clientRootFolder, 'root-'); + }); + }); + }); +}); diff --git a/generators/base-application/support/field-utils.js b/generators/base-application/support/field-utils.js new file mode 100644 index 000000000000..ae12d9b577b6 --- /dev/null +++ b/generators/base-application/support/field-utils.js @@ -0,0 +1,64 @@ +/** + * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.js'; + +const { CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; +const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; +const { + BOOLEAN, + BIG_DECIMAL, + DOUBLE, + DURATION, + FLOAT, + INSTANT, + INTEGER, + LOCAL_DATE, + LONG, + STRING, + UUID, + ZONED_DATE_TIME, + IMAGE_BLOB, + ANY_BLOB, + TEXT_BLOB, + BLOB, +} = CommonDBTypes; + +// eslint-disable-next-line import/prefer-default-export +export function fieldIsEnum(fieldType) { + return ![ + STRING, + INTEGER, + LONG, + FLOAT, + DOUBLE, + BIG_DECIMAL, + LOCAL_DATE, + INSTANT, + ZONED_DATE_TIME, + DURATION, + UUID, + BOOLEAN, + BYTES, + BYTE_BUFFER, + ANY_BLOB, + BLOB, + IMAGE_BLOB, + TEXT_BLOB, + ].includes(fieldType); +} diff --git a/generators/base-application/support/field-utils.mjs b/generators/base-application/support/field-utils.mjs deleted file mode 100644 index 9b1abb3bb97b..000000000000 --- a/generators/base-application/support/field-utils.mjs +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.mjs'; - -const { CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; -const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; -const { - BOOLEAN, - BIG_DECIMAL, - DOUBLE, - DURATION, - FLOAT, - INSTANT, - INTEGER, - LOCAL_DATE, - LONG, - STRING, - UUID, - ZONED_DATE_TIME, - IMAGE_BLOB, - ANY_BLOB, - TEXT_BLOB, - BLOB, -} = CommonDBTypes; - -// eslint-disable-next-line import/prefer-default-export -export function fieldIsEnum(fieldType) { - return ![ - STRING, - INTEGER, - LONG, - FLOAT, - DOUBLE, - BIG_DECIMAL, - LOCAL_DATE, - INSTANT, - ZONED_DATE_TIME, - DURATION, - UUID, - BOOLEAN, - BYTES, - BYTE_BUFFER, - ANY_BLOB, - BLOB, - IMAGE_BLOB, - TEXT_BLOB, - ].includes(fieldType); -} diff --git a/generators/base-application/support/index.mts b/generators/base-application/support/index.mts deleted file mode 100644 index 4f5da16eec30..000000000000 --- a/generators/base-application/support/index.mts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2013-2023 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 './debug.mjs'; -export * from './doc.mjs'; -export * from './enum.mjs'; -export * from './entity.mjs'; -export * from './entities.mjs'; -export * from './field-utils.mjs'; -export { default as prepareEntity } from './prepare-entity.mjs'; -export * from './prepare-entity.mjs'; -export { default as prepareField } from './prepare-field.mjs'; -export * from './prepare-field.mjs'; -export { default as prepareRelationship } from './prepare-relationship.mjs'; -export * from './relationship.mjs'; -export * from './update-application-entities-transform.mjs'; diff --git a/generators/base-application/support/index.ts b/generators/base-application/support/index.ts new file mode 100644 index 000000000000..34013272a17a --- /dev/null +++ b/generators/base-application/support/index.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2013-2023 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 './debug.js'; +export * from './doc.js'; +export * from './enum.js'; +export * from './entity.js'; +export * from './entities.js'; +export * from './field-utils.js'; +export { default as prepareEntity } from './prepare-entity.js'; +export * from './prepare-entity.js'; +export { default as prepareField } from './prepare-field.js'; +export * from './prepare-field.js'; +export { default as prepareRelationship } from './prepare-relationship.js'; +export * from './relationship.js'; +export * from './update-application-entities-transform.js'; diff --git a/generators/base-application/support/prepare-entity.mts b/generators/base-application/support/prepare-entity.mts deleted file mode 100644 index 63d3671ed26b..000000000000 --- a/generators/base-application/support/prepare-entity.mts +++ /dev/null @@ -1,684 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import pluralize from 'pluralize'; - -import type BaseGenerator from '../../base-core/index.mjs'; -import { getDatabaseTypeData, hibernateSnakeCase } from '../../server/support/index.mjs'; -import { - createFaker, - parseChangelog, - stringHashCode, - upperFirstCamelCase, - getMicroserviceAppName, - mutateData, -} from '../../base/support/index.mjs'; -import { fieldToReference } from './prepare-field.mjs'; -import { getTypescriptKeyType, getEntityParentPathAddition } from '../../client/support/index.mjs'; -import { - applicationTypes, - authenticationTypes, - binaryOptions, - databaseTypes, - entityOptions, - fieldTypes, - searchEngineTypes, -} from '../../../jdl/jhipster/index.mjs'; -import { fieldIsEnum } from './field-utils.mjs'; - -import { Entity } from '../types/index.mjs'; -import type CoreGenerator from '../../base-core/generator.mjs'; - -const { sortedUniq, intersection } = _; - -const NO_SEARCH_ENGINE = searchEngineTypes.NO; -const { PaginationTypes, ServiceTypes, MapperTypes } = entityOptions; -const { GATEWAY, MICROSERVICE } = applicationTypes; -const { OAUTH2 } = authenticationTypes; -const { CommonDBTypes } = fieldTypes; - -const { BOOLEAN, LONG, STRING, UUID, INTEGER } = CommonDBTypes; -const { NO: NO_DTO, MAPSTRUCT } = MapperTypes; -const { PAGINATION, INFINITE_SCROLL } = PaginationTypes; -const { SERVICE_IMPL } = ServiceTypes; -const NO_SERVICE = ServiceTypes.NO; -const NO_PAGINATION = PaginationTypes.NO; -const NO_MAPPER = MapperTypes.NO; - -const { CASSANDRA, COUCHBASE, NEO4J, SQL, MONGODB } = databaseTypes; - -const { INSTANT, ZONED_DATE_TIME, DURATION, LOCAL_DATE, BIG_DECIMAL } = fieldTypes.CommonDBTypes; - -const { BYTES, BYTE_BUFFER } = fieldTypes.RelationalOnlyDBTypes; -const { IMAGE, TEXT } = fieldTypes.BlobTypes; - -const BASE_TEMPLATE_DATA = { - primaryKey: undefined, - entityPackage: undefined, - skipUiGrouping: false, - anyFieldHasDocumentation: false, - existingEnum: false, - searchEngine: NO_SEARCH_ENGINE, - microserviceName: undefined, - - requiresPersistableImplementation: false, - anyFieldIsDateDerived: false, - anyFieldIsTimeDerived: false, - anyFieldIsInstant: false, - anyFieldIsUUID: false, - anyFieldIsZonedDateTime: false, - anyFieldIsDuration: false, - anyFieldIsLocalDate: false, - anyFieldIsBigDecimal: false, - anyFieldIsBlobDerived: false, - anyFieldHasImageContentType: false, - anyFieldHasTextContentType: false, - anyFieldHasFileBasedContentType: false, - anyPropertyHasValidation: false, - fieldsContainNoOwnerOneToOne: false, - - get otherRelationships() { - return []; - }, - - get enums() { - return []; - }, - // these variable hold field and relationship names for question options during update - get fieldNameChoices() { - return []; - }, - get differentRelationships() { - return {}; - }, -}; - -function _derivedProperties(entityWithConfig) { - const pagination = entityWithConfig.pagination; - const dto = entityWithConfig.dto; - const service = entityWithConfig.service; - _.defaults(entityWithConfig, { - paginationPagination: pagination === PAGINATION, - paginationInfiniteScroll: pagination === INFINITE_SCROLL, - paginationNo: pagination === NO_PAGINATION, - dtoMapstruct: dto === MAPSTRUCT, - serviceImpl: service === SERVICE_IMPL, - serviceNo: service === NO_SERVICE, - }); -} - -export const entityDefaultConfig = { - pagination: binaryOptions.DefaultValues[binaryOptions.Options.PAGINATION], - anyPropertyHasValidation: false, - dto: binaryOptions.DefaultValues[binaryOptions.Options.DTO], - service: binaryOptions.DefaultValues[binaryOptions.Options.SERVICE], - jpaMetamodelFiltering: false, - readOnly: false, - embedded: false, - entityAngularJSSuffix: '', - fluentMethods: true, - clientRootFolder: '', - get fields() { - return []; - }, - get relationships() { - return []; - }, -}; - -export default function prepareEntity(entityWithConfig, generator, application) { - const entityName = _.upperFirst(entityWithConfig.name); - _.defaults(entityWithConfig, entityDefaultConfig, BASE_TEMPLATE_DATA); - - if (entityWithConfig.changelogDate) { - entityWithConfig.changelogDateForRecent = parseChangelog(String(entityWithConfig.changelogDate)); - } - - entityWithConfig.entityAngularJSSuffix = entityWithConfig.angularJSSuffix; - if (entityWithConfig.entityAngularJSSuffix && !entityWithConfig.entityAngularJSSuffix.startsWith('-')) { - entityWithConfig.entityAngularJSSuffix = `-${entityWithConfig.entityAngularJSSuffix}`; - } - - entityWithConfig.useMicroserviceJson = entityWithConfig.useMicroserviceJson || entityWithConfig.microserviceName !== undefined; - entityWithConfig.microserviceAppName = ''; - if (generator.jhipsterConfig.applicationType === GATEWAY && entityWithConfig.useMicroserviceJson) { - if (!entityWithConfig.microserviceName) { - throw new Error('Microservice name for the entity is not found. Entity cannot be generated!'); - } - entityWithConfig.microserviceAppName = getMicroserviceAppName({ microserviceName: entityWithConfig.microserviceName }); - entityWithConfig.skipServer = true; - } - - _.defaults(entityWithConfig, { - entityNameCapitalized: entityName, - entityClass: _.upperFirst(entityName), - entityInstance: _.lowerFirst(entityName), - entityTableName: hibernateSnakeCase(entityName), - entityNamePlural: pluralize(entityName), - }); - - const dto = entityWithConfig.dto && entityWithConfig.dto !== NO_DTO; - if (dto) { - _.defaults(entityWithConfig, { - dtoClass: `${entityWithConfig.entityClass}${application.dtoSuffix ?? ''}`, - dtoInstance: `${entityWithConfig.entityInstance}${application.dtoSuffix ?? ''}`, - }); - } - - _.defaults(entityWithConfig, { - persistClass: `${entityWithConfig.entityClass}${application.entitySuffix ?? ''}`, - persistInstance: `${entityWithConfig.entityInstance}${application.entitySuffix ?? ''}`, - }); - - _.defaults(entityWithConfig, { - restClass: dto ? entityWithConfig.dtoClass : entityWithConfig.persistClass, - restInstance: dto ? entityWithConfig.dtoInstance : entityWithConfig.persistInstance, - }); - - _.defaults(entityWithConfig, { - entityNamePluralizedAndSpinalCased: _.kebabCase(entityWithConfig.entityNamePlural), - entityClassPlural: _.upperFirst(entityWithConfig.entityNamePlural), - entityInstancePlural: _.lowerFirst(entityWithConfig.entityNamePlural), - }); - - _.defaults(entityWithConfig, { - // Implement i18n variant ex: 'male', 'female' when applied - entityI18nVariant: 'default', - entityClassHumanized: _.startCase(entityWithConfig.entityNameCapitalized), - entityClassPluralHumanized: _.startCase(entityWithConfig.entityClassPlural), - }); - - entityWithConfig.entityFileName = _.kebabCase( - entityWithConfig.entityNameCapitalized + _.upperFirst(entityWithConfig.entityAngularJSSuffix), - ); - entityWithConfig.entityFolderName = entityWithConfig.clientRootFolder - ? `${entityWithConfig.clientRootFolder}/${entityWithConfig.entityFileName}` - : entityWithConfig.entityFileName; - entityWithConfig.entityModelFileName = entityWithConfig.entityFolderName; - entityWithConfig.entityParentPathAddition = getEntityParentPathAddition(entityWithConfig.clientRootFolder); - entityWithConfig.entityPluralFileName = entityWithConfig.entityNamePluralizedAndSpinalCased + entityWithConfig.entityAngularJSSuffix; - entityWithConfig.entityServiceFileName = entityWithConfig.entityFileName; - - entityWithConfig.entityAngularName = entityWithConfig.entityClass + upperFirstCamelCase(entityWithConfig.entityAngularJSSuffix); - entityWithConfig.entityAngularNamePlural = pluralize(entityWithConfig.entityAngularName); - entityWithConfig.entityReactName = entityWithConfig.entityClass + upperFirstCamelCase(entityWithConfig.entityAngularJSSuffix); - - entityWithConfig.entityApiUrl = entityWithConfig.entityNamePluralizedAndSpinalCased; - entityWithConfig.entityStateName = _.kebabCase(entityWithConfig.entityAngularName); - entityWithConfig.entityUrl = entityWithConfig.entityStateName; - - entityWithConfig.entityTranslationKey = entityWithConfig.clientRootFolder - ? _.camelCase(`${entityWithConfig.clientRootFolder}-${entityWithConfig.entityInstance}`) - : entityWithConfig.entityInstance; - entityWithConfig.entityTranslationKeyMenu = _.camelCase( - entityWithConfig.clientRootFolder - ? `${entityWithConfig.clientRootFolder}-${entityWithConfig.entityStateName}` - : entityWithConfig.entityStateName, - ); - - entityWithConfig.i18nKeyPrefix = `${entityWithConfig.frontendAppName}.${entityWithConfig.entityTranslationKey}`; - entityWithConfig.i18nAlertHeaderPrefix = entityWithConfig.i18nKeyPrefix; - if (entityWithConfig.microserviceAppName) { - entityWithConfig.i18nAlertHeaderPrefix = `${entityWithConfig.microserviceAppName}.${entityWithConfig.entityTranslationKey}`; - } - - const { microserviceName, entityFileName, microfrontend } = entityWithConfig; - entityWithConfig.entityApi = microserviceName ? `services/${microserviceName.toLowerCase()}/` : ''; - entityWithConfig.entityPage = - microfrontend && microserviceName && entityWithConfig.applicationType === MICROSERVICE - ? `${microserviceName.toLowerCase()}/${entityFileName}` - : `${entityFileName}`; - - const hasBuiltInUserField = entityWithConfig.relationships.some(relationship => relationship.otherEntity.builtInUser); - entityWithConfig.saveUserSnapshot = - application.applicationType === MICROSERVICE && - application.authenticationType === OAUTH2 && - hasBuiltInUserField && - entityWithConfig.dto === NO_MAPPER; - - entityWithConfig.generateFakeData = type => { - const fieldsToGenerate = - type === 'cypress' ? entityWithConfig.fields.filter(field => !field.id || !field.autoGenerate) : entityWithConfig.fields; - const fieldEntries = fieldsToGenerate.map(field => { - const fieldData = field.generateFakeData(type); - if (!field.nullable && fieldData === null) return undefined; - return [field.fieldName, fieldData]; - }); - const withError = fieldEntries.find(entry => !entry); - if (withError) { - generator.log.warn(`Error generating a full sample for entity ${entityName}`); - return undefined; - } - return Object.fromEntries(fieldEntries); - }; - _derivedProperties(entityWithConfig); - - return entityWithConfig; -} - -export function derivedPrimaryKeyProperties(primaryKey) { - _.defaults(primaryKey, { - hasUUID: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === UUID), - hasLong: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === LONG), - hasInteger: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === INTEGER), - typeUUID: primaryKey.type === UUID, - typeString: primaryKey.type === STRING, - typeLong: primaryKey.type === LONG, - typeInteger: primaryKey.type === INTEGER, - typeNumeric: !primaryKey.composite && primaryKey.fields[0].fieldTypeNumeric, - }); -} - -export function prepareEntityPrimaryKeyForTemplates( - this: CoreGenerator | void, - { entity: entityWithConfig, enableCompositeId = true, application }: { entity: any; enableCompositeId?: boolean; application?: any }, -) { - const idFields = entityWithConfig.fields.filter(field => field.id); - const idRelationships = entityWithConfig.relationships.filter(relationship => relationship.id); - let idCount = idFields.length + idRelationships.length; - - if (idCount === 0) { - let idField = entityWithConfig.fields.find(field => field.fieldName === 'id'); - if (idField) { - idField.id = true; - idField.autoGenerate = idField.autoGenerate ?? true; - } else { - if (entityWithConfig.microserviceName && !application?.microfrontend) { - this?.log.warn( - "Microservice entities should have the id field type specified (e.g., id String) to make sure gateway and microservice types don't conflict", - ); - } - idField = { - fieldName: 'id', - id: true, - fieldNameHumanized: 'ID', - fieldTranslationKey: 'global.field.id', - autoGenerate: true, - }; - entityWithConfig.fields.unshift(idField); - } - idFields.push(idField); - idCount++; - } else if (idRelationships.length > 0) { - idRelationships.forEach(relationship => { - // relationships id data are not available at this point, so calculate it when needed. - relationship.derivedPrimaryKey = { - get derivedFields() { - return relationship.otherEntity.primaryKey.fields.map(field => ({ - originalField: field, - ...field, - derived: true, - derivedEntity: relationship.otherEntity, - jpaGeneratedValue: false, - liquibaseAutoIncrement: false, - // Mapsid is generated by relationship select - autoGenerate: true, - readonly: true, - get derivedPath() { - if (field.derivedPath) { - if (relationship.otherEntity.primaryKey.derived) { - return [relationship.relationshipName, ...field.derivedPath.splice(1)]; - } - return [relationship.relationshipName, ...field.derivedPath]; - } - return [relationship.relationshipName, field.fieldName]; - }, - get path() { - return [relationship.relationshipName, ...field.path]; - }, - get fieldName() { - return idCount === 1 ? field.fieldName : `${relationship.relationshipName}${field.fieldNameCapitalized}`; - }, - get fieldNameCapitalized() { - return idCount === 1 - ? field.fieldNameCapitalized - : `${relationship.relationshipNameCapitalized}${field.fieldNameCapitalized}`; - }, - get columnName() { - return idCount === 1 ? field.columnName : `${hibernateSnakeCase(relationship.relationshipName)}_${field.columnName}`; - }, - get reference() { - return fieldToReference(entityWithConfig, this); - }, - get relationshipsPath() { - return [relationship, ...field.relationshipsPath]; - }, - })); - }, - }; - }); - } - - if (idCount === 1 && idRelationships.length === 1) { - const relationshipId = idRelationships[0]; - // One-To-One relationships with id uses @MapsId. - // Almost every info is taken from the parent, except some info like autoGenerate and derived. - // calling fieldName as id is for backward compatibility, in the future we may want to prefix it with relationship name. - entityWithConfig.primaryKey = { - fieldName: 'id', - derived: true, - // MapsId copy the id from the relationship. - autoGenerate: true, - get fields() { - return this.derivedFields; - }, - get derivedFields() { - return relationshipId.derivedPrimaryKey.derivedFields; - }, - get ownFields() { - return relationshipId.otherEntity.primaryKey.ownFields; - }, - relationships: idRelationships, - get name() { - return relationshipId.otherEntity.primaryKey.name; - }, - get hibernateSnakeCaseName() { - return hibernateSnakeCase(relationshipId.otherEntity.primaryKey.name); - }, - get nameCapitalized() { - return relationshipId.otherEntity.primaryKey.nameCapitalized; - }, - get type() { - return relationshipId.otherEntity.primaryKey.type; - }, - get tsType() { - return relationshipId.otherEntity.primaryKey.tsType; - }, - get composite() { - return relationshipId.otherEntity.primaryKey.composite; - }, - get ids() { - return this.fields.map(field => fieldToId(field)); - }, - }; - } else { - const composite = enableCompositeId ? idCount > 1 : false; - let primaryKeyName; - let primaryKeyType; - if (composite) { - primaryKeyName = 'id'; - primaryKeyType = `${entityWithConfig.entityClass}Id`; - } else { - const idField = idFields[0]; - idField.dynamic = false; - // Allow ids type to be empty and fallback to default type for the database. - if (!idField.fieldType) { - idField.fieldType = application?.pkType ?? getDatabaseTypeData(entityWithConfig.databaseType).defaultPrimaryKeyType; - } - primaryKeyName = idField.fieldName; - primaryKeyType = idField.fieldType; - } - - entityWithConfig.primaryKey = { - derived: false, - name: primaryKeyName, - hibernateSnakeCaseName: hibernateSnakeCase(primaryKeyName), - nameCapitalized: _.upperFirst(primaryKeyName), - type: primaryKeyType, - tsType: getTypescriptKeyType(primaryKeyType), - composite, - relationships: idRelationships, - // Fields declared in this entity - ownFields: idFields, - // Fields declared and inherited - get fields() { - return [...this.ownFields, ...this.derivedFields]; - }, - get autoGenerate() { - return this.composite ? false : this.fields[0].autoGenerate; - }, - // Fields inherited from id relationships. - get derivedFields() { - return this.relationships.map(rel => rel.derivedPrimaryKey.derivedFields).flat(); - }, - get ids() { - return this.fields.map(field => fieldToId(field)); - }, - }; - } - return entityWithConfig; -} - -function fieldToId(field) { - return { - field, - get name() { - return field.fieldName; - }, - get nameCapitalized() { - return field.fieldNameCapitalized; - }, - get nameDotted() { - return field.derivedPath ? field.derivedPath.join('.') : field.fieldName; - }, - get nameDottedAsserted() { - return field.derivedPath ? `${field.derivedPath.join('!.')}!` : `${field.fieldName}!`; - }, - get setter() { - return `set${this.nameCapitalized}`; - }, - get getter() { - return (field.fieldType === BOOLEAN ? 'is' : 'get') + this.nameCapitalized; - }, - get autoGenerate() { - return !!field.autoGenerate; - }, - get relationshipsPath() { - return field.relationshipsPath; - }, - }; -} - -/** - * Copy required application config into entity. - * Some entity features are related to the backend instead of the current app. - * This allows to entities files based on the backend features. - * - * @param {Object} entity - entity to copy the config into. - * @param {Object} config - config object. - * @returns {Object} the entity parameter for chaining. - */ -export function loadRequiredConfigIntoEntity(this: BaseGenerator | void, entity, config) { - _.defaults(entity, { - applicationType: config.applicationType, - baseName: config.baseName, - frontendAppName: config.frontendAppName, - authenticationType: config.authenticationType, - reactive: config.reactive, - microfrontend: config.microfrontend, - // Workaround different paths - clientFramework: config.clientFramework, - - databaseType: config.databaseType, - prodDatabaseType: config.prodDatabaseType, - - skipUiGrouping: config.skipUiGrouping, - searchEngine: config.searchEngine, - - jhiPrefix: config.jhiPrefix, - entitySuffix: config.entitySuffix, - dtoSuffix: config.dtoSuffix, - packageName: config.packageName, - packageFolder: config.packageFolder, - }); - if (entity.searchEngine === true && (!entity.microserviceName || entity.microserviceName === config.baseName)) { - // If the entity belongs to this application and searchEngine is true. - if (config.searchEngine && config.searchEngine !== NO_SEARCH_ENGINE) { - // Replace with the searchEngine from the application. - entity.searchEngine = config.searchEngine; - } else { - entity.searchEngine = NO_SEARCH_ENGINE; - this?.log.warn('Search engine is enabled at entity level, but disabled at application level. Search engine will be disabled'); - } - } - if (config.applicationType === MICROSERVICE) { - _.defaults(entity, { - microserviceName: config.baseName, - }); - } - return entity; -} - -export function preparePostEntityCommonDerivedProperties(entity: Entity) { - const { fields } = entity; - const fieldsType = sortedUniq(fields.map(({ fieldType }) => fieldType).filter(fieldType => !fieldIsEnum(fieldType))); - - // TODO move to server generator - entity.anyFieldHasDocumentation = entity.fields.some(({ documentation }) => documentation); - - entity.anyFieldIsZonedDateTime = fieldsType.includes(ZONED_DATE_TIME); - entity.anyFieldIsInstant = fieldsType.includes(INSTANT); - entity.anyFieldIsDuration = fieldsType.includes(DURATION); - entity.anyFieldIsLocalDate = fieldsType.includes(LOCAL_DATE); - entity.anyFieldIsBigDecimal = fieldsType.includes(BIG_DECIMAL); - entity.anyFieldIsUUID = fieldsType.includes(UUID); - - entity.anyFieldIsTimeDerived = entity.anyFieldIsZonedDateTime || entity.anyFieldIsInstant; - entity.anyFieldIsDateDerived = entity.anyFieldIsTimeDerived || entity.anyFieldIsLocalDate; - - entity.anyFieldIsBlobDerived = intersection(fieldsType, [BYTES, BYTE_BUFFER]).length > 0; - if (entity.anyFieldIsBlobDerived) { - const blobFields = fields.filter(({ fieldType }) => [BYTES, BYTE_BUFFER].includes(fieldType)); - const blobFieldsContentType = sortedUniq(blobFields.map(({ fieldTypeBlobContent }) => fieldTypeBlobContent)); - entity.anyFieldHasImageContentType = blobFieldsContentType.includes(IMAGE); - entity.anyFieldHasFileBasedContentType = blobFieldsContentType.some(fieldTypeBlobContent => fieldTypeBlobContent !== TEXT); - entity.anyFieldHasTextContentType = blobFieldsContentType.includes(TEXT); - } - - preparePostEntityCommonDerivedPropertiesNotTyped(entity); -} - -function preparePostEntityCommonDerivedPropertiesNotTyped(entity: any) { - const { relationships, fields } = entity; - const oneToOneRelationships = relationships.filter(({ relationshipType }) => relationshipType === 'one-to-one'); - entity.fieldsContainNoOwnerOneToOne = oneToOneRelationships.some(({ ownerSide }) => !ownerSide); - - entity.anyPropertyHasValidation = - entity.anyPropertyHasValidation || relationships.some(({ relationshipValidate }) => relationshipValidate); - - const relationshipsByOtherEntity = relationships - .map(relationship => [relationship.otherEntity.entityNameCapitalized, relationship]) - .reduce((relationshipsByOtherEntity: any, [type, relationship]) => { - if (!relationshipsByOtherEntity[type]) { - relationshipsByOtherEntity[type] = [relationship]; - } else { - relationshipsByOtherEntity[type].push(relationship); - } - return relationshipsByOtherEntity; - }, {}); - - entity.relationshipsByOtherEntity = relationshipsByOtherEntity; - entity.differentRelationships = relationshipsByOtherEntity; - - entity.anyPropertyHasValidation = entity.anyPropertyHasValidation || fields.some(({ fieldValidate }) => fieldValidate); - - entity.allReferences = [ - ...entity.fields.map(field => field.reference), - ...entity.relationships.map(relationship => relationship.reference), - ]; - - entity.otherEntities = _.uniq(entity.relationships.map(rel => rel.otherEntity)); - - entity.updatableEntity = - entity.fields.some(field => !field.id && !field.transient) || - entity.relationships.some(relationship => !relationship.id && relationship.ownerSide); - - entity.allReferences - .filter(reference => reference.relationship && reference.relationship.relatedField) - .forEach(reference => { - reference.relatedReference = reference.relationship.relatedField.reference; - }); - - entity.relationships.forEach(relationship => { - relationship.relationshipCollection = ['one-to-many', 'many-to-many'].includes(relationship.relationshipType); - relationship.relationshipReferenceField = relationship.relationshipCollection - ? relationship.relationshipFieldNamePlural - : relationship.relationshipFieldName; - }); - entity.entityContainsCollectionField = entity.relationships.some(relationship => relationship.relationshipCollection); - - if (entity.primaryKey) { - derivedPrimaryKeyProperties(entity.primaryKey); - entity.requiresPersistableImplementation = - entity.requiresPersistableImplementation || entity.fields.some(field => field.requiresPersistableImplementation); - } - - const types = entity.relationships - .filter(rel => rel.otherEntity.primaryKey) - .map(rel => rel.otherEntity.primaryKey.fields.map(f => f.fieldType)) - .flat(); - entity.otherEntityPrimaryKeyTypes = Array.from(new Set(types)); - entity.otherEntityPrimaryKeyTypesIncludesUUID = types.includes(UUID); - - entity.relationships.forEach(relationship => { - if (!relationship.otherEntity.primaryKey) { - relationship.bagRelationship = false; - relationship.relationshipEagerLoad = false; - return; - } - - mutateData(relationship, { - bagRelationship: relationship.ownerSide && relationship.collection, - relationshipEagerLoad: ({ relationshipEagerLoad, bagRelationship, ownerSide, otherEntity, otherEntityField }) => - relationshipEagerLoad ?? - (bagRelationship || - entity.eagerLoad || - // Fetch relationships if otherEntityField differs otherwise the id is enough - (ownerSide && otherEntity.primaryKey.name !== otherEntityField)), - }); - }); - entity.relationshipsContainEagerLoad = entity.relationships.some(relationship => relationship.relationshipEagerLoad); - entity.containsBagRelationships = entity.relationships.some(relationship => relationship.bagRelationship); - entity.implementsEagerLoadApis = // Cassandra doesn't provides *WithEagerRelationships apis - ![CASSANDRA, COUCHBASE, NEO4J].includes(entity.databaseType) && - // Only sql and mongodb provides *WithEagerReationships apis for imperative implementation - (entity.reactive || [SQL, MONGODB].includes(entity.databaseType)) && - entity.relationshipsContainEagerLoad; - entity.eagerRelations = entity.relationships.filter(rel => rel.relationshipEagerLoad); - entity.regularEagerRelations = entity.eagerRelations.filter(rel => rel.id !== true); - - entity.reactiveEagerRelations = entity.relationships.filter( - rel => rel.relationshipType === 'many-to-one' || (rel.relationshipType === 'one-to-one' && rel.ownerSide === true), - ); - entity.reactiveRegularEagerRelations = entity.reactiveEagerRelations.filter(rel => rel.id !== true); -} - -export function preparePostEntitiesCommonDerivedProperties(entities) { - for (const entity of entities.filter(entity => !entity.dtoReferences)) { - entity.dtoReferences = [ - ...entity.fields.map(field => field.reference), - ...entity.relationships - .map(relationship => relationship.reference) - .filter(reference => reference.owned || reference.relationship.otherEntity.embedded), - ]; - entity.otherReferences = entity.otherRelationships.map(relationship => relationship.reference); - } - - for (const entity of entities.filter(entity => !entity.otherDtoReferences)) { - // Get all required back references for dto. - entity.otherDtoReferences = entity.otherReferences.filter(reference => reference.entity.dtoReferences.includes(reference)); - } -} - -export async function addFakerToEntity(entityWithConfig: any, nativeLanguage = 'en') { - entityWithConfig.faker = entityWithConfig.faker || (await createFaker(nativeLanguage)); - entityWithConfig.resetFakerSeed = (suffix = '') => - entityWithConfig.faker.seed(stringHashCode(entityWithConfig.name.toLowerCase() + suffix)); - entityWithConfig.resetFakerSeed(); -} diff --git a/generators/base-application/support/prepare-entity.spec.mts b/generators/base-application/support/prepare-entity.spec.mts deleted file mode 100644 index 55c5ee1e221a..000000000000 --- a/generators/base-application/support/prepare-entity.spec.mts +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect } from 'chai'; -import { formatDateForChangelog } from '../../base/support/index.mjs'; -import { prepareEntityPrimaryKeyForTemplates, entityDefaultConfig } from './prepare-entity.mjs'; -import BaseGenerator from '../../base/index.mjs'; -import { getConfigWithDefaults } from '../../../jdl/jhipster/index.mjs'; - -describe('generator - base-application - support - prepareEntity', () => { - const defaultGenerator = { jhipsterConfig: getConfigWithDefaults() }; - Object.setPrototypeOf(defaultGenerator, BaseGenerator.prototype); - - describe('prepareEntityPrimaryKeyForTemplates', () => { - describe('with field with id name', () => { - describe('without @Id', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let entity: any = { - ...entityDefaultConfig, - name: 'Entity', - changelogDate: formatDateForChangelog(new Date()), - fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], - }; - beforeEach(() => { - entity = prepareEntityPrimaryKeyForTemplates({ entity }); - }); - it('should adopt id field as @Id', () => { - expect(entity.fields[0]).to.eql({ - autoGenerate: true, - dynamic: false, - fieldName: 'id', - fieldType: 'CustomType', - id: true, - path: ['id'], - relationshipsPath: [], - }); - }); - - it('should contains correct structure', () => { - expect(entity.primaryKey).to.deep.include({ - name: 'id', - nameCapitalized: 'Id', - type: 'CustomType', - derived: false, - composite: false, - }); - expect(entity.primaryKey.fields[0]).to.equal(entity.fields[0]); - }); - }); - - describe('with @Id', () => { - let entity = { - ...entityDefaultConfig, - name: 'Entity', - changelogDate: formatDateForChangelog(new Date()), - fields: [ - { fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }, - { fieldName: 'uuid', fieldType: 'UUID', id: true, path: ['uuid'], relationshipsPath: [] }, - ], - }; - beforeEach(() => { - entity = prepareEntityPrimaryKeyForTemplates({ entity }); - }); - it('should not adopt id field as @Id', () => { - expect(entity.fields[0]).to.eql({ - fieldName: 'id', - fieldType: 'CustomType', - path: ['id'], - relationshipsPath: [], - }); - }); - }); - - describe('with multiple @Id relationships and field', () => { - let entity1; - let entity2; - let entity3; - let entity4; - - beforeEach(() => { - entity1 = { - ...entityDefaultConfig, - name: 'Entity1', - entityClass: 'Entity1', - entityInstance: 'entity1', - fields: [ - { - fieldName: 'id', - fieldNameCapitalized: 'Id', - columnName: 'id', - fieldType: 'String', - id: true, - path: ['id'], - relationshipsPath: [], - }, - ], - }; - entity2 = { - ...entityDefaultConfig, - name: 'Entity2', - entityClass: 'Entity2', - entityInstance: 'entity2', - fields: [ - { - fieldName: 'uuid', - fieldNameCapitalized: 'Uuid', - columnName: 'uuid', - fieldType: 'UUID', - id: true, - autoGenerate: true, - path: ['uuid'], - relationshipsPath: [], - }, - ], - }; - entity3 = { - ...entityDefaultConfig, - name: 'Entity3', - entityClass: 'Entity3', - entityInstance: 'entity3', - relationships: [ - { - relationshipName: 'entity2', - relationshipNameCapitalized: 'Entity2', - relationshipType: 'one-to-one', - id: true, - otherEntity: entity2, - }, - ], - }; - entity4 = { - ...entityDefaultConfig, - name: 'Entity4', - entityClass: 'Entity4', - entityInstance: 'entity4', - fields: [ - { - fieldName: 'uuid', - fieldType: 'UUID', - columnName: 'uuid', - id: true, - autoGenerate: false, - path: ['uuid'], - relationshipsPath: [], - }, - ], - relationships: [ - { - relationshipName: 'otherEntity1', - relationshipNameCapitalized: 'OtherEntity1', - id: true, - otherEntity: entity1, - relationshipType: 'many-to-one', - }, - { - relationshipName: 'otherEntity3', - relationshipNameCapitalized: 'OtherEntity3', - id: true, - otherEntity: entity3, - relationshipType: 'many-to-one', - }, - ], - }; - - entity1 = prepareEntityPrimaryKeyForTemplates({ entity: entity1, enableCompositeId: true }); - entity2 = prepareEntityPrimaryKeyForTemplates({ entity: entity2, enableCompositeId: true }); - entity3 = prepareEntityPrimaryKeyForTemplates({ entity: entity3, enableCompositeId: true }); - entity4 = prepareEntityPrimaryKeyForTemplates({ entity: entity4, enableCompositeId: true }); - }); - - it('should prepare correct primaryKey for entity1', () => { - expect(entity1.primaryKey.fields).to.have.lengthOf(1); - expect(entity1.primaryKey).to.deep.include({ - name: 'id', - nameCapitalized: 'Id', - type: 'String', - tsType: 'string', - derived: false, - composite: false, - }); - expect(entity1.primaryKey.fields[0]).to.equal(entity1.fields[0]); - }); - - it('should prepare correct primaryKey for entity2', () => { - expect(entity2.primaryKey.fields).to.have.lengthOf(1); - expect(entity2.primaryKey).to.deep.include({ - name: 'uuid', - nameCapitalized: 'Uuid', - type: 'UUID', - tsType: 'string', - derived: false, - composite: false, - }); - expect(entity2.primaryKey.fields[0]).to.equal(entity2.fields[0]); - }); - - it('should prepare correct primaryKey for one-to-one relationship id', () => { - expect(entity3.primaryKey.fields).to.have.lengthOf(1); - expect(entity3.primaryKey).to.deep.include({ - name: 'uuid', - nameCapitalized: 'Uuid', - type: 'UUID', - derived: true, - composite: false, - }); - expect(entity3.primaryKey.fields[0]).to.deep.include({ - fieldName: 'uuid', - autoGenerate: true, - }); - }); - - it('should prepare correct primaryKey for entity4', () => { - expect(entity4.primaryKey.fields).to.have.lengthOf(3); - expect(entity4.primaryKey).to.deep.include({ - name: 'id', - nameCapitalized: 'Id', - type: 'Entity4Id', - composite: true, - autoGenerate: false, - }); - }); - - it('should prepare correct own id', () => { - expect(entity4.primaryKey.fields[0]).to.deep.include({ - fieldName: 'uuid', - }); - }); - - it('should prepare correct relationship id field', () => { - const field = entity4.primaryKey.fields[1]; - expect(field).to.deep.include({ - ...entity1.primaryKey.fields[0], - fieldName: 'otherEntity1Id', - fieldNameCapitalized: 'OtherEntity1Id', - columnName: 'other_entity1_id', - derivedPath: ['otherEntity1', 'id'], - path: ['otherEntity1', 'id'], - relationshipsPath: [entity4.relationships[0]], - autoGenerate: true, - derivedEntity: entity1, - reference: field.reference, - }); - }); - - it('should prepare correct relationship id ids', () => { - const field = entity4.primaryKey.ids[1]; - expect(field).to.deep.include({ - name: 'otherEntity1Id', - nameCapitalized: 'OtherEntity1Id', - nameDotted: 'otherEntity1.id', - nameDottedAsserted: 'otherEntity1!.id!', - setter: 'setOtherEntity1Id', - getter: 'getOtherEntity1Id', - }); - }); - - it('should prepare correct relationship id with derived primaryKey field', () => { - const field = entity4.primaryKey.fields[2]; - expect(field).to.deep.include({ - ...entity3.primaryKey.fields[0], - derived: true, - fieldName: 'otherEntity3Uuid', - fieldNameCapitalized: 'OtherEntity3Uuid', - columnName: 'other_entity3_uuid', - derivedPath: ['otherEntity3', 'uuid'], - path: ['otherEntity3', 'entity2', 'uuid'], - relationshipsPath: [entity4.relationships[1], entity3.relationships[0]], - derivedEntity: entity3, - reference: field.reference, - }); - }); - - it('should prepare correct relationship id with derived primaryKey field ids', () => { - const field = entity4.primaryKey.ids[2]; - expect(field).to.deep.include({ - name: 'otherEntity3Uuid', - nameCapitalized: 'OtherEntity3Uuid', - nameDotted: 'otherEntity3.uuid', - nameDottedAsserted: 'otherEntity3!.uuid!', - setter: 'setOtherEntity3Uuid', - getter: 'getOtherEntity3Uuid', - }); - }); - }); - }); - }); -}); diff --git a/generators/base-application/support/prepare-entity.spec.ts b/generators/base-application/support/prepare-entity.spec.ts new file mode 100644 index 000000000000..433c96250a88 --- /dev/null +++ b/generators/base-application/support/prepare-entity.spec.ts @@ -0,0 +1,303 @@ +/** + * Copyright 2013-2023 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 { expect } from 'chai'; +import { formatDateForChangelog } from '../../base/support/index.js'; +import { prepareEntityPrimaryKeyForTemplates, entityDefaultConfig } from './prepare-entity.js'; +import BaseGenerator from '../../base/index.js'; +import { getConfigWithDefaults } from '../../../jdl/jhipster/index.js'; + +describe('generator - base-application - support - prepareEntity', () => { + const defaultGenerator = { jhipsterConfig: getConfigWithDefaults() }; + Object.setPrototypeOf(defaultGenerator, BaseGenerator.prototype); + + describe('prepareEntityPrimaryKeyForTemplates', () => { + describe('with field with id name', () => { + describe('without @Id', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let entity: any = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + }; + beforeEach(() => { + entity = prepareEntityPrimaryKeyForTemplates({ entity }); + }); + it('should adopt id field as @Id', () => { + expect(entity.fields[0]).to.eql({ + autoGenerate: true, + dynamic: false, + fieldName: 'id', + fieldType: 'CustomType', + id: true, + path: ['id'], + relationshipsPath: [], + }); + }); + + it('should contains correct structure', () => { + expect(entity.primaryKey).to.deep.include({ + name: 'id', + nameCapitalized: 'Id', + type: 'CustomType', + derived: false, + composite: false, + }); + expect(entity.primaryKey.fields[0]).to.equal(entity.fields[0]); + }); + }); + + describe('with @Id', () => { + let entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [ + { fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }, + { fieldName: 'uuid', fieldType: 'UUID', id: true, path: ['uuid'], relationshipsPath: [] }, + ], + }; + beforeEach(() => { + entity = prepareEntityPrimaryKeyForTemplates({ entity }); + }); + it('should not adopt id field as @Id', () => { + expect(entity.fields[0]).to.eql({ + fieldName: 'id', + fieldType: 'CustomType', + path: ['id'], + relationshipsPath: [], + }); + }); + }); + + describe('with multiple @Id relationships and field', () => { + let entity1; + let entity2; + let entity3; + let entity4; + + beforeEach(() => { + entity1 = { + ...entityDefaultConfig, + name: 'Entity1', + entityClass: 'Entity1', + entityInstance: 'entity1', + fields: [ + { + fieldName: 'id', + fieldNameCapitalized: 'Id', + columnName: 'id', + fieldType: 'String', + id: true, + path: ['id'], + relationshipsPath: [], + }, + ], + }; + entity2 = { + ...entityDefaultConfig, + name: 'Entity2', + entityClass: 'Entity2', + entityInstance: 'entity2', + fields: [ + { + fieldName: 'uuid', + fieldNameCapitalized: 'Uuid', + columnName: 'uuid', + fieldType: 'UUID', + id: true, + autoGenerate: true, + path: ['uuid'], + relationshipsPath: [], + }, + ], + }; + entity3 = { + ...entityDefaultConfig, + name: 'Entity3', + entityClass: 'Entity3', + entityInstance: 'entity3', + relationships: [ + { + relationshipName: 'entity2', + relationshipNameCapitalized: 'Entity2', + relationshipType: 'one-to-one', + id: true, + otherEntity: entity2, + }, + ], + }; + entity4 = { + ...entityDefaultConfig, + name: 'Entity4', + entityClass: 'Entity4', + entityInstance: 'entity4', + fields: [ + { + fieldName: 'uuid', + fieldType: 'UUID', + columnName: 'uuid', + id: true, + autoGenerate: false, + path: ['uuid'], + relationshipsPath: [], + }, + ], + relationships: [ + { + relationshipName: 'otherEntity1', + relationshipNameCapitalized: 'OtherEntity1', + id: true, + otherEntity: entity1, + relationshipType: 'many-to-one', + }, + { + relationshipName: 'otherEntity3', + relationshipNameCapitalized: 'OtherEntity3', + id: true, + otherEntity: entity3, + relationshipType: 'many-to-one', + }, + ], + }; + + entity1 = prepareEntityPrimaryKeyForTemplates({ entity: entity1, enableCompositeId: true }); + entity2 = prepareEntityPrimaryKeyForTemplates({ entity: entity2, enableCompositeId: true }); + entity3 = prepareEntityPrimaryKeyForTemplates({ entity: entity3, enableCompositeId: true }); + entity4 = prepareEntityPrimaryKeyForTemplates({ entity: entity4, enableCompositeId: true }); + }); + + it('should prepare correct primaryKey for entity1', () => { + expect(entity1.primaryKey.fields).to.have.lengthOf(1); + expect(entity1.primaryKey).to.deep.include({ + name: 'id', + nameCapitalized: 'Id', + type: 'String', + tsType: 'string', + derived: false, + composite: false, + }); + expect(entity1.primaryKey.fields[0]).to.equal(entity1.fields[0]); + }); + + it('should prepare correct primaryKey for entity2', () => { + expect(entity2.primaryKey.fields).to.have.lengthOf(1); + expect(entity2.primaryKey).to.deep.include({ + name: 'uuid', + nameCapitalized: 'Uuid', + type: 'UUID', + tsType: 'string', + derived: false, + composite: false, + }); + expect(entity2.primaryKey.fields[0]).to.equal(entity2.fields[0]); + }); + + it('should prepare correct primaryKey for one-to-one relationship id', () => { + expect(entity3.primaryKey.fields).to.have.lengthOf(1); + expect(entity3.primaryKey).to.deep.include({ + name: 'uuid', + nameCapitalized: 'Uuid', + type: 'UUID', + derived: true, + composite: false, + }); + expect(entity3.primaryKey.fields[0]).to.deep.include({ + fieldName: 'uuid', + autoGenerate: true, + }); + }); + + it('should prepare correct primaryKey for entity4', () => { + expect(entity4.primaryKey.fields).to.have.lengthOf(3); + expect(entity4.primaryKey).to.deep.include({ + name: 'id', + nameCapitalized: 'Id', + type: 'Entity4Id', + composite: true, + autoGenerate: false, + }); + }); + + it('should prepare correct own id', () => { + expect(entity4.primaryKey.fields[0]).to.deep.include({ + fieldName: 'uuid', + }); + }); + + it('should prepare correct relationship id field', () => { + const field = entity4.primaryKey.fields[1]; + expect(field).to.deep.include({ + ...entity1.primaryKey.fields[0], + fieldName: 'otherEntity1Id', + fieldNameCapitalized: 'OtherEntity1Id', + columnName: 'other_entity1_id', + derivedPath: ['otherEntity1', 'id'], + path: ['otherEntity1', 'id'], + relationshipsPath: [entity4.relationships[0]], + autoGenerate: true, + derivedEntity: entity1, + reference: field.reference, + }); + }); + + it('should prepare correct relationship id ids', () => { + const field = entity4.primaryKey.ids[1]; + expect(field).to.deep.include({ + name: 'otherEntity1Id', + nameCapitalized: 'OtherEntity1Id', + nameDotted: 'otherEntity1.id', + nameDottedAsserted: 'otherEntity1!.id!', + setter: 'setOtherEntity1Id', + getter: 'getOtherEntity1Id', + }); + }); + + it('should prepare correct relationship id with derived primaryKey field', () => { + const field = entity4.primaryKey.fields[2]; + expect(field).to.deep.include({ + ...entity3.primaryKey.fields[0], + derived: true, + fieldName: 'otherEntity3Uuid', + fieldNameCapitalized: 'OtherEntity3Uuid', + columnName: 'other_entity3_uuid', + derivedPath: ['otherEntity3', 'uuid'], + path: ['otherEntity3', 'entity2', 'uuid'], + relationshipsPath: [entity4.relationships[1], entity3.relationships[0]], + derivedEntity: entity3, + reference: field.reference, + }); + }); + + it('should prepare correct relationship id with derived primaryKey field ids', () => { + const field = entity4.primaryKey.ids[2]; + expect(field).to.deep.include({ + name: 'otherEntity3Uuid', + nameCapitalized: 'OtherEntity3Uuid', + nameDotted: 'otherEntity3.uuid', + nameDottedAsserted: 'otherEntity3!.uuid!', + setter: 'setOtherEntity3Uuid', + getter: 'getOtherEntity3Uuid', + }); + }); + }); + }); + }); +}); diff --git a/generators/base-application/support/prepare-entity.ts b/generators/base-application/support/prepare-entity.ts new file mode 100644 index 000000000000..50106595e84e --- /dev/null +++ b/generators/base-application/support/prepare-entity.ts @@ -0,0 +1,684 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import pluralize from 'pluralize'; + +import type BaseGenerator from '../../base-core/index.js'; +import { getDatabaseTypeData, hibernateSnakeCase } from '../../server/support/index.js'; +import { + createFaker, + parseChangelog, + stringHashCode, + upperFirstCamelCase, + getMicroserviceAppName, + mutateData, +} from '../../base/support/index.js'; +import { fieldToReference } from './prepare-field.js'; +import { getTypescriptKeyType, getEntityParentPathAddition } from '../../client/support/index.js'; +import { + applicationTypes, + authenticationTypes, + binaryOptions, + databaseTypes, + entityOptions, + fieldTypes, + searchEngineTypes, +} from '../../../jdl/jhipster/index.js'; +import { fieldIsEnum } from './field-utils.js'; + +import { Entity } from '../types/index.js'; +import type CoreGenerator from '../../base-core/generator.js'; + +const { sortedUniq, intersection } = _; + +const NO_SEARCH_ENGINE = searchEngineTypes.NO; +const { PaginationTypes, ServiceTypes, MapperTypes } = entityOptions; +const { GATEWAY, MICROSERVICE } = applicationTypes; +const { OAUTH2 } = authenticationTypes; +const { CommonDBTypes } = fieldTypes; + +const { BOOLEAN, LONG, STRING, UUID, INTEGER } = CommonDBTypes; +const { NO: NO_DTO, MAPSTRUCT } = MapperTypes; +const { PAGINATION, INFINITE_SCROLL } = PaginationTypes; +const { SERVICE_IMPL } = ServiceTypes; +const NO_SERVICE = ServiceTypes.NO; +const NO_PAGINATION = PaginationTypes.NO; +const NO_MAPPER = MapperTypes.NO; + +const { CASSANDRA, COUCHBASE, NEO4J, SQL, MONGODB } = databaseTypes; + +const { INSTANT, ZONED_DATE_TIME, DURATION, LOCAL_DATE, BIG_DECIMAL } = fieldTypes.CommonDBTypes; + +const { BYTES, BYTE_BUFFER } = fieldTypes.RelationalOnlyDBTypes; +const { IMAGE, TEXT } = fieldTypes.BlobTypes; + +const BASE_TEMPLATE_DATA = { + primaryKey: undefined, + entityPackage: undefined, + skipUiGrouping: false, + anyFieldHasDocumentation: false, + existingEnum: false, + searchEngine: NO_SEARCH_ENGINE, + microserviceName: undefined, + + requiresPersistableImplementation: false, + anyFieldIsDateDerived: false, + anyFieldIsTimeDerived: false, + anyFieldIsInstant: false, + anyFieldIsUUID: false, + anyFieldIsZonedDateTime: false, + anyFieldIsDuration: false, + anyFieldIsLocalDate: false, + anyFieldIsBigDecimal: false, + anyFieldIsBlobDerived: false, + anyFieldHasImageContentType: false, + anyFieldHasTextContentType: false, + anyFieldHasFileBasedContentType: false, + anyPropertyHasValidation: false, + fieldsContainNoOwnerOneToOne: false, + + get otherRelationships() { + return []; + }, + + get enums() { + return []; + }, + // these variable hold field and relationship names for question options during update + get fieldNameChoices() { + return []; + }, + get differentRelationships() { + return {}; + }, +}; + +function _derivedProperties(entityWithConfig) { + const pagination = entityWithConfig.pagination; + const dto = entityWithConfig.dto; + const service = entityWithConfig.service; + _.defaults(entityWithConfig, { + paginationPagination: pagination === PAGINATION, + paginationInfiniteScroll: pagination === INFINITE_SCROLL, + paginationNo: pagination === NO_PAGINATION, + dtoMapstruct: dto === MAPSTRUCT, + serviceImpl: service === SERVICE_IMPL, + serviceNo: service === NO_SERVICE, + }); +} + +export const entityDefaultConfig = { + pagination: binaryOptions.DefaultValues[binaryOptions.Options.PAGINATION], + anyPropertyHasValidation: false, + dto: binaryOptions.DefaultValues[binaryOptions.Options.DTO], + service: binaryOptions.DefaultValues[binaryOptions.Options.SERVICE], + jpaMetamodelFiltering: false, + readOnly: false, + embedded: false, + entityAngularJSSuffix: '', + fluentMethods: true, + clientRootFolder: '', + get fields() { + return []; + }, + get relationships() { + return []; + }, +}; + +export default function prepareEntity(entityWithConfig, generator, application) { + const entityName = _.upperFirst(entityWithConfig.name); + _.defaults(entityWithConfig, entityDefaultConfig, BASE_TEMPLATE_DATA); + + if (entityWithConfig.changelogDate) { + entityWithConfig.changelogDateForRecent = parseChangelog(String(entityWithConfig.changelogDate)); + } + + entityWithConfig.entityAngularJSSuffix = entityWithConfig.angularJSSuffix; + if (entityWithConfig.entityAngularJSSuffix && !entityWithConfig.entityAngularJSSuffix.startsWith('-')) { + entityWithConfig.entityAngularJSSuffix = `-${entityWithConfig.entityAngularJSSuffix}`; + } + + entityWithConfig.useMicroserviceJson = entityWithConfig.useMicroserviceJson || entityWithConfig.microserviceName !== undefined; + entityWithConfig.microserviceAppName = ''; + if (generator.jhipsterConfig.applicationType === GATEWAY && entityWithConfig.useMicroserviceJson) { + if (!entityWithConfig.microserviceName) { + throw new Error('Microservice name for the entity is not found. Entity cannot be generated!'); + } + entityWithConfig.microserviceAppName = getMicroserviceAppName({ microserviceName: entityWithConfig.microserviceName }); + entityWithConfig.skipServer = true; + } + + _.defaults(entityWithConfig, { + entityNameCapitalized: entityName, + entityClass: _.upperFirst(entityName), + entityInstance: _.lowerFirst(entityName), + entityTableName: hibernateSnakeCase(entityName), + entityNamePlural: pluralize(entityName), + }); + + const dto = entityWithConfig.dto && entityWithConfig.dto !== NO_DTO; + if (dto) { + _.defaults(entityWithConfig, { + dtoClass: `${entityWithConfig.entityClass}${application.dtoSuffix ?? ''}`, + dtoInstance: `${entityWithConfig.entityInstance}${application.dtoSuffix ?? ''}`, + }); + } + + _.defaults(entityWithConfig, { + persistClass: `${entityWithConfig.entityClass}${application.entitySuffix ?? ''}`, + persistInstance: `${entityWithConfig.entityInstance}${application.entitySuffix ?? ''}`, + }); + + _.defaults(entityWithConfig, { + restClass: dto ? entityWithConfig.dtoClass : entityWithConfig.persistClass, + restInstance: dto ? entityWithConfig.dtoInstance : entityWithConfig.persistInstance, + }); + + _.defaults(entityWithConfig, { + entityNamePluralizedAndSpinalCased: _.kebabCase(entityWithConfig.entityNamePlural), + entityClassPlural: _.upperFirst(entityWithConfig.entityNamePlural), + entityInstancePlural: _.lowerFirst(entityWithConfig.entityNamePlural), + }); + + _.defaults(entityWithConfig, { + // Implement i18n variant ex: 'male', 'female' when applied + entityI18nVariant: 'default', + entityClassHumanized: _.startCase(entityWithConfig.entityNameCapitalized), + entityClassPluralHumanized: _.startCase(entityWithConfig.entityClassPlural), + }); + + entityWithConfig.entityFileName = _.kebabCase( + entityWithConfig.entityNameCapitalized + _.upperFirst(entityWithConfig.entityAngularJSSuffix), + ); + entityWithConfig.entityFolderName = entityWithConfig.clientRootFolder + ? `${entityWithConfig.clientRootFolder}/${entityWithConfig.entityFileName}` + : entityWithConfig.entityFileName; + entityWithConfig.entityModelFileName = entityWithConfig.entityFolderName; + entityWithConfig.entityParentPathAddition = getEntityParentPathAddition(entityWithConfig.clientRootFolder); + entityWithConfig.entityPluralFileName = entityWithConfig.entityNamePluralizedAndSpinalCased + entityWithConfig.entityAngularJSSuffix; + entityWithConfig.entityServiceFileName = entityWithConfig.entityFileName; + + entityWithConfig.entityAngularName = entityWithConfig.entityClass + upperFirstCamelCase(entityWithConfig.entityAngularJSSuffix); + entityWithConfig.entityAngularNamePlural = pluralize(entityWithConfig.entityAngularName); + entityWithConfig.entityReactName = entityWithConfig.entityClass + upperFirstCamelCase(entityWithConfig.entityAngularJSSuffix); + + entityWithConfig.entityApiUrl = entityWithConfig.entityNamePluralizedAndSpinalCased; + entityWithConfig.entityStateName = _.kebabCase(entityWithConfig.entityAngularName); + entityWithConfig.entityUrl = entityWithConfig.entityStateName; + + entityWithConfig.entityTranslationKey = entityWithConfig.clientRootFolder + ? _.camelCase(`${entityWithConfig.clientRootFolder}-${entityWithConfig.entityInstance}`) + : entityWithConfig.entityInstance; + entityWithConfig.entityTranslationKeyMenu = _.camelCase( + entityWithConfig.clientRootFolder + ? `${entityWithConfig.clientRootFolder}-${entityWithConfig.entityStateName}` + : entityWithConfig.entityStateName, + ); + + entityWithConfig.i18nKeyPrefix = `${entityWithConfig.frontendAppName}.${entityWithConfig.entityTranslationKey}`; + entityWithConfig.i18nAlertHeaderPrefix = entityWithConfig.i18nKeyPrefix; + if (entityWithConfig.microserviceAppName) { + entityWithConfig.i18nAlertHeaderPrefix = `${entityWithConfig.microserviceAppName}.${entityWithConfig.entityTranslationKey}`; + } + + const { microserviceName, entityFileName, microfrontend } = entityWithConfig; + entityWithConfig.entityApi = microserviceName ? `services/${microserviceName.toLowerCase()}/` : ''; + entityWithConfig.entityPage = + microfrontend && microserviceName && entityWithConfig.applicationType === MICROSERVICE + ? `${microserviceName.toLowerCase()}/${entityFileName}` + : `${entityFileName}`; + + const hasBuiltInUserField = entityWithConfig.relationships.some(relationship => relationship.otherEntity.builtInUser); + entityWithConfig.saveUserSnapshot = + application.applicationType === MICROSERVICE && + application.authenticationType === OAUTH2 && + hasBuiltInUserField && + entityWithConfig.dto === NO_MAPPER; + + entityWithConfig.generateFakeData = type => { + const fieldsToGenerate = + type === 'cypress' ? entityWithConfig.fields.filter(field => !field.id || !field.autoGenerate) : entityWithConfig.fields; + const fieldEntries = fieldsToGenerate.map(field => { + const fieldData = field.generateFakeData(type); + if (!field.nullable && fieldData === null) return undefined; + return [field.fieldName, fieldData]; + }); + const withError = fieldEntries.find(entry => !entry); + if (withError) { + generator.log.warn(`Error generating a full sample for entity ${entityName}`); + return undefined; + } + return Object.fromEntries(fieldEntries); + }; + _derivedProperties(entityWithConfig); + + return entityWithConfig; +} + +export function derivedPrimaryKeyProperties(primaryKey) { + _.defaults(primaryKey, { + hasUUID: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === UUID), + hasLong: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === LONG), + hasInteger: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === INTEGER), + typeUUID: primaryKey.type === UUID, + typeString: primaryKey.type === STRING, + typeLong: primaryKey.type === LONG, + typeInteger: primaryKey.type === INTEGER, + typeNumeric: !primaryKey.composite && primaryKey.fields[0].fieldTypeNumeric, + }); +} + +export function prepareEntityPrimaryKeyForTemplates( + this: CoreGenerator | void, + { entity: entityWithConfig, enableCompositeId = true, application }: { entity: any; enableCompositeId?: boolean; application?: any }, +) { + const idFields = entityWithConfig.fields.filter(field => field.id); + const idRelationships = entityWithConfig.relationships.filter(relationship => relationship.id); + let idCount = idFields.length + idRelationships.length; + + if (idCount === 0) { + let idField = entityWithConfig.fields.find(field => field.fieldName === 'id'); + if (idField) { + idField.id = true; + idField.autoGenerate = idField.autoGenerate ?? true; + } else { + if (entityWithConfig.microserviceName && !application?.microfrontend) { + this?.log.warn( + "Microservice entities should have the id field type specified (e.g., id String) to make sure gateway and microservice types don't conflict", + ); + } + idField = { + fieldName: 'id', + id: true, + fieldNameHumanized: 'ID', + fieldTranslationKey: 'global.field.id', + autoGenerate: true, + }; + entityWithConfig.fields.unshift(idField); + } + idFields.push(idField); + idCount++; + } else if (idRelationships.length > 0) { + idRelationships.forEach(relationship => { + // relationships id data are not available at this point, so calculate it when needed. + relationship.derivedPrimaryKey = { + get derivedFields() { + return relationship.otherEntity.primaryKey.fields.map(field => ({ + originalField: field, + ...field, + derived: true, + derivedEntity: relationship.otherEntity, + jpaGeneratedValue: false, + liquibaseAutoIncrement: false, + // Mapsid is generated by relationship select + autoGenerate: true, + readonly: true, + get derivedPath() { + if (field.derivedPath) { + if (relationship.otherEntity.primaryKey.derived) { + return [relationship.relationshipName, ...field.derivedPath.splice(1)]; + } + return [relationship.relationshipName, ...field.derivedPath]; + } + return [relationship.relationshipName, field.fieldName]; + }, + get path() { + return [relationship.relationshipName, ...field.path]; + }, + get fieldName() { + return idCount === 1 ? field.fieldName : `${relationship.relationshipName}${field.fieldNameCapitalized}`; + }, + get fieldNameCapitalized() { + return idCount === 1 + ? field.fieldNameCapitalized + : `${relationship.relationshipNameCapitalized}${field.fieldNameCapitalized}`; + }, + get columnName() { + return idCount === 1 ? field.columnName : `${hibernateSnakeCase(relationship.relationshipName)}_${field.columnName}`; + }, + get reference() { + return fieldToReference(entityWithConfig, this); + }, + get relationshipsPath() { + return [relationship, ...field.relationshipsPath]; + }, + })); + }, + }; + }); + } + + if (idCount === 1 && idRelationships.length === 1) { + const relationshipId = idRelationships[0]; + // One-To-One relationships with id uses @MapsId. + // Almost every info is taken from the parent, except some info like autoGenerate and derived. + // calling fieldName as id is for backward compatibility, in the future we may want to prefix it with relationship name. + entityWithConfig.primaryKey = { + fieldName: 'id', + derived: true, + // MapsId copy the id from the relationship. + autoGenerate: true, + get fields() { + return this.derivedFields; + }, + get derivedFields() { + return relationshipId.derivedPrimaryKey.derivedFields; + }, + get ownFields() { + return relationshipId.otherEntity.primaryKey.ownFields; + }, + relationships: idRelationships, + get name() { + return relationshipId.otherEntity.primaryKey.name; + }, + get hibernateSnakeCaseName() { + return hibernateSnakeCase(relationshipId.otherEntity.primaryKey.name); + }, + get nameCapitalized() { + return relationshipId.otherEntity.primaryKey.nameCapitalized; + }, + get type() { + return relationshipId.otherEntity.primaryKey.type; + }, + get tsType() { + return relationshipId.otherEntity.primaryKey.tsType; + }, + get composite() { + return relationshipId.otherEntity.primaryKey.composite; + }, + get ids() { + return this.fields.map(field => fieldToId(field)); + }, + }; + } else { + const composite = enableCompositeId ? idCount > 1 : false; + let primaryKeyName; + let primaryKeyType; + if (composite) { + primaryKeyName = 'id'; + primaryKeyType = `${entityWithConfig.entityClass}Id`; + } else { + const idField = idFields[0]; + idField.dynamic = false; + // Allow ids type to be empty and fallback to default type for the database. + if (!idField.fieldType) { + idField.fieldType = application?.pkType ?? getDatabaseTypeData(entityWithConfig.databaseType).defaultPrimaryKeyType; + } + primaryKeyName = idField.fieldName; + primaryKeyType = idField.fieldType; + } + + entityWithConfig.primaryKey = { + derived: false, + name: primaryKeyName, + hibernateSnakeCaseName: hibernateSnakeCase(primaryKeyName), + nameCapitalized: _.upperFirst(primaryKeyName), + type: primaryKeyType, + tsType: getTypescriptKeyType(primaryKeyType), + composite, + relationships: idRelationships, + // Fields declared in this entity + ownFields: idFields, + // Fields declared and inherited + get fields() { + return [...this.ownFields, ...this.derivedFields]; + }, + get autoGenerate() { + return this.composite ? false : this.fields[0].autoGenerate; + }, + // Fields inherited from id relationships. + get derivedFields() { + return this.relationships.map(rel => rel.derivedPrimaryKey.derivedFields).flat(); + }, + get ids() { + return this.fields.map(field => fieldToId(field)); + }, + }; + } + return entityWithConfig; +} + +function fieldToId(field) { + return { + field, + get name() { + return field.fieldName; + }, + get nameCapitalized() { + return field.fieldNameCapitalized; + }, + get nameDotted() { + return field.derivedPath ? field.derivedPath.join('.') : field.fieldName; + }, + get nameDottedAsserted() { + return field.derivedPath ? `${field.derivedPath.join('!.')}!` : `${field.fieldName}!`; + }, + get setter() { + return `set${this.nameCapitalized}`; + }, + get getter() { + return (field.fieldType === BOOLEAN ? 'is' : 'get') + this.nameCapitalized; + }, + get autoGenerate() { + return !!field.autoGenerate; + }, + get relationshipsPath() { + return field.relationshipsPath; + }, + }; +} + +/** + * Copy required application config into entity. + * Some entity features are related to the backend instead of the current app. + * This allows to entities files based on the backend features. + * + * @param {Object} entity - entity to copy the config into. + * @param {Object} config - config object. + * @returns {Object} the entity parameter for chaining. + */ +export function loadRequiredConfigIntoEntity(this: BaseGenerator | void, entity, config) { + _.defaults(entity, { + applicationType: config.applicationType, + baseName: config.baseName, + frontendAppName: config.frontendAppName, + authenticationType: config.authenticationType, + reactive: config.reactive, + microfrontend: config.microfrontend, + // Workaround different paths + clientFramework: config.clientFramework, + + databaseType: config.databaseType, + prodDatabaseType: config.prodDatabaseType, + + skipUiGrouping: config.skipUiGrouping, + searchEngine: config.searchEngine, + + jhiPrefix: config.jhiPrefix, + entitySuffix: config.entitySuffix, + dtoSuffix: config.dtoSuffix, + packageName: config.packageName, + packageFolder: config.packageFolder, + }); + if (entity.searchEngine === true && (!entity.microserviceName || entity.microserviceName === config.baseName)) { + // If the entity belongs to this application and searchEngine is true. + if (config.searchEngine && config.searchEngine !== NO_SEARCH_ENGINE) { + // Replace with the searchEngine from the application. + entity.searchEngine = config.searchEngine; + } else { + entity.searchEngine = NO_SEARCH_ENGINE; + this?.log.warn('Search engine is enabled at entity level, but disabled at application level. Search engine will be disabled'); + } + } + if (config.applicationType === MICROSERVICE) { + _.defaults(entity, { + microserviceName: config.baseName, + }); + } + return entity; +} + +export function preparePostEntityCommonDerivedProperties(entity: Entity) { + const { fields } = entity; + const fieldsType = sortedUniq(fields.map(({ fieldType }) => fieldType).filter(fieldType => !fieldIsEnum(fieldType))); + + // TODO move to server generator + entity.anyFieldHasDocumentation = entity.fields.some(({ documentation }) => documentation); + + entity.anyFieldIsZonedDateTime = fieldsType.includes(ZONED_DATE_TIME); + entity.anyFieldIsInstant = fieldsType.includes(INSTANT); + entity.anyFieldIsDuration = fieldsType.includes(DURATION); + entity.anyFieldIsLocalDate = fieldsType.includes(LOCAL_DATE); + entity.anyFieldIsBigDecimal = fieldsType.includes(BIG_DECIMAL); + entity.anyFieldIsUUID = fieldsType.includes(UUID); + + entity.anyFieldIsTimeDerived = entity.anyFieldIsZonedDateTime || entity.anyFieldIsInstant; + entity.anyFieldIsDateDerived = entity.anyFieldIsTimeDerived || entity.anyFieldIsLocalDate; + + entity.anyFieldIsBlobDerived = intersection(fieldsType, [BYTES, BYTE_BUFFER]).length > 0; + if (entity.anyFieldIsBlobDerived) { + const blobFields = fields.filter(({ fieldType }) => [BYTES, BYTE_BUFFER].includes(fieldType)); + const blobFieldsContentType = sortedUniq(blobFields.map(({ fieldTypeBlobContent }) => fieldTypeBlobContent)); + entity.anyFieldHasImageContentType = blobFieldsContentType.includes(IMAGE); + entity.anyFieldHasFileBasedContentType = blobFieldsContentType.some(fieldTypeBlobContent => fieldTypeBlobContent !== TEXT); + entity.anyFieldHasTextContentType = blobFieldsContentType.includes(TEXT); + } + + preparePostEntityCommonDerivedPropertiesNotTyped(entity); +} + +function preparePostEntityCommonDerivedPropertiesNotTyped(entity: any) { + const { relationships, fields } = entity; + const oneToOneRelationships = relationships.filter(({ relationshipType }) => relationshipType === 'one-to-one'); + entity.fieldsContainNoOwnerOneToOne = oneToOneRelationships.some(({ ownerSide }) => !ownerSide); + + entity.anyPropertyHasValidation = + entity.anyPropertyHasValidation || relationships.some(({ relationshipValidate }) => relationshipValidate); + + const relationshipsByOtherEntity = relationships + .map(relationship => [relationship.otherEntity.entityNameCapitalized, relationship]) + .reduce((relationshipsByOtherEntity: any, [type, relationship]) => { + if (!relationshipsByOtherEntity[type]) { + relationshipsByOtherEntity[type] = [relationship]; + } else { + relationshipsByOtherEntity[type].push(relationship); + } + return relationshipsByOtherEntity; + }, {}); + + entity.relationshipsByOtherEntity = relationshipsByOtherEntity; + entity.differentRelationships = relationshipsByOtherEntity; + + entity.anyPropertyHasValidation = entity.anyPropertyHasValidation || fields.some(({ fieldValidate }) => fieldValidate); + + entity.allReferences = [ + ...entity.fields.map(field => field.reference), + ...entity.relationships.map(relationship => relationship.reference), + ]; + + entity.otherEntities = _.uniq(entity.relationships.map(rel => rel.otherEntity)); + + entity.updatableEntity = + entity.fields.some(field => !field.id && !field.transient) || + entity.relationships.some(relationship => !relationship.id && relationship.ownerSide); + + entity.allReferences + .filter(reference => reference.relationship && reference.relationship.relatedField) + .forEach(reference => { + reference.relatedReference = reference.relationship.relatedField.reference; + }); + + entity.relationships.forEach(relationship => { + relationship.relationshipCollection = ['one-to-many', 'many-to-many'].includes(relationship.relationshipType); + relationship.relationshipReferenceField = relationship.relationshipCollection + ? relationship.relationshipFieldNamePlural + : relationship.relationshipFieldName; + }); + entity.entityContainsCollectionField = entity.relationships.some(relationship => relationship.relationshipCollection); + + if (entity.primaryKey) { + derivedPrimaryKeyProperties(entity.primaryKey); + entity.requiresPersistableImplementation = + entity.requiresPersistableImplementation || entity.fields.some(field => field.requiresPersistableImplementation); + } + + const types = entity.relationships + .filter(rel => rel.otherEntity.primaryKey) + .map(rel => rel.otherEntity.primaryKey.fields.map(f => f.fieldType)) + .flat(); + entity.otherEntityPrimaryKeyTypes = Array.from(new Set(types)); + entity.otherEntityPrimaryKeyTypesIncludesUUID = types.includes(UUID); + + entity.relationships.forEach(relationship => { + if (!relationship.otherEntity.primaryKey) { + relationship.bagRelationship = false; + relationship.relationshipEagerLoad = false; + return; + } + + mutateData(relationship, { + bagRelationship: relationship.ownerSide && relationship.collection, + relationshipEagerLoad: ({ relationshipEagerLoad, bagRelationship, ownerSide, otherEntity, otherEntityField }) => + relationshipEagerLoad ?? + (bagRelationship || + entity.eagerLoad || + // Fetch relationships if otherEntityField differs otherwise the id is enough + (ownerSide && otherEntity.primaryKey.name !== otherEntityField)), + }); + }); + entity.relationshipsContainEagerLoad = entity.relationships.some(relationship => relationship.relationshipEagerLoad); + entity.containsBagRelationships = entity.relationships.some(relationship => relationship.bagRelationship); + entity.implementsEagerLoadApis = // Cassandra doesn't provides *WithEagerRelationships apis + ![CASSANDRA, COUCHBASE, NEO4J].includes(entity.databaseType) && + // Only sql and mongodb provides *WithEagerReationships apis for imperative implementation + (entity.reactive || [SQL, MONGODB].includes(entity.databaseType)) && + entity.relationshipsContainEagerLoad; + entity.eagerRelations = entity.relationships.filter(rel => rel.relationshipEagerLoad); + entity.regularEagerRelations = entity.eagerRelations.filter(rel => rel.id !== true); + + entity.reactiveEagerRelations = entity.relationships.filter( + rel => rel.relationshipType === 'many-to-one' || (rel.relationshipType === 'one-to-one' && rel.ownerSide === true), + ); + entity.reactiveRegularEagerRelations = entity.reactiveEagerRelations.filter(rel => rel.id !== true); +} + +export function preparePostEntitiesCommonDerivedProperties(entities) { + for (const entity of entities.filter(entity => !entity.dtoReferences)) { + entity.dtoReferences = [ + ...entity.fields.map(field => field.reference), + ...entity.relationships + .map(relationship => relationship.reference) + .filter(reference => reference.owned || reference.relationship.otherEntity.embedded), + ]; + entity.otherReferences = entity.otherRelationships.map(relationship => relationship.reference); + } + + for (const entity of entities.filter(entity => !entity.otherDtoReferences)) { + // Get all required back references for dto. + entity.otherDtoReferences = entity.otherReferences.filter(reference => reference.entity.dtoReferences.includes(reference)); + } +} + +export async function addFakerToEntity(entityWithConfig: any, nativeLanguage = 'en') { + entityWithConfig.faker = entityWithConfig.faker || (await createFaker(nativeLanguage)); + entityWithConfig.resetFakerSeed = (suffix = '') => + entityWithConfig.faker.seed(stringHashCode(entityWithConfig.name.toLowerCase() + suffix)); + entityWithConfig.resetFakerSeed(); +} diff --git a/generators/base-application/support/prepare-field.js b/generators/base-application/support/prepare-field.js new file mode 100644 index 000000000000..05784ef82327 --- /dev/null +++ b/generators/base-application/support/prepare-field.js @@ -0,0 +1,396 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { fieldTypes, validations } from '../../../jdl/jhipster/index.js'; +import { getTypescriptType, prepareField as prepareClientFieldForTemplates } from '../../client/support/index.js'; +import { prepareField as prepareServerFieldForTemplates } from '../../server/support/index.js'; +import { fieldIsEnum } from './field-utils.js'; +import { mutateData } from '../../base/support/config.js'; + +const { BlobTypes, CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; +const { + Validations: { MIN, MINLENGTH, MINBYTES, MAX, MAXBYTES, MAXLENGTH, PATTERN, REQUIRED, UNIQUE }, +} = validations; + +const { TEXT, IMAGE, ANY } = BlobTypes; +const { + BOOLEAN, + BIG_DECIMAL, + DOUBLE, + DURATION, + FLOAT, + INSTANT, + INTEGER, + LOCAL_DATE, + LONG, + STRING, + UUID, + ZONED_DATE_TIME, + IMAGE_BLOB, + ANY_BLOB, + TEXT_BLOB, + BLOB, +} = CommonDBTypes; +const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; + +const fakeStringTemplateForFieldName = columnName => { + let fakeTemplate; + if (columnName === 'first_name') { + fakeTemplate = 'person.firstName'; + } else if (columnName === 'last_name') { + fakeTemplate = 'person.lastName'; + } else if (columnName === 'job_title') { + fakeTemplate = 'person.jobTitle'; + } else if (columnName === 'telephone' || columnName === 'phone') { + fakeTemplate = 'phone.number'; + } else if (columnName === 'zip_code' || columnName === 'post_code') { + fakeTemplate = 'location.zipCode'; + } else if (columnName === 'city') { + fakeTemplate = 'location.city'; + } else if (columnName === 'street_name' || columnName === 'street') { + fakeTemplate = 'location.street'; + } else if (columnName === 'country') { + fakeTemplate = 'location.country'; + } else if (columnName === 'country_code') { + fakeTemplate = 'location.countryCode'; + } else if (columnName === 'color') { + fakeTemplate = 'color.human'; + } else if (columnName === 'account') { + fakeTemplate = 'finance.account'; + } else if (columnName === 'account_name') { + fakeTemplate = 'finance.accountName'; + } else if (columnName === 'currency_code') { + fakeTemplate = 'finance.currencyCode'; + } else if (columnName === 'currency_name') { + fakeTemplate = 'finance.currencyName'; + } else if (columnName === 'currency_symbol') { + fakeTemplate = 'finance.currencySymbol'; + } else if (columnName === 'iban') { + fakeTemplate = 'finance.iban'; + } else if (columnName === 'bic') { + fakeTemplate = 'finance.bic'; + } else if (columnName === 'email') { + fakeTemplate = 'internet.email'; + } else if (columnName === 'url') { + fakeTemplate = 'internet.url'; + } else { + fakeTemplate = 'word.words'; + } + return `{{${fakeTemplate}}}`; +}; + +/** + * @param {*} field + * @param {import('@faker-js/faker').Faker} faker + * @param {*} changelogDate + * @param {string} type csv, cypress, json-serializable, ts + * @returns fake value + */ +function generateFakeDataForField(field, faker, changelogDate, type = 'csv') { + let data; + if (field.fakerTemplate) { + data = faker.faker(field.fakerTemplate); + } else if (field.fieldValidate && field.fieldValidateRules.includes('pattern')) { + const re = field.createRandexp(); + if (!re) { + return undefined; + } + const generated = re.gen(); + if (type === 'csv' || type === 'cypress') { + data = generated.replace(/"/g, ''); + } else { + data = generated; + } + if (data.length === 0) { + this.log.warn(`Generated value for pattern ${field.fieldValidateRulesPattern} is not valid.`); + data = undefined; + } + } else if (field.fieldIsEnum) { + if (field.fieldValues.length !== 0) { + const enumValues = field.enumValues; + data = enumValues[faker.number.int(enumValues.length - 1)].name; + } else { + this.log.warn(`Enum ${field.fieldType} is not valid`); + data = undefined; + } + } else if (field.fieldType === DURATION && type === 'cypress') { + data = `PT${faker.number.int({ min: 1, max: 59 })}M`; + + // eslint-disable-next-line no-template-curly-in-string + } else if ([FLOAT, '${floatType}', DOUBLE, BIG_DECIMAL].includes(field.fieldType)) { + data = faker.number.float({ + max: field.fieldValidateRulesMax ? parseInt(field.fieldValidateRulesMax, 10) : 32767, + min: field.fieldValidateRulesMin ? parseInt(field.fieldValidateRulesMin, 10) : 0, + precision: 0.01, + }); + } else if ([INTEGER, LONG, DURATION].includes(field.fieldType)) { + data = faker.number.int({ + max: field.fieldValidateRulesMax ? parseInt(field.fieldValidateRulesMax, 10) : 32767, + min: field.fieldValidateRulesMin ? parseInt(field.fieldValidateRulesMin, 10) : 0, + }); + } else if ([INSTANT, ZONED_DATE_TIME, LOCAL_DATE].includes(field.fieldType)) { + // Iso: YYYY-MM-DDTHH:mm:ss.sssZ + const date = faker.date.recent({ days: 1, refDate: changelogDate }); + const isoDate = date.toISOString(); + if (field.fieldType === LOCAL_DATE) { + data = isoDate.split('T')[0]; + } else if (type === 'json-serializable') { + data = date; + } else { + // Write the date without milliseconds so Java can parse it + // See https://stackoverflow.com/a/34053802/150868 + // YYYY-MM-DDTHH:mm:ss + data = isoDate.split('.')[0]; + if (type === 'cypress' || type === 'ts') { + // YYYY-MM-DDTHH:mm + data = data.substr(0, data.length - 3); + } + } + } else if (field.fieldType === BYTES && field.fieldTypeBlobContent !== TEXT) { + data = '../fake-data/blob/hipster.png'; + } else if (field.fieldType === BYTES && field.fieldTypeBlobContent === TEXT) { + data = '../fake-data/blob/hipster.txt'; + } else if (field.fieldType === STRING) { + data = field.id ? faker.string.uuid() : faker.helpers.fake(fakeStringTemplateForFieldName(field.columnName)); + } else if (field.fieldType === UUID) { + data = faker.string.uuid(); + } else if (field.fieldType === BOOLEAN) { + data = faker.datatype.boolean(); + } else { + this.log.warn(`Fake data for field ${field.fieldType} is not supported`); + } + + if (field.fieldType === BYTES && type === 'json-serializable') { + data = Buffer.from(data).toString('base64'); + } + + // Validation rules + if (data !== undefined && field.fieldValidate === true) { + // manage String max length + if (field.fieldValidateRules.includes(MAXLENGTH)) { + const maxlength = field.fieldValidateRulesMaxlength; + data = data.substring(0, maxlength); + } + + // manage String min length + if (field.fieldValidateRules.includes(MINLENGTH)) { + const minlength = field.fieldValidateRulesMinlength; + data = data.length > minlength ? data : data + 'X'.repeat(minlength - data.length); + } + + // test if generated data is still compatible with the regexp as we potentially modify it with min/maxLength + if (field.fieldValidateRules.includes(PATTERN) && !new RegExp(`^${field.fieldValidateRulesPattern}$`).test(data)) { + data = undefined; + } + } + if (data !== undefined) { + // eslint-disable-next-line no-template-curly-in-string + if (type === 'ts' && ![BOOLEAN, INTEGER, LONG, FLOAT, '${floatType}', DOUBLE, BIG_DECIMAL].includes(field.fieldType)) { + data = `'${typeof data === 'string' ? data.replace(/'/g, "\\'") : data}'`; + } else if (type === 'csv' && field.fieldValidate && field.fieldValidateRules.includes(PATTERN)) { + data = `"${typeof data === 'string' ? data.replace(/"/g, '\\"') : data}"`; + } + } + + return data; +} + +function _derivedProperties(field) { + const fieldType = field.fieldType; + const fieldTypeBlobContent = field.fieldTypeBlobContent; + const validationRules = field.fieldValidate ? field.fieldValidateRules : []; + _.defaults(field, { + blobContentTypeText: fieldTypeBlobContent === TEXT, + blobContentTypeImage: fieldTypeBlobContent === IMAGE, + blobContentTypeAny: fieldTypeBlobContent === ANY, + fieldTypeBoolean: fieldType === BOOLEAN, + fieldTypeBigDecimal: fieldType === BIG_DECIMAL, + fieldTypeDouble: fieldType === DOUBLE, + fieldTypeDuration: fieldType === DURATION, + fieldTypeFloat: fieldType === FLOAT, + fieldTypeInstant: fieldType === INSTANT, + fieldTypeInteger: fieldType === INTEGER, + fieldTypeLocalDate: fieldType === LOCAL_DATE, + fieldTypeLong: fieldType === LONG, + fieldTypeString: fieldType === STRING, + fieldTypeUUID: fieldType === UUID, + fieldTypeZonedDateTime: fieldType === ZONED_DATE_TIME, + fieldTypeImageBlob: fieldType === IMAGE_BLOB, + fieldTypeAnyBlob: fieldType === ANY_BLOB, + fieldTypeTextBlob: fieldType === TEXT_BLOB, + fieldTypeBlob: fieldType === BLOB, + fieldTypeBytes: fieldType === BYTES, + fieldTypeByteBuffer: fieldType === BYTE_BUFFER, + fieldTypeNumeric: + fieldType === INTEGER || fieldType === LONG || fieldType === FLOAT || fieldType === DOUBLE || fieldType === BIG_DECIMAL, + fieldTypeBinary: fieldType === BYTES || fieldType === BYTE_BUFFER, + fieldTypeTimed: fieldType === ZONED_DATE_TIME || fieldType === INSTANT, + fieldTypeCharSequence: fieldType === STRING || fieldType === UUID, + fieldTypeTemporal: fieldType === ZONED_DATE_TIME || fieldType === INSTANT || fieldType === LOCAL_DATE, + fieldValidationRequired: validationRules.includes(REQUIRED), + fieldValidationMin: validationRules.includes(MIN), + fieldValidationMinLength: validationRules.includes(MINLENGTH), + fieldValidationMax: validationRules.includes(MAX), + fieldValidationMaxLength: validationRules.includes(MAXLENGTH), + fieldValidationPattern: validationRules.includes(PATTERN), + fieldValidationUnique: validationRules.includes(UNIQUE), + fieldValidationMinBytes: validationRules.includes(MINBYTES), + fieldValidationMaxBytes: validationRules.includes(MAXBYTES), + }); +} + +export default function prepareField(entityWithConfig, field, generator) { + prepareCommonFieldForTemplates(entityWithConfig, field, generator); + + if (entityWithConfig.prodDatabaseType || entityWithConfig.databaseType) { + prepareServerFieldForTemplates(entityWithConfig, field, generator); + } + + prepareClientFieldForTemplates(entityWithConfig, field, generator); + return field; +} + +function prepareCommonFieldForTemplates(entityWithConfig, field, generator) { + mutateData(field, { + path: [field.fieldName], + propertyName: field.fieldName, + propertyNameCapitalized: ({ propertyName, propertyNameCapitalized }) => propertyNameCapitalized ?? _.upperFirst(propertyName), + fieldNameCapitalized: ({ fieldName, fieldNameCapitalized }) => fieldNameCapitalized ?? _.upperFirst(fieldName), + fieldNameUnderscored: ({ fieldName, fieldNameUnderscored }) => fieldNameUnderscored ?? _.snakeCase(fieldName), + fieldNameHumanized: ({ fieldName, fieldNameHumanized }) => fieldNameHumanized ?? _.startCase(fieldName), + fieldTranslationKey: ({ fieldName, fieldTranslationKey }) => fieldTranslationKey ?? `${entityWithConfig.i18nKeyPrefix}.${fieldName}`, + tsType: ({ fieldType, tsType }) => tsType ?? getTypescriptType(fieldType), + }); + + _.defaults(field, { + entity: entityWithConfig, + }); + const fieldType = field.fieldType; + + field.fieldIsEnum = !field.id && fieldIsEnum(fieldType); + if (field.fieldIsEnum) { + field.enumFileName = _.kebabCase(field.fieldType); + field.enumValues = getEnumValuesWithCustomValues(field.fieldValues); + } + + field.fieldWithContentType = (fieldType === BYTES || fieldType === BYTE_BUFFER) && field.fieldTypeBlobContent !== TEXT; + if (field.fieldWithContentType) { + field.contentTypeFieldName = `${field.fieldName}ContentType`; + } + + field.fieldValidate = Array.isArray(field.fieldValidateRules) && field.fieldValidateRules.length >= 1; + _.defaults(field, { + nullable: !(field.fieldValidate === true && field.fieldValidateRules.includes(REQUIRED)), + }); + field.unique = field.fieldValidate === true && field.fieldValidateRules.includes(UNIQUE); + if (field.fieldValidate === true && field.fieldValidateRules.includes(MAXLENGTH)) { + field.maxlength = field.fieldValidateRulesMaxlength || 255; + } + + const faker = entityWithConfig.faker; + field.createRandexp = () => { + // check if regex is valid. If not, issue warning and we skip fake data generation. + try { + // eslint-disable-next-line no-new + new RegExp(field.fieldValidateRulesPattern); + } catch (e) { + generator.log.warn(`${field.fieldName} pattern is not valid: ${field.fieldValidateRulesPattern}. Skipping generating fake data. `); + return undefined; + } + const re = faker.createRandexp(field.fieldValidateRulesPattern); + if (!re) { + generator.log.warn(`Error creating generator for pattern ${field.fieldValidateRulesPattern}`); + } + return re; + }; + + field.uniqueValue = []; + + field.generateFakeData = (type = 'csv') => { + let data = generateFakeDataForField.call(generator, field, faker, entityWithConfig.changelogDateForRecent, type); + // manage uniqueness + if ((field.fieldValidate === true && field.fieldValidateRules.includes(UNIQUE)) || field.id) { + let i = 0; + while (field.uniqueValue.indexOf(data) !== -1) { + if (i++ === 5) { + data = undefined; + break; + } + data = generateFakeDataForField.call(generator, field, faker, entityWithConfig.changelogDateForRecent, type); + } + if (data === undefined) { + generator.log.warn(`Error generating a unique value field ${field.fieldName} and type ${field.fieldType}`); + } else { + field.uniqueValue.push(data); + } + } + if (data === undefined) { + generator.log.warn(`Error generating fake data for field ${entityWithConfig.name}.${field.fieldName}`); + } + return data; + }; + field.relationshipsPath = []; + + field.reference = fieldToReference(entityWithConfig, field); + _derivedProperties(field); + return field; +} + +/** + * From an enum's values (with or without custom values), returns the enum's values without custom values. + * @param {String} [enumValues] - an enum's values. + * @return {Array} the formatted enum's values. + */ +export function getEnumValuesWithCustomValues(enumValues) { + if (!enumValues || enumValues === '') { + throw new Error('Enumeration values must be passed to get the formatted values.'); + } + return enumValues.split(',').map(enumValue => { + if (!enumValue.includes('(')) { + return { name: enumValue.trim(), value: enumValue.trim() }; + } + const matched = /\s*(.+?)\s*\((.+?)\)/.exec(enumValue); + return { + name: matched[1], + value: matched[2], + }; + }); +} + +export function fieldToReference(entity, field, pathPrefix = []) { + return { + id: field.id, + entity, + field, + multiple: false, + owned: true, + doc: field.documentation, + get propertyJavadoc() { + return field.fieldJavadoc; + }, + get propertyApiDescription() { + return field.fieldApiDescription; + }, + label: field.fieldNameHumanized, + name: field.fieldName, + type: field.fieldType, + nameCapitalized: field.fieldNameCapitalized, + path: [...pathPrefix, field.fieldName], + }; +} diff --git a/generators/base-application/support/prepare-field.mjs b/generators/base-application/support/prepare-field.mjs deleted file mode 100644 index 7ab4c0d4418b..000000000000 --- a/generators/base-application/support/prepare-field.mjs +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { fieldTypes, validations } from '../../../jdl/jhipster/index.mjs'; -import { getTypescriptType, prepareField as prepareClientFieldForTemplates } from '../../client/support/index.mjs'; -import { prepareField as prepareServerFieldForTemplates } from '../../server/support/index.mjs'; -import { fieldIsEnum } from './field-utils.mjs'; -import { mutateData } from '../../base/support/config.mjs'; - -const { BlobTypes, CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; -const { - Validations: { MIN, MINLENGTH, MINBYTES, MAX, MAXBYTES, MAXLENGTH, PATTERN, REQUIRED, UNIQUE }, -} = validations; - -const { TEXT, IMAGE, ANY } = BlobTypes; -const { - BOOLEAN, - BIG_DECIMAL, - DOUBLE, - DURATION, - FLOAT, - INSTANT, - INTEGER, - LOCAL_DATE, - LONG, - STRING, - UUID, - ZONED_DATE_TIME, - IMAGE_BLOB, - ANY_BLOB, - TEXT_BLOB, - BLOB, -} = CommonDBTypes; -const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; - -const fakeStringTemplateForFieldName = columnName => { - let fakeTemplate; - if (columnName === 'first_name') { - fakeTemplate = 'person.firstName'; - } else if (columnName === 'last_name') { - fakeTemplate = 'person.lastName'; - } else if (columnName === 'job_title') { - fakeTemplate = 'person.jobTitle'; - } else if (columnName === 'telephone' || columnName === 'phone') { - fakeTemplate = 'phone.number'; - } else if (columnName === 'zip_code' || columnName === 'post_code') { - fakeTemplate = 'location.zipCode'; - } else if (columnName === 'city') { - fakeTemplate = 'location.city'; - } else if (columnName === 'street_name' || columnName === 'street') { - fakeTemplate = 'location.street'; - } else if (columnName === 'country') { - fakeTemplate = 'location.country'; - } else if (columnName === 'country_code') { - fakeTemplate = 'location.countryCode'; - } else if (columnName === 'color') { - fakeTemplate = 'color.human'; - } else if (columnName === 'account') { - fakeTemplate = 'finance.account'; - } else if (columnName === 'account_name') { - fakeTemplate = 'finance.accountName'; - } else if (columnName === 'currency_code') { - fakeTemplate = 'finance.currencyCode'; - } else if (columnName === 'currency_name') { - fakeTemplate = 'finance.currencyName'; - } else if (columnName === 'currency_symbol') { - fakeTemplate = 'finance.currencySymbol'; - } else if (columnName === 'iban') { - fakeTemplate = 'finance.iban'; - } else if (columnName === 'bic') { - fakeTemplate = 'finance.bic'; - } else if (columnName === 'email') { - fakeTemplate = 'internet.email'; - } else if (columnName === 'url') { - fakeTemplate = 'internet.url'; - } else { - fakeTemplate = 'word.words'; - } - return `{{${fakeTemplate}}}`; -}; - -/** - * @param {*} field - * @param {import('@faker-js/faker').Faker} faker - * @param {*} changelogDate - * @param {string} type csv, cypress, json-serializable, ts - * @returns fake value - */ -function generateFakeDataForField(field, faker, changelogDate, type = 'csv') { - let data; - if (field.fakerTemplate) { - data = faker.faker(field.fakerTemplate); - } else if (field.fieldValidate && field.fieldValidateRules.includes('pattern')) { - const re = field.createRandexp(); - if (!re) { - return undefined; - } - const generated = re.gen(); - if (type === 'csv' || type === 'cypress') { - data = generated.replace(/"/g, ''); - } else { - data = generated; - } - if (data.length === 0) { - this.log.warn(`Generated value for pattern ${field.fieldValidateRulesPattern} is not valid.`); - data = undefined; - } - } else if (field.fieldIsEnum) { - if (field.fieldValues.length !== 0) { - const enumValues = field.enumValues; - data = enumValues[faker.number.int(enumValues.length - 1)].name; - } else { - this.log.warn(`Enum ${field.fieldType} is not valid`); - data = undefined; - } - } else if (field.fieldType === DURATION && type === 'cypress') { - data = `PT${faker.number.int({ min: 1, max: 59 })}M`; - - // eslint-disable-next-line no-template-curly-in-string - } else if ([FLOAT, '${floatType}', DOUBLE, BIG_DECIMAL].includes(field.fieldType)) { - data = faker.number.float({ - max: field.fieldValidateRulesMax ? parseInt(field.fieldValidateRulesMax, 10) : 32767, - min: field.fieldValidateRulesMin ? parseInt(field.fieldValidateRulesMin, 10) : 0, - precision: 0.01, - }); - } else if ([INTEGER, LONG, DURATION].includes(field.fieldType)) { - data = faker.number.int({ - max: field.fieldValidateRulesMax ? parseInt(field.fieldValidateRulesMax, 10) : 32767, - min: field.fieldValidateRulesMin ? parseInt(field.fieldValidateRulesMin, 10) : 0, - }); - } else if ([INSTANT, ZONED_DATE_TIME, LOCAL_DATE].includes(field.fieldType)) { - // Iso: YYYY-MM-DDTHH:mm:ss.sssZ - const date = faker.date.recent({ days: 1, refDate: changelogDate }); - const isoDate = date.toISOString(); - if (field.fieldType === LOCAL_DATE) { - data = isoDate.split('T')[0]; - } else if (type === 'json-serializable') { - data = date; - } else { - // Write the date without milliseconds so Java can parse it - // See https://stackoverflow.com/a/34053802/150868 - // YYYY-MM-DDTHH:mm:ss - data = isoDate.split('.')[0]; - if (type === 'cypress' || type === 'ts') { - // YYYY-MM-DDTHH:mm - data = data.substr(0, data.length - 3); - } - } - } else if (field.fieldType === BYTES && field.fieldTypeBlobContent !== TEXT) { - data = '../fake-data/blob/hipster.png'; - } else if (field.fieldType === BYTES && field.fieldTypeBlobContent === TEXT) { - data = '../fake-data/blob/hipster.txt'; - } else if (field.fieldType === STRING) { - data = field.id ? faker.string.uuid() : faker.helpers.fake(fakeStringTemplateForFieldName(field.columnName)); - } else if (field.fieldType === UUID) { - data = faker.string.uuid(); - } else if (field.fieldType === BOOLEAN) { - data = faker.datatype.boolean(); - } else { - this.log.warn(`Fake data for field ${field.fieldType} is not supported`); - } - - if (field.fieldType === BYTES && type === 'json-serializable') { - data = Buffer.from(data).toString('base64'); - } - - // Validation rules - if (data !== undefined && field.fieldValidate === true) { - // manage String max length - if (field.fieldValidateRules.includes(MAXLENGTH)) { - const maxlength = field.fieldValidateRulesMaxlength; - data = data.substring(0, maxlength); - } - - // manage String min length - if (field.fieldValidateRules.includes(MINLENGTH)) { - const minlength = field.fieldValidateRulesMinlength; - data = data.length > minlength ? data : data + 'X'.repeat(minlength - data.length); - } - - // test if generated data is still compatible with the regexp as we potentially modify it with min/maxLength - if (field.fieldValidateRules.includes(PATTERN) && !new RegExp(`^${field.fieldValidateRulesPattern}$`).test(data)) { - data = undefined; - } - } - if (data !== undefined) { - // eslint-disable-next-line no-template-curly-in-string - if (type === 'ts' && ![BOOLEAN, INTEGER, LONG, FLOAT, '${floatType}', DOUBLE, BIG_DECIMAL].includes(field.fieldType)) { - data = `'${typeof data === 'string' ? data.replace(/'/g, "\\'") : data}'`; - } else if (type === 'csv' && field.fieldValidate && field.fieldValidateRules.includes(PATTERN)) { - data = `"${typeof data === 'string' ? data.replace(/"/g, '\\"') : data}"`; - } - } - - return data; -} - -function _derivedProperties(field) { - const fieldType = field.fieldType; - const fieldTypeBlobContent = field.fieldTypeBlobContent; - const validationRules = field.fieldValidate ? field.fieldValidateRules : []; - _.defaults(field, { - blobContentTypeText: fieldTypeBlobContent === TEXT, - blobContentTypeImage: fieldTypeBlobContent === IMAGE, - blobContentTypeAny: fieldTypeBlobContent === ANY, - fieldTypeBoolean: fieldType === BOOLEAN, - fieldTypeBigDecimal: fieldType === BIG_DECIMAL, - fieldTypeDouble: fieldType === DOUBLE, - fieldTypeDuration: fieldType === DURATION, - fieldTypeFloat: fieldType === FLOAT, - fieldTypeInstant: fieldType === INSTANT, - fieldTypeInteger: fieldType === INTEGER, - fieldTypeLocalDate: fieldType === LOCAL_DATE, - fieldTypeLong: fieldType === LONG, - fieldTypeString: fieldType === STRING, - fieldTypeUUID: fieldType === UUID, - fieldTypeZonedDateTime: fieldType === ZONED_DATE_TIME, - fieldTypeImageBlob: fieldType === IMAGE_BLOB, - fieldTypeAnyBlob: fieldType === ANY_BLOB, - fieldTypeTextBlob: fieldType === TEXT_BLOB, - fieldTypeBlob: fieldType === BLOB, - fieldTypeBytes: fieldType === BYTES, - fieldTypeByteBuffer: fieldType === BYTE_BUFFER, - fieldTypeNumeric: - fieldType === INTEGER || fieldType === LONG || fieldType === FLOAT || fieldType === DOUBLE || fieldType === BIG_DECIMAL, - fieldTypeBinary: fieldType === BYTES || fieldType === BYTE_BUFFER, - fieldTypeTimed: fieldType === ZONED_DATE_TIME || fieldType === INSTANT, - fieldTypeCharSequence: fieldType === STRING || fieldType === UUID, - fieldTypeTemporal: fieldType === ZONED_DATE_TIME || fieldType === INSTANT || fieldType === LOCAL_DATE, - fieldValidationRequired: validationRules.includes(REQUIRED), - fieldValidationMin: validationRules.includes(MIN), - fieldValidationMinLength: validationRules.includes(MINLENGTH), - fieldValidationMax: validationRules.includes(MAX), - fieldValidationMaxLength: validationRules.includes(MAXLENGTH), - fieldValidationPattern: validationRules.includes(PATTERN), - fieldValidationUnique: validationRules.includes(UNIQUE), - fieldValidationMinBytes: validationRules.includes(MINBYTES), - fieldValidationMaxBytes: validationRules.includes(MAXBYTES), - }); -} - -export default function prepareField(entityWithConfig, field, generator) { - prepareCommonFieldForTemplates(entityWithConfig, field, generator); - - if (entityWithConfig.prodDatabaseType || entityWithConfig.databaseType) { - prepareServerFieldForTemplates(entityWithConfig, field, generator); - } - - prepareClientFieldForTemplates(entityWithConfig, field, generator); - return field; -} - -function prepareCommonFieldForTemplates(entityWithConfig, field, generator) { - mutateData(field, { - path: [field.fieldName], - propertyName: field.fieldName, - propertyNameCapitalized: ({ propertyName, propertyNameCapitalized }) => propertyNameCapitalized ?? _.upperFirst(propertyName), - fieldNameCapitalized: ({ fieldName, fieldNameCapitalized }) => fieldNameCapitalized ?? _.upperFirst(fieldName), - fieldNameUnderscored: ({ fieldName, fieldNameUnderscored }) => fieldNameUnderscored ?? _.snakeCase(fieldName), - fieldNameHumanized: ({ fieldName, fieldNameHumanized }) => fieldNameHumanized ?? _.startCase(fieldName), - fieldTranslationKey: ({ fieldName, fieldTranslationKey }) => fieldTranslationKey ?? `${entityWithConfig.i18nKeyPrefix}.${fieldName}`, - tsType: ({ fieldType, tsType }) => tsType ?? getTypescriptType(fieldType), - }); - - _.defaults(field, { - entity: entityWithConfig, - }); - const fieldType = field.fieldType; - - field.fieldIsEnum = !field.id && fieldIsEnum(fieldType); - if (field.fieldIsEnum) { - field.enumFileName = _.kebabCase(field.fieldType); - field.enumValues = getEnumValuesWithCustomValues(field.fieldValues); - } - - field.fieldWithContentType = (fieldType === BYTES || fieldType === BYTE_BUFFER) && field.fieldTypeBlobContent !== TEXT; - if (field.fieldWithContentType) { - field.contentTypeFieldName = `${field.fieldName}ContentType`; - } - - field.fieldValidate = Array.isArray(field.fieldValidateRules) && field.fieldValidateRules.length >= 1; - _.defaults(field, { - nullable: !(field.fieldValidate === true && field.fieldValidateRules.includes(REQUIRED)), - }); - field.unique = field.fieldValidate === true && field.fieldValidateRules.includes(UNIQUE); - if (field.fieldValidate === true && field.fieldValidateRules.includes(MAXLENGTH)) { - field.maxlength = field.fieldValidateRulesMaxlength || 255; - } - - const faker = entityWithConfig.faker; - field.createRandexp = () => { - // check if regex is valid. If not, issue warning and we skip fake data generation. - try { - // eslint-disable-next-line no-new - new RegExp(field.fieldValidateRulesPattern); - } catch (e) { - generator.log.warn(`${field.fieldName} pattern is not valid: ${field.fieldValidateRulesPattern}. Skipping generating fake data. `); - return undefined; - } - const re = faker.createRandexp(field.fieldValidateRulesPattern); - if (!re) { - generator.log.warn(`Error creating generator for pattern ${field.fieldValidateRulesPattern}`); - } - return re; - }; - - field.uniqueValue = []; - - field.generateFakeData = (type = 'csv') => { - let data = generateFakeDataForField.call(generator, field, faker, entityWithConfig.changelogDateForRecent, type); - // manage uniqueness - if ((field.fieldValidate === true && field.fieldValidateRules.includes(UNIQUE)) || field.id) { - let i = 0; - while (field.uniqueValue.indexOf(data) !== -1) { - if (i++ === 5) { - data = undefined; - break; - } - data = generateFakeDataForField.call(generator, field, faker, entityWithConfig.changelogDateForRecent, type); - } - if (data === undefined) { - generator.log.warn(`Error generating a unique value field ${field.fieldName} and type ${field.fieldType}`); - } else { - field.uniqueValue.push(data); - } - } - if (data === undefined) { - generator.log.warn(`Error generating fake data for field ${entityWithConfig.name}.${field.fieldName}`); - } - return data; - }; - field.relationshipsPath = []; - - field.reference = fieldToReference(entityWithConfig, field); - _derivedProperties(field); - return field; -} - -/** - * From an enum's values (with or without custom values), returns the enum's values without custom values. - * @param {String} [enumValues] - an enum's values. - * @return {Array} the formatted enum's values. - */ -export function getEnumValuesWithCustomValues(enumValues) { - if (!enumValues || enumValues === '') { - throw new Error('Enumeration values must be passed to get the formatted values.'); - } - return enumValues.split(',').map(enumValue => { - if (!enumValue.includes('(')) { - return { name: enumValue.trim(), value: enumValue.trim() }; - } - const matched = /\s*(.+?)\s*\((.+?)\)/.exec(enumValue); - return { - name: matched[1], - value: matched[2], - }; - }); -} - -export function fieldToReference(entity, field, pathPrefix = []) { - return { - id: field.id, - entity, - field, - multiple: false, - owned: true, - doc: field.documentation, - get propertyJavadoc() { - return field.fieldJavadoc; - }, - get propertyApiDescription() { - return field.fieldApiDescription; - }, - label: field.fieldNameHumanized, - name: field.fieldName, - type: field.fieldType, - nameCapitalized: field.fieldNameCapitalized, - path: [...pathPrefix, field.fieldName], - }; -} diff --git a/generators/base-application/support/prepare-field.spec.mts b/generators/base-application/support/prepare-field.spec.mts deleted file mode 100644 index 4baab9796031..000000000000 --- a/generators/base-application/support/prepare-field.spec.mts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect } from 'chai'; -import prepareEntityForTemplates, { loadRequiredConfigIntoEntity } from './prepare-entity.mjs'; -import prepareFieldForTemplates, { getEnumValuesWithCustomValues } from './prepare-field.mjs'; -import { formatDateForChangelog } from '../../base/support/index.mjs'; -import BaseGenerator from '../../base/index.mjs'; -import { getConfigWithDefaults } from '../../../jdl/jhipster/index.mjs'; - -const defaultConfig = getConfigWithDefaults(); - -describe('generator - base-application - support - prepareField', () => { - const defaultGenerator = { jhipsterConfig: defaultConfig }; - Object.setPrototypeOf(defaultGenerator, BaseGenerator.prototype); - - const defaultEntity = prepareEntityForTemplates( - loadRequiredConfigIntoEntity({ changelogDate: formatDateForChangelog(new Date()), name: 'Entity' }, defaultConfig), - defaultGenerator, - defaultConfig, - ); - - describe('prepareFieldForTemplates', () => { - describe('when called', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let field: any = { fieldName: 'name', fieldType: 'String' }; - beforeEach(() => { - field = prepareFieldForTemplates(defaultEntity, field, defaultGenerator); - }); - it('should prepare path and relationshipsPath correctly', () => { - expect(field.path).to.deep.eq(['name']); - expect(field.relationshipsPath).to.deep.eq([]); - }); - }); - describe('with dto != mapstruct and @MapstructExpression', () => { - const field = { fieldName: 'name', fieldType: 'String', mapstructExpression: 'java()' }; - it('should fail', () => { - expect(() => prepareFieldForTemplates(defaultEntity, field, defaultGenerator)).to.throw( - /^@MapstructExpression requires an Entity with mapstruct dto \[Entity.name\].$/, - ); - }); - }); - describe('with dto == mapstruct and @MapstructExpression', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let field: any = { fieldName: 'name', fieldType: 'String', mapstructExpression: 'java()' }; - beforeEach(() => { - field = prepareFieldForTemplates({ ...defaultEntity, dto: 'mapstruct' }, field, defaultGenerator); - }); - it('should set field as transient and readonly', () => { - expect(field.transient).to.be.true; - expect(field.readonly).to.be.true; - }); - }); - }); - - describe('getEnumValuesWithCustomValues', () => { - describe('when not passing anything', () => { - it('should fail', () => { - expect(() => getEnumValuesWithCustomValues()).to.throw(/^Enumeration values must be passed to get the formatted values\.$/); - }); - }); - describe('when passing an empty string', () => { - it('should fail', () => { - expect(() => getEnumValuesWithCustomValues('')).to.throw(/^Enumeration values must be passed to get the formatted values\.$/); - }); - }); - describe('when passing a string without custom enum values', () => { - it('should return a formatted list', () => { - expect(getEnumValuesWithCustomValues('FRANCE, ENGLAND, ICELAND')).to.deep.equal([ - { name: 'FRANCE', value: 'FRANCE' }, - { name: 'ENGLAND', value: 'ENGLAND' }, - { name: 'ICELAND', value: 'ICELAND' }, - ]); - }); - }); - describe('when passing a string with some custom enum values', () => { - it('should return a formatted list', () => { - expect(getEnumValuesWithCustomValues('FRANCE(france), ENGLAND, ICELAND (viking_country)')).to.deep.equal([ - { name: 'FRANCE', value: 'france' }, - { name: 'ENGLAND', value: 'ENGLAND' }, - { name: 'ICELAND', value: 'viking_country' }, - ]); - }); - }); - describe('when passing a string custom enum values for each value', () => { - it('should return a formatted list', () => { - expect(getEnumValuesWithCustomValues('FRANCE(france), ENGLAND(england), ICELAND (iceland)')).to.deep.equal([ - { name: 'FRANCE', value: 'france' }, - { name: 'ENGLAND', value: 'england' }, - { name: 'ICELAND', value: 'iceland' }, - ]); - }); - }); - }); -}); diff --git a/generators/base-application/support/prepare-field.spec.ts b/generators/base-application/support/prepare-field.spec.ts new file mode 100644 index 000000000000..8982f30d3155 --- /dev/null +++ b/generators/base-application/support/prepare-field.spec.ts @@ -0,0 +1,111 @@ +/** + * Copyright 2013-2023 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 { expect } from 'chai'; +import prepareEntityForTemplates, { loadRequiredConfigIntoEntity } from './prepare-entity.js'; +import prepareFieldForTemplates, { getEnumValuesWithCustomValues } from './prepare-field.js'; +import { formatDateForChangelog } from '../../base/support/index.js'; +import BaseGenerator from '../../base/index.js'; +import { getConfigWithDefaults } from '../../../jdl/jhipster/index.js'; + +const defaultConfig = getConfigWithDefaults(); + +describe('generator - base-application - support - prepareField', () => { + const defaultGenerator = { jhipsterConfig: defaultConfig }; + Object.setPrototypeOf(defaultGenerator, BaseGenerator.prototype); + + const defaultEntity = prepareEntityForTemplates( + loadRequiredConfigIntoEntity({ changelogDate: formatDateForChangelog(new Date()), name: 'Entity' }, defaultConfig), + defaultGenerator, + defaultConfig, + ); + + describe('prepareFieldForTemplates', () => { + describe('when called', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let field: any = { fieldName: 'name', fieldType: 'String' }; + beforeEach(() => { + field = prepareFieldForTemplates(defaultEntity, field, defaultGenerator); + }); + it('should prepare path and relationshipsPath correctly', () => { + expect(field.path).to.deep.eq(['name']); + expect(field.relationshipsPath).to.deep.eq([]); + }); + }); + describe('with dto != mapstruct and @MapstructExpression', () => { + const field = { fieldName: 'name', fieldType: 'String', mapstructExpression: 'java()' }; + it('should fail', () => { + expect(() => prepareFieldForTemplates(defaultEntity, field, defaultGenerator)).to.throw( + /^@MapstructExpression requires an Entity with mapstruct dto \[Entity.name\].$/, + ); + }); + }); + describe('with dto == mapstruct and @MapstructExpression', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let field: any = { fieldName: 'name', fieldType: 'String', mapstructExpression: 'java()' }; + beforeEach(() => { + field = prepareFieldForTemplates({ ...defaultEntity, dto: 'mapstruct' }, field, defaultGenerator); + }); + it('should set field as transient and readonly', () => { + expect(field.transient).to.be.true; + expect(field.readonly).to.be.true; + }); + }); + }); + + describe('getEnumValuesWithCustomValues', () => { + describe('when not passing anything', () => { + it('should fail', () => { + expect(() => getEnumValuesWithCustomValues()).to.throw(/^Enumeration values must be passed to get the formatted values\.$/); + }); + }); + describe('when passing an empty string', () => { + it('should fail', () => { + expect(() => getEnumValuesWithCustomValues('')).to.throw(/^Enumeration values must be passed to get the formatted values\.$/); + }); + }); + describe('when passing a string without custom enum values', () => { + it('should return a formatted list', () => { + expect(getEnumValuesWithCustomValues('FRANCE, ENGLAND, ICELAND')).to.deep.equal([ + { name: 'FRANCE', value: 'FRANCE' }, + { name: 'ENGLAND', value: 'ENGLAND' }, + { name: 'ICELAND', value: 'ICELAND' }, + ]); + }); + }); + describe('when passing a string with some custom enum values', () => { + it('should return a formatted list', () => { + expect(getEnumValuesWithCustomValues('FRANCE(france), ENGLAND, ICELAND (viking_country)')).to.deep.equal([ + { name: 'FRANCE', value: 'france' }, + { name: 'ENGLAND', value: 'ENGLAND' }, + { name: 'ICELAND', value: 'viking_country' }, + ]); + }); + }); + describe('when passing a string custom enum values for each value', () => { + it('should return a formatted list', () => { + expect(getEnumValuesWithCustomValues('FRANCE(france), ENGLAND(england), ICELAND (iceland)')).to.deep.equal([ + { name: 'FRANCE', value: 'france' }, + { name: 'ENGLAND', value: 'england' }, + { name: 'ICELAND', value: 'iceland' }, + ]); + }); + }); + }); +}); diff --git a/generators/base-application/support/prepare-relationship.js b/generators/base-application/support/prepare-relationship.js new file mode 100644 index 000000000000..cf2a729b898c --- /dev/null +++ b/generators/base-application/support/prepare-relationship.js @@ -0,0 +1,281 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import pluralize from 'pluralize'; + +import { + databaseTypes, + entityOptions, + reservedKeywords, + validations, + checkAndReturnRelationshipOnValue, +} from '../../../jdl/jhipster/index.js'; +import { upperFirstCamelCase } from '../../base/support/string.js'; +import { getJoinTableName, hibernateSnakeCase } from '../../server/support/index.js'; +import { stringifyApplicationData } from './debug.js'; +import { mutateData } from '../../base/support/config.js'; + +const { isReservedTableName } = reservedKeywords; +const { NEO4J, NO: DATABASE_NO } = databaseTypes; +const { MapperTypes } = entityOptions; +const { + Validations: { REQUIRED }, +} = validations; + +const { MAPSTRUCT } = MapperTypes; + +function _defineOnUpdateAndOnDelete(relationship, generator) { + relationship.onDelete = checkAndReturnRelationshipOnValue(relationship.options?.onDelete, generator); + relationship.onUpdate = checkAndReturnRelationshipOnValue(relationship.options?.onUpdate, generator); +} + +export default function prepareRelationship(entityWithConfig, relationship, generator, ignoreMissingRequiredRelationship) { + const entityName = entityWithConfig.name; + const otherEntityName = relationship.otherEntityName; + const jhiTablePrefix = entityWithConfig.jhiTablePrefix || hibernateSnakeCase(entityWithConfig.jhiPrefix); + + if (!relationship.otherEntity) { + throw new Error( + `Error at entity ${entityName}: could not find the entity of the relationship ${stringifyApplicationData(relationship)}`, + ); + } + const otherEntityData = relationship.otherEntity; + if (!relationship.otherEntityField && otherEntityData.primaryKey) { + relationship.otherEntityField = otherEntityData.primaryKey.name; + } + + Object.assign(relationship, { + relationshipLeftSide: relationship.relationshipSide === 'left', + relationshipRightSide: relationship.relationshipSide === 'right', + collection: relationship.relationshipType === 'one-to-many' || relationship.relationshipType === 'many-to-many', + relationshipOneToOne: relationship.relationshipType === 'one-to-one', + relationshipOneToMany: relationship.relationshipType === 'one-to-many', + relationshipManyToOne: relationship.relationshipType === 'many-to-one', + relationshipManyToMany: relationship.relationshipType === 'many-to-many', + otherEntityUser: relationship.otherEntityName === 'user', + }); + + mutateData(relationship, { + // let ownerSide true when type is 'many-to-one' for convenience. + // means that this side should control the reference. + ownerSide: ({ ownerSide, relationshipLeftSide, relationshipManyToOne, relationshipOneToMany }) => + ownerSide ?? (relationshipManyToOne || (relationshipLeftSide && !relationshipOneToMany)), + persistableRelationship: ({ persistableRelationship, ownerSide }) => persistableRelationship ?? ownerSide, + relationshipUpdateBackReference: ({ relationshipUpdateBackReference, ownerSide, relationshipRightSide }) => + relationshipUpdateBackReference ?? (entityWithConfig.databaseType === 'neo4j' ? relationshipRightSide : !ownerSide), + }); + + relationship.otherSideReferenceExists = false; + + relationship.otherEntityIsEmbedded = otherEntityData.embedded; + + // Look for fields at the other other side of the relationship + if (otherEntityData.relationships) { + const otherRelationship = relationship.otherRelationship; + if (otherRelationship) { + relationship.otherSideReferenceExists = true; + _.defaults(relationship, { + otherRelationship, + otherEntityRelationshipName: otherRelationship.relationshipName, + otherEntityRelationshipNamePlural: otherRelationship.relationshipNamePlural, + otherEntityRelationshipNameCapitalized: otherRelationship.relationshipNameCapitalized, + otherEntityRelationshipNameCapitalizedPlural: relationship.relationshipNameCapitalizedPlural, + }); + } else if ( + !ignoreMissingRequiredRelationship && + entityWithConfig.databaseType !== NEO4J && + entityWithConfig.databaseType !== DATABASE_NO && + (relationship.relationshipType === 'one-to-many' || relationship.ownerSide === false) + ) { + if (otherEntityData.builtInUser) { + throw new Error(`Error at entity ${entityName}: relationships with built-in User cannot have back reference`); + } + throw new Error( + `Error at entity ${entityName}: could not find the other side of the relationship ${stringifyApplicationData(relationship)}`, + ); + } else { + generator.debug(`Entity ${entityName}: Could not find the other side of the relationship ${stringifyApplicationData(relationship)}`); + } + } + + relationship.relatedField = otherEntityData.fields.find(field => field.fieldName === relationship.otherEntityField); + if (!relationship.relatedField && otherEntityData.primaryKey && otherEntityData.primaryKey.derived) { + Object.defineProperty(relationship, 'relatedField', { + get() { + return otherEntityData.primaryKey.derivedFields.find(field => field.fieldName === relationship.otherEntityField); + }, + }); + } + if (relationship.relatedField) { + relationship.otherEntityFieldCapitalized = relationship.relatedField.fieldNameCapitalized; + relationship.relatedField.relatedByOtherEntity = true; + } else { + relationship.otherEntityFieldCapitalized = _.upperFirst(relationship.otherEntityField); + } + + if (relationship.otherEntityRelationshipName !== undefined) { + _.defaults(relationship, { + otherEntityRelationshipNamePlural: pluralize(relationship.otherEntityRelationshipName), + otherEntityRelationshipNameCapitalized: _.upperFirst(relationship.otherEntityRelationshipName), + }); + _.defaults(relationship, { + otherEntityRelationshipNameCapitalizedPlural: pluralize(relationship.otherEntityRelationshipNameCapitalized), + }); + } + + const relationshipName = relationship.relationshipName; + _.defaults(relationship, { + relationshipNamePlural: pluralize(relationshipName), + relationshipFieldName: _.lowerFirst(relationshipName), + relationshipNameCapitalized: _.upperFirst(relationshipName), + relationshipNameHumanized: _.startCase(relationshipName), + columnName: hibernateSnakeCase(relationshipName), + columnNamePrefix: relationship.id && relationship.relationshipType === 'one-to-one' ? '' : `${hibernateSnakeCase(relationshipName)}_`, + otherEntityNamePlural: pluralize(otherEntityName), + otherEntityNameCapitalized: _.upperFirst(otherEntityName), + otherEntityTableName: + otherEntityData.entityTableName || + hibernateSnakeCase(otherEntityData.builtInUser ? `${jhiTablePrefix}_${otherEntityName}` : otherEntityName), + }); + + _.defaults(relationship, { + relationshipFieldNamePlural: pluralize(relationship.relationshipFieldName), + relationshipNameCapitalizedPlural: + relationship.relationshipName.length > 1 + ? pluralize(relationship.relationshipNameCapitalized) + : _.upperFirst(pluralize(relationship.relationshipName)), + otherEntityNameCapitalizedPlural: pluralize(relationship.otherEntityNameCapitalized), + }); + + mutateData(relationship, { + propertyName: relationship.collection ? relationship.relationshipFieldNamePlural : relationship.relationshipFieldName, + propertyNameCapitalized: ({ propertyName, propertyNameCapitalized }) => propertyNameCapitalized ?? _.upperFirst(propertyName), + }); + + if (entityWithConfig.dto === MAPSTRUCT) { + if (otherEntityData.dto !== MAPSTRUCT && !otherEntityData.builtInUser) { + generator.log.warn( + `Entity ${entityName}: this entity has the DTO option, and it has a relationship with entity "${otherEntityName}" that doesn't have the DTO option. This will result in an error.`, + ); + } + } + + if (entityWithConfig.prodDatabaseType) { + if (isReservedTableName(relationship.otherEntityTableName, entityWithConfig.prodDatabaseType) && jhiTablePrefix) { + const otherEntityTableName = relationship.otherEntityTableName; + relationship.otherEntityTableName = `${jhiTablePrefix}_${otherEntityTableName}`; + } + } + + if (relationship.otherEntityAngularName === undefined) { + if (otherEntityData.builtInUser) { + relationship.otherEntityAngularName = 'User'; + } else { + const otherEntityAngularSuffix = otherEntityData ? otherEntityData.angularJSSuffix || '' : ''; + relationship.otherEntityAngularName = _.upperFirst(relationship.otherEntityName) + upperFirstCamelCase(otherEntityAngularSuffix); + } + } + + _.defaults(relationship, { + otherEntityStateName: _.kebabCase(relationship.otherEntityAngularName), + jpaMetamodelFiltering: otherEntityData.jpaMetamodelFiltering, + unique: relationship.id || (relationship.ownerSide && relationship.relationshipType === 'one-to-one'), + }); + + if (!otherEntityData.builtInUser) { + _.defaults(relationship, { + otherEntityFileName: _.kebabCase(relationship.otherEntityAngularName), + otherEntityFolderName: _.kebabCase(relationship.otherEntityAngularName), + }); + + const otherEntityClientRootFolder = otherEntityData.clientRootFolder || otherEntityData.microserviceName || ''; + if (entityWithConfig.skipUiGrouping || !otherEntityClientRootFolder) { + relationship.otherEntityClientRootFolder = ''; + } else { + relationship.otherEntityClientRootFolder = `${otherEntityClientRootFolder}/`; + } + if (otherEntityClientRootFolder) { + if (entityWithConfig.clientRootFolder === otherEntityClientRootFolder) { + relationship.otherEntityModulePath = relationship.otherEntityFolderName; + } else { + relationship.otherEntityModulePath = `${ + entityWithConfig.entityParentPathAddition ? `${entityWithConfig.entityParentPathAddition}/` : '' + }${otherEntityClientRootFolder}/${relationship.otherEntityFolderName}`; + } + relationship.otherEntityModelName = `${otherEntityClientRootFolder}/${relationship.otherEntityFileName}`; + relationship.otherEntityPath = `${otherEntityClientRootFolder}/${relationship.otherEntityFolderName}`; + } else { + relationship.otherEntityModulePath = `${ + entityWithConfig.entityParentPathAddition ? `${entityWithConfig.entityParentPathAddition}/` : '' + }${relationship.otherEntityFolderName}`; + relationship.otherEntityModelName = relationship.otherEntityFileName; + relationship.otherEntityPath = relationship.otherEntityFolderName; + } + } + + if (relationship.relationshipValidateRules && relationship.relationshipValidateRules.includes(REQUIRED)) { + if (entityName.toLowerCase() === relationship.otherEntityName.toLowerCase()) { + generator.log.warn(`Error at entity ${entityName}: required relationships to the same entity are not supported.`); + } else { + relationship.relationshipValidate = relationship.relationshipRequired = true; + } + } + relationship.nullable = !(relationship.relationshipValidate === true && relationship.relationshipRequired); + + relationship.shouldWriteJoinTable = relationship.relationshipType === 'many-to-many' && relationship.ownerSide; + if (relationship.shouldWriteJoinTable) { + relationship.joinTable = { + name: getJoinTableName(entityWithConfig.entityTableName, relationship.relationshipName, { + prodDatabaseType: entityWithConfig.prodDatabaseType, + }).value, + }; + } + + relationship.reference = relationshipToReference(entityWithConfig, relationship); + + _defineOnUpdateAndOnDelete(relationship, generator); + + return relationship; +} + +function relationshipToReference(entity, relationship, pathPrefix = []) { + const collection = relationship.collection; + const name = collection ? relationship.relationshipNamePlural : relationship.relationshipName; + const reference = { + id: relationship.id, + entity, + relationship, + owned: relationship.ownerSide, + collection, + doc: relationship.documentation, + get propertyJavadoc() { + return relationship.relationshipJavadoc; + }, + get propertyApiDescription() { + return relationship.relationshipApiDescription; + }, + name, + nameCapitalized: collection ? relationship.relationshipNameCapitalizedPlural : relationship.relationshipNameCapitalized, + get type() { + return relationship.otherEntity.primaryKey ? relationship.otherEntity.primaryKey.type : undefined; + }, + path: [...pathPrefix, name], + }; + return reference; +} diff --git a/generators/base-application/support/prepare-relationship.mjs b/generators/base-application/support/prepare-relationship.mjs deleted file mode 100644 index 07a174797b32..000000000000 --- a/generators/base-application/support/prepare-relationship.mjs +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import pluralize from 'pluralize'; - -import { - databaseTypes, - entityOptions, - reservedKeywords, - validations, - checkAndReturnRelationshipOnValue, -} from '../../../jdl/jhipster/index.mjs'; -import { upperFirstCamelCase } from '../../base/support/string.mjs'; -import { getJoinTableName, hibernateSnakeCase } from '../../server/support/index.mjs'; -import { stringifyApplicationData } from './debug.mjs'; -import { mutateData } from '../../base/support/config.mjs'; - -const { isReservedTableName } = reservedKeywords; -const { NEO4J, NO: DATABASE_NO } = databaseTypes; -const { MapperTypes } = entityOptions; -const { - Validations: { REQUIRED }, -} = validations; - -const { MAPSTRUCT } = MapperTypes; - -function _defineOnUpdateAndOnDelete(relationship, generator) { - relationship.onDelete = checkAndReturnRelationshipOnValue(relationship.options?.onDelete, generator); - relationship.onUpdate = checkAndReturnRelationshipOnValue(relationship.options?.onUpdate, generator); -} - -export default function prepareRelationship(entityWithConfig, relationship, generator, ignoreMissingRequiredRelationship) { - const entityName = entityWithConfig.name; - const otherEntityName = relationship.otherEntityName; - const jhiTablePrefix = entityWithConfig.jhiTablePrefix || hibernateSnakeCase(entityWithConfig.jhiPrefix); - - if (!relationship.otherEntity) { - throw new Error( - `Error at entity ${entityName}: could not find the entity of the relationship ${stringifyApplicationData(relationship)}`, - ); - } - const otherEntityData = relationship.otherEntity; - if (!relationship.otherEntityField && otherEntityData.primaryKey) { - relationship.otherEntityField = otherEntityData.primaryKey.name; - } - - Object.assign(relationship, { - relationshipLeftSide: relationship.relationshipSide === 'left', - relationshipRightSide: relationship.relationshipSide === 'right', - collection: relationship.relationshipType === 'one-to-many' || relationship.relationshipType === 'many-to-many', - relationshipOneToOne: relationship.relationshipType === 'one-to-one', - relationshipOneToMany: relationship.relationshipType === 'one-to-many', - relationshipManyToOne: relationship.relationshipType === 'many-to-one', - relationshipManyToMany: relationship.relationshipType === 'many-to-many', - otherEntityUser: relationship.otherEntityName === 'user', - }); - - mutateData(relationship, { - // let ownerSide true when type is 'many-to-one' for convenience. - // means that this side should control the reference. - ownerSide: ({ ownerSide, relationshipLeftSide, relationshipManyToOne, relationshipOneToMany }) => - ownerSide ?? (relationshipManyToOne || (relationshipLeftSide && !relationshipOneToMany)), - persistableRelationship: ({ persistableRelationship, ownerSide }) => persistableRelationship ?? ownerSide, - relationshipUpdateBackReference: ({ relationshipUpdateBackReference, ownerSide, relationshipRightSide }) => - relationshipUpdateBackReference ?? (entityWithConfig.databaseType === 'neo4j' ? relationshipRightSide : !ownerSide), - }); - - relationship.otherSideReferenceExists = false; - - relationship.otherEntityIsEmbedded = otherEntityData.embedded; - - // Look for fields at the other other side of the relationship - if (otherEntityData.relationships) { - const otherRelationship = relationship.otherRelationship; - if (otherRelationship) { - relationship.otherSideReferenceExists = true; - _.defaults(relationship, { - otherRelationship, - otherEntityRelationshipName: otherRelationship.relationshipName, - otherEntityRelationshipNamePlural: otherRelationship.relationshipNamePlural, - otherEntityRelationshipNameCapitalized: otherRelationship.relationshipNameCapitalized, - otherEntityRelationshipNameCapitalizedPlural: relationship.relationshipNameCapitalizedPlural, - }); - } else if ( - !ignoreMissingRequiredRelationship && - entityWithConfig.databaseType !== NEO4J && - entityWithConfig.databaseType !== DATABASE_NO && - (relationship.relationshipType === 'one-to-many' || relationship.ownerSide === false) - ) { - if (otherEntityData.builtInUser) { - throw new Error(`Error at entity ${entityName}: relationships with built-in User cannot have back reference`); - } - throw new Error( - `Error at entity ${entityName}: could not find the other side of the relationship ${stringifyApplicationData(relationship)}`, - ); - } else { - generator.debug(`Entity ${entityName}: Could not find the other side of the relationship ${stringifyApplicationData(relationship)}`); - } - } - - relationship.relatedField = otherEntityData.fields.find(field => field.fieldName === relationship.otherEntityField); - if (!relationship.relatedField && otherEntityData.primaryKey && otherEntityData.primaryKey.derived) { - Object.defineProperty(relationship, 'relatedField', { - get() { - return otherEntityData.primaryKey.derivedFields.find(field => field.fieldName === relationship.otherEntityField); - }, - }); - } - if (relationship.relatedField) { - relationship.otherEntityFieldCapitalized = relationship.relatedField.fieldNameCapitalized; - relationship.relatedField.relatedByOtherEntity = true; - } else { - relationship.otherEntityFieldCapitalized = _.upperFirst(relationship.otherEntityField); - } - - if (relationship.otherEntityRelationshipName !== undefined) { - _.defaults(relationship, { - otherEntityRelationshipNamePlural: pluralize(relationship.otherEntityRelationshipName), - otherEntityRelationshipNameCapitalized: _.upperFirst(relationship.otherEntityRelationshipName), - }); - _.defaults(relationship, { - otherEntityRelationshipNameCapitalizedPlural: pluralize(relationship.otherEntityRelationshipNameCapitalized), - }); - } - - const relationshipName = relationship.relationshipName; - _.defaults(relationship, { - relationshipNamePlural: pluralize(relationshipName), - relationshipFieldName: _.lowerFirst(relationshipName), - relationshipNameCapitalized: _.upperFirst(relationshipName), - relationshipNameHumanized: _.startCase(relationshipName), - columnName: hibernateSnakeCase(relationshipName), - columnNamePrefix: relationship.id && relationship.relationshipType === 'one-to-one' ? '' : `${hibernateSnakeCase(relationshipName)}_`, - otherEntityNamePlural: pluralize(otherEntityName), - otherEntityNameCapitalized: _.upperFirst(otherEntityName), - otherEntityTableName: - otherEntityData.entityTableName || - hibernateSnakeCase(otherEntityData.builtInUser ? `${jhiTablePrefix}_${otherEntityName}` : otherEntityName), - }); - - _.defaults(relationship, { - relationshipFieldNamePlural: pluralize(relationship.relationshipFieldName), - relationshipNameCapitalizedPlural: - relationship.relationshipName.length > 1 - ? pluralize(relationship.relationshipNameCapitalized) - : _.upperFirst(pluralize(relationship.relationshipName)), - otherEntityNameCapitalizedPlural: pluralize(relationship.otherEntityNameCapitalized), - }); - - mutateData(relationship, { - propertyName: relationship.collection ? relationship.relationshipFieldNamePlural : relationship.relationshipFieldName, - propertyNameCapitalized: ({ propertyName, propertyNameCapitalized }) => propertyNameCapitalized ?? _.upperFirst(propertyName), - }); - - if (entityWithConfig.dto === MAPSTRUCT) { - if (otherEntityData.dto !== MAPSTRUCT && !otherEntityData.builtInUser) { - generator.log.warn( - `Entity ${entityName}: this entity has the DTO option, and it has a relationship with entity "${otherEntityName}" that doesn't have the DTO option. This will result in an error.`, - ); - } - } - - if (entityWithConfig.prodDatabaseType) { - if (isReservedTableName(relationship.otherEntityTableName, entityWithConfig.prodDatabaseType) && jhiTablePrefix) { - const otherEntityTableName = relationship.otherEntityTableName; - relationship.otherEntityTableName = `${jhiTablePrefix}_${otherEntityTableName}`; - } - } - - if (relationship.otherEntityAngularName === undefined) { - if (otherEntityData.builtInUser) { - relationship.otherEntityAngularName = 'User'; - } else { - const otherEntityAngularSuffix = otherEntityData ? otherEntityData.angularJSSuffix || '' : ''; - relationship.otherEntityAngularName = _.upperFirst(relationship.otherEntityName) + upperFirstCamelCase(otherEntityAngularSuffix); - } - } - - _.defaults(relationship, { - otherEntityStateName: _.kebabCase(relationship.otherEntityAngularName), - jpaMetamodelFiltering: otherEntityData.jpaMetamodelFiltering, - unique: relationship.id || (relationship.ownerSide && relationship.relationshipType === 'one-to-one'), - }); - - if (!otherEntityData.builtInUser) { - _.defaults(relationship, { - otherEntityFileName: _.kebabCase(relationship.otherEntityAngularName), - otherEntityFolderName: _.kebabCase(relationship.otherEntityAngularName), - }); - - const otherEntityClientRootFolder = otherEntityData.clientRootFolder || otherEntityData.microserviceName || ''; - if (entityWithConfig.skipUiGrouping || !otherEntityClientRootFolder) { - relationship.otherEntityClientRootFolder = ''; - } else { - relationship.otherEntityClientRootFolder = `${otherEntityClientRootFolder}/`; - } - if (otherEntityClientRootFolder) { - if (entityWithConfig.clientRootFolder === otherEntityClientRootFolder) { - relationship.otherEntityModulePath = relationship.otherEntityFolderName; - } else { - relationship.otherEntityModulePath = `${ - entityWithConfig.entityParentPathAddition ? `${entityWithConfig.entityParentPathAddition}/` : '' - }${otherEntityClientRootFolder}/${relationship.otherEntityFolderName}`; - } - relationship.otherEntityModelName = `${otherEntityClientRootFolder}/${relationship.otherEntityFileName}`; - relationship.otherEntityPath = `${otherEntityClientRootFolder}/${relationship.otherEntityFolderName}`; - } else { - relationship.otherEntityModulePath = `${ - entityWithConfig.entityParentPathAddition ? `${entityWithConfig.entityParentPathAddition}/` : '' - }${relationship.otherEntityFolderName}`; - relationship.otherEntityModelName = relationship.otherEntityFileName; - relationship.otherEntityPath = relationship.otherEntityFolderName; - } - } - - if (relationship.relationshipValidateRules && relationship.relationshipValidateRules.includes(REQUIRED)) { - if (entityName.toLowerCase() === relationship.otherEntityName.toLowerCase()) { - generator.log.warn(`Error at entity ${entityName}: required relationships to the same entity are not supported.`); - } else { - relationship.relationshipValidate = relationship.relationshipRequired = true; - } - } - relationship.nullable = !(relationship.relationshipValidate === true && relationship.relationshipRequired); - - relationship.shouldWriteJoinTable = relationship.relationshipType === 'many-to-many' && relationship.ownerSide; - if (relationship.shouldWriteJoinTable) { - relationship.joinTable = { - name: getJoinTableName(entityWithConfig.entityTableName, relationship.relationshipName, { - prodDatabaseType: entityWithConfig.prodDatabaseType, - }).value, - }; - } - - relationship.reference = relationshipToReference(entityWithConfig, relationship); - - _defineOnUpdateAndOnDelete(relationship, generator); - - return relationship; -} - -function relationshipToReference(entity, relationship, pathPrefix = []) { - const collection = relationship.collection; - const name = collection ? relationship.relationshipNamePlural : relationship.relationshipName; - const reference = { - id: relationship.id, - entity, - relationship, - owned: relationship.ownerSide, - collection, - doc: relationship.documentation, - get propertyJavadoc() { - return relationship.relationshipJavadoc; - }, - get propertyApiDescription() { - return relationship.relationshipApiDescription; - }, - name, - nameCapitalized: collection ? relationship.relationshipNameCapitalizedPlural : relationship.relationshipNameCapitalized, - get type() { - return relationship.otherEntity.primaryKey ? relationship.otherEntity.primaryKey.type : undefined; - }, - path: [...pathPrefix, name], - }; - return reference; -} diff --git a/generators/base-application/support/relationship.mts b/generators/base-application/support/relationship.mts deleted file mode 100644 index 0a0dfe1b5801..000000000000 --- a/generators/base-application/support/relationship.mts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; - -import { Relationship, Entity } from '../../../jdl/converters/types.js'; -import { ValidationResult } from '../../base/api.mjs'; -import { stringifyApplicationData } from './debug.mjs'; -import { findEntityInEntities } from './entity.mjs'; - -const { upperFirst, lowerFirst } = _; - -export const otherRelationshipType = relationshipType => relationshipType.split('-').reverse().join('-'); - -export const findOtherRelationshipInRelationships = (entityName: string, relationship: Relationship, inRelationships: Relationship[]) => { - return inRelationships.find(otherRelationship => { - if (upperFirst(otherRelationship.otherEntityName) !== entityName) { - return false; - } - - if (relationship.otherEntityRelationshipName) { - return relationship.otherEntityRelationshipName === otherRelationship.relationshipName; - } - if (otherRelationship.otherEntityRelationshipName) { - return otherRelationship.otherEntityRelationshipName === relationship.relationshipName; - } - - return false; - }); -}; - -export const loadEntitiesAnnotations = (entities: Entity[]) => { - for (const entity of entities) { - // Load field annotations - for (const field of entity.fields ?? []) { - if (field.options) { - Object.assign(field, field.options); - } - } - - // Load relationships annotations - for (const relationship of entity.relationships ?? []) { - if (relationship.options) { - Object.assign(relationship, relationship.options); - } - } - } -}; - -export const loadEntitiesOtherSide = (entities: Entity[]): ValidationResult => { - const result: { warning: string[] } = { warning: [] }; - for (const entity of entities) { - for (const relationship of entity.relationships ?? []) { - const otherEntity = findEntityInEntities(upperFirst(relationship.otherEntityName), entities); - if (!otherEntity) { - throw new Error(`Error at entity ${entity.name}: could not find the entity ${relationship.otherEntityName}`); - } - otherEntity.otherRelationships = otherEntity.otherRelationships || []; - otherEntity.otherRelationships.push(relationship); - - relationship.otherEntity = otherEntity; - const otherRelationship = findOtherRelationshipInRelationships(entity.name, relationship, otherEntity.relationships ?? []); - if (otherRelationship) { - relationship.otherRelationship = otherRelationship; - otherRelationship.otherEntityRelationshipName = otherRelationship.otherEntityRelationshipName ?? relationship.relationshipName; - relationship.otherEntityRelationshipName = relationship.otherEntityRelationshipName ?? otherRelationship.relationshipName; - if ( - otherRelationship.otherEntityRelationshipName !== relationship.relationshipName || - relationship.otherEntityRelationshipName !== otherRelationship.relationshipName - ) { - throw new Error( - `Error at entity ${entity.name}: relationship name is not synchronized ${stringifyApplicationData( - relationship, - )} with ${stringifyApplicationData(relationship.otherRelationship)}`, - ); - } - if (relationship.relationshipType !== otherRelationshipType(relationship.otherRelationship.relationshipType)) { - throw new Error( - `Error at entity ${entity.name}: relationship type is not synchronized ${stringifyApplicationData( - relationship, - )} with ${stringifyApplicationData(otherRelationship)}`, - ); - } - } - } - } - return result; -}; - -export const addOtherRelationship = (entity: Entity, otherEntity: Entity, relationship: Relationship) => { - relationship.otherEntityRelationshipName = relationship.otherEntityRelationshipName ?? lowerFirst(entity.name); - const otherRelationship: Relationship = { - otherEntity: entity, - otherEntityName: lowerFirst(entity.name), - ownerSide: !relationship.ownerSide, - otherEntityRelationshipName: relationship.relationshipName, - relationshipName: relationship.otherEntityRelationshipName as string, - relationshipType: otherRelationshipType(relationship.relationshipType), - otherRelationship: relationship, - }; - otherEntity.relationships = otherEntity.relationships ?? []; - otherEntity.relationships.push(otherRelationship); - return otherRelationship; -}; diff --git a/generators/base-application/support/relationship.ts b/generators/base-application/support/relationship.ts new file mode 100644 index 000000000000..c2cb8ab88277 --- /dev/null +++ b/generators/base-application/support/relationship.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; + +import { Relationship, Entity } from '../../../jdl/converters/types.js'; +import { ValidationResult } from '../../base/api.js'; +import { stringifyApplicationData } from './debug.js'; +import { findEntityInEntities } from './entity.js'; + +const { upperFirst, lowerFirst } = _; + +export const otherRelationshipType = relationshipType => relationshipType.split('-').reverse().join('-'); + +export const findOtherRelationshipInRelationships = (entityName: string, relationship: Relationship, inRelationships: Relationship[]) => { + return inRelationships.find(otherRelationship => { + if (upperFirst(otherRelationship.otherEntityName) !== entityName) { + return false; + } + + if (relationship.otherEntityRelationshipName) { + return relationship.otherEntityRelationshipName === otherRelationship.relationshipName; + } + if (otherRelationship.otherEntityRelationshipName) { + return otherRelationship.otherEntityRelationshipName === relationship.relationshipName; + } + + return false; + }); +}; + +export const loadEntitiesAnnotations = (entities: Entity[]) => { + for (const entity of entities) { + // Load field annotations + for (const field of entity.fields ?? []) { + if (field.options) { + Object.assign(field, field.options); + } + } + + // Load relationships annotations + for (const relationship of entity.relationships ?? []) { + if (relationship.options) { + Object.assign(relationship, relationship.options); + } + } + } +}; + +export const loadEntitiesOtherSide = (entities: Entity[]): ValidationResult => { + const result: { warning: string[] } = { warning: [] }; + for (const entity of entities) { + for (const relationship of entity.relationships ?? []) { + const otherEntity = findEntityInEntities(upperFirst(relationship.otherEntityName), entities); + if (!otherEntity) { + throw new Error(`Error at entity ${entity.name}: could not find the entity ${relationship.otherEntityName}`); + } + otherEntity.otherRelationships = otherEntity.otherRelationships || []; + otherEntity.otherRelationships.push(relationship); + + relationship.otherEntity = otherEntity; + const otherRelationship = findOtherRelationshipInRelationships(entity.name, relationship, otherEntity.relationships ?? []); + if (otherRelationship) { + relationship.otherRelationship = otherRelationship; + otherRelationship.otherEntityRelationshipName = otherRelationship.otherEntityRelationshipName ?? relationship.relationshipName; + relationship.otherEntityRelationshipName = relationship.otherEntityRelationshipName ?? otherRelationship.relationshipName; + if ( + otherRelationship.otherEntityRelationshipName !== relationship.relationshipName || + relationship.otherEntityRelationshipName !== otherRelationship.relationshipName + ) { + throw new Error( + `Error at entity ${entity.name}: relationship name is not synchronized ${stringifyApplicationData( + relationship, + )} with ${stringifyApplicationData(relationship.otherRelationship)}`, + ); + } + if (relationship.relationshipType !== otherRelationshipType(relationship.otherRelationship.relationshipType)) { + throw new Error( + `Error at entity ${entity.name}: relationship type is not synchronized ${stringifyApplicationData( + relationship, + )} with ${stringifyApplicationData(otherRelationship)}`, + ); + } + } + } + } + return result; +}; + +export const addOtherRelationship = (entity: Entity, otherEntity: Entity, relationship: Relationship) => { + relationship.otherEntityRelationshipName = relationship.otherEntityRelationshipName ?? lowerFirst(entity.name); + const otherRelationship: Relationship = { + otherEntity: entity, + otherEntityName: lowerFirst(entity.name), + ownerSide: !relationship.ownerSide, + otherEntityRelationshipName: relationship.relationshipName, + relationshipName: relationship.otherEntityRelationshipName as string, + relationshipType: otherRelationshipType(relationship.relationshipType), + otherRelationship: relationship, + }; + otherEntity.relationships = otherEntity.relationships ?? []; + otherEntity.relationships.push(otherRelationship); + return otherRelationship; +}; diff --git a/generators/base-application/support/update-application-entities-transform.mts b/generators/base-application/support/update-application-entities-transform.mts deleted file mode 100644 index a05b3b588a2f..000000000000 --- a/generators/base-application/support/update-application-entities-transform.mts +++ /dev/null @@ -1,61 +0,0 @@ -import { readdir } from 'fs/promises'; -import { loadFile } from 'mem-fs'; -import type { MemFsEditorFile } from 'mem-fs-editor'; -import { Minimatch } from 'minimatch'; -import { transform } from 'p-transform'; -import { basename, join } from 'path'; -import { GENERATOR_JHIPSTER } from '../../generator-constants.mjs'; - -export const updateApplicationEntitiesTransform = ({ - destinationPath, - throwOnMissingConfig = true, -}: { - destinationPath: string; - throwOnMissingConfig?: boolean; -}) => { - let yoRcFileInMemory: MemFsEditorFile | undefined; - const entities: string[] = []; - const yoRcFilePath = join(destinationPath, '.yo-rc.json'); - const entitiesMatcher = new Minimatch(`${destinationPath}/.jhipster/*.json`); - - return transform( - file => { - if (file.path === yoRcFilePath) { - yoRcFileInMemory = file; - return undefined; - } - if (entitiesMatcher.match(file.path)) { - entities.push(basename(file.path).replace('.json', '')); - } - return file; - }, - async function () { - try { - entities.push(...(await readdir(join(destinationPath, '.jhipster'))).map(file => file.replace('.json', ''))); - } catch { - // Directory does not exist - } - if (entities.length > 0) { - // The mem-fs instance requires another file instance to emit a change event - const yoRcFile: MemFsEditorFile = loadFile(yoRcFilePath) as any; - // Prefer in-memory file if it exists - const yoRcFileContents = yoRcFileInMemory?.contents ?? yoRcFile.contents; - if (yoRcFileContents) { - const contents = JSON.parse(yoRcFileContents.toString()); - if (contents[GENERATOR_JHIPSTER]) { - contents[GENERATOR_JHIPSTER].entities = [...new Set([...(contents[GENERATOR_JHIPSTER].entities ?? []), ...entities])]; - yoRcFile.contents = Buffer.from(JSON.stringify(contents, null, 2)); - yoRcFileInMemory = yoRcFile; - } else if (throwOnMissingConfig) { - throw new Error(`File ${yoRcFile!.path} is not a valid JHipster configuration file`); - } - } else if (throwOnMissingConfig) { - throw new Error(`File ${yoRcFile!.path} has no contents`); - } - } - if (yoRcFileInMemory) { - this.push(yoRcFileInMemory); - } - }, - ); -}; diff --git a/generators/base-application/support/update-application-entities-transform.ts b/generators/base-application/support/update-application-entities-transform.ts new file mode 100644 index 000000000000..feee4bcbc6c6 --- /dev/null +++ b/generators/base-application/support/update-application-entities-transform.ts @@ -0,0 +1,61 @@ +import { readdir } from 'fs/promises'; +import { loadFile } from 'mem-fs'; +import type { MemFsEditorFile } from 'mem-fs-editor'; +import { Minimatch } from 'minimatch'; +import { transform } from 'p-transform'; +import { basename, join } from 'path'; +import { GENERATOR_JHIPSTER } from '../../generator-constants.js'; + +export const updateApplicationEntitiesTransform = ({ + destinationPath, + throwOnMissingConfig = true, +}: { + destinationPath: string; + throwOnMissingConfig?: boolean; +}) => { + let yoRcFileInMemory: MemFsEditorFile | undefined; + const entities: string[] = []; + const yoRcFilePath = join(destinationPath, '.yo-rc.json'); + const entitiesMatcher = new Minimatch(`${destinationPath}/.jhipster/*.json`); + + return transform( + file => { + if (file.path === yoRcFilePath) { + yoRcFileInMemory = file; + return undefined; + } + if (entitiesMatcher.match(file.path)) { + entities.push(basename(file.path).replace('.json', '')); + } + return file; + }, + async function () { + try { + entities.push(...(await readdir(join(destinationPath, '.jhipster'))).map(file => file.replace('.json', ''))); + } catch { + // Directory does not exist + } + if (entities.length > 0) { + // The mem-fs instance requires another file instance to emit a change event + const yoRcFile: MemFsEditorFile = loadFile(yoRcFilePath) as any; + // Prefer in-memory file if it exists + const yoRcFileContents = yoRcFileInMemory?.contents ?? yoRcFile.contents; + if (yoRcFileContents) { + const contents = JSON.parse(yoRcFileContents.toString()); + if (contents[GENERATOR_JHIPSTER]) { + contents[GENERATOR_JHIPSTER].entities = [...new Set([...(contents[GENERATOR_JHIPSTER].entities ?? []), ...entities])]; + yoRcFile.contents = Buffer.from(JSON.stringify(contents, null, 2)); + yoRcFileInMemory = yoRcFile; + } else if (throwOnMissingConfig) { + throw new Error(`File ${yoRcFile!.path} is not a valid JHipster configuration file`); + } + } else if (throwOnMissingConfig) { + throw new Error(`File ${yoRcFile!.path} has no contents`); + } + } + if (yoRcFileInMemory) { + this.push(yoRcFileInMemory); + } + }, + ); +}; diff --git a/generators/base-application/tasks.d.mts b/generators/base-application/tasks.d.mts deleted file mode 100644 index b244c8645b97..000000000000 --- a/generators/base-application/tasks.d.mts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Storage } from 'yeoman-generator'; -import { ControlTaskParam, BaseGeneratorDefinition, SourceTaskParam, GenericSourceTypeDefinition } from '../base/tasks.mjs'; -import { CommonClientServerApplication } from './types.mjs'; -import { Entity, Field, Relationship } from './types/index.mjs'; -import { ClientSourceType } from '../client/types.mjs'; -import { BaseChangelog } from '../base-entity-changes/types.js'; - -export type GenericApplicationDefinition = { - applicationType: ApplicationType; - entityType: Entity; -}; - -type ConfiguringEachEntityTaskParam = { - entityName: string; - /** Entity storage */ - entityStorage: Storage; - /** Proxy object for the entitystorage */ - entityConfig: Record; -}; - -type LoadingEntitiesTaskParam = { - entitiesToLoad: { - entityName: string; - /** Entity storage */ - entityStorage: Storage; - /** Proxy object for the entitystorage */ - entityConfig: Record; - }[]; -}; - -type ApplicationTaskParam = { - application: Definition['applicationType'] & { user: Definition['entityType'] }; -}; - -type ApplicationDefaultsTaskParam = { - /** - * Parameter properties accepts: - * - functions: receives the application and the return value is set at the application property. - * - non functions: application property will receive the property in case current value is undefined. - * - * Applies each object in order. - * - * @example - * // application = { prop: 'foo-bar', prop2: 'foo2' } - * applicationDefaults( - * application, - * { prop: 'foo', prop2: ({ prop }) => prop + 2 }, - * { prop: ({ prop }) => prop + '-bar', prop2: 'won\'t override' }, - * ); - */ - applicationDefaults: (...defaults: Record[]) => void; -}; - -export type EntitiesTaskParam = { - entities: Definition['entityType'][]; -}; - -type EachEntityTaskParam = { - entity: Definition['entityType']; - entityName: string; - description: string; -}; - -type PreparingEachEntityFieldTaskParam = - EachEntityTaskParam & { - field: Field; - fieldName: string; - }; - -type PreparingEachEntityRelationshipTaskParam = - EachEntityTaskParam & { - relationship: Relationship; - relationshipName: string; - }; - -type ClientSource = { - addEntitiesToClient?: (param: ControlTaskParam & { source: ExtendsSelf } & EntitiesTaskParam) => any; -}; - -export type BaseApplicationGeneratorDefinition< - Definition extends { applicationType: any; entityType: any; sourceType: any } = GenericApplicationDefinition & - GenericSourceTypeDefinition any>>, -> = BaseGeneratorDefinition & - // Add application to existing priorities - Record<'loadingTaskParam' | 'preparingTaskParam', ApplicationTaskParam & ApplicationDefaultsTaskParam> & - Record< - | 'postPreparingTaskParam' - | 'defaultTaskParam' - | 'postWritingTaskParam' - | 'preConflictsTaskParam' - | 'installTaskParam' - | 'postInstallTaskParam' - | 'endTaskParam', - ApplicationTaskParam - > & - Record<'writingTaskParam', ApplicationTaskParam & { configChanges?: Record }> & - // Add entities to existing priorities - Record<'defaultTaskParam', EntitiesTaskParam & { entityChanges?: BaseChangelog[] }> & - // Add application and control to new priorities - Record< - | 'configuringEachEntityTaskParam' - | 'loadingEntitiesTaskParam' - | 'preparingEachEntityTaskParam' - | 'preparingEachEntityFieldTaskParam' - | 'preparingEachEntityRelationshipTaskParam' - | 'postPreparingEachEntityTaskParam' - | 'writingEntitiesTaskParam' - | 'postWritingEntitiesTaskParam', - ControlTaskParam & ApplicationTaskParam - > & { - // Add additional types to each priority - applicationType: Definition['applicationType']; - configuringEachEntityTaskParam: ConfiguringEachEntityTaskParam; - loadingEntitiesTaskParam: LoadingEntitiesTaskParam; - preparingEachEntityTaskParam: EachEntityTaskParam; - preparingEachEntityFieldTaskParam: PreparingEachEntityFieldTaskParam; - preparingEachEntityRelationshipTaskParam: PreparingEachEntityRelationshipTaskParam; - postPreparingEachEntityTaskParam: EachEntityTaskParam; - writingEntitiesTaskParam: EntitiesTaskParam & { entityChanges?: BaseChangelog[] }; - postWritingEntitiesTaskParam: SourceTaskParam & EntitiesTaskParam & { entityChanges?: BaseChangelog[] }; - }; diff --git a/generators/base-application/tasks.d.ts b/generators/base-application/tasks.d.ts new file mode 100644 index 000000000000..4c52080b25f1 --- /dev/null +++ b/generators/base-application/tasks.d.ts @@ -0,0 +1,121 @@ +import type { Storage } from 'yeoman-generator'; +import { ControlTaskParam, BaseGeneratorDefinition, SourceTaskParam, GenericSourceTypeDefinition } from '../base/tasks.js'; +import { CommonClientServerApplication } from './types.js'; +import { Entity, Field, Relationship } from './types/index.js'; +import { ClientSourceType } from '../client/types.js'; +import { BaseChangelog } from '../base-entity-changes/types.js'; + +export type GenericApplicationDefinition = { + applicationType: ApplicationType; + entityType: Entity; +}; + +type ConfiguringEachEntityTaskParam = { + entityName: string; + /** Entity storage */ + entityStorage: Storage; + /** Proxy object for the entitystorage */ + entityConfig: Record; +}; + +type LoadingEntitiesTaskParam = { + entitiesToLoad: { + entityName: string; + /** Entity storage */ + entityStorage: Storage; + /** Proxy object for the entitystorage */ + entityConfig: Record; + }[]; +}; + +type ApplicationTaskParam = { + application: Definition['applicationType'] & { user: Definition['entityType'] }; +}; + +type ApplicationDefaultsTaskParam = { + /** + * Parameter properties accepts: + * - functions: receives the application and the return value is set at the application property. + * - non functions: application property will receive the property in case current value is undefined. + * + * Applies each object in order. + * + * @example + * // application = { prop: 'foo-bar', prop2: 'foo2' } + * applicationDefaults( + * application, + * { prop: 'foo', prop2: ({ prop }) => prop + 2 }, + * { prop: ({ prop }) => prop + '-bar', prop2: 'won\'t override' }, + * ); + */ + applicationDefaults: (...defaults: Record[]) => void; +}; + +export type EntitiesTaskParam = { + entities: Definition['entityType'][]; +}; + +type EachEntityTaskParam = { + entity: Definition['entityType']; + entityName: string; + description: string; +}; + +type PreparingEachEntityFieldTaskParam = + EachEntityTaskParam & { + field: Field; + fieldName: string; + }; + +type PreparingEachEntityRelationshipTaskParam = + EachEntityTaskParam & { + relationship: Relationship; + relationshipName: string; + }; + +type ClientSource = { + addEntitiesToClient?: (param: ControlTaskParam & { source: ExtendsSelf } & EntitiesTaskParam) => any; +}; + +export type BaseApplicationGeneratorDefinition< + Definition extends { applicationType: any; entityType: any; sourceType: any } = GenericApplicationDefinition & + GenericSourceTypeDefinition any>>, +> = BaseGeneratorDefinition & + // Add application to existing priorities + Record<'loadingTaskParam' | 'preparingTaskParam', ApplicationTaskParam & ApplicationDefaultsTaskParam> & + Record< + | 'postPreparingTaskParam' + | 'defaultTaskParam' + | 'postWritingTaskParam' + | 'preConflictsTaskParam' + | 'installTaskParam' + | 'postInstallTaskParam' + | 'endTaskParam', + ApplicationTaskParam + > & + Record<'writingTaskParam', ApplicationTaskParam & { configChanges?: Record }> & + // Add entities to existing priorities + Record<'defaultTaskParam', EntitiesTaskParam & { entityChanges?: BaseChangelog[] }> & + // Add application and control to new priorities + Record< + | 'configuringEachEntityTaskParam' + | 'loadingEntitiesTaskParam' + | 'preparingEachEntityTaskParam' + | 'preparingEachEntityFieldTaskParam' + | 'preparingEachEntityRelationshipTaskParam' + | 'postPreparingEachEntityTaskParam' + | 'writingEntitiesTaskParam' + | 'postWritingEntitiesTaskParam', + ControlTaskParam & ApplicationTaskParam + > & { + // Add additional types to each priority + applicationType: Definition['applicationType']; + configuringEachEntityTaskParam: ConfiguringEachEntityTaskParam; + loadingEntitiesTaskParam: LoadingEntitiesTaskParam; + preparingEachEntityTaskParam: EachEntityTaskParam; + preparingEachEntityFieldTaskParam: PreparingEachEntityFieldTaskParam; + preparingEachEntityRelationshipTaskParam: PreparingEachEntityRelationshipTaskParam; + postPreparingEachEntityTaskParam: EachEntityTaskParam; + writingEntitiesTaskParam: EntitiesTaskParam & { entityChanges?: BaseChangelog[] }; + postWritingEntitiesTaskParam: SourceTaskParam & EntitiesTaskParam & { entityChanges?: BaseChangelog[] }; + }; diff --git a/generators/base-application/types-export.d.ts b/generators/base-application/types-export.d.ts index 01b1ac79e98a..6b4e078dd450 100644 --- a/generators/base-application/types-export.d.ts +++ b/generators/base-application/types-export.d.ts @@ -1,4 +1,4 @@ -import Generator from './index.mjs'; +import Generator from './index.js'; // Remove generics support declare class GeneratorBaseApplication extends Generator {} diff --git a/generators/base-application/types.d.mts b/generators/base-application/types.d.mts deleted file mode 100644 index d79a8549befb..000000000000 --- a/generators/base-application/types.d.mts +++ /dev/null @@ -1,122 +0,0 @@ -import { ClientApplication } from '../client/types.mjs'; -import { I18nApplication } from '../languages/types.mjs'; -import { SpringBootApplication } from '../server/types.mjs'; -import { DeterministicOptionWithDerivedProperties, OptionWithDerivedProperties } from './application-options.mjs'; - -export type BaseApplication = { - jhipsterVersion: string; - baseName: string; - capitalizedBaseName: string; - dasherizedBaseName: string; - humanizedBaseName: string; - camelizedBaseName: string; - hipster: string; - lowercaseBaseName: string; - upperFirstCamelCaseBaseName: string; - documentationArchiveUrl: string; - - projectVersion: string; - projectDescription: string; - - jhiPrefix: string; - entitySuffix: string; - dtoSuffix: string; - - skipCommitHook: boolean; - skipJhipsterDependencies: boolean; - fakerSeed?: string; - - nodeVersion: string; - nodePackageManager: string; - nodeDependencies: Record; - - skipClient?: boolean; - skipServer?: boolean; -} & I18nApplication; - -/* ApplicationType Start */ -type MicroservicesArchitectureApplication = { - microfrontend: boolean; - gatewayServerPort: number; -}; - -type GatewayApplication = MicroservicesArchitectureApplication & { - microfrontends: string[]; -}; - -type ApplicationType = DeterministicOptionWithDerivedProperties< - 'applicationType', - ['monolith', 'gateway', 'microservice'], - [Record, GatewayApplication, MicroservicesArchitectureApplication] ->; - -/* ApplicationType End */ - -/* AuthenticationType Start */ -type UserManagement = - | { - skipUserManagement: true; - } - | { - skipUserManagement: false; - user: any; - }; - -type JwtApplication = UserManagement & { - jwtSecretKey: string; -}; - -type Oauth2Application = { - jwtSecretKey: string; - user: any; -}; - -type SessionApplication = UserManagement & { - rememberMeKey: string; -}; - -type AuthenticationType = DeterministicOptionWithDerivedProperties< - 'authenticationType', - ['jwt', 'oauth2', 'session'], - [JwtApplication, Oauth2Application, SessionApplication] ->; - -/* AuthenticationType End */ - -type QuirksApplication = { - cypressBootstrapEntities?: boolean; -}; - -export type CommonClientServerApplication = BaseApplication & - QuirksApplication & - AuthenticationType & - SpringBootApplication & - ClientApplication & - ApplicationType & { - clientRootDir: string; - clientSrcDir: string; - clientTestDir?: string; - clientDistDir?: string; - devServerPort: number; - pages: string[]; - - serverPort: number; - backendType?: string; - backendTypeJavaAny?: boolean; - backendTypeSpringBoot?: boolean; - temporaryDir?: string; - - dockerServicesDir?: string; - dockerServices?: string[]; - prettierExtensions?: string; - - generateUserManagement?: boolean; - generateBuiltInUserEntity?: boolean; - generateBuiltInAuthorityEntity?: boolean; - }; - -type ServiceDiscoveryApplication = OptionWithDerivedProperties<'serviceDiscoveryType', ['no', 'eureka', 'consul']>; - -type MonitoringApplication = OptionWithDerivedProperties<'monitoring', ['no', 'elk', 'prometheus']>; - -export type PlatformApplication = ServiceDiscoveryApplication & MonitoringApplication; diff --git a/generators/base-application/types.d.ts b/generators/base-application/types.d.ts new file mode 100644 index 000000000000..af752b5f471b --- /dev/null +++ b/generators/base-application/types.d.ts @@ -0,0 +1,122 @@ +import { ClientApplication } from '../client/types.js'; +import { I18nApplication } from '../languages/types.js'; +import { SpringBootApplication } from '../server/types.js'; +import { DeterministicOptionWithDerivedProperties, OptionWithDerivedProperties } from './application-options.js'; + +export type BaseApplication = { + jhipsterVersion: string; + baseName: string; + capitalizedBaseName: string; + dasherizedBaseName: string; + humanizedBaseName: string; + camelizedBaseName: string; + hipster: string; + lowercaseBaseName: string; + upperFirstCamelCaseBaseName: string; + documentationArchiveUrl: string; + + projectVersion: string; + projectDescription: string; + + jhiPrefix: string; + entitySuffix: string; + dtoSuffix: string; + + skipCommitHook: boolean; + skipJhipsterDependencies: boolean; + fakerSeed?: string; + + nodeVersion: string; + nodePackageManager: string; + nodeDependencies: Record; + + skipClient?: boolean; + skipServer?: boolean; +} & I18nApplication; + +/* ApplicationType Start */ +type MicroservicesArchitectureApplication = { + microfrontend: boolean; + gatewayServerPort: number; +}; + +type GatewayApplication = MicroservicesArchitectureApplication & { + microfrontends: string[]; +}; + +type ApplicationType = DeterministicOptionWithDerivedProperties< + 'applicationType', + ['monolith', 'gateway', 'microservice'], + [Record, GatewayApplication, MicroservicesArchitectureApplication] +>; + +/* ApplicationType End */ + +/* AuthenticationType Start */ +type UserManagement = + | { + skipUserManagement: true; + } + | { + skipUserManagement: false; + user: any; + }; + +type JwtApplication = UserManagement & { + jwtSecretKey: string; +}; + +type Oauth2Application = { + jwtSecretKey: string; + user: any; +}; + +type SessionApplication = UserManagement & { + rememberMeKey: string; +}; + +type AuthenticationType = DeterministicOptionWithDerivedProperties< + 'authenticationType', + ['jwt', 'oauth2', 'session'], + [JwtApplication, Oauth2Application, SessionApplication] +>; + +/* AuthenticationType End */ + +type QuirksApplication = { + cypressBootstrapEntities?: boolean; +}; + +export type CommonClientServerApplication = BaseApplication & + QuirksApplication & + AuthenticationType & + SpringBootApplication & + ClientApplication & + ApplicationType & { + clientRootDir: string; + clientSrcDir: string; + clientTestDir?: string; + clientDistDir?: string; + devServerPort: number; + pages: string[]; + + serverPort: number; + backendType?: string; + backendTypeJavaAny?: boolean; + backendTypeSpringBoot?: boolean; + temporaryDir?: string; + + dockerServicesDir?: string; + dockerServices?: string[]; + prettierExtensions?: string; + + generateUserManagement?: boolean; + generateBuiltInUserEntity?: boolean; + generateBuiltInAuthorityEntity?: boolean; + }; + +type ServiceDiscoveryApplication = OptionWithDerivedProperties<'serviceDiscoveryType', ['no', 'eureka', 'consul']>; + +type MonitoringApplication = OptionWithDerivedProperties<'monitoring', ['no', 'elk', 'prometheus']>; + +export type PlatformApplication = ServiceDiscoveryApplication & MonitoringApplication; diff --git a/generators/base-application/types/entity.d.mts b/generators/base-application/types/entity.d.mts deleted file mode 100644 index eccd58990c78..000000000000 --- a/generators/base-application/types/entity.d.mts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2013-2023 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 Field from './field.mjs'; -import Relationship from './relationship.mjs'; - -export type BaseEntity = { - name: string; - changelogDate?: string; - dto?: string; - - primaryKey?: Record; - fields?: Field[]; - relationships?: Relationship[]; - - readOnly?: boolean; - embedded?: boolean; - skipClient?: boolean; - skipServer?: boolean; -}; - -type Entity = Required & { - builtIn?: boolean; - microserviceName?: string; - - entityNameCapitalized: string; - entityClass: string; - entityInstance: string; - entityTableName: string; - entityNamePlural: string; - - dtoClass?: string; - dtoInstance?: string; - - persistClass: string; - persistInstance: string; - restClass: string; - restInstance: string; - - entityNamePluralizedAndSpinalCased: string; - entityClassPlural: string; - entityInstancePlural: string; - - entityI18nVariant: string; - entityClassHumanized: string; - entityClassPluralHumanized: string; - - entityFileName: string; - entityFolderName: string; - entityModelFileName: string; - entityParentPathAddition: string; - entityPluralFileName: string; - entityServiceFileName: string; - - entityAngularName: string; - entityAngularNamePlural: string; - entityReactName: string; - - entityApiUrl: string; - entityStateName: string; - entityUrl: string; - - entityTranslationKey: string; - entityTranslationKeyMenu: string; - - i18nKeyPrefix: string; - i18nAlertHeaderPrefix: string; - - entityApi: string; - entityPage: string; - - anyFieldIsBigDecimal: boolean; - /** - * Any file is of type Bytes or ByteBuffer - */ - anyFieldIsBlobDerived: boolean; - /** - * Any field is of type ZonedDateTime, Instant or LocalDate - */ - anyFieldIsDateDerived: boolean; - anyFieldIsDuration: boolean; - anyFieldIsInstant: boolean; - anyFieldIsLocalDate: boolean; - /** - * Any field is of type ZonedDateTime or Instant - */ - anyFieldIsTimeDerived: boolean; - anyFieldIsUUID: boolean; - anyFieldIsZonedDateTime: boolean; - - anyFieldHasDocumentation: boolean; - anyFieldHasImageContentType: boolean; - anyFieldHasTextContentType: boolean; - /** - * Any field has image or any contentType - */ - anyFieldHasFileBasedContentType: boolean; -}; - -export default Entity; diff --git a/generators/base-application/types/entity.d.ts b/generators/base-application/types/entity.d.ts new file mode 100644 index 000000000000..387094b80851 --- /dev/null +++ b/generators/base-application/types/entity.d.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2013-2023 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 Field from './field.js'; +import Relationship from './relationship.js'; + +export type BaseEntity = { + name: string; + changelogDate?: string; + dto?: string; + + primaryKey?: Record; + fields?: Field[]; + relationships?: Relationship[]; + + readOnly?: boolean; + embedded?: boolean; + skipClient?: boolean; + skipServer?: boolean; +}; + +type Entity = Required & { + builtIn?: boolean; + microserviceName?: string; + + entityNameCapitalized: string; + entityClass: string; + entityInstance: string; + entityTableName: string; + entityNamePlural: string; + + dtoClass?: string; + dtoInstance?: string; + + persistClass: string; + persistInstance: string; + restClass: string; + restInstance: string; + + entityNamePluralizedAndSpinalCased: string; + entityClassPlural: string; + entityInstancePlural: string; + + entityI18nVariant: string; + entityClassHumanized: string; + entityClassPluralHumanized: string; + + entityFileName: string; + entityFolderName: string; + entityModelFileName: string; + entityParentPathAddition: string; + entityPluralFileName: string; + entityServiceFileName: string; + + entityAngularName: string; + entityAngularNamePlural: string; + entityReactName: string; + + entityApiUrl: string; + entityStateName: string; + entityUrl: string; + + entityTranslationKey: string; + entityTranslationKeyMenu: string; + + i18nKeyPrefix: string; + i18nAlertHeaderPrefix: string; + + entityApi: string; + entityPage: string; + + anyFieldIsBigDecimal: boolean; + /** + * Any file is of type Bytes or ByteBuffer + */ + anyFieldIsBlobDerived: boolean; + /** + * Any field is of type ZonedDateTime, Instant or LocalDate + */ + anyFieldIsDateDerived: boolean; + anyFieldIsDuration: boolean; + anyFieldIsInstant: boolean; + anyFieldIsLocalDate: boolean; + /** + * Any field is of type ZonedDateTime or Instant + */ + anyFieldIsTimeDerived: boolean; + anyFieldIsUUID: boolean; + anyFieldIsZonedDateTime: boolean; + + anyFieldHasDocumentation: boolean; + anyFieldHasImageContentType: boolean; + anyFieldHasTextContentType: boolean; + /** + * Any field has image or any contentType + */ + anyFieldHasFileBasedContentType: boolean; +}; + +export default Entity; diff --git a/generators/base-application/types/field.d.mts b/generators/base-application/types/field.d.ts similarity index 100% rename from generators/base-application/types/field.d.mts rename to generators/base-application/types/field.d.ts diff --git a/generators/base-application/types/index.d.mts b/generators/base-application/types/index.d.mts deleted file mode 100644 index 3c8dd7dbc1c8..000000000000 --- a/generators/base-application/types/index.d.mts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2023 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 type { default as Entity, BaseEntity } from './entity.mjs'; -export type { default as Field } from './field.mjs'; -export type { default as Relationship } from './relationship.mjs'; diff --git a/generators/base-application/types/index.d.ts b/generators/base-application/types/index.d.ts new file mode 100644 index 000000000000..845c62fc8299 --- /dev/null +++ b/generators/base-application/types/index.d.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2013-2023 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 type { default as Entity, BaseEntity } from './entity.js'; +export type { default as Field } from './field.js'; +export type { default as Relationship } from './relationship.js'; diff --git a/generators/base-application/types/relationship.d.mts b/generators/base-application/types/relationship.d.ts similarity index 100% rename from generators/base-application/types/relationship.d.mts rename to generators/base-application/types/relationship.d.ts diff --git a/generators/base-core/generator-core.spec.mts b/generators/base-core/generator-core.spec.mts deleted file mode 100644 index 871166667774..000000000000 --- a/generators/base-core/generator-core.spec.mts +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import { jestExpect } from 'esmocha'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; - -import Base from './index.mjs'; -import { createJHipsterLogger } from '../base/support/logger.mjs'; - -const BaseGenerator: any = Base.prototype; - -BaseGenerator.log = msg => { - // eslint-disable-next-line no-console - console.log(msg); -}; - -BaseGenerator.logger = createJHipsterLogger(); - -describe('generator - base-core', () => { - describe('passing arguments', () => { - let Dummy; - beforeEach(async () => { - await helpers.prepareTemporaryDir(); - Dummy = helpers.createDummyGenerator(Base); - }); - - it('no argument', async () => { - const base = new Dummy([], { sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - jdlFiles: { - type: String, - }, - }); - jestExpect(base.jdlFiles).toBe(undefined); - }); - it('undefined positional arguments', async () => { - const base = new Dummy({ positionalArguments: [], sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - jdlFiles: { - type: String, - }, - }); - jestExpect(base.jdlFiles).toBe(undefined); - }); - it('undefined argument', async () => { - const base = new Dummy([undefined], { sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - jdlFiles: { - type: String, - }, - }); - jestExpect(base.jdlFiles).toBe(undefined); - }); - it('undefined positional arguments', async () => { - const base = new Dummy({ positionalArguments: [undefined], sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - jdlFiles: { - type: String, - }, - }); - jestExpect(base.jdlFiles).toBe(undefined); - }); - it('string arguments', async () => { - const base = new Dummy(['foo'], { sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - jdlFiles: { - type: String, - }, - }); - jestExpect(base.jdlFiles).toBe('foo'); - }); - it('vararg arguments', async () => { - const base = new Dummy(['bar', 'foo'], { sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - first: { - type: String, - }, - jdlFiles: { - type: Array, - }, - }); - jestExpect(base.first).toBe('bar'); - jestExpect(base.jdlFiles).toMatchObject(['foo']); - }); - it('vararg arguments using positionalArguments', async () => { - const base = new Dummy({ positionalArguments: ['bar', ['foo']], sharedData: {}, env: await helpers.createTestEnv() }); - base.parseJHipsterArguments({ - first: { - type: String, - }, - jdlFiles: { - type: Array, - }, - }); - jestExpect(base.first).toBe('bar'); - jestExpect(base.jdlFiles).toMatchObject(['foo']); - }); - }); -}); diff --git a/generators/base-core/generator-core.spec.ts b/generators/base-core/generator-core.spec.ts new file mode 100644 index 000000000000..c493c62c2085 --- /dev/null +++ b/generators/base-core/generator-core.spec.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-unused-expressions */ +import { jestExpect } from 'esmocha'; +import { basicHelpers as helpers } from '../../test/support/index.js'; + +import Base from './index.js'; +import { createJHipsterLogger } from '../base/support/logger.js'; + +const BaseGenerator: any = Base.prototype; + +BaseGenerator.log = msg => { + // eslint-disable-next-line no-console + console.log(msg); +}; + +BaseGenerator.logger = createJHipsterLogger(); + +describe('generator - base-core', () => { + describe('passing arguments', () => { + let Dummy; + beforeEach(async () => { + await helpers.prepareTemporaryDir(); + Dummy = helpers.createDummyGenerator(Base); + }); + + it('no argument', async () => { + const base = new Dummy([], { sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + jdlFiles: { + type: String, + }, + }); + jestExpect(base.jdlFiles).toBe(undefined); + }); + it('undefined positional arguments', async () => { + const base = new Dummy({ positionalArguments: [], sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + jdlFiles: { + type: String, + }, + }); + jestExpect(base.jdlFiles).toBe(undefined); + }); + it('undefined argument', async () => { + const base = new Dummy([undefined], { sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + jdlFiles: { + type: String, + }, + }); + jestExpect(base.jdlFiles).toBe(undefined); + }); + it('undefined positional arguments', async () => { + const base = new Dummy({ positionalArguments: [undefined], sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + jdlFiles: { + type: String, + }, + }); + jestExpect(base.jdlFiles).toBe(undefined); + }); + it('string arguments', async () => { + const base = new Dummy(['foo'], { sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + jdlFiles: { + type: String, + }, + }); + jestExpect(base.jdlFiles).toBe('foo'); + }); + it('vararg arguments', async () => { + const base = new Dummy(['bar', 'foo'], { sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + first: { + type: String, + }, + jdlFiles: { + type: Array, + }, + }); + jestExpect(base.first).toBe('bar'); + jestExpect(base.jdlFiles).toMatchObject(['foo']); + }); + it('vararg arguments using positionalArguments', async () => { + const base = new Dummy({ positionalArguments: ['bar', ['foo']], sharedData: {}, env: await helpers.createTestEnv() }); + base.parseJHipsterArguments({ + first: { + type: String, + }, + jdlFiles: { + type: Array, + }, + }); + jestExpect(base.first).toBe('bar'); + jestExpect(base.jdlFiles).toMatchObject(['foo']); + }); + }); +}); diff --git a/generators/base-core/generator.mts b/generators/base-core/generator.mts deleted file mode 100644 index 7c89909e4537..000000000000 --- a/generators/base-core/generator.mts +++ /dev/null @@ -1,1112 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, join as joinPath, dirname, relative, isAbsolute, join, extname } from 'path'; -import { relative as posixRelative } from 'path/posix'; -import { createHash } from 'crypto'; -import { fileURLToPath } from 'url'; -import { statSync, rmSync, existsSync, readFileSync } from 'fs'; -import assert from 'assert'; -import { requireNamespace } from '@yeoman/namespace'; -import chalk from 'chalk'; -import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; -import * as _ from 'lodash-es'; -import { simpleGit } from 'simple-git'; -import type { CopyOptions } from 'mem-fs-editor'; -import type { Data as TemplateData, Options as TemplateOptions } from 'ejs'; -import semver, { lt as semverLessThan } from 'semver'; -import YeomanGenerator, { type ComposeOptions, type Storage } from 'yeoman-generator'; -import type Environment from 'yeoman-environment'; -import latestVersion from 'latest-version'; -import SharedData from '../base/shared-data.mjs'; -import { CUSTOM_PRIORITIES, PRIORITY_NAMES, PRIORITY_PREFIX } from '../base/priorities.mjs'; -import { createJHipster7Context, formatDateForChangelog, joinCallbacks, Logger } from '../base/support/index.mjs'; - -import type { - JHipsterGeneratorOptions, - JHipsterGeneratorFeatures, - EditFileCallback, - EditFileOptions, - CascatedEditFileCallback, - JHipsterOptions, - ValidationResult, - WriteFileOptions, - JHipsterArguments, - JHipsterConfigs, - JHipsterCommandDefinition, -} from '../base/api.mjs'; -import { packageJson } from '../../lib/index.mjs'; -import { CommonClientServerApplication, type BaseApplication } from '../base-application/types.mjs'; -import { GENERATOR_BOOTSTRAP } from '../generator-list.mjs'; -import NeedleApi from '../needle-api.mjs'; -import command from '../base/command.mjs'; -import { GENERATOR_JHIPSTER, YO_RC_FILE } from '../generator-constants.mjs'; -import { convertConfigToOption } from '../../lib/internal/index.mjs'; - -const { merge, get, set } = _; -const { - INITIALIZING, - PROMPTING, - CONFIGURING, - COMPOSING, - LOADING, - PREPARING, - POST_PREPARING, - DEFAULT, - WRITING, - POST_WRITING, - INSTALL, - POST_INSTALL, - END, -} = PRIORITY_NAMES; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const asPriority = (priorityName: string) => `${PRIORITY_PREFIX}${priorityName}`; - -const relativeDir = (from: string, to: string) => { - const rel = posixRelative(from, to); - return rel ? `${rel}/` : ''; -}; - -/** - * This is the base class for a generator for every generator. - */ -export default class CoreGenerator extends YeomanGenerator { - static asPriority = asPriority; - - static INITIALIZING = asPriority(INITIALIZING); - - static PROMPTING = asPriority(PROMPTING); - - static CONFIGURING = asPriority(CONFIGURING); - - static COMPOSING = asPriority(COMPOSING); - - static LOADING = asPriority(LOADING); - - static PREPARING = asPriority(PREPARING); - - static POST_PREPARING = asPriority(POST_PREPARING); - - static DEFAULT = asPriority(DEFAULT); - - static WRITING = asPriority(WRITING); - - static POST_WRITING = asPriority(POST_WRITING); - - static INSTALL = asPriority(INSTALL); - - static POST_INSTALL = asPriority(POST_INSTALL); - - static END = asPriority(END); - - useVersionPlaceholders?: boolean; - skipChecks?: boolean; - experimental?: boolean; - debugEnabled?: boolean; - jhipster7Migration?: boolean; - relativeDir = relativeDir; - relative = posixRelative; - - readonly sharedData!: SharedData; - readonly logger: Logger; - jhipsterConfig!: Record; - /** - * @deprecated - */ - jhipsterTemplatesFolders!: string[]; - - blueprintStorage?: Storage; - - private _jhipsterGenerator?: string; - private _needleApi?: NeedleApi; - - // Override the type of `env` to be a full Environment - declare env: Environment; - declare log: Logger; - - constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { - super(args, options, { - skipParseOptions: true, - tasksMatchingPriority: true, - taskPrefix: PRIORITY_PREFIX, - unique: 'namespace', - ...features, - }); - - let jhipsterOldVersion = null; - if (!this.options.help) { - /* Force config to use 'generator-jhipster' namespace. */ - this._config = this._getStorage('generator-jhipster'); - - /* JHipster config using proxy mode used as a plain object instead of using get/set. */ - this.jhipsterConfig = this.config.createProxy(); - - jhipsterOldVersion = this.jhipsterConfig.jhipsterVersion ?? null; - this.sharedData = this.createSharedData({ jhipsterOldVersion, help: this.options.help }) as any; - - /* Options parsing must be executed after forcing jhipster storage namespace and after sharedData have been populated */ - this.parseJHipsterOptions(command.options); - - // Don't write jhipsterVersion to .yo-rc.json when reproducible - if ( - this.options.namespace.startsWith('jhipster:') && - !this.options.namespace.startsWith('jhipster:bootstrap') && - this.getFeatures().storeJHipsterVersion !== false && - !this.options.reproducibleTests && - !this.jhipsterConfig.jhipsterVersion - ) { - this.jhipsterConfig.jhipsterVersion = packageJson.version; - } - } - - this.logger = this.log as any; - - if (this.options.help) { - return; - } - - this.registerPriorities(CUSTOM_PRIORITIES); - - if (this.getFeatures().jhipsterBootstrap ?? true) { - // jhipster:bootstrap is always required. Run it once the environment starts. - this.env.queueTask('environment:run', async () => this.composeWithJHipster(GENERATOR_BOOTSTRAP).then(), { - once: 'queueJhipsterBootstrap', - startQueue: false, - }); - } - - // Add base template folder. - this.jhipsterTemplatesFolders = [this.templatePath()]; - this.jhipster7Migration = this.features.jhipster7Migration ?? false; - } - - /** - * Override yeoman generator's usage function to fine tune --help message. - */ - usage(): string { - return super.usage().replace('yo jhipster:', 'jhipster '); - } - - /** - * @deprecated - */ - get needleApi() { - if (this._needleApi === undefined || this._needleApi === null) { - this._needleApi = new NeedleApi(this); - } - return this._needleApi; - } - - /** - * Warn or throws check failure based on current skipChecks option. - * @param message - */ - handleCheckFailure(message: string) { - if (this.skipChecks) { - this.log.warn(message); - } else { - throw new Error(`${message} -You can ignore this error by passing '--skip-checks' to jhipster command.`); - } - } - - /** - * Check if the JHipster version used to generate an existing project is less than the passed version argument - * - * @param {string} version - A valid semver version string - */ - isJhipsterVersionLessThan(version) { - const jhipsterOldVersion = this.sharedData.getControl().jhipsterOldVersion; - if (!jhipsterOldVersion) { - // if old version is unknown then can't compare (the project may be null) and return false - return false; - } - return semverLessThan(jhipsterOldVersion, version); - } - - /** - * Get arguments for the priority - */ - getArgsForPriority(priorityName: string) { - const control = this.sharedData.getControl(); - if (priorityName === POST_WRITING || priorityName === PREPARING || priorityName === POST_PREPARING) { - const source = this.sharedData.getSource(); - return [{ control, source }]; - } - if (priorityName === WRITING) { - if (existsSync(this.destinationPath(YO_RC_FILE))) { - try { - const oldConfig = JSON.parse(readFileSync(this.destinationPath(YO_RC_FILE)).toString())[GENERATOR_JHIPSTER]; - const newConfig: any = this.config.getAll(); - const keys = [...new Set([...Object.keys(oldConfig), ...Object.keys(newConfig)])]; - const configChanges = Object.fromEntries( - keys - .filter(key => - Array.isArray(newConfig[key]) - ? newConfig[key].length === oldConfig[key].length && - newConfig[key].find((element, index) => element !== oldConfig[key][index]) - : newConfig[key] !== oldConfig[key], - ) - .map(key => [key, { newValue: newConfig[key], oldValue: oldConfig[key] }]), - ); - return [{ control, configChanges }]; - } catch { - // Fail to parse - } - } - } - return [{ control }]; - } - - /** - * Override yeoman-generator method that gets methods to be queued, filtering the result. - */ - getTaskNames(): string[] { - let priorities = super.getTaskNames(); - if (!this.features.disableSkipPriorities && this.options.skipPriorities) { - // Make sure yeoman-generator will not throw on empty tasks due to filtered priorities. - this.customLifecycle = priorities.length > 0; - priorities = priorities.filter(priorityName => !this.options.skipPriorities!.includes(priorityName)); - } - return priorities; - } - - parseJHipsterCommand(commandDef: JHipsterCommandDefinition) { - if (commandDef.arguments) { - this.parseJHipsterArguments(commandDef.arguments); - } else if (commandDef.configs) { - this.parseJHipsterArguments( - Object.fromEntries( - Object.entries(commandDef.configs as Record) - .filter(([_name, def]) => def.argument) - .map(([name, def]) => [ - name, - { - description: def.description, - ...def.argument, - }, - ]), - ) as any, - ); - } - if (commandDef.options || commandDef.configs) { - this.parseJHipsterOptions(commandDef.options, commandDef.configs); - } - } - - parseJHipsterOptions(options: JHipsterOptions | undefined, configs: JHipsterConfigs | boolean = {}, common = false) { - if (typeof configs === 'boolean') { - common = configs; - configs = {}; - } - - Object.entries(options ?? {}) - .concat(Object.entries(configs).map(([name, def]) => [name, convertConfigToOption(name, def)]) as any) - .forEach(([optionName, optionDesc]) => { - if (!optionDesc?.type || !optionDesc.scope || (common && optionDesc.scope === 'generator')) return; - let optionValue; - // Hidden options are test options, which doesn't rely on commander for options parsing. - // We must parse environment variables manually - if (this.options[optionDesc.name ?? optionName] === undefined && optionDesc.env && process.env[optionDesc.env]) { - optionValue = process.env[optionDesc.env]; - } else { - optionValue = this.options[optionDesc.name ?? optionName]; - } - if (optionValue !== undefined) { - optionValue = optionDesc.type !== Array && optionDesc.type !== Function ? optionDesc.type(optionValue) : optionValue; - if (optionDesc.scope === 'storage') { - this.config.set(optionName, optionValue); - } else if (optionDesc.scope === 'blueprint') { - this.blueprintStorage!.set(optionName, optionValue); - } else if (optionDesc.scope === 'control') { - this.sharedData.getControl()[optionName] = optionValue; - } else if (optionDesc.scope === 'generator') { - this[optionName] = optionValue; - } else { - throw new Error(`Scope ${optionDesc.scope} not supported`); - } - } else if (optionDesc.default && optionDesc.scope === 'generator' && this[optionName] === undefined) { - this[optionName] = optionDesc.default; - } - }); - } - - parseJHipsterArguments(jhipsterArguments: JHipsterArguments = {}) { - const hasPositionalArguments = Boolean(this.options.positionalArguments); - let positionalArguments: unknown[] = hasPositionalArguments ? this.options.positionalArguments! : this._args; - const argumentEntries = Object.entries(jhipsterArguments); - if (hasPositionalArguments && positionalArguments.length > argumentEntries.length) { - throw new Error('More arguments than allowed'); - } - - argumentEntries.find(([argumentName, argumentDef]) => { - if (positionalArguments.length > 0) { - let argument; - if (hasPositionalArguments || argumentDef.type !== Array) { - // Positional arguments already parsed or a single argument. - argument = positionalArguments.shift(); - } else { - // Varags argument. - argument = positionalArguments; - positionalArguments = []; - } - if (argument !== undefined) { - const convertedValue = !argumentDef.type || argumentDef.type === Array ? argument : argumentDef.type(argument as any); - if ((argumentDef.scope ?? 'generator') === 'generator') { - this[argumentName] = convertedValue; - } else if (argumentDef.scope === 'storage') { - this.config.set(argumentName, convertedValue); - } else if (argumentDef.scope === 'blueprint') { - this.blueprintStorage!.set(argumentName, convertedValue); - } - } - } else { - if (argumentDef.required) { - throw new Error(`Missing required argument ${argumentName}`); - } - return true; - } - return false; - }); - - // Arguments should only be parsed by the root generator, cleanup to don't be forwarded. - this.options.positionalArguments = []; - } - - prepareQuestions(configs: JHipsterConfigs = {}) { - return Object.entries(configs) - .filter(([_name, def]) => def?.prompt) - .map(([name, def]) => { - const promptSpec = typeof def.prompt === 'function' ? def.prompt(this) : { ...def.prompt }; - let storage: any; - if ((def.scope ?? 'storage') === 'storage') { - storage = this.config; - if (promptSpec.default === undefined) { - promptSpec.default = () => (this as any).jhipsterConfigWithDefaults?.[name]; - } - } else if (def.scope === 'blueprint') { - storage = this.blueprintStorage; - } else if (def.scope === 'generator') { - storage = { - getPath: path => get(this, path), - setPath: (path, value) => set(this, path, value), - }; - } - return { - name, - choices: def.choices, - ...promptSpec, - storage, - }; - }); - } - - /** - * Generate a date to be used by Liquibase changelogs. - * - * @param {Boolean} [reproducible=true] - Set true if the changelog date can be reproducible. - * Set false to create a changelog date incrementing the last one. - * @return {String} Changelog date. - */ - dateFormatForLiquibase(reproducible?: boolean) { - const control = this.sharedData.getControl(); - reproducible = reproducible ?? control.reproducible; - // Use started counter or use stored creationTimestamp if creationTimestamp option is passed - const creationTimestamp = this.options.creationTimestamp ? this.config.get('creationTimestamp') : undefined; - let now = new Date(); - // Miliseconds is ignored for changelogDate. - now.setMilliseconds(0); - // Run reproducible timestamp when regenerating the project with reproducible option or an specific timestamp. - if (reproducible || creationTimestamp) { - if (control.reproducibleLiquibaseTimestamp) { - // Counter already started. - now = control.reproducibleLiquibaseTimestamp; - } else { - // Create a new counter - const newCreationTimestamp = creationTimestamp ?? this.config.get('creationTimestamp'); - now = newCreationTimestamp ? new Date(newCreationTimestamp as any) : now; - now.setMilliseconds(0); - } - now.setMinutes(now.getMinutes() + 1); - control.reproducibleLiquibaseTimestamp = now; - - // Reproducible build can create future timestamp, save it. - const lastLiquibaseTimestamp = this.jhipsterConfig.lastLiquibaseTimestamp; - if (!lastLiquibaseTimestamp || now.getTime() > lastLiquibaseTimestamp) { - this.config.set('lastLiquibaseTimestamp', now.getTime()); - } - } else { - // Get and store lastLiquibaseTimestamp, a future timestamp can be used - let lastLiquibaseTimestamp = this.jhipsterConfig.lastLiquibaseTimestamp; - if (lastLiquibaseTimestamp) { - lastLiquibaseTimestamp = new Date(lastLiquibaseTimestamp); - if (lastLiquibaseTimestamp >= now) { - now = lastLiquibaseTimestamp; - now.setSeconds(now.getSeconds() + 1); - now.setMilliseconds(0); - } - } - this.jhipsterConfig.lastLiquibaseTimestamp = now.getTime(); - } - return formatDateForChangelog(now); - } - - /** - * Alternative templatePath that fetches from the blueprinted generator, instead of the blueprint. - */ - jhipsterTemplatePath(...path: string[]) { - let existingGenerator: string; - try { - existingGenerator = this._jhipsterGenerator ?? requireNamespace(this.options.namespace).generator; - } catch (error) { - if (this.options.namespace) { - const split = this.options.namespace.split(':', 2); - existingGenerator = split.length === 1 ? split[0] : split[1]; - } else { - throw new Error('Could not determine the generator name'); - } - } - this._jhipsterGenerator = existingGenerator; - return this._jhipsterGenerator - ? this.fetchFromInstalledJHipster(this._jhipsterGenerator, 'templates', ...path) - : this.templatePath(...path); - } - - /** - * Compose with a jhipster generator using default jhipster config. - * @return {object} the composed generator - */ - async composeWithJHipster(generator: string, options?: ComposeOptions) { - assert(typeof generator === 'string', 'generator should to be a string'); - if (!isAbsolute(generator)) { - const namespace = generator.includes(':') ? generator : `jhipster:${generator}`; - if (await this.env.get(namespace)) { - generator = namespace; - } else { - // Keep test compatibility were jhipster lookup does not run. - const found = ['/index.js', '/index.cjs', '/index.mjs', '/index.ts', '/index.cts', '/index.mts'].find(extension => { - const pathToLook = join(__dirname, `../${generator}${extension}`); - return existsSync(pathToLook) ? pathToLook : undefined; - }); - if (!found) { - throw new Error(`Generator ${generator} was not found`); - } - generator = join(__dirname, `../${generator}${found}`); - } - } - - return this.composeWith(generator, { - forwardOptions: false, - ...options, - generatorOptions: { - ...this.options, - positionalArguments: undefined, - ...options?.generatorOptions, - } as any, - }); - } - - /** - * Compose with a jhipster generator using default jhipster config, but queue it immediately. - */ - async dependsOnJHipster(generator: string, options?: ComposeOptions) { - return this.composeWithJHipster(generator, { - ...options, - schedule: false, - }); - } - - /** - * Remove File - * @param file - */ - removeFile(...path: string[]) { - const destinationFile = this.destinationPath(...path); - const relativePath = relative((this.env as any).logCwd, destinationFile); - // Delete from memory fs to keep updated. - this.fs.delete(destinationFile); - try { - if (destinationFile && statSync(destinationFile).isFile()) { - this.log.info(`Removing legacy file ${relativePath}`); - rmSync(destinationFile, { force: true }); - } - } catch { - this.log.info(`Could not remove legacy file ${relativePath}`); - } - return destinationFile; - } - - /** - * Remove Folder - * @param path - */ - removeFolder(...path: string[]) { - const destinationFolder = this.destinationPath(...path); - const relativePath = relative((this.env as any).logCwd, destinationFolder); - // Delete from memory fs to keep updated. - this.fs.delete(`${destinationFolder}/**`); - try { - if (statSync(destinationFolder).isDirectory()) { - this.log.info(`Removing legacy folder ${relativePath}`); - rmSync(destinationFolder, { recursive: true }); - } - } catch (error) { - this.log.log(`Could not remove folder ${destinationFolder}`); - } - } - - /** - * Fetch files from the generator-jhipster instance installed - */ - fetchFromInstalledJHipster(...path: string[]) { - if (path) { - return joinPath(__dirname, '..', ...path); - } - return path; - } - - /** - * Utility function to write file. - * - * @param source - * @param destination - destination - * @param data - template data - * @param options - options passed to ejs render - * @param copyOptions - */ - writeFile(source: string, destination: string, data: TemplateData = this, options?: TemplateOptions, copyOptions: CopyOptions = {}) { - // Convert to any because ejs types doesn't support string[] https://github.com/DefinitelyTyped/DefinitelyTyped/pull/63315 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const root: any = this.jhipsterTemplatesFolders ?? this.templatePath(); - return this.renderTemplate(source, destination, data, { root, ...options }, { noGlob: true, ...copyOptions }); - } - - /** - * write the given files using provided options. - */ - async writeFiles(options: WriteFileOptions): Promise { - const paramCount = Object.keys(options).filter(key => ['sections', 'blocks', 'templates'].includes(key)).length; - assert(paramCount > 0, 'One of sections, blocks or templates is required'); - assert(paramCount === 1, 'Only one of sections, blocks or templates must be provided'); - - const { sections, blocks, templates, rootTemplatesPath, context = this, transform: methodTransform = [] } = options as any; - const { _: commonSpec = {} } = sections || {}; - const { transform: sectionTransform = [] } = commonSpec; - const startTime = new Date().getMilliseconds(); - - /* Build lookup order first has preference. - * Example - * rootTemplatesPath = ['reactive', 'common'] - * jhipsterTemplatesFolders = ['/.../generator-jhispter-blueprint/server/templates', '/.../generator-jhispter/server/templates'] - * - * /.../generator-jhispter-blueprint/server/templates/reactive/templatePath - * /.../generator-jhispter-blueprint/server/templates/common/templatePath - * /.../generator-jhispter/server/templates/reactive/templatePath - * /.../generator-jhispter/server/templates/common/templatePath - */ - let rootTemplatesAbsolutePath; - if (!rootTemplatesPath) { - rootTemplatesAbsolutePath = (this as any).jhipsterTemplatesFolders; - } else if (typeof rootTemplatesPath === 'string' && isAbsolute(rootTemplatesPath)) { - rootTemplatesAbsolutePath = rootTemplatesPath; - } else { - rootTemplatesAbsolutePath = (this as any).jhipsterTemplatesFolders - .map(templateFolder => [].concat(rootTemplatesPath).map(relativePath => join(templateFolder, relativePath))) - .flat(); - } - - const normalizeEjs = file => file.replace('.ejs', ''); - const resolveCallback = (val, fallback?) => { - if (val === undefined) { - if (typeof fallback === 'function') { - return resolveCallback(fallback); - } - return fallback; - } - if (typeof val === 'boolean' || typeof val === 'string') { - return val; - } - if (typeof val === 'function') { - return val.call(this, context) || false; - } - throw new Error(`Type not supported ${val}`); - }; - - const renderTemplate = async ({ sourceFile, destinationFile, options, noEjs, transform, binary }) => { - const extension = extname(sourceFile); - const isBinary = binary || ['.png', '.jpg', '.gif', '.svg', '.ico'].includes(extension); - const appendEjs = noEjs === undefined ? !isBinary && extension !== '.ejs' : !noEjs; - const ejsFile = appendEjs || extension === '.ejs'; - let targetFile; - if (typeof destinationFile === 'function') { - targetFile = resolveCallback(destinationFile); - } else { - targetFile = appendEjs ? normalizeEjs(destinationFile) : destinationFile; - } - - let sourceFileFrom; - if (Array.isArray(rootTemplatesAbsolutePath)) { - // Look for existing templates - const existingTemplates = rootTemplatesAbsolutePath - .map(rootPath => this.templatePath(rootPath, sourceFile)) - .filter(templateFile => existsSync(appendEjs ? `${templateFile}.ejs` : templateFile)); - - if (existingTemplates.length > 1) { - const moreThanOneMessage = `Multiples templates were found for file ${sourceFile}, using the first -templates: ${JSON.stringify(existingTemplates, null, 2)}`; - if (existingTemplates.length > 2) { - this.log.warn(`Possible blueprint conflict detected: ${moreThanOneMessage}`); - } else { - this.log.debug(moreThanOneMessage); - } - } - sourceFileFrom = existingTemplates.shift(); - - if (sourceFileFrom === undefined) { - throw new Error(`Template file ${sourceFile} was not found at ${rootTemplatesAbsolutePath}`); - } - } else if (typeof rootTemplatesAbsolutePath === 'string') { - sourceFileFrom = this.templatePath(rootTemplatesAbsolutePath, sourceFile); - } else { - sourceFileFrom = this.templatePath(sourceFile); - } - if (appendEjs) { - sourceFileFrom = `${sourceFileFrom}.ejs`; - } - - if (!ejsFile) { - await (this as any).copyTemplateAsync(sourceFileFrom, targetFile); - } else { - let useAsync = true; - if (context.entityClass) { - if (!context.baseName) { - throw new Error('baseName is require at templates context'); - } - const sourceBasename = basename(sourceFileFrom); - const seed = `${context.entityClass}-${sourceBasename}${context.fakerSeed ?? ''}`; - Object.values((this.sharedData as any).getApplication()?.sharedEntities ?? {}).forEach((entity: any) => { - entity.resetFakerSeed(seed); - }); - // Async calls will make the render method to be scheduled, allowing the faker key to change in the meantime. - useAsync = false; - } - - const renderOptions = { - ...(options?.renderOptions ?? {}), - // Set root for ejs to lookup for partials. - root: rootTemplatesAbsolutePath, - // ejs caching cause problem https://github.com/jhipster/generator-jhipster/pull/20757 - cache: false, - }; - const copyOptions = { noGlob: true }; - // TODO drop for v8 final release - const data = (this as any).jhipster7Migration ? createJHipster7Context(this, context, { ignoreWarnings: true }) : context; - if (useAsync) { - await (this as any).renderTemplateAsync(sourceFileFrom, targetFile, data, renderOptions, copyOptions); - } else { - (this as any).renderTemplate(sourceFileFrom, targetFile, data, renderOptions, copyOptions); - } - } - if (!isBinary && transform && transform.length) { - (this as any).editFile(targetFile, ...transform); - } - return targetFile; - }; - - let parsedBlocks = blocks; - if (sections) { - assert(typeof sections === 'object', 'sections must be an object'); - const parsedSections = Object.entries(sections) - .map(([sectionName, sectionBlocks]) => { - if (sectionName.startsWith('_')) return undefined; - assert(Array.isArray(sectionBlocks), `Section must be an array for ${sectionName}`); - return { sectionName, sectionBlocks }; - }) - .filter(Boolean); - - parsedBlocks = parsedSections - .map(({ sectionName, sectionBlocks }: any) => { - return sectionBlocks.map((block, blockIdx) => { - const blockSpecPath = `${sectionName}[${blockIdx}]`; - assert(typeof block === 'object', `Block must be an object for ${blockSpecPath}`); - return { blockSpecPath, ...block }; - }); - }) - .flat(); - } - - let parsedTemplates; - if (parsedBlocks) { - parsedTemplates = parsedBlocks - .map((block, blockIdx) => { - const { - blockSpecPath = `${blockIdx}`, - path: blockPathValue = './', - from: blockFromCallback, - to: blockToCallback, - condition: blockConditionCallback, - transform: blockTransform = [], - renameTo: blockRenameTo, - } = block; - assert(typeof block === 'object', `Block must be an object for ${blockSpecPath}`); - assert(Array.isArray(block.templates), `Block templates must be an array for ${blockSpecPath}`); - const condition = resolveCallback(blockConditionCallback); - if (condition !== undefined && !condition) { - return undefined; - } - if (typeof blockPathValue === 'function') { - throw new Error(`Block path should be static for ${blockSpecPath}`); - } - const blockPath = resolveCallback(blockFromCallback, blockPathValue); - const blockTo = resolveCallback(blockToCallback, blockPath) || blockPath; - return block.templates.map((fileSpec, fileIdx) => { - const fileSpecPath = `${blockSpecPath}[${fileIdx}]`; - assert( - typeof fileSpec === 'object' || typeof fileSpec === 'string' || typeof fileSpec === 'function', - `File must be an object, a string or a function for ${fileSpecPath}`, - ); - if (typeof fileSpec === 'function') { - fileSpec = fileSpec.call(this, context); - } - let { noEjs } = fileSpec; - let derivedTransform; - if (typeof blockTransform === 'boolean') { - noEjs = !blockTransform; - derivedTransform = [...methodTransform, ...sectionTransform]; - } else { - derivedTransform = [...methodTransform, ...sectionTransform, ...blockTransform]; - } - if (typeof fileSpec === 'string') { - const sourceFile = join(blockPath, fileSpec); - let destinationFile; - if (blockRenameTo) { - destinationFile = this.destinationPath(blockRenameTo.call(this, context, fileSpec, this)); - } else { - destinationFile = this.destinationPath(blockTo, fileSpec); - } - return { sourceFile, destinationFile, noEjs, transform: derivedTransform }; - } - - const { options, file, renameTo, transform: fileTransform = [], binary } = fileSpec; - let { sourceFile, destinationFile } = fileSpec; - - if (typeof fileTransform === 'boolean') { - noEjs = !fileTransform; - } else if (Array.isArray(fileTransform)) { - derivedTransform = [...derivedTransform, ...fileTransform]; - } else if (fileTransform !== undefined) { - throw new Error(`Transform ${fileTransform} value is not supported`); - } - - const normalizedFile = resolveCallback(sourceFile || file); - sourceFile = join(blockPath, normalizedFile); - destinationFile = this.destinationPath(blockTo, join(resolveCallback(destinationFile || renameTo, normalizedFile))); - - const override = resolveCallback(fileSpec.override); - if (override !== undefined && !override && (this as any).fs.exists(destinationFile)) { - this.log.debug(`skipping file ${destinationFile}`); - return undefined; - } - - // TODO remove for jhipster 8 - if (noEjs === undefined) { - const { method } = fileSpec; - if (method === 'copy') { - noEjs = true; - } - } - - return { - sourceFile, - destinationFile, - options, - transform: derivedTransform, - noEjs, - binary, - }; - }); - }) - .flat() - .filter(template => template); - } else { - parsedTemplates = templates.map(template => { - if (typeof template === 'string') { - return { sourceFile: template, destinationFile: template }; - } - return template; - }); - } - - const files = await Promise.all(parsedTemplates.map(template => renderTemplate(template))); - this.log.debug(`Time taken to write files: ${new Date().getMilliseconds() - startTime}ms`); - return files.filter(file => file); - } - - /** - * Edit file content. - * Edits an empty file if `options.create` is truthy or no callback is passed. - * @example - * // Throws if `foo.txt` doesn't exists or append the content. - * editFile('foo.txt', content => content + 'foo.txt content'); - * @example - * // Appends `foo.txt` content if whether exists or not. - * editFile('foo.txt', { create: true }, content => content + 'foo.txt content'); - * @example - * // Appends `foo.txt` content if whether exists or not using the returned cascaded callback. - * editFile('foo.txt')(content => content + 'foo.txt content'); - */ - editFile(file: string, ...transformCallbacks: EditFileCallback[]): CascatedEditFileCallback; - editFile(file: string, options: EditFileOptions, ...transformCallbacks: EditFileCallback[]): CascatedEditFileCallback; - - editFile( - file: string, - options?: EditFileOptions | EditFileCallback, - ...transformCallbacks: EditFileCallback[] - ): CascatedEditFileCallback { - let actualOptions: EditFileOptions; - if (typeof options === 'function') { - transformCallbacks = [options, ...transformCallbacks]; - actualOptions = {}; - } else if (options === undefined) { - actualOptions = {}; - } else { - actualOptions = options; - } - let filePath = this.destinationPath(file); - if (!this.env.sharedFs.existsInMemory(filePath) && this.env.sharedFs.existsInMemory(`${filePath}.jhi`)) { - filePath = `${filePath}.jhi`; - } - - let originalContent; - try { - originalContent = this.readDestination(filePath); - } catch (_error) { - // null return should be treated like an error. - } - - if (!originalContent) { - const { ignoreNonExisting, create } = actualOptions; - const errorMessage = typeof ignoreNonExisting === 'string' ? ` ${ignoreNonExisting}.` : ''; - if (ignoreNonExisting) { - this.log(`${chalk.yellow('\nUnable to find ')}${filePath}.${chalk.yellow(errorMessage)}\n`); - // return a noop. - const noop = () => noop; - return noop; - } - if (!create || transformCallbacks.length === 0) { - throw new Error(`Unable to find ${filePath}. ${errorMessage}`); - } - // allow to edit non existing files - originalContent = ''; - } - - let newContent = originalContent; - const writeCallback = (...callbacks: EditFileCallback[]): CascatedEditFileCallback => { - try { - newContent = joinCallbacks(...callbacks).call(this, newContent, filePath); - if (actualOptions.assertModified && originalContent === newContent) { - throw new Error(`Fail to edit file '${file}'.`); - } - this.writeDestination(filePath, newContent); - } catch (error: unknown) { - if (error instanceof Error) { - throw new Error(`Error editing file ${filePath}: ${error.message} at ${error.stack}`); - } - throw new Error(`Unknown Error ${error}`); - } - return writeCallback; - }; - - return writeCallback(...transformCallbacks); - } - - /** - * Convert value to a yaml and write to destination - */ - writeDestinationYaml(filepath: string, value: Record) { - this.writeDestination(filepath, stringifyYaml(value)); - } - - /** - * Merge value to an existing yaml and write to destination - * Removes every comment (due to parsing/merging process) except the at the top of the file. - */ - mergeDestinationYaml(filepath: string, value: Record) { - this.editFile(filepath, content => { - const lines = content.split('\n'); - const headerComments: string[] = []; - lines.find(line => { - if (line.startsWith('#')) { - headerComments.push(line); - return false; - } - return true; - }); - return headerComments.join('\n').concat('\n', stringifyYaml(merge(parseYaml(content), value))); - }); - } - - /** - * Merge value to an existing json and write to destination - */ - mergeDestinationJson(filepath: string, value: Record) { - this.editFile(filepath, { create: true }, content => { - return JSON.stringify(merge(content ? JSON.parse(content) : {}, value), null, 2); - }); - } - - /** - * Shallow clone or convert dependencies to placeholder if needed. - */ - prepareDependencies( - map: Record, - valuePlaceholder: (value: string) => string = value => `${_.snakeCase(value).toUpperCase()}_VERSION`, - ): Record { - if (this.useVersionPlaceholders) { - return Object.fromEntries(Object.keys(map).map(dep => [dep, valuePlaceholder(dep)])); - } - return { - ...map, - }; - } - - loadNodeDependencies(destination: Record, source: Record): void { - Object.assign(destination, this.prepareDependencies(source)); - } - - loadNodeDependenciesFromPackageJson( - destination: Record, - packageJsonFile: string = this.templatePath('../resources/package.json'), - ): void { - const { devDependencies, dependencies } = this.fs.readJSON(packageJsonFile, {}) as any; - this.loadNodeDependencies(destination, { ...devDependencies, ...dependencies }); - } - - /** - * Print ValidationResult info/warnings or throw result Error. - */ - validateResult(result: ValidationResult, { throwOnError = true } = {}) { - // Don't print check info by default for cleaner outputs. - if (result.debug) { - if (Array.isArray(result.debug)) { - for (const debug of result.debug) { - this.log.debug(debug); - } - } else { - this.log.debug(result.debug); - } - } - if (result.info) { - if (Array.isArray(result.info)) { - for (const info of result.info) { - this.log.info(info); - } - } else { - this.log.info(result.info); - } - } - if (result.warning) { - if (Array.isArray(result.warning)) { - for (const warning of result.warning) { - this.log.warn(warning); - } - } else { - this.log.warn(result.warning); - } - } - if (result.error) { - if (Array.isArray(result.error)) { - if (throwOnError && result.error.length > 0) { - throw new Error(result.error[0]); - } - for (const error of result.error) { - this.log.warn(error); - } - } else if (throwOnError) { - throw new Error(result.error); - } else { - this.log.warn(result.error); - } - } - } - - /** - * Checks if there is a newer JHipster version available. - */ - protected async checkForNewVersion() { - try { - const latestJhipster = await latestVersion(GENERATOR_JHIPSTER); - if (semver.lt(packageJson.version, latestJhipster)) { - this.log.warn( - `${ - chalk.yellow(' ______________________________________________________________________________\n\n') + - chalk.yellow(' JHipster update available: ') + - chalk.green.bold(latestJhipster) + - chalk.gray(` (current: ${packageJson.version})`) - }\n`, - ); - this.log.log(chalk.yellow(` Run ${chalk.magenta(`npm install -g ${GENERATOR_JHIPSTER}`)} to update.\n`)); - this.log.log(chalk.yellow(' ______________________________________________________________________________\n')); - } - } catch { - // Ignore error - } - } - - /** - * Create a simple-git instance using current destinationPath as baseDir. - */ - createGit() { - return simpleGit({ baseDir: this.destinationPath() }).env({ - ...process.env, - LANG: 'en', - }); - } - - private calculateApplicationId(applicationPath: string) { - const dirname = basename(applicationPath); - return `${createHash('shake256', { outputLength: 1 }).update(applicationPath, 'utf8').digest('hex')}-${dirname}`; - } - - protected getSharedApplication(applicationFolder: string = this.destinationPath()) { - return this.options.sharedData.applications?.[this.calculateApplicationId(applicationFolder)]; - } - - private createSharedData({ - jhipsterOldVersion, - help, - }: { - jhipsterOldVersion: string | null; - help?: boolean; - }): SharedData { - const applicationId = this.options.applicationId ?? this.calculateApplicationId(this.destinationPath()); - if (this.options.sharedData.applications === undefined) { - this.options.sharedData.applications = {}; - } - const sharedApplications = help ? {} : this.options.sharedData.applications; - if (!sharedApplications[applicationId]) { - sharedApplications[applicationId] = {}; - } - const { ignoreNeedlesError } = this.options; - - return new SharedData(sharedApplications[applicationId], { jhipsterOldVersion, ignoreNeedlesError }); - } -} diff --git a/generators/base-core/generator.ts b/generators/base-core/generator.ts new file mode 100644 index 000000000000..220344ed0fca --- /dev/null +++ b/generators/base-core/generator.ts @@ -0,0 +1,1112 @@ +/** + * Copyright 2013-2023 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 { basename, join as joinPath, dirname, relative, isAbsolute, join, extname } from 'path'; +import { relative as posixRelative } from 'path/posix'; +import { createHash } from 'crypto'; +import { fileURLToPath } from 'url'; +import { statSync, rmSync, existsSync, readFileSync } from 'fs'; +import assert from 'assert'; +import { requireNamespace } from '@yeoman/namespace'; +import chalk from 'chalk'; +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; +import * as _ from 'lodash-es'; +import { simpleGit } from 'simple-git'; +import type { CopyOptions } from 'mem-fs-editor'; +import type { Data as TemplateData, Options as TemplateOptions } from 'ejs'; +import semver, { lt as semverLessThan } from 'semver'; +import YeomanGenerator, { type ComposeOptions, type Storage } from 'yeoman-generator'; +import type Environment from 'yeoman-environment'; +import latestVersion from 'latest-version'; +import SharedData from '../base/shared-data.js'; +import { CUSTOM_PRIORITIES, PRIORITY_NAMES, PRIORITY_PREFIX } from '../base/priorities.js'; +import { createJHipster7Context, formatDateForChangelog, joinCallbacks, Logger } from '../base/support/index.js'; + +import type { + JHipsterGeneratorOptions, + JHipsterGeneratorFeatures, + EditFileCallback, + EditFileOptions, + CascatedEditFileCallback, + JHipsterOptions, + ValidationResult, + WriteFileOptions, + JHipsterArguments, + JHipsterConfigs, + JHipsterCommandDefinition, +} from '../base/api.js'; +import { packageJson } from '../../lib/index.js'; +import { CommonClientServerApplication, type BaseApplication } from '../base-application/types.js'; +import { GENERATOR_BOOTSTRAP } from '../generator-list.js'; +import NeedleApi from '../needle-api.js'; +import command from '../base/command.js'; +import { GENERATOR_JHIPSTER, YO_RC_FILE } from '../generator-constants.js'; +import { convertConfigToOption } from '../../lib/internal/index.js'; + +const { merge, get, set } = _; +const { + INITIALIZING, + PROMPTING, + CONFIGURING, + COMPOSING, + LOADING, + PREPARING, + POST_PREPARING, + DEFAULT, + WRITING, + POST_WRITING, + INSTALL, + POST_INSTALL, + END, +} = PRIORITY_NAMES; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const asPriority = (priorityName: string) => `${PRIORITY_PREFIX}${priorityName}`; + +const relativeDir = (from: string, to: string) => { + const rel = posixRelative(from, to); + return rel ? `${rel}/` : ''; +}; + +/** + * This is the base class for a generator for every generator. + */ +export default class CoreGenerator extends YeomanGenerator { + static asPriority = asPriority; + + static INITIALIZING = asPriority(INITIALIZING); + + static PROMPTING = asPriority(PROMPTING); + + static CONFIGURING = asPriority(CONFIGURING); + + static COMPOSING = asPriority(COMPOSING); + + static LOADING = asPriority(LOADING); + + static PREPARING = asPriority(PREPARING); + + static POST_PREPARING = asPriority(POST_PREPARING); + + static DEFAULT = asPriority(DEFAULT); + + static WRITING = asPriority(WRITING); + + static POST_WRITING = asPriority(POST_WRITING); + + static INSTALL = asPriority(INSTALL); + + static POST_INSTALL = asPriority(POST_INSTALL); + + static END = asPriority(END); + + useVersionPlaceholders?: boolean; + skipChecks?: boolean; + experimental?: boolean; + debugEnabled?: boolean; + jhipster7Migration?: boolean; + relativeDir = relativeDir; + relative = posixRelative; + + readonly sharedData!: SharedData; + readonly logger: Logger; + jhipsterConfig!: Record; + /** + * @deprecated + */ + jhipsterTemplatesFolders!: string[]; + + blueprintStorage?: Storage; + + private _jhipsterGenerator?: string; + private _needleApi?: NeedleApi; + + // Override the type of `env` to be a full Environment + declare env: Environment; + declare log: Logger; + + constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { + super(args, options, { + skipParseOptions: true, + tasksMatchingPriority: true, + taskPrefix: PRIORITY_PREFIX, + unique: 'namespace', + ...features, + }); + + let jhipsterOldVersion = null; + if (!this.options.help) { + /* Force config to use 'generator-jhipster' namespace. */ + this._config = this._getStorage('generator-jhipster'); + + /* JHipster config using proxy mode used as a plain object instead of using get/set. */ + this.jhipsterConfig = this.config.createProxy(); + + jhipsterOldVersion = this.jhipsterConfig.jhipsterVersion ?? null; + this.sharedData = this.createSharedData({ jhipsterOldVersion, help: this.options.help }) as any; + + /* Options parsing must be executed after forcing jhipster storage namespace and after sharedData have been populated */ + this.parseJHipsterOptions(command.options); + + // Don't write jhipsterVersion to .yo-rc.json when reproducible + if ( + this.options.namespace.startsWith('jhipster:') && + !this.options.namespace.startsWith('jhipster:bootstrap') && + this.getFeatures().storeJHipsterVersion !== false && + !this.options.reproducibleTests && + !this.jhipsterConfig.jhipsterVersion + ) { + this.jhipsterConfig.jhipsterVersion = packageJson.version; + } + } + + this.logger = this.log as any; + + if (this.options.help) { + return; + } + + this.registerPriorities(CUSTOM_PRIORITIES); + + if (this.getFeatures().jhipsterBootstrap ?? true) { + // jhipster:bootstrap is always required. Run it once the environment starts. + this.env.queueTask('environment:run', async () => this.composeWithJHipster(GENERATOR_BOOTSTRAP).then(), { + once: 'queueJhipsterBootstrap', + startQueue: false, + }); + } + + // Add base template folder. + this.jhipsterTemplatesFolders = [this.templatePath()]; + this.jhipster7Migration = this.features.jhipster7Migration ?? false; + } + + /** + * Override yeoman generator's usage function to fine tune --help message. + */ + usage(): string { + return super.usage().replace('yo jhipster:', 'jhipster '); + } + + /** + * @deprecated + */ + get needleApi() { + if (this._needleApi === undefined || this._needleApi === null) { + this._needleApi = new NeedleApi(this); + } + return this._needleApi; + } + + /** + * Warn or throws check failure based on current skipChecks option. + * @param message + */ + handleCheckFailure(message: string) { + if (this.skipChecks) { + this.log.warn(message); + } else { + throw new Error(`${message} +You can ignore this error by passing '--skip-checks' to jhipster command.`); + } + } + + /** + * Check if the JHipster version used to generate an existing project is less than the passed version argument + * + * @param {string} version - A valid semver version string + */ + isJhipsterVersionLessThan(version) { + const jhipsterOldVersion = this.sharedData.getControl().jhipsterOldVersion; + if (!jhipsterOldVersion) { + // if old version is unknown then can't compare (the project may be null) and return false + return false; + } + return semverLessThan(jhipsterOldVersion, version); + } + + /** + * Get arguments for the priority + */ + getArgsForPriority(priorityName: string) { + const control = this.sharedData.getControl(); + if (priorityName === POST_WRITING || priorityName === PREPARING || priorityName === POST_PREPARING) { + const source = this.sharedData.getSource(); + return [{ control, source }]; + } + if (priorityName === WRITING) { + if (existsSync(this.destinationPath(YO_RC_FILE))) { + try { + const oldConfig = JSON.parse(readFileSync(this.destinationPath(YO_RC_FILE)).toString())[GENERATOR_JHIPSTER]; + const newConfig: any = this.config.getAll(); + const keys = [...new Set([...Object.keys(oldConfig), ...Object.keys(newConfig)])]; + const configChanges = Object.fromEntries( + keys + .filter(key => + Array.isArray(newConfig[key]) + ? newConfig[key].length === oldConfig[key].length && + newConfig[key].find((element, index) => element !== oldConfig[key][index]) + : newConfig[key] !== oldConfig[key], + ) + .map(key => [key, { newValue: newConfig[key], oldValue: oldConfig[key] }]), + ); + return [{ control, configChanges }]; + } catch { + // Fail to parse + } + } + } + return [{ control }]; + } + + /** + * Override yeoman-generator method that gets methods to be queued, filtering the result. + */ + getTaskNames(): string[] { + let priorities = super.getTaskNames(); + if (!this.features.disableSkipPriorities && this.options.skipPriorities) { + // Make sure yeoman-generator will not throw on empty tasks due to filtered priorities. + this.customLifecycle = priorities.length > 0; + priorities = priorities.filter(priorityName => !this.options.skipPriorities!.includes(priorityName)); + } + return priorities; + } + + parseJHipsterCommand(commandDef: JHipsterCommandDefinition) { + if (commandDef.arguments) { + this.parseJHipsterArguments(commandDef.arguments); + } else if (commandDef.configs) { + this.parseJHipsterArguments( + Object.fromEntries( + Object.entries(commandDef.configs as Record) + .filter(([_name, def]) => def.argument) + .map(([name, def]) => [ + name, + { + description: def.description, + ...def.argument, + }, + ]), + ) as any, + ); + } + if (commandDef.options || commandDef.configs) { + this.parseJHipsterOptions(commandDef.options, commandDef.configs); + } + } + + parseJHipsterOptions(options: JHipsterOptions | undefined, configs: JHipsterConfigs | boolean = {}, common = false) { + if (typeof configs === 'boolean') { + common = configs; + configs = {}; + } + + Object.entries(options ?? {}) + .concat(Object.entries(configs).map(([name, def]) => [name, convertConfigToOption(name, def)]) as any) + .forEach(([optionName, optionDesc]) => { + if (!optionDesc?.type || !optionDesc.scope || (common && optionDesc.scope === 'generator')) return; + let optionValue; + // Hidden options are test options, which doesn't rely on commander for options parsing. + // We must parse environment variables manually + if (this.options[optionDesc.name ?? optionName] === undefined && optionDesc.env && process.env[optionDesc.env]) { + optionValue = process.env[optionDesc.env]; + } else { + optionValue = this.options[optionDesc.name ?? optionName]; + } + if (optionValue !== undefined) { + optionValue = optionDesc.type !== Array && optionDesc.type !== Function ? optionDesc.type(optionValue) : optionValue; + if (optionDesc.scope === 'storage') { + this.config.set(optionName, optionValue); + } else if (optionDesc.scope === 'blueprint') { + this.blueprintStorage!.set(optionName, optionValue); + } else if (optionDesc.scope === 'control') { + this.sharedData.getControl()[optionName] = optionValue; + } else if (optionDesc.scope === 'generator') { + this[optionName] = optionValue; + } else { + throw new Error(`Scope ${optionDesc.scope} not supported`); + } + } else if (optionDesc.default && optionDesc.scope === 'generator' && this[optionName] === undefined) { + this[optionName] = optionDesc.default; + } + }); + } + + parseJHipsterArguments(jhipsterArguments: JHipsterArguments = {}) { + const hasPositionalArguments = Boolean(this.options.positionalArguments); + let positionalArguments: unknown[] = hasPositionalArguments ? this.options.positionalArguments! : this._args; + const argumentEntries = Object.entries(jhipsterArguments); + if (hasPositionalArguments && positionalArguments.length > argumentEntries.length) { + throw new Error('More arguments than allowed'); + } + + argumentEntries.find(([argumentName, argumentDef]) => { + if (positionalArguments.length > 0) { + let argument; + if (hasPositionalArguments || argumentDef.type !== Array) { + // Positional arguments already parsed or a single argument. + argument = positionalArguments.shift(); + } else { + // Varags argument. + argument = positionalArguments; + positionalArguments = []; + } + if (argument !== undefined) { + const convertedValue = !argumentDef.type || argumentDef.type === Array ? argument : argumentDef.type(argument as any); + if ((argumentDef.scope ?? 'generator') === 'generator') { + this[argumentName] = convertedValue; + } else if (argumentDef.scope === 'storage') { + this.config.set(argumentName, convertedValue); + } else if (argumentDef.scope === 'blueprint') { + this.blueprintStorage!.set(argumentName, convertedValue); + } + } + } else { + if (argumentDef.required) { + throw new Error(`Missing required argument ${argumentName}`); + } + return true; + } + return false; + }); + + // Arguments should only be parsed by the root generator, cleanup to don't be forwarded. + this.options.positionalArguments = []; + } + + prepareQuestions(configs: JHipsterConfigs = {}) { + return Object.entries(configs) + .filter(([_name, def]) => def?.prompt) + .map(([name, def]) => { + const promptSpec = typeof def.prompt === 'function' ? def.prompt(this) : { ...def.prompt }; + let storage: any; + if ((def.scope ?? 'storage') === 'storage') { + storage = this.config; + if (promptSpec.default === undefined) { + promptSpec.default = () => (this as any).jhipsterConfigWithDefaults?.[name]; + } + } else if (def.scope === 'blueprint') { + storage = this.blueprintStorage; + } else if (def.scope === 'generator') { + storage = { + getPath: path => get(this, path), + setPath: (path, value) => set(this, path, value), + }; + } + return { + name, + choices: def.choices, + ...promptSpec, + storage, + }; + }); + } + + /** + * Generate a date to be used by Liquibase changelogs. + * + * @param {Boolean} [reproducible=true] - Set true if the changelog date can be reproducible. + * Set false to create a changelog date incrementing the last one. + * @return {String} Changelog date. + */ + dateFormatForLiquibase(reproducible?: boolean) { + const control = this.sharedData.getControl(); + reproducible = reproducible ?? control.reproducible; + // Use started counter or use stored creationTimestamp if creationTimestamp option is passed + const creationTimestamp = this.options.creationTimestamp ? this.config.get('creationTimestamp') : undefined; + let now = new Date(); + // Miliseconds is ignored for changelogDate. + now.setMilliseconds(0); + // Run reproducible timestamp when regenerating the project with reproducible option or an specific timestamp. + if (reproducible || creationTimestamp) { + if (control.reproducibleLiquibaseTimestamp) { + // Counter already started. + now = control.reproducibleLiquibaseTimestamp; + } else { + // Create a new counter + const newCreationTimestamp = creationTimestamp ?? this.config.get('creationTimestamp'); + now = newCreationTimestamp ? new Date(newCreationTimestamp as any) : now; + now.setMilliseconds(0); + } + now.setMinutes(now.getMinutes() + 1); + control.reproducibleLiquibaseTimestamp = now; + + // Reproducible build can create future timestamp, save it. + const lastLiquibaseTimestamp = this.jhipsterConfig.lastLiquibaseTimestamp; + if (!lastLiquibaseTimestamp || now.getTime() > lastLiquibaseTimestamp) { + this.config.set('lastLiquibaseTimestamp', now.getTime()); + } + } else { + // Get and store lastLiquibaseTimestamp, a future timestamp can be used + let lastLiquibaseTimestamp = this.jhipsterConfig.lastLiquibaseTimestamp; + if (lastLiquibaseTimestamp) { + lastLiquibaseTimestamp = new Date(lastLiquibaseTimestamp); + if (lastLiquibaseTimestamp >= now) { + now = lastLiquibaseTimestamp; + now.setSeconds(now.getSeconds() + 1); + now.setMilliseconds(0); + } + } + this.jhipsterConfig.lastLiquibaseTimestamp = now.getTime(); + } + return formatDateForChangelog(now); + } + + /** + * Alternative templatePath that fetches from the blueprinted generator, instead of the blueprint. + */ + jhipsterTemplatePath(...path: string[]) { + let existingGenerator: string; + try { + existingGenerator = this._jhipsterGenerator ?? requireNamespace(this.options.namespace).generator; + } catch (error) { + if (this.options.namespace) { + const split = this.options.namespace.split(':', 2); + existingGenerator = split.length === 1 ? split[0] : split[1]; + } else { + throw new Error('Could not determine the generator name'); + } + } + this._jhipsterGenerator = existingGenerator; + return this._jhipsterGenerator + ? this.fetchFromInstalledJHipster(this._jhipsterGenerator, 'templates', ...path) + : this.templatePath(...path); + } + + /** + * Compose with a jhipster generator using default jhipster config. + * @return {object} the composed generator + */ + async composeWithJHipster(generator: string, options?: ComposeOptions) { + assert(typeof generator === 'string', 'generator should to be a string'); + if (!isAbsolute(generator)) { + const namespace = generator.includes(':') ? generator : `jhipster:${generator}`; + if (await this.env.get(namespace)) { + generator = namespace; + } else { + // Keep test compatibility were jhipster lookup does not run. + const found = ['/index.js', '/index.cjs', '/index.mjs', '/index.ts', '/index.cts', '/index.mts'].find(extension => { + const pathToLook = join(__dirname, `../${generator}${extension}`); + return existsSync(pathToLook) ? pathToLook : undefined; + }); + if (!found) { + throw new Error(`Generator ${generator} was not found`); + } + generator = join(__dirname, `../${generator}${found}`); + } + } + + return this.composeWith(generator, { + forwardOptions: false, + ...options, + generatorOptions: { + ...this.options, + positionalArguments: undefined, + ...options?.generatorOptions, + } as any, + }); + } + + /** + * Compose with a jhipster generator using default jhipster config, but queue it immediately. + */ + async dependsOnJHipster(generator: string, options?: ComposeOptions) { + return this.composeWithJHipster(generator, { + ...options, + schedule: false, + }); + } + + /** + * Remove File + * @param file + */ + removeFile(...path: string[]) { + const destinationFile = this.destinationPath(...path); + const relativePath = relative((this.env as any).logCwd, destinationFile); + // Delete from memory fs to keep updated. + this.fs.delete(destinationFile); + try { + if (destinationFile && statSync(destinationFile).isFile()) { + this.log.info(`Removing legacy file ${relativePath}`); + rmSync(destinationFile, { force: true }); + } + } catch { + this.log.info(`Could not remove legacy file ${relativePath}`); + } + return destinationFile; + } + + /** + * Remove Folder + * @param path + */ + removeFolder(...path: string[]) { + const destinationFolder = this.destinationPath(...path); + const relativePath = relative((this.env as any).logCwd, destinationFolder); + // Delete from memory fs to keep updated. + this.fs.delete(`${destinationFolder}/**`); + try { + if (statSync(destinationFolder).isDirectory()) { + this.log.info(`Removing legacy folder ${relativePath}`); + rmSync(destinationFolder, { recursive: true }); + } + } catch (error) { + this.log.log(`Could not remove folder ${destinationFolder}`); + } + } + + /** + * Fetch files from the generator-jhipster instance installed + */ + fetchFromInstalledJHipster(...path: string[]) { + if (path) { + return joinPath(__dirname, '..', ...path); + } + return path; + } + + /** + * Utility function to write file. + * + * @param source + * @param destination - destination + * @param data - template data + * @param options - options passed to ejs render + * @param copyOptions + */ + writeFile(source: string, destination: string, data: TemplateData = this, options?: TemplateOptions, copyOptions: CopyOptions = {}) { + // Convert to any because ejs types doesn't support string[] https://github.com/DefinitelyTyped/DefinitelyTyped/pull/63315 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const root: any = this.jhipsterTemplatesFolders ?? this.templatePath(); + return this.renderTemplate(source, destination, data, { root, ...options }, { noGlob: true, ...copyOptions }); + } + + /** + * write the given files using provided options. + */ + async writeFiles(options: WriteFileOptions): Promise { + const paramCount = Object.keys(options).filter(key => ['sections', 'blocks', 'templates'].includes(key)).length; + assert(paramCount > 0, 'One of sections, blocks or templates is required'); + assert(paramCount === 1, 'Only one of sections, blocks or templates must be provided'); + + const { sections, blocks, templates, rootTemplatesPath, context = this, transform: methodTransform = [] } = options as any; + const { _: commonSpec = {} } = sections || {}; + const { transform: sectionTransform = [] } = commonSpec; + const startTime = new Date().getMilliseconds(); + + /* Build lookup order first has preference. + * Example + * rootTemplatesPath = ['reactive', 'common'] + * jhipsterTemplatesFolders = ['/.../generator-jhispter-blueprint/server/templates', '/.../generator-jhispter/server/templates'] + * + * /.../generator-jhispter-blueprint/server/templates/reactive/templatePath + * /.../generator-jhispter-blueprint/server/templates/common/templatePath + * /.../generator-jhispter/server/templates/reactive/templatePath + * /.../generator-jhispter/server/templates/common/templatePath + */ + let rootTemplatesAbsolutePath; + if (!rootTemplatesPath) { + rootTemplatesAbsolutePath = (this as any).jhipsterTemplatesFolders; + } else if (typeof rootTemplatesPath === 'string' && isAbsolute(rootTemplatesPath)) { + rootTemplatesAbsolutePath = rootTemplatesPath; + } else { + rootTemplatesAbsolutePath = (this as any).jhipsterTemplatesFolders + .map(templateFolder => [].concat(rootTemplatesPath).map(relativePath => join(templateFolder, relativePath))) + .flat(); + } + + const normalizeEjs = file => file.replace('.ejs', ''); + const resolveCallback = (val, fallback?) => { + if (val === undefined) { + if (typeof fallback === 'function') { + return resolveCallback(fallback); + } + return fallback; + } + if (typeof val === 'boolean' || typeof val === 'string') { + return val; + } + if (typeof val === 'function') { + return val.call(this, context) || false; + } + throw new Error(`Type not supported ${val}`); + }; + + const renderTemplate = async ({ sourceFile, destinationFile, options, noEjs, transform, binary }) => { + const extension = extname(sourceFile); + const isBinary = binary || ['.png', '.jpg', '.gif', '.svg', '.ico'].includes(extension); + const appendEjs = noEjs === undefined ? !isBinary && extension !== '.ejs' : !noEjs; + const ejsFile = appendEjs || extension === '.ejs'; + let targetFile; + if (typeof destinationFile === 'function') { + targetFile = resolveCallback(destinationFile); + } else { + targetFile = appendEjs ? normalizeEjs(destinationFile) : destinationFile; + } + + let sourceFileFrom; + if (Array.isArray(rootTemplatesAbsolutePath)) { + // Look for existing templates + const existingTemplates = rootTemplatesAbsolutePath + .map(rootPath => this.templatePath(rootPath, sourceFile)) + .filter(templateFile => existsSync(appendEjs ? `${templateFile}.ejs` : templateFile)); + + if (existingTemplates.length > 1) { + const moreThanOneMessage = `Multiples templates were found for file ${sourceFile}, using the first +templates: ${JSON.stringify(existingTemplates, null, 2)}`; + if (existingTemplates.length > 2) { + this.log.warn(`Possible blueprint conflict detected: ${moreThanOneMessage}`); + } else { + this.log.debug(moreThanOneMessage); + } + } + sourceFileFrom = existingTemplates.shift(); + + if (sourceFileFrom === undefined) { + throw new Error(`Template file ${sourceFile} was not found at ${rootTemplatesAbsolutePath}`); + } + } else if (typeof rootTemplatesAbsolutePath === 'string') { + sourceFileFrom = this.templatePath(rootTemplatesAbsolutePath, sourceFile); + } else { + sourceFileFrom = this.templatePath(sourceFile); + } + if (appendEjs) { + sourceFileFrom = `${sourceFileFrom}.ejs`; + } + + if (!ejsFile) { + await (this as any).copyTemplateAsync(sourceFileFrom, targetFile); + } else { + let useAsync = true; + if (context.entityClass) { + if (!context.baseName) { + throw new Error('baseName is require at templates context'); + } + const sourceBasename = basename(sourceFileFrom); + const seed = `${context.entityClass}-${sourceBasename}${context.fakerSeed ?? ''}`; + Object.values((this.sharedData as any).getApplication()?.sharedEntities ?? {}).forEach((entity: any) => { + entity.resetFakerSeed(seed); + }); + // Async calls will make the render method to be scheduled, allowing the faker key to change in the meantime. + useAsync = false; + } + + const renderOptions = { + ...(options?.renderOptions ?? {}), + // Set root for ejs to lookup for partials. + root: rootTemplatesAbsolutePath, + // ejs caching cause problem https://github.com/jhipster/generator-jhipster/pull/20757 + cache: false, + }; + const copyOptions = { noGlob: true }; + // TODO drop for v8 final release + const data = (this as any).jhipster7Migration ? createJHipster7Context(this, context, { ignoreWarnings: true }) : context; + if (useAsync) { + await (this as any).renderTemplateAsync(sourceFileFrom, targetFile, data, renderOptions, copyOptions); + } else { + (this as any).renderTemplate(sourceFileFrom, targetFile, data, renderOptions, copyOptions); + } + } + if (!isBinary && transform && transform.length) { + (this as any).editFile(targetFile, ...transform); + } + return targetFile; + }; + + let parsedBlocks = blocks; + if (sections) { + assert(typeof sections === 'object', 'sections must be an object'); + const parsedSections = Object.entries(sections) + .map(([sectionName, sectionBlocks]) => { + if (sectionName.startsWith('_')) return undefined; + assert(Array.isArray(sectionBlocks), `Section must be an array for ${sectionName}`); + return { sectionName, sectionBlocks }; + }) + .filter(Boolean); + + parsedBlocks = parsedSections + .map(({ sectionName, sectionBlocks }: any) => { + return sectionBlocks.map((block, blockIdx) => { + const blockSpecPath = `${sectionName}[${blockIdx}]`; + assert(typeof block === 'object', `Block must be an object for ${blockSpecPath}`); + return { blockSpecPath, ...block }; + }); + }) + .flat(); + } + + let parsedTemplates; + if (parsedBlocks) { + parsedTemplates = parsedBlocks + .map((block, blockIdx) => { + const { + blockSpecPath = `${blockIdx}`, + path: blockPathValue = './', + from: blockFromCallback, + to: blockToCallback, + condition: blockConditionCallback, + transform: blockTransform = [], + renameTo: blockRenameTo, + } = block; + assert(typeof block === 'object', `Block must be an object for ${blockSpecPath}`); + assert(Array.isArray(block.templates), `Block templates must be an array for ${blockSpecPath}`); + const condition = resolveCallback(blockConditionCallback); + if (condition !== undefined && !condition) { + return undefined; + } + if (typeof blockPathValue === 'function') { + throw new Error(`Block path should be static for ${blockSpecPath}`); + } + const blockPath = resolveCallback(blockFromCallback, blockPathValue); + const blockTo = resolveCallback(blockToCallback, blockPath) || blockPath; + return block.templates.map((fileSpec, fileIdx) => { + const fileSpecPath = `${blockSpecPath}[${fileIdx}]`; + assert( + typeof fileSpec === 'object' || typeof fileSpec === 'string' || typeof fileSpec === 'function', + `File must be an object, a string or a function for ${fileSpecPath}`, + ); + if (typeof fileSpec === 'function') { + fileSpec = fileSpec.call(this, context); + } + let { noEjs } = fileSpec; + let derivedTransform; + if (typeof blockTransform === 'boolean') { + noEjs = !blockTransform; + derivedTransform = [...methodTransform, ...sectionTransform]; + } else { + derivedTransform = [...methodTransform, ...sectionTransform, ...blockTransform]; + } + if (typeof fileSpec === 'string') { + const sourceFile = join(blockPath, fileSpec); + let destinationFile; + if (blockRenameTo) { + destinationFile = this.destinationPath(blockRenameTo.call(this, context, fileSpec, this)); + } else { + destinationFile = this.destinationPath(blockTo, fileSpec); + } + return { sourceFile, destinationFile, noEjs, transform: derivedTransform }; + } + + const { options, file, renameTo, transform: fileTransform = [], binary } = fileSpec; + let { sourceFile, destinationFile } = fileSpec; + + if (typeof fileTransform === 'boolean') { + noEjs = !fileTransform; + } else if (Array.isArray(fileTransform)) { + derivedTransform = [...derivedTransform, ...fileTransform]; + } else if (fileTransform !== undefined) { + throw new Error(`Transform ${fileTransform} value is not supported`); + } + + const normalizedFile = resolveCallback(sourceFile || file); + sourceFile = join(blockPath, normalizedFile); + destinationFile = this.destinationPath(blockTo, join(resolveCallback(destinationFile || renameTo, normalizedFile))); + + const override = resolveCallback(fileSpec.override); + if (override !== undefined && !override && (this as any).fs.exists(destinationFile)) { + this.log.debug(`skipping file ${destinationFile}`); + return undefined; + } + + // TODO remove for jhipster 8 + if (noEjs === undefined) { + const { method } = fileSpec; + if (method === 'copy') { + noEjs = true; + } + } + + return { + sourceFile, + destinationFile, + options, + transform: derivedTransform, + noEjs, + binary, + }; + }); + }) + .flat() + .filter(template => template); + } else { + parsedTemplates = templates.map(template => { + if (typeof template === 'string') { + return { sourceFile: template, destinationFile: template }; + } + return template; + }); + } + + const files = await Promise.all(parsedTemplates.map(template => renderTemplate(template))); + this.log.debug(`Time taken to write files: ${new Date().getMilliseconds() - startTime}ms`); + return files.filter(file => file); + } + + /** + * Edit file content. + * Edits an empty file if `options.create` is truthy or no callback is passed. + * @example + * // Throws if `foo.txt` doesn't exists or append the content. + * editFile('foo.txt', content => content + 'foo.txt content'); + * @example + * // Appends `foo.txt` content if whether exists or not. + * editFile('foo.txt', { create: true }, content => content + 'foo.txt content'); + * @example + * // Appends `foo.txt` content if whether exists or not using the returned cascaded callback. + * editFile('foo.txt')(content => content + 'foo.txt content'); + */ + editFile(file: string, ...transformCallbacks: EditFileCallback[]): CascatedEditFileCallback; + editFile(file: string, options: EditFileOptions, ...transformCallbacks: EditFileCallback[]): CascatedEditFileCallback; + + editFile( + file: string, + options?: EditFileOptions | EditFileCallback, + ...transformCallbacks: EditFileCallback[] + ): CascatedEditFileCallback { + let actualOptions: EditFileOptions; + if (typeof options === 'function') { + transformCallbacks = [options, ...transformCallbacks]; + actualOptions = {}; + } else if (options === undefined) { + actualOptions = {}; + } else { + actualOptions = options; + } + let filePath = this.destinationPath(file); + if (!this.env.sharedFs.existsInMemory(filePath) && this.env.sharedFs.existsInMemory(`${filePath}.jhi`)) { + filePath = `${filePath}.jhi`; + } + + let originalContent; + try { + originalContent = this.readDestination(filePath); + } catch (_error) { + // null return should be treated like an error. + } + + if (!originalContent) { + const { ignoreNonExisting, create } = actualOptions; + const errorMessage = typeof ignoreNonExisting === 'string' ? ` ${ignoreNonExisting}.` : ''; + if (ignoreNonExisting) { + this.log(`${chalk.yellow('\nUnable to find ')}${filePath}.${chalk.yellow(errorMessage)}\n`); + // return a noop. + const noop = () => noop; + return noop; + } + if (!create || transformCallbacks.length === 0) { + throw new Error(`Unable to find ${filePath}. ${errorMessage}`); + } + // allow to edit non existing files + originalContent = ''; + } + + let newContent = originalContent; + const writeCallback = (...callbacks: EditFileCallback[]): CascatedEditFileCallback => { + try { + newContent = joinCallbacks(...callbacks).call(this, newContent, filePath); + if (actualOptions.assertModified && originalContent === newContent) { + throw new Error(`Fail to edit file '${file}'.`); + } + this.writeDestination(filePath, newContent); + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error(`Error editing file ${filePath}: ${error.message} at ${error.stack}`); + } + throw new Error(`Unknown Error ${error}`); + } + return writeCallback; + }; + + return writeCallback(...transformCallbacks); + } + + /** + * Convert value to a yaml and write to destination + */ + writeDestinationYaml(filepath: string, value: Record) { + this.writeDestination(filepath, stringifyYaml(value)); + } + + /** + * Merge value to an existing yaml and write to destination + * Removes every comment (due to parsing/merging process) except the at the top of the file. + */ + mergeDestinationYaml(filepath: string, value: Record) { + this.editFile(filepath, content => { + const lines = content.split('\n'); + const headerComments: string[] = []; + lines.find(line => { + if (line.startsWith('#')) { + headerComments.push(line); + return false; + } + return true; + }); + return headerComments.join('\n').concat('\n', stringifyYaml(merge(parseYaml(content), value))); + }); + } + + /** + * Merge value to an existing json and write to destination + */ + mergeDestinationJson(filepath: string, value: Record) { + this.editFile(filepath, { create: true }, content => { + return JSON.stringify(merge(content ? JSON.parse(content) : {}, value), null, 2); + }); + } + + /** + * Shallow clone or convert dependencies to placeholder if needed. + */ + prepareDependencies( + map: Record, + valuePlaceholder: (value: string) => string = value => `${_.snakeCase(value).toUpperCase()}_VERSION`, + ): Record { + if (this.useVersionPlaceholders) { + return Object.fromEntries(Object.keys(map).map(dep => [dep, valuePlaceholder(dep)])); + } + return { + ...map, + }; + } + + loadNodeDependencies(destination: Record, source: Record): void { + Object.assign(destination, this.prepareDependencies(source)); + } + + loadNodeDependenciesFromPackageJson( + destination: Record, + packageJsonFile: string = this.templatePath('../resources/package.json'), + ): void { + const { devDependencies, dependencies } = this.fs.readJSON(packageJsonFile, {}) as any; + this.loadNodeDependencies(destination, { ...devDependencies, ...dependencies }); + } + + /** + * Print ValidationResult info/warnings or throw result Error. + */ + validateResult(result: ValidationResult, { throwOnError = true } = {}) { + // Don't print check info by default for cleaner outputs. + if (result.debug) { + if (Array.isArray(result.debug)) { + for (const debug of result.debug) { + this.log.debug(debug); + } + } else { + this.log.debug(result.debug); + } + } + if (result.info) { + if (Array.isArray(result.info)) { + for (const info of result.info) { + this.log.info(info); + } + } else { + this.log.info(result.info); + } + } + if (result.warning) { + if (Array.isArray(result.warning)) { + for (const warning of result.warning) { + this.log.warn(warning); + } + } else { + this.log.warn(result.warning); + } + } + if (result.error) { + if (Array.isArray(result.error)) { + if (throwOnError && result.error.length > 0) { + throw new Error(result.error[0]); + } + for (const error of result.error) { + this.log.warn(error); + } + } else if (throwOnError) { + throw new Error(result.error); + } else { + this.log.warn(result.error); + } + } + } + + /** + * Checks if there is a newer JHipster version available. + */ + protected async checkForNewVersion() { + try { + const latestJhipster = await latestVersion(GENERATOR_JHIPSTER); + if (semver.lt(packageJson.version, latestJhipster)) { + this.log.warn( + `${ + chalk.yellow(' ______________________________________________________________________________\n\n') + + chalk.yellow(' JHipster update available: ') + + chalk.green.bold(latestJhipster) + + chalk.gray(` (current: ${packageJson.version})`) + }\n`, + ); + this.log.log(chalk.yellow(` Run ${chalk.magenta(`npm install -g ${GENERATOR_JHIPSTER}`)} to update.\n`)); + this.log.log(chalk.yellow(' ______________________________________________________________________________\n')); + } + } catch { + // Ignore error + } + } + + /** + * Create a simple-git instance using current destinationPath as baseDir. + */ + createGit() { + return simpleGit({ baseDir: this.destinationPath() }).env({ + ...process.env, + LANG: 'en', + }); + } + + private calculateApplicationId(applicationPath: string) { + const dirname = basename(applicationPath); + return `${createHash('shake256', { outputLength: 1 }).update(applicationPath, 'utf8').digest('hex')}-${dirname}`; + } + + protected getSharedApplication(applicationFolder: string = this.destinationPath()) { + return this.options.sharedData.applications?.[this.calculateApplicationId(applicationFolder)]; + } + + private createSharedData({ + jhipsterOldVersion, + help, + }: { + jhipsterOldVersion: string | null; + help?: boolean; + }): SharedData { + const applicationId = this.options.applicationId ?? this.calculateApplicationId(this.destinationPath()); + if (this.options.sharedData.applications === undefined) { + this.options.sharedData.applications = {}; + } + const sharedApplications = help ? {} : this.options.sharedData.applications; + if (!sharedApplications[applicationId]) { + sharedApplications[applicationId] = {}; + } + const { ignoreNeedlesError } = this.options; + + return new SharedData(sharedApplications[applicationId], { jhipsterOldVersion, ignoreNeedlesError }); + } +} diff --git a/generators/base-core/index.mts b/generators/base-core/index.mts deleted file mode 100644 index c17317764df8..000000000000 --- a/generators/base-core/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/base-core/index.ts b/generators/base-core/index.ts new file mode 100644 index 000000000000..0147693ed244 --- /dev/null +++ b/generators/base-core/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/base-core/types-export.d.ts b/generators/base-core/types-export.d.ts index 2e9141dc69d7..96f5e22e5e44 100644 --- a/generators/base-core/types-export.d.ts +++ b/generators/base-core/types-export.d.ts @@ -1 +1 @@ -export type { default } from './index.mjs'; +export type { default } from './index.js'; diff --git a/generators/base-entity-changes/generator.mts b/generators/base-entity-changes/generator.mts deleted file mode 100644 index 6e70d1caecca..000000000000 --- a/generators/base-entity-changes/generator.mts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright 2013-2023 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 { existsSync, readFileSync } from 'fs'; -import GeneratorBaseApplication from '../base-application/index.mjs'; -import { PRIORITY_NAMES } from '../base-application/priorities.mjs'; -import { loadEntitiesAnnotations, loadEntitiesOtherSide } from '../base-application/support/index.mjs'; -import { relationshipEquals, relationshipNeedsForeignKeyRecreationOnly } from '../liquibase/support/index.mjs'; -import { addEntitiesOtherRelationships } from '../server/support/index.mjs'; -import type { BaseChangelog } from './types.js'; - -const { DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES } = PRIORITY_NAMES; - -const baseChangelog: () => Omit = () => ({ - newEntity: false, - changedEntity: false, - incremental: false, - previousEntity: undefined, - addedFields: [], - removedFields: [], - addedRelationships: [], - removedRelationships: [], - relationshipsToRecreateForeignKeysOnly: [], - changelogData: {}, -}); - -/** - * This is the base class for a generator for every generator. - */ -export default abstract class GeneratorBaseEntityChanges extends GeneratorBaseApplication { - recreateInitialChangelog!: boolean; - private entityChanges!: any[]; - - abstract isChangelogNew({ entityName, changelogDate }): boolean; - - protected getTaskFirstArgForPriority(priorityName): any { - const firstArg = super.getTaskFirstArgForPriority(priorityName); - if ([DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { - this.entityChanges = this.generateIncrementalChanges(); - } - if ([DEFAULT].includes(priorityName)) { - return { ...firstArg, entityChanges: this.entityChanges }; - } - if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { - // const { entities = [] } = this.options; - // const filteredEntities = data.entities.filter(entity => entities.includes(entity.name)); - return { ...firstArg, entityChanges: this.entityChanges }; - } - return firstArg; - } - - /** - * Generate changelog from differences between the liquibase entity and current entity. - */ - protected generateIncrementalChanges(): BaseChangelog[] { - const recreateInitialChangelog = this.recreateInitialChangelog; - const { generateBuiltInUserEntity, incrementalChangelog } = this.sharedData.getApplication(); - const entityNames = this.getExistingEntityNames(); - - const entitiesByName = Object.fromEntries(entityNames.map(entityName => [entityName, this.sharedData.getEntity(entityName)])); - const entitiesWithExistingChangelog = entityNames.filter( - entityName => !this.isChangelogNew({ entityName, changelogDate: entitiesByName[entityName].annotations?.changelogDate }), - ); - const previousEntitiesByName = Object.fromEntries( - entityNames - .filter(entityName => existsSync(this.getEntityConfigPath(entityName))) - .map(entityName => [ - entityName, - { name: entityName, ...JSON.parse(readFileSync(this.getEntityConfigPath(entityName)).toString()) }, - ]), - ); - if (generateBuiltInUserEntity) { - const user = this.sharedData.getEntity('User'); - previousEntitiesByName.User = user; - } - - const entities: any[] = Object.values(previousEntitiesByName); - loadEntitiesAnnotations(entities); - loadEntitiesOtherSide(entities); - addEntitiesOtherRelationships(entities); - - // Compare entity changes and create changelogs - return entityNames.map(entityName => { - const newConfig: any = entitiesByName[entityName]; - const newFields: any[] = (newConfig.fields || []).filter((field: any) => !field.transient); - const newRelationships: any[] = newConfig.relationships || []; - - const oldConfig: any = previousEntitiesByName[entityName]; - - if (!oldConfig || recreateInitialChangelog || !incrementalChangelog || !entitiesWithExistingChangelog.includes(entityName)) { - return { - ...baseChangelog(), - incremental: newConfig.incrementalChangelog, - changelogDate: newConfig.changelogDate, - newEntity: true, - entity: newConfig, - entityName, - }; - } - - (this as any)._debug(`Calculating diffs for ${entityName}`); - - const oldFields: any[] = (oldConfig.fields || []).filter((field: any) => !field.transient); - const oldFieldNames: string[] = oldFields.filter(field => !field.id).map(field => field.fieldName); - const newFieldNames: string[] = newFields.filter(field => !field.id).map(field => field.fieldName); - - // Calculate new fields - const addedFieldNames = newFieldNames.filter(fieldName => !oldFieldNames.includes(fieldName)); - const addedFields = addedFieldNames.map(fieldName => newFields.find(field => fieldName === field.fieldName)); - // Calculate removed fields - const removedFieldNames = oldFieldNames.filter(fieldName => !newFieldNames.includes(fieldName)); - const removedFields = removedFieldNames.map(fieldName => oldFields.find(field => fieldName === field.fieldName)); - - const oldRelationships: any[] = oldConfig.relationships || []; - - // Calculate changed/newly added relationships - const addedRelationships = newRelationships.filter( - newRelationship => - // id changes are not supported - !newRelationship.id && - // check if the same relationship wasn't already part of the old config - !oldRelationships.some(oldRelationship => relationshipEquals(oldRelationship, newRelationship)), - ); - - // Calculate to be removed relationships - const removedRelationships = oldRelationships.filter( - oldRelationship => - // id changes are not supported - !oldRelationship.id && - // check if there are relationships not anymore in the new config - !newRelationships.some(newRelationship => relationshipEquals(newRelationship, oldRelationship)), - ); - - // calculate relationships that only need a foreign key recreation from the ones that are added - // we need both the added and the removed ones here - const relationshipsToRecreateForeignKeysOnly = addedRelationships - .filter(addedRelationship => - removedRelationships.some(removedRelationship => - relationshipNeedsForeignKeyRecreationOnly(removedRelationship, addedRelationship), - ), - ) - .concat( - removedRelationships.filter(removedRelationship => - addedRelationships.some(addedRelationship => relationshipNeedsForeignKeyRecreationOnly(addedRelationship, removedRelationship)), - ), - ); - - return { - ...baseChangelog(), - previousEntity: oldConfig, - entity: newConfig, - incremental: true, - changedEntity: true, - entityName, - addedFields, - removedFields, - addedRelationships, - removedRelationships, - relationshipsToRecreateForeignKeysOnly, - }; - }); - } -} diff --git a/generators/base-entity-changes/generator.ts b/generators/base-entity-changes/generator.ts new file mode 100644 index 000000000000..15bccc572259 --- /dev/null +++ b/generators/base-entity-changes/generator.ts @@ -0,0 +1,178 @@ +/** + * Copyright 2013-2023 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 { existsSync, readFileSync } from 'fs'; +import GeneratorBaseApplication from '../base-application/index.js'; +import { PRIORITY_NAMES } from '../base-application/priorities.js'; +import { loadEntitiesAnnotations, loadEntitiesOtherSide } from '../base-application/support/index.js'; +import { relationshipEquals, relationshipNeedsForeignKeyRecreationOnly } from '../liquibase/support/index.js'; +import { addEntitiesOtherRelationships } from '../server/support/index.js'; +import type { BaseChangelog } from './types.js'; + +const { DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES } = PRIORITY_NAMES; + +const baseChangelog: () => Omit = () => ({ + newEntity: false, + changedEntity: false, + incremental: false, + previousEntity: undefined, + addedFields: [], + removedFields: [], + addedRelationships: [], + removedRelationships: [], + relationshipsToRecreateForeignKeysOnly: [], + changelogData: {}, +}); + +/** + * This is the base class for a generator for every generator. + */ +export default abstract class GeneratorBaseEntityChanges extends GeneratorBaseApplication { + recreateInitialChangelog!: boolean; + private entityChanges!: any[]; + + abstract isChangelogNew({ entityName, changelogDate }): boolean; + + protected getTaskFirstArgForPriority(priorityName): any { + const firstArg = super.getTaskFirstArgForPriority(priorityName); + if ([DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { + this.entityChanges = this.generateIncrementalChanges(); + } + if ([DEFAULT].includes(priorityName)) { + return { ...firstArg, entityChanges: this.entityChanges }; + } + if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) { + // const { entities = [] } = this.options; + // const filteredEntities = data.entities.filter(entity => entities.includes(entity.name)); + return { ...firstArg, entityChanges: this.entityChanges }; + } + return firstArg; + } + + /** + * Generate changelog from differences between the liquibase entity and current entity. + */ + protected generateIncrementalChanges(): BaseChangelog[] { + const recreateInitialChangelog = this.recreateInitialChangelog; + const { generateBuiltInUserEntity, incrementalChangelog } = this.sharedData.getApplication(); + const entityNames = this.getExistingEntityNames(); + + const entitiesByName = Object.fromEntries(entityNames.map(entityName => [entityName, this.sharedData.getEntity(entityName)])); + const entitiesWithExistingChangelog = entityNames.filter( + entityName => !this.isChangelogNew({ entityName, changelogDate: entitiesByName[entityName].annotations?.changelogDate }), + ); + const previousEntitiesByName = Object.fromEntries( + entityNames + .filter(entityName => existsSync(this.getEntityConfigPath(entityName))) + .map(entityName => [ + entityName, + { name: entityName, ...JSON.parse(readFileSync(this.getEntityConfigPath(entityName)).toString()) }, + ]), + ); + if (generateBuiltInUserEntity) { + const user = this.sharedData.getEntity('User'); + previousEntitiesByName.User = user; + } + + const entities: any[] = Object.values(previousEntitiesByName); + loadEntitiesAnnotations(entities); + loadEntitiesOtherSide(entities); + addEntitiesOtherRelationships(entities); + + // Compare entity changes and create changelogs + return entityNames.map(entityName => { + const newConfig: any = entitiesByName[entityName]; + const newFields: any[] = (newConfig.fields || []).filter((field: any) => !field.transient); + const newRelationships: any[] = newConfig.relationships || []; + + const oldConfig: any = previousEntitiesByName[entityName]; + + if (!oldConfig || recreateInitialChangelog || !incrementalChangelog || !entitiesWithExistingChangelog.includes(entityName)) { + return { + ...baseChangelog(), + incremental: newConfig.incrementalChangelog, + changelogDate: newConfig.changelogDate, + newEntity: true, + entity: newConfig, + entityName, + }; + } + + (this as any)._debug(`Calculating diffs for ${entityName}`); + + const oldFields: any[] = (oldConfig.fields || []).filter((field: any) => !field.transient); + const oldFieldNames: string[] = oldFields.filter(field => !field.id).map(field => field.fieldName); + const newFieldNames: string[] = newFields.filter(field => !field.id).map(field => field.fieldName); + + // Calculate new fields + const addedFieldNames = newFieldNames.filter(fieldName => !oldFieldNames.includes(fieldName)); + const addedFields = addedFieldNames.map(fieldName => newFields.find(field => fieldName === field.fieldName)); + // Calculate removed fields + const removedFieldNames = oldFieldNames.filter(fieldName => !newFieldNames.includes(fieldName)); + const removedFields = removedFieldNames.map(fieldName => oldFields.find(field => fieldName === field.fieldName)); + + const oldRelationships: any[] = oldConfig.relationships || []; + + // Calculate changed/newly added relationships + const addedRelationships = newRelationships.filter( + newRelationship => + // id changes are not supported + !newRelationship.id && + // check if the same relationship wasn't already part of the old config + !oldRelationships.some(oldRelationship => relationshipEquals(oldRelationship, newRelationship)), + ); + + // Calculate to be removed relationships + const removedRelationships = oldRelationships.filter( + oldRelationship => + // id changes are not supported + !oldRelationship.id && + // check if there are relationships not anymore in the new config + !newRelationships.some(newRelationship => relationshipEquals(newRelationship, oldRelationship)), + ); + + // calculate relationships that only need a foreign key recreation from the ones that are added + // we need both the added and the removed ones here + const relationshipsToRecreateForeignKeysOnly = addedRelationships + .filter(addedRelationship => + removedRelationships.some(removedRelationship => + relationshipNeedsForeignKeyRecreationOnly(removedRelationship, addedRelationship), + ), + ) + .concat( + removedRelationships.filter(removedRelationship => + addedRelationships.some(addedRelationship => relationshipNeedsForeignKeyRecreationOnly(addedRelationship, removedRelationship)), + ), + ); + + return { + ...baseChangelog(), + previousEntity: oldConfig, + entity: newConfig, + incremental: true, + changedEntity: true, + entityName, + addedFields, + removedFields, + addedRelationships, + removedRelationships, + relationshipsToRecreateForeignKeysOnly, + }; + }); + } +} diff --git a/generators/base-entity-changes/index.mts b/generators/base-entity-changes/index.mts deleted file mode 100644 index c17317764df8..000000000000 --- a/generators/base-entity-changes/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/base-entity-changes/index.ts b/generators/base-entity-changes/index.ts new file mode 100644 index 000000000000..0147693ed244 --- /dev/null +++ b/generators/base-entity-changes/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/base-entity-changes/types-export.d.ts b/generators/base-entity-changes/types-export.d.ts index 2e9141dc69d7..96f5e22e5e44 100644 --- a/generators/base-entity-changes/types-export.d.ts +++ b/generators/base-entity-changes/types-export.d.ts @@ -1 +1 @@ -export type { default } from './index.mjs'; +export type { default } from './index.js'; diff --git a/generators/base-workspaces/command.mts b/generators/base-workspaces/command.mts deleted file mode 100644 index c36a578a615d..000000000000 --- a/generators/base-workspaces/command.mts +++ /dev/null @@ -1,7 +0,0 @@ -import { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: {}, -}; - -export default command; diff --git a/generators/base-workspaces/command.ts b/generators/base-workspaces/command.ts new file mode 100644 index 000000000000..9a52554793a2 --- /dev/null +++ b/generators/base-workspaces/command.ts @@ -0,0 +1,7 @@ +import { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: {}, +}; + +export default command; diff --git a/generators/base-workspaces/generator.mts b/generators/base-workspaces/generator.mts deleted file mode 100644 index 4e4716170b28..000000000000 --- a/generators/base-workspaces/generator.mts +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright 2013-2021 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 { readdir } from 'fs/promises'; -import { existsSync } from 'fs'; -import chalk from 'chalk'; - -import BaseGenerator from '../base/index.mjs'; -import { PRIORITY_NAMES, CUSTOM_PRIORITIES } from './priorities.mjs'; -import { YO_RC_FILE } from '../generator-constants.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import command from './command.mjs'; -import { normalizePathEnd } from '../base/support/path.mjs'; - -const { - PROMPTING_WORKSPACES, - CONFIGURING_WORKSPACES, - LOADING_WORKSPACES, - PREPARING_WORKSPACES, - DEFAULT, - WRITING, - POST_WRITING, - PRE_CONFLICTS, - INSTALL, - END, -} = PRIORITY_NAMES; - -/** - * This is the base class for a generator that generates entities. - */ -export default abstract class BaseWorkspacesGenerator extends BaseGenerator { - static PROMPTING_WORKSPACES = BaseGenerator.asPriority(PROMPTING_WORKSPACES); - - static CONFIGURING_WORKSPACES = BaseGenerator.asPriority(CONFIGURING_WORKSPACES); - - static LOADING_WORKSPACES = BaseGenerator.asPriority(LOADING_WORKSPACES); - - static PREPARING_WORKSPACES = BaseGenerator.asPriority(PREPARING_WORKSPACES); - - appsFolders?: string[]; - directoryPath!: string; - - constructor(args, options, features) { - super(args, options, features); - - if (!this.options.help) { - this.registerPriorities(CUSTOM_PRIORITIES); - - this.parseJHipsterOptions(command.options); - } - } - - protected loadWorkspacesConfig({ context = this } = {}) { - context.appsFolders = this.jhipsterConfig.appsFolders; - context.directoryPath = this.jhipsterConfig.directoryPath ?? './'; - } - - protected configureWorkspacesConfig() { - this.jhipsterConfig.directoryPath = normalizePathEnd(this.jhipsterConfig.directoryPath ?? './'); - } - - protected async askForWorkspacesConfig() { - let appsFolders; - await this.prompt( - [ - { - type: 'input', - name: 'directoryPath', - message: 'Enter the root directory where your applications are located', - default: '../', - validate: async input => { - const path = this.destinationPath(input); - if (existsSync(path)) { - const applications = await this.findApplicationFolders(path); - return applications.length === 0 ? `No application found in ${path}` : true; - } - return `${path} is not a directory or doesn't exist`; - }, - }, - { - type: 'checkbox', - name: 'appsFolders', - when: async answers => { - const directoryPath = answers.directoryPath; - appsFolders = (await this.findApplicationFolders(directoryPath)).filter( - app => app !== 'jhipster-registry' && app !== 'registry', - ); - this.log.log(chalk.green(`${appsFolders.length} applications found at ${this.destinationPath(directoryPath)}\n`)); - return true; - }, - message: 'Which applications do you want to include in your configuration?', - choices: () => appsFolders, - default: () => appsFolders, - validate: input => (input.length === 0 ? 'Please choose at least one application' : true), - }, - ], - this.config, - ); - } - - protected async findApplicationFolders(directoryPath = this.directoryPath ?? '.') { - return (await readdir(this.destinationPath(directoryPath), { withFileTypes: true })) - .filter(dirent => dirent.isDirectory()) - .map(dirent => dirent.name) - .filter( - folder => - existsSync(this.destinationPath(directoryPath, folder, 'package.json')) && - existsSync(this.destinationPath(directoryPath, folder, YO_RC_FILE)), - ); - } - - private async resolveApplicationFolders({ - directoryPath = this.directoryPath, - appsFolders = this.appsFolders ?? [], - }: { directoryPath?: string; appsFolders?: string[] } = {}) { - return Object.fromEntries(appsFolders.map(appFolder => [appFolder, this.destinationPath(directoryPath ?? '.', appFolder)])); - } - - async bootstrapApplications() { - const resolvedApplicationFolders = await this.resolveApplicationFolders(); - for (const [_appFolder, resolvedFolder] of Object.entries(resolvedApplicationFolders)) { - await this.composeWithJHipster(GENERATOR_BOOTSTRAP_APPLICATION, { - generatorOptions: { destinationRoot: resolvedFolder, reproducible: true }, - } as any); - } - this.getSharedApplication(this.destinationPath()).workspacesApplications = Object.entries(resolvedApplicationFolders).map( - ([appFolder, resolvedFolder], index) => { - const application = this.getSharedApplication(resolvedFolder)?.sharedApplication; - application.appFolder = appFolder; - application.composePort = 8080 + index; - return application; - }, - ); - } - - getArgsForPriority(priorityName): any { - const args = super.getArgsForPriority(priorityName); - if ( - ![ - PROMPTING_WORKSPACES, - CONFIGURING_WORKSPACES, - LOADING_WORKSPACES, - PREPARING_WORKSPACES, - DEFAULT, - WRITING, - POST_WRITING, - PRE_CONFLICTS, - INSTALL, - END, - ].includes(priorityName) - ) { - return args; - } - const [first, ...others] = args ?? []; - const sharedData = this.getSharedApplication(this.destinationPath()); - const deployment = sharedData.sharedDeployment; - const workspaces = sharedData.sharedWorkspaces; - const applications = sharedData.workspacesApplications; - return [ - { - ...first, - workspaces, - deployment, - applications, - }, - ...others, - ]; - } -} diff --git a/generators/base-workspaces/generator.ts b/generators/base-workspaces/generator.ts new file mode 100644 index 000000000000..78ebfe113f0c --- /dev/null +++ b/generators/base-workspaces/generator.ts @@ -0,0 +1,185 @@ +/** + * Copyright 2013-2021 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 { readdir } from 'fs/promises'; +import { existsSync } from 'fs'; +import chalk from 'chalk'; + +import BaseGenerator from '../base/index.js'; +import { PRIORITY_NAMES, CUSTOM_PRIORITIES } from './priorities.js'; +import { YO_RC_FILE } from '../generator-constants.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import command from './command.js'; +import { normalizePathEnd } from '../base/support/path.js'; + +const { + PROMPTING_WORKSPACES, + CONFIGURING_WORKSPACES, + LOADING_WORKSPACES, + PREPARING_WORKSPACES, + DEFAULT, + WRITING, + POST_WRITING, + PRE_CONFLICTS, + INSTALL, + END, +} = PRIORITY_NAMES; + +/** + * This is the base class for a generator that generates entities. + */ +export default abstract class BaseWorkspacesGenerator extends BaseGenerator { + static PROMPTING_WORKSPACES = BaseGenerator.asPriority(PROMPTING_WORKSPACES); + + static CONFIGURING_WORKSPACES = BaseGenerator.asPriority(CONFIGURING_WORKSPACES); + + static LOADING_WORKSPACES = BaseGenerator.asPriority(LOADING_WORKSPACES); + + static PREPARING_WORKSPACES = BaseGenerator.asPriority(PREPARING_WORKSPACES); + + appsFolders?: string[]; + directoryPath!: string; + + constructor(args, options, features) { + super(args, options, features); + + if (!this.options.help) { + this.registerPriorities(CUSTOM_PRIORITIES); + + this.parseJHipsterOptions(command.options); + } + } + + protected loadWorkspacesConfig({ context = this } = {}) { + context.appsFolders = this.jhipsterConfig.appsFolders; + context.directoryPath = this.jhipsterConfig.directoryPath ?? './'; + } + + protected configureWorkspacesConfig() { + this.jhipsterConfig.directoryPath = normalizePathEnd(this.jhipsterConfig.directoryPath ?? './'); + } + + protected async askForWorkspacesConfig() { + let appsFolders; + await this.prompt( + [ + { + type: 'input', + name: 'directoryPath', + message: 'Enter the root directory where your applications are located', + default: '../', + validate: async input => { + const path = this.destinationPath(input); + if (existsSync(path)) { + const applications = await this.findApplicationFolders(path); + return applications.length === 0 ? `No application found in ${path}` : true; + } + return `${path} is not a directory or doesn't exist`; + }, + }, + { + type: 'checkbox', + name: 'appsFolders', + when: async answers => { + const directoryPath = answers.directoryPath; + appsFolders = (await this.findApplicationFolders(directoryPath)).filter( + app => app !== 'jhipster-registry' && app !== 'registry', + ); + this.log.log(chalk.green(`${appsFolders.length} applications found at ${this.destinationPath(directoryPath)}\n`)); + return true; + }, + message: 'Which applications do you want to include in your configuration?', + choices: () => appsFolders, + default: () => appsFolders, + validate: input => (input.length === 0 ? 'Please choose at least one application' : true), + }, + ], + this.config, + ); + } + + protected async findApplicationFolders(directoryPath = this.directoryPath ?? '.') { + return (await readdir(this.destinationPath(directoryPath), { withFileTypes: true })) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + .filter( + folder => + existsSync(this.destinationPath(directoryPath, folder, 'package.json')) && + existsSync(this.destinationPath(directoryPath, folder, YO_RC_FILE)), + ); + } + + private async resolveApplicationFolders({ + directoryPath = this.directoryPath, + appsFolders = this.appsFolders ?? [], + }: { directoryPath?: string; appsFolders?: string[] } = {}) { + return Object.fromEntries(appsFolders.map(appFolder => [appFolder, this.destinationPath(directoryPath ?? '.', appFolder)])); + } + + async bootstrapApplications() { + const resolvedApplicationFolders = await this.resolveApplicationFolders(); + for (const [_appFolder, resolvedFolder] of Object.entries(resolvedApplicationFolders)) { + await this.composeWithJHipster(GENERATOR_BOOTSTRAP_APPLICATION, { + generatorOptions: { destinationRoot: resolvedFolder, reproducible: true }, + } as any); + } + this.getSharedApplication(this.destinationPath()).workspacesApplications = Object.entries(resolvedApplicationFolders).map( + ([appFolder, resolvedFolder], index) => { + const application = this.getSharedApplication(resolvedFolder)?.sharedApplication; + application.appFolder = appFolder; + application.composePort = 8080 + index; + return application; + }, + ); + } + + getArgsForPriority(priorityName): any { + const args = super.getArgsForPriority(priorityName); + if ( + ![ + PROMPTING_WORKSPACES, + CONFIGURING_WORKSPACES, + LOADING_WORKSPACES, + PREPARING_WORKSPACES, + DEFAULT, + WRITING, + POST_WRITING, + PRE_CONFLICTS, + INSTALL, + END, + ].includes(priorityName) + ) { + return args; + } + const [first, ...others] = args ?? []; + const sharedData = this.getSharedApplication(this.destinationPath()); + const deployment = sharedData.sharedDeployment; + const workspaces = sharedData.sharedWorkspaces; + const applications = sharedData.workspacesApplications; + return [ + { + ...first, + workspaces, + deployment, + applications, + }, + ...others, + ]; + } +} diff --git a/generators/base-workspaces/index.mts b/generators/base-workspaces/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/base-workspaces/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/base-workspaces/index.ts b/generators/base-workspaces/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/base-workspaces/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/base-workspaces/internal/deployments.mts b/generators/base-workspaces/internal/deployments.mts deleted file mode 100644 index 71cdd880e496..000000000000 --- a/generators/base-workspaces/internal/deployments.mts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2013-2021 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 * as _ from 'lodash-es'; -import { applicationOptions, deploymentOptions } from '../../../jdl/index.js'; -import { loadDerivedPlatformConfig, loadPlatformConfig, loadDerivedServerAndPlatformProperties } from '../../server/support/index.mjs'; -import type { GeneratorBaseCore } from '../../index.js'; - -const { OptionNames } = applicationOptions; -const { Options: DeploymentOptions } = deploymentOptions; - -const { JWT_SECRET_KEY } = OptionNames; - -export function loadDeploymentConfig( - this: GeneratorBaseCore, - { - config = _.defaults({}, this.jhipsterConfig, DeploymentOptions.defaults(this.jhipsterConfig.deploymentType)), - deployment = this, - }: { config?: any; deployment?: any } = {}, -) { - deployment.appsFolders = config.appsFolders; - deployment.directoryPath = config.directoryPath; - deployment.clusteredDbApps = config.clusteredDbApps; - deployment.dockerRepositoryName = config.dockerRepositoryName; - deployment.dockerPushCommand = config.dockerPushCommand; - deployment.adminPassword = config.adminPassword; - deployment.jwtSecretKey = config[JWT_SECRET_KEY]; - loadPlatformConfig({ config, application: deployment }); - loadDerivedPlatformConfig({ application: deployment }); - loadDerivedServerAndPlatformProperties({ application: deployment }); -} diff --git a/generators/base-workspaces/internal/deployments.ts b/generators/base-workspaces/internal/deployments.ts new file mode 100644 index 000000000000..50b61960902f --- /dev/null +++ b/generators/base-workspaces/internal/deployments.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2013-2021 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 * as _ from 'lodash-es'; +import { applicationOptions, deploymentOptions } from '../../../jdl/index.js'; +import { loadDerivedPlatformConfig, loadPlatformConfig, loadDerivedServerAndPlatformProperties } from '../../server/support/index.js'; +import type { GeneratorBaseCore } from '../../index.js'; + +const { OptionNames } = applicationOptions; +const { Options: DeploymentOptions } = deploymentOptions; + +const { JWT_SECRET_KEY } = OptionNames; + +export function loadDeploymentConfig( + this: GeneratorBaseCore, + { + config = _.defaults({}, this.jhipsterConfig, DeploymentOptions.defaults(this.jhipsterConfig.deploymentType)), + deployment = this, + }: { config?: any; deployment?: any } = {}, +) { + deployment.appsFolders = config.appsFolders; + deployment.directoryPath = config.directoryPath; + deployment.clusteredDbApps = config.clusteredDbApps; + deployment.dockerRepositoryName = config.dockerRepositoryName; + deployment.dockerPushCommand = config.dockerPushCommand; + deployment.adminPassword = config.adminPassword; + deployment.jwtSecretKey = config[JWT_SECRET_KEY]; + loadPlatformConfig({ config, application: deployment }); + loadDerivedPlatformConfig({ application: deployment }); + loadDerivedServerAndPlatformProperties({ application: deployment }); +} diff --git a/generators/base-workspaces/internal/docker-base.js b/generators/base-workspaces/internal/docker-base.js new file mode 100644 index 000000000000..f62f9817de67 --- /dev/null +++ b/generators/base-workspaces/internal/docker-base.js @@ -0,0 +1,150 @@ +/** + * Copyright 2013-2023 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 { existsSync } from 'fs'; +import chalk from 'chalk'; + +import { convertSecretToBase64, createBase64Secret, removeFieldsWithNullishValues } from '../../base/support/index.js'; +import { applicationTypes, buildToolTypes, getConfigWithDefaults } from '../../../jdl/jhipster/index.js'; +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'; + +const { MAVEN } = buildToolTypes; +const { MONOLITH, MICROSERVICE, GATEWAY } = applicationTypes; + +export { checkDocker } from '../../docker/support/index.js'; + +/** + * Check Images + */ +export function checkImages() { + this.log.log('\nChecking Docker images in applications directories...'); + + let imagePath = ''; + let runCommand = ''; + this.hasWarning = false; + this.warningMessage = 'To generate the missing Docker image(s), please run:\n'; + this.appsFolders.forEach((appsFolder, index) => { + const appConfig = this.appConfigs[index]; + if (appConfig.buildTool === MAVEN) { + imagePath = this.destinationPath(`${this.directoryPath + appsFolder}/target/jib-cache`); + runCommand = `./mvnw -ntp -Pprod verify jib:dockerBuild${process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : ''}`; + } else { + imagePath = this.destinationPath(`${this.directoryPath + appsFolder}/build/jib-cache`); + runCommand = `./gradlew bootJar -Pprod jibDockerBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''}`; + } + if (!existsSync(imagePath)) { + this.hasWarning = true; + this.warningMessage += ` ${chalk.cyan(runCommand)} in ${this.destinationPath(this.directoryPath + appsFolder)}\n`; + } + }); +} + +/** + * Generate Jwt Secret + */ +export function generateJwtSecret() { + if (this.jwtSecretKey === undefined) { + this.jwtSecretKey = this.jhipsterConfig.jwtSecretKey = createBase64Secret(this.options.reproducibleTests); + } +} + +/** + * Configure Image Names + */ +export function configureImageNames() { + for (let i = 0; i < this.appsFolders.length; i++) { + const originalImageName = this.appConfigs[i].baseName.toLowerCase(); + const targetImageName = this.dockerRepositoryName ? `${this.dockerRepositoryName}/${originalImageName}` : originalImageName; + this.appConfigs[i].targetImageName = targetImageName; + } +} + +/** + * Load config from this.appFolders + */ +export function loadConfigs() { + this.appConfigs = []; + this.gatewayNb = 0; + this.monolithicNb = 0; + this.microserviceNb = 0; + const serverPort = 8080; + + const getJhipsterConfig = yoRcPath => this.createStorage(yoRcPath, GENERATOR_JHIPSTER); + + // Loading configs + this.log.debug(`Apps folders: ${this.appsFolders}`); + this.appsFolders.forEach((appFolder, index) => { + const path = this.destinationPath(`${this.directoryPath + appFolder}`); + this.log.debug(chalk.red.bold(`App folder ${path}`)); + if (this.fs.exists(`${path}/.yo-rc.json`)) { + const config = getConfigWithDefaults(removeFieldsWithNullishValues(getJhipsterConfig(`${path}/.yo-rc.json`).getAll())); + config.composePort = serverPort + index; + this.log.debug(chalk.red.bold(`${config.baseName} has compose port ${config.composePort} and appIndex ${config.applicationIndex}`)); + + loadDerivedAppConfig({ application: config }); + loadDerivedPlatformConfig({ application: config }); + loadDerivedServerConfig({ application: config }); + + if (config.applicationType === MONOLITH) { + this.monolithicNb++; + } else if (config.applicationType === GATEWAY) { + this.gatewayNb++; + } else if (config.applicationType === MICROSERVICE) { + this.microserviceNb++; + } + + this.portsToBind = this.monolithicNb + this.gatewayNb; + config.appFolder = appFolder; + this.appConfigs.push(config); + } else { + throw new Error(`Application '${appFolder}' is not found in the path '${this.directoryPath}'`); + } + }); +} + +export function setClusteredApps() { + for (let i = 0; i < this.appsFolders.length; i++) { + for (let j = 0; j < this.clusteredDbApps.length; j++) { + this.appConfigs[i].clusteredDb = this.appsFolders[i] === this.clusteredDbApps[j]; + } + } +} + +export function loadFromYoRc() { + loadDeploymentConfig.call(this); + + this.useKafka = false; + this.usePulsar = false; + this.useMemcached = false; + this.useRedis = false; + + loadConfigs.call(this); + if (this.microserviceNb > 0 || this.gatewayNb > 0) { + this.deploymentApplicationType = MICROSERVICE; + } else { + this.deploymentApplicationType = MONOLITH; + } + setClusteredApps.call(this); + if (!this.adminPassword) { + this.adminPassword = 'admin'; // TODO find a better way to do this + this.adminPasswordBase64 = convertSecretToBase64(this.adminPassword); + } +} diff --git a/generators/base-workspaces/internal/docker-base.mjs b/generators/base-workspaces/internal/docker-base.mjs deleted file mode 100644 index 7e79ba97f7ed..000000000000 --- a/generators/base-workspaces/internal/docker-base.mjs +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright 2013-2023 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 { existsSync } from 'fs'; -import chalk from 'chalk'; - -import { convertSecretToBase64, createBase64Secret, removeFieldsWithNullishValues } from '../../base/support/index.mjs'; -import { applicationTypes, buildToolTypes, getConfigWithDefaults } from '../../../jdl/jhipster/index.mjs'; -import { GENERATOR_JHIPSTER } from '../../generator-constants.mjs'; -import { loadDeploymentConfig } from '../../base-workspaces/internal/index.mjs'; -import { loadDerivedAppConfig } from '../../app/support/index.mjs'; -import { loadDerivedPlatformConfig, loadDerivedServerConfig } from '../../server/support/index.mjs'; - -const { MAVEN } = buildToolTypes; -const { MONOLITH, MICROSERVICE, GATEWAY } = applicationTypes; - -export { checkDocker } from '../../docker/support/index.mjs'; - -/** - * Check Images - */ -export function checkImages() { - this.log.log('\nChecking Docker images in applications directories...'); - - let imagePath = ''; - let runCommand = ''; - this.hasWarning = false; - this.warningMessage = 'To generate the missing Docker image(s), please run:\n'; - this.appsFolders.forEach((appsFolder, index) => { - const appConfig = this.appConfigs[index]; - if (appConfig.buildTool === MAVEN) { - imagePath = this.destinationPath(`${this.directoryPath + appsFolder}/target/jib-cache`); - runCommand = `./mvnw -ntp -Pprod verify jib:dockerBuild${process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : ''}`; - } else { - imagePath = this.destinationPath(`${this.directoryPath + appsFolder}/build/jib-cache`); - runCommand = `./gradlew bootJar -Pprod jibDockerBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''}`; - } - if (!existsSync(imagePath)) { - this.hasWarning = true; - this.warningMessage += ` ${chalk.cyan(runCommand)} in ${this.destinationPath(this.directoryPath + appsFolder)}\n`; - } - }); -} - -/** - * Generate Jwt Secret - */ -export function generateJwtSecret() { - if (this.jwtSecretKey === undefined) { - this.jwtSecretKey = this.jhipsterConfig.jwtSecretKey = createBase64Secret(this.options.reproducibleTests); - } -} - -/** - * Configure Image Names - */ -export function configureImageNames() { - for (let i = 0; i < this.appsFolders.length; i++) { - const originalImageName = this.appConfigs[i].baseName.toLowerCase(); - const targetImageName = this.dockerRepositoryName ? `${this.dockerRepositoryName}/${originalImageName}` : originalImageName; - this.appConfigs[i].targetImageName = targetImageName; - } -} - -/** - * Load config from this.appFolders - */ -export function loadConfigs() { - this.appConfigs = []; - this.gatewayNb = 0; - this.monolithicNb = 0; - this.microserviceNb = 0; - const serverPort = 8080; - - const getJhipsterConfig = yoRcPath => this.createStorage(yoRcPath, GENERATOR_JHIPSTER); - - // Loading configs - this.log.debug(`Apps folders: ${this.appsFolders}`); - this.appsFolders.forEach((appFolder, index) => { - const path = this.destinationPath(`${this.directoryPath + appFolder}`); - this.log.debug(chalk.red.bold(`App folder ${path}`)); - if (this.fs.exists(`${path}/.yo-rc.json`)) { - const config = getConfigWithDefaults(removeFieldsWithNullishValues(getJhipsterConfig(`${path}/.yo-rc.json`).getAll())); - config.composePort = serverPort + index; - this.log.debug(chalk.red.bold(`${config.baseName} has compose port ${config.composePort} and appIndex ${config.applicationIndex}`)); - - loadDerivedAppConfig({ application: config }); - loadDerivedPlatformConfig({ application: config }); - loadDerivedServerConfig({ application: config }); - - if (config.applicationType === MONOLITH) { - this.monolithicNb++; - } else if (config.applicationType === GATEWAY) { - this.gatewayNb++; - } else if (config.applicationType === MICROSERVICE) { - this.microserviceNb++; - } - - this.portsToBind = this.monolithicNb + this.gatewayNb; - config.appFolder = appFolder; - this.appConfigs.push(config); - } else { - throw new Error(`Application '${appFolder}' is not found in the path '${this.directoryPath}'`); - } - }); -} - -export function setClusteredApps() { - for (let i = 0; i < this.appsFolders.length; i++) { - for (let j = 0; j < this.clusteredDbApps.length; j++) { - this.appConfigs[i].clusteredDb = this.appsFolders[i] === this.clusteredDbApps[j]; - } - } -} - -export function loadFromYoRc() { - loadDeploymentConfig.call(this); - - this.useKafka = false; - this.usePulsar = false; - this.useMemcached = false; - this.useRedis = false; - - loadConfigs.call(this); - if (this.microserviceNb > 0 || this.gatewayNb > 0) { - this.deploymentApplicationType = MICROSERVICE; - } else { - this.deploymentApplicationType = MONOLITH; - } - setClusteredApps.call(this); - if (!this.adminPassword) { - this.adminPassword = 'admin'; // TODO find a better way to do this - this.adminPasswordBase64 = convertSecretToBase64(this.adminPassword); - } -} diff --git a/generators/base-workspaces/internal/docker-dependencies.mts b/generators/base-workspaces/internal/docker-dependencies.mts deleted file mode 100644 index c5194f69d700..000000000000 --- a/generators/base-workspaces/internal/docker-dependencies.mts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2013-2021 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 { dockerContainers as elasticDockerContainer } from '../../generator-constants.mjs'; -import { dockerPlaceholderGenerator, getDockerfileContainers } from '../../docker/utils.mjs'; - -export async function loadDockerDependenciesTask(this: any, { context = this } = {}) { - const dockerfile = this.readTemplate(this.jhipsterTemplatePath('../../server/resources/Dockerfile')); - context.dockerContainers = this.prepareDependencies( - { - ...elasticDockerContainer, - ...getDockerfileContainers(dockerfile), - }, - dockerPlaceholderGenerator, - ); -} diff --git a/generators/base-workspaces/internal/docker-dependencies.ts b/generators/base-workspaces/internal/docker-dependencies.ts new file mode 100644 index 000000000000..05e567e16b32 --- /dev/null +++ b/generators/base-workspaces/internal/docker-dependencies.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2013-2021 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 { dockerContainers as elasticDockerContainer } from '../../generator-constants.js'; +import { dockerPlaceholderGenerator, getDockerfileContainers } from '../../docker/utils.js'; + +export async function loadDockerDependenciesTask(this: any, { context = this } = {}) { + const dockerfile = this.readTemplate(this.jhipsterTemplatePath('../../server/resources/Dockerfile')); + context.dockerContainers = this.prepareDependencies( + { + ...elasticDockerContainer, + ...getDockerfileContainers(dockerfile), + }, + dockerPlaceholderGenerator, + ); +} diff --git a/generators/base-workspaces/internal/docker-prompts.js b/generators/base-workspaces/internal/docker-prompts.js new file mode 100644 index 000000000000..338935367ce1 --- /dev/null +++ b/generators/base-workspaces/internal/docker-prompts.js @@ -0,0 +1,405 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; + +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import { loadConfigs } from './docker-base.js'; +import { applicationTypes, monitoringTypes, serviceDiscoveryTypes } from '../../../jdl/jhipster/index.js'; +import { convertSecretToBase64 } from '../../base/support/index.js'; + +const { MICROSERVICE, MONOLITH, GATEWAY } = applicationTypes; +const { PROMETHEUS } = monitoringTypes; +const monitoring = monitoringTypes; + +const NO_MONITORING = monitoring.NO; +const { CONSUL, EUREKA, NO: NO_SERVICE_DISCOVERY } = serviceDiscoveryTypes; + +export default { + askForApplicationType, + askForGatewayType, + askForPath, + askForApps, + askForClustersMode, + askForMonitoring, + askForServiceDiscovery, + askForAdminPassword, + askForDockerRepositoryName, + askForDockerPushCommand, + loadConfigs, +}; + +/** + * Ask For Application Type + */ +async function askForApplicationType() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'list', + name: 'deploymentApplicationType', + message: 'Which *type* of application would you like to deploy?', + choices: [ + { + value: MONOLITH, + name: 'Monolithic application', + }, + { + value: MICROSERVICE, + name: 'Microservice application', + }, + ], + default: MONOLITH, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.deploymentApplicationType = props.deploymentApplicationType; +} + +/** + * Ask For Gateway Type + */ +async function askForGatewayType() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + if (this.deploymentApplicationType !== MICROSERVICE) return; + + const prompts = [ + { + type: 'list', + name: 'gatewayType', + message: 'Which *type* of gateway would you like to use?', + choices: [ + { + value: 'SpringCloudGateway', + name: 'JHipster gateway based on Spring Cloud Gateway', + }, + ], + default: 'SpringCloudGateway', + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.gatewayType = props.gatewayType; +} + +/** + * Ask For Path + */ +async function askForPath() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const deploymentApplicationType = this.deploymentApplicationType; + let messageAskForPath; + if (deploymentApplicationType === MONOLITH) { + messageAskForPath = 'Enter the root directory where your applications are located'; + } else { + messageAskForPath = 'Enter the root directory where your gateway(s) and microservices are located'; + } + const prompts = [ + { + type: 'input', + name: 'directoryPath', + message: messageAskForPath, + default: this.directoryPath || '../', + validate: async input => { + const path = this.destinationPath(input); + try { + if (statSync(path).isDirectory) { + const appsFolders = getAppFolders.call(this, path, deploymentApplicationType); + + if (appsFolders.length === 0) { + return deploymentApplicationType === MONOLITH + ? `No monolith found in ${path}` + : `No microservice or gateway found in ${path}`; + } + return true; + } + } catch { + // Ignore error + } + return `${path} is not a directory or doesn't exist`; + }, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.directoryPath = props.directoryPath; + // Patch the path if there is no trailing "/" + if (!this.directoryPath.endsWith('/')) { + this.log.log(chalk.yellow(`The path "${this.directoryPath}" does not end with a trailing "/", adding it anyway.`)); + this.directoryPath += '/'; + } + + this.appsFolders = getAppFolders.call(this, this.destinationPath(this.directoryPath), deploymentApplicationType); + + // Removing registry from appsFolders, using reverse for loop + for (let i = this.appsFolders.length - 1; i >= 0; i--) { + if (this.appsFolders[i] === 'jhipster-registry' || this.appsFolders[i] === 'registry') { + this.appsFolders.splice(i, 1); + } + } + + this.log.log(chalk.green(`${this.appsFolders.length} applications found at ${this.destinationPath(this.directoryPath)}\n`)); +} + +/** + * Ask For Apps + */ +async function askForApps() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const messageAskForApps = 'Which applications do you want to include in your configuration?'; + + const prompts = [ + { + type: 'checkbox', + name: 'chosenApps', + message: messageAskForApps, + choices: this.appsFolders ?? [], + default: this.jhipsterConfig.appsFolders, + validate: input => (input.length === 0 ? 'Please choose at least one application' : true), + }, + ]; + + const props = await this.prompt(prompts); + this.appsFolders = this.jhipsterConfig.appsFolders = props.chosenApps; + loadConfigs.call(this); +} + +/** + * Ask For Clusters Mode + */ +async function askForClustersMode() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const clusteredDbApps = []; + this.appConfigs.forEach((appConfig, index) => { + if (appConfig.databaseTypeMongodb || appConfig.databaseTypeCouchbase) { + clusteredDbApps.push(this.appsFolders[index]); + } + }); + if (clusteredDbApps.length === 0) return; + + const prompts = [ + { + type: 'checkbox', + name: 'clusteredDbApps', + message: 'Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)?', + choices: clusteredDbApps, + default: this.clusteredDbApps, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.clusteredDbApps = props.clusteredDbApps; +} + +/** + * Ask For Monitoring + */ +async function askForMonitoring() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'list', + name: 'monitoring', + message: 'Do you want to setup monitoring for your applications ?', + choices: [ + { + value: NO_MONITORING, + name: 'No', + }, + { + value: PROMETHEUS, + name: 'Yes, for metrics only with Prometheus', + }, + ], + default: this.monitoring ? this.monitoring : NO_MONITORING, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.monitoring = props.monitoring; +} + +/** + * Ask For Service Discovery + */ +async function askForServiceDiscovery() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const serviceDiscoveryEnabledApps = []; + this.appConfigs.forEach(appConfig => { + if (appConfig.serviceDiscoveryAny) { + serviceDiscoveryEnabledApps.push({ + baseName: appConfig.baseName, + serviceDiscoveryType: appConfig.serviceDiscoveryType, + }); + } + }); + + if (serviceDiscoveryEnabledApps.length === 0) { + this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = NO_SERVICE_DISCOVERY; + return; + } + + if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryType === CONSUL)) { + this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = CONSUL; + this.log.log(chalk.green('Consul detected as the service discovery and configuration provider used by your apps')); + } else if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryType === EUREKA)) { + this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = EUREKA; + this.log.log(chalk.green('JHipster registry detected as the service discovery and configuration provider used by your apps')); + } else { + this.log.warn( + chalk.yellow('Unable to determine the service discovery and configuration provider to use from your apps configuration.'), + ); + this.log.verboseInfo('Your service discovery enabled apps:'); + serviceDiscoveryEnabledApps.forEach(app => { + this.log.verboseInfo(` -${app.baseName} (${app.serviceDiscoveryType})`); + }); + + const prompts = [ + { + type: 'list', + name: 'serviceDiscoveryType', + message: 'Which Service Discovery registry and Configuration server would you like to use ?', + choices: [ + { + value: CONSUL, + name: 'Consul', + }, + { + value: EUREKA, + name: 'JHipster Registry', + }, + { + value: NO_SERVICE_DISCOVERY, + name: 'No Service Discovery and Configuration', + }, + ], + default: CONSUL, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.serviceDiscoveryType = props.serviceDiscoveryType; + } +} + +/** + * Ask For Admin Password + */ +async function askForAdminPassword() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + if (this.serviceDiscoveryType !== EUREKA) return; + + const prompts = [ + { + type: 'input', + name: 'adminPassword', + message: 'Enter the admin password used to secure the JHipster Registry', + default: 'admin', + validate: input => (input.length < 5 ? 'The password must have at least 5 characters' : true), + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.adminPassword = props.adminPassword; + this.adminPasswordBase64 = convertSecretToBase64(this.adminPassword); +} + +/** + * Ask For Docker Repository Name + */ +async function askForDockerRepositoryName() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'input', + name: 'dockerRepositoryName', + message: 'What should we use for the base Docker repository name?', + default: this.dockerRepositoryName, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.dockerRepositoryName = props.dockerRepositoryName; +} + +/** + * Ask For Docker Push Command + */ +async function askForDockerPushCommand() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'input', + name: 'dockerPushCommand', + message: 'What command should we use for push Docker image to repository?', + default: this.dockerPushCommand ? this.dockerPushCommand : 'docker push', + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.dockerPushCommand = props.dockerPushCommand; +} + +/** + * Get App Folders + * @param input path to join to destination path + * @param deploymentApplicationType type of application being composed + * @returns {Array} array of string representing app folders + */ +export function getAppFolders(directory, deploymentApplicationType) { + const files = readdirSync(directory); + const appsFolders = []; + + files.forEach(file => { + try { + if (statSync(join(directory, file)).isDirectory()) { + const yoRcFile = join(directory, file, '.yo-rc.json'); + if (statSync(yoRcFile).isFile()) { + try { + const fileData = JSON.parse(readFileSync(yoRcFile).toString()); + if ( + fileData['generator-jhipster'].baseName !== undefined && + (deploymentApplicationType === undefined || + deploymentApplicationType === (fileData['generator-jhipster'].applicationType ?? MONOLITH) || + (deploymentApplicationType === MICROSERVICE && fileData['generator-jhipster'].applicationType === GATEWAY)) + ) { + appsFolders.push(file.match(/([^/]*)\/*$/)[1]); + } + } catch (err) { + this.log.error(chalk.red(`${yoRcFile}: this .yo-rc.json can't be read`)); + this.log.debug('Error:', err); + } + } + } + } catch { + // Not a file or directory + } + }); + + return appsFolders; +} diff --git a/generators/base-workspaces/internal/docker-prompts.mjs b/generators/base-workspaces/internal/docker-prompts.mjs deleted file mode 100644 index ead612f2a104..000000000000 --- a/generators/base-workspaces/internal/docker-prompts.mjs +++ /dev/null @@ -1,405 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; - -import { readFileSync, readdirSync, statSync } from 'node:fs'; -import { join } from 'node:path'; -import { loadConfigs } from './docker-base.mjs'; -import { applicationTypes, monitoringTypes, serviceDiscoveryTypes } from '../../../jdl/jhipster/index.mjs'; -import { convertSecretToBase64 } from '../../base/support/index.mjs'; - -const { MICROSERVICE, MONOLITH, GATEWAY } = applicationTypes; -const { PROMETHEUS } = monitoringTypes; -const monitoring = monitoringTypes; - -const NO_MONITORING = monitoring.NO; -const { CONSUL, EUREKA, NO: NO_SERVICE_DISCOVERY } = serviceDiscoveryTypes; - -export default { - askForApplicationType, - askForGatewayType, - askForPath, - askForApps, - askForClustersMode, - askForMonitoring, - askForServiceDiscovery, - askForAdminPassword, - askForDockerRepositoryName, - askForDockerPushCommand, - loadConfigs, -}; - -/** - * Ask For Application Type - */ -async function askForApplicationType() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'list', - name: 'deploymentApplicationType', - message: 'Which *type* of application would you like to deploy?', - choices: [ - { - value: MONOLITH, - name: 'Monolithic application', - }, - { - value: MICROSERVICE, - name: 'Microservice application', - }, - ], - default: MONOLITH, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.deploymentApplicationType = props.deploymentApplicationType; -} - -/** - * Ask For Gateway Type - */ -async function askForGatewayType() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - if (this.deploymentApplicationType !== MICROSERVICE) return; - - const prompts = [ - { - type: 'list', - name: 'gatewayType', - message: 'Which *type* of gateway would you like to use?', - choices: [ - { - value: 'SpringCloudGateway', - name: 'JHipster gateway based on Spring Cloud Gateway', - }, - ], - default: 'SpringCloudGateway', - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.gatewayType = props.gatewayType; -} - -/** - * Ask For Path - */ -async function askForPath() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const deploymentApplicationType = this.deploymentApplicationType; - let messageAskForPath; - if (deploymentApplicationType === MONOLITH) { - messageAskForPath = 'Enter the root directory where your applications are located'; - } else { - messageAskForPath = 'Enter the root directory where your gateway(s) and microservices are located'; - } - const prompts = [ - { - type: 'input', - name: 'directoryPath', - message: messageAskForPath, - default: this.directoryPath || '../', - validate: async input => { - const path = this.destinationPath(input); - try { - if (statSync(path).isDirectory) { - const appsFolders = getAppFolders.call(this, path, deploymentApplicationType); - - if (appsFolders.length === 0) { - return deploymentApplicationType === MONOLITH - ? `No monolith found in ${path}` - : `No microservice or gateway found in ${path}`; - } - return true; - } - } catch { - // Ignore error - } - return `${path} is not a directory or doesn't exist`; - }, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.directoryPath = props.directoryPath; - // Patch the path if there is no trailing "/" - if (!this.directoryPath.endsWith('/')) { - this.log.log(chalk.yellow(`The path "${this.directoryPath}" does not end with a trailing "/", adding it anyway.`)); - this.directoryPath += '/'; - } - - this.appsFolders = getAppFolders.call(this, this.destinationPath(this.directoryPath), deploymentApplicationType); - - // Removing registry from appsFolders, using reverse for loop - for (let i = this.appsFolders.length - 1; i >= 0; i--) { - if (this.appsFolders[i] === 'jhipster-registry' || this.appsFolders[i] === 'registry') { - this.appsFolders.splice(i, 1); - } - } - - this.log.log(chalk.green(`${this.appsFolders.length} applications found at ${this.destinationPath(this.directoryPath)}\n`)); -} - -/** - * Ask For Apps - */ -async function askForApps() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const messageAskForApps = 'Which applications do you want to include in your configuration?'; - - const prompts = [ - { - type: 'checkbox', - name: 'chosenApps', - message: messageAskForApps, - choices: this.appsFolders ?? [], - default: this.jhipsterConfig.appsFolders, - validate: input => (input.length === 0 ? 'Please choose at least one application' : true), - }, - ]; - - const props = await this.prompt(prompts); - this.appsFolders = this.jhipsterConfig.appsFolders = props.chosenApps; - loadConfigs.call(this); -} - -/** - * Ask For Clusters Mode - */ -async function askForClustersMode() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const clusteredDbApps = []; - this.appConfigs.forEach((appConfig, index) => { - if (appConfig.databaseTypeMongodb || appConfig.databaseTypeCouchbase) { - clusteredDbApps.push(this.appsFolders[index]); - } - }); - if (clusteredDbApps.length === 0) return; - - const prompts = [ - { - type: 'checkbox', - name: 'clusteredDbApps', - message: 'Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)?', - choices: clusteredDbApps, - default: this.clusteredDbApps, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.clusteredDbApps = props.clusteredDbApps; -} - -/** - * Ask For Monitoring - */ -async function askForMonitoring() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'list', - name: 'monitoring', - message: 'Do you want to setup monitoring for your applications ?', - choices: [ - { - value: NO_MONITORING, - name: 'No', - }, - { - value: PROMETHEUS, - name: 'Yes, for metrics only with Prometheus', - }, - ], - default: this.monitoring ? this.monitoring : NO_MONITORING, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.monitoring = props.monitoring; -} - -/** - * Ask For Service Discovery - */ -async function askForServiceDiscovery() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const serviceDiscoveryEnabledApps = []; - this.appConfigs.forEach(appConfig => { - if (appConfig.serviceDiscoveryAny) { - serviceDiscoveryEnabledApps.push({ - baseName: appConfig.baseName, - serviceDiscoveryType: appConfig.serviceDiscoveryType, - }); - } - }); - - if (serviceDiscoveryEnabledApps.length === 0) { - this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = NO_SERVICE_DISCOVERY; - return; - } - - if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryType === CONSUL)) { - this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = CONSUL; - this.log.log(chalk.green('Consul detected as the service discovery and configuration provider used by your apps')); - } else if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryType === EUREKA)) { - this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = EUREKA; - this.log.log(chalk.green('JHipster registry detected as the service discovery and configuration provider used by your apps')); - } else { - this.log.warn( - chalk.yellow('Unable to determine the service discovery and configuration provider to use from your apps configuration.'), - ); - this.log.verboseInfo('Your service discovery enabled apps:'); - serviceDiscoveryEnabledApps.forEach(app => { - this.log.verboseInfo(` -${app.baseName} (${app.serviceDiscoveryType})`); - }); - - const prompts = [ - { - type: 'list', - name: 'serviceDiscoveryType', - message: 'Which Service Discovery registry and Configuration server would you like to use ?', - choices: [ - { - value: CONSUL, - name: 'Consul', - }, - { - value: EUREKA, - name: 'JHipster Registry', - }, - { - value: NO_SERVICE_DISCOVERY, - name: 'No Service Discovery and Configuration', - }, - ], - default: CONSUL, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.serviceDiscoveryType = props.serviceDiscoveryType; - } -} - -/** - * Ask For Admin Password - */ -async function askForAdminPassword() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - if (this.serviceDiscoveryType !== EUREKA) return; - - const prompts = [ - { - type: 'input', - name: 'adminPassword', - message: 'Enter the admin password used to secure the JHipster Registry', - default: 'admin', - validate: input => (input.length < 5 ? 'The password must have at least 5 characters' : true), - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.adminPassword = props.adminPassword; - this.adminPasswordBase64 = convertSecretToBase64(this.adminPassword); -} - -/** - * Ask For Docker Repository Name - */ -async function askForDockerRepositoryName() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'input', - name: 'dockerRepositoryName', - message: 'What should we use for the base Docker repository name?', - default: this.dockerRepositoryName, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.dockerRepositoryName = props.dockerRepositoryName; -} - -/** - * Ask For Docker Push Command - */ -async function askForDockerPushCommand() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'input', - name: 'dockerPushCommand', - message: 'What command should we use for push Docker image to repository?', - default: this.dockerPushCommand ? this.dockerPushCommand : 'docker push', - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.dockerPushCommand = props.dockerPushCommand; -} - -/** - * Get App Folders - * @param input path to join to destination path - * @param deploymentApplicationType type of application being composed - * @returns {Array} array of string representing app folders - */ -export function getAppFolders(directory, deploymentApplicationType) { - const files = readdirSync(directory); - const appsFolders = []; - - files.forEach(file => { - try { - if (statSync(join(directory, file)).isDirectory()) { - const yoRcFile = join(directory, file, '.yo-rc.json'); - if (statSync(yoRcFile).isFile()) { - try { - const fileData = JSON.parse(readFileSync(yoRcFile).toString()); - if ( - fileData['generator-jhipster'].baseName !== undefined && - (deploymentApplicationType === undefined || - deploymentApplicationType === (fileData['generator-jhipster'].applicationType ?? MONOLITH) || - (deploymentApplicationType === MICROSERVICE && fileData['generator-jhipster'].applicationType === GATEWAY)) - ) { - appsFolders.push(file.match(/([^/]*)\/*$/)[1]); - } - } catch (err) { - this.log.error(chalk.red(`${yoRcFile}: this .yo-rc.json can't be read`)); - this.log.debug('Error:', err); - } - } - } - } catch { - // Not a file or directory - } - }); - - return appsFolders; -} diff --git a/generators/base-workspaces/internal/index.mts b/generators/base-workspaces/internal/index.mts deleted file mode 100644 index de854f32ef34..000000000000 --- a/generators/base-workspaces/internal/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2021 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 './deployments.mjs'; -export * from './docker-dependencies.mjs'; diff --git a/generators/base-workspaces/internal/index.ts b/generators/base-workspaces/internal/index.ts new file mode 100644 index 000000000000..cfd289dc93ca --- /dev/null +++ b/generators/base-workspaces/internal/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2021 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 './deployments.js'; +export * from './docker-dependencies.js'; diff --git a/generators/base-workspaces/priorities.mts b/generators/base-workspaces/priorities.mts deleted file mode 100644 index 0b89b701d670..000000000000 --- a/generators/base-workspaces/priorities.mts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright 2013-2023 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 { QUEUE_PREFIX, PRIORITY_NAMES as PRIORITY_NAMES_BASE, QUEUES as QUEUES_BASE } from '../base/priorities.mjs'; - -const { DEFAULT } = PRIORITY_NAMES_BASE; - -/** Custom priorities */ -const PROMPTING_WORKSPACES = 'promptingWorkspaces'; -const PROMPTING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${PROMPTING_WORKSPACES}`; - -const CONFIGURING_WORKSPACES = 'configuringWorkspaces'; -const CONFIGURING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${CONFIGURING_WORKSPACES}`; - -const LOADING_WORKSPACES = 'loadingWorkspaces'; -const LOADING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${LOADING_WORKSPACES}`; - -const PREPARING_WORKSPACES = 'preparingWorkspaces'; -const PREPARING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${PREPARING_WORKSPACES}`; - -/** - * Custom priorities to improve jhipster workflow. - */ -export const CUSTOM_PRIORITIES = [ - { - priorityName: PROMPTING_WORKSPACES, - queueName: PROMPTING_WORKSPACES_QUEUE, - before: CONFIGURING_WORKSPACES, - args: generator => generator.getArgsForPriority(PROMPTING_WORKSPACES), - }, - { - priorityName: CONFIGURING_WORKSPACES, - queueName: CONFIGURING_WORKSPACES_QUEUE, - before: LOADING_WORKSPACES, - args: generator => generator.getArgsForPriority(CONFIGURING_WORKSPACES), - }, - { - priorityName: LOADING_WORKSPACES, - queueName: LOADING_WORKSPACES_QUEUE, - before: PREPARING_WORKSPACES, - args: generator => generator.getArgsForPriority(LOADING_WORKSPACES), - }, - { - priorityName: PREPARING_WORKSPACES, - queueName: PREPARING_WORKSPACES_QUEUE, - before: DEFAULT, - args: generator => generator.getArgsForPriority(PREPARING_WORKSPACES), - }, -].reverse(); - -const WORKSPACES_QUEUES = { - PROMPTING_WORKSPACES_QUEUE, - CONFIGURING_WORKSPACES_QUEUE, - LOADING_WORKSPACES_QUEUE, - PREPARING_WORKSPACES_QUEUE, -}; - -export const WORKSPACES_PRIORITY_NAMES = { - PROMPTING_WORKSPACES, - CONFIGURING_WORKSPACES, - LOADING_WORKSPACES, - PREPARING_WORKSPACES, -}; - -export const PRIORITY_NAMES = { - ...PRIORITY_NAMES_BASE, - ...WORKSPACES_PRIORITY_NAMES, -}; - -export const QUEUES = { - ...QUEUES_BASE, - ...WORKSPACES_QUEUES, -}; - -export const PRIORITY_NAMES_LIST = [ - PRIORITY_NAMES.INITIALIZING, - PRIORITY_NAMES.PROMPTING, - PRIORITY_NAMES.CONFIGURING, - PRIORITY_NAMES.COMPOSING, - PRIORITY_NAMES.LOADING, - PRIORITY_NAMES.PREPARING, - PROMPTING_WORKSPACES, - CONFIGURING_WORKSPACES, - LOADING_WORKSPACES, - PREPARING_WORKSPACES, - PRIORITY_NAMES.DEFAULT, - PRIORITY_NAMES.WRITING, - PRIORITY_NAMES.POST_WRITING, - PRIORITY_NAMES_BASE.INSTALL, - PRIORITY_NAMES_BASE.POST_INSTALL, - PRIORITY_NAMES_BASE.END, -]; diff --git a/generators/base-workspaces/priorities.ts b/generators/base-workspaces/priorities.ts new file mode 100644 index 000000000000..c365fcf10250 --- /dev/null +++ b/generators/base-workspaces/priorities.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2013-2023 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 { QUEUE_PREFIX, PRIORITY_NAMES as PRIORITY_NAMES_BASE, QUEUES as QUEUES_BASE } from '../base/priorities.js'; + +const { DEFAULT } = PRIORITY_NAMES_BASE; + +/** Custom priorities */ +const PROMPTING_WORKSPACES = 'promptingWorkspaces'; +const PROMPTING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${PROMPTING_WORKSPACES}`; + +const CONFIGURING_WORKSPACES = 'configuringWorkspaces'; +const CONFIGURING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${CONFIGURING_WORKSPACES}`; + +const LOADING_WORKSPACES = 'loadingWorkspaces'; +const LOADING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${LOADING_WORKSPACES}`; + +const PREPARING_WORKSPACES = 'preparingWorkspaces'; +const PREPARING_WORKSPACES_QUEUE = `${QUEUE_PREFIX}${PREPARING_WORKSPACES}`; + +/** + * Custom priorities to improve jhipster workflow. + */ +export const CUSTOM_PRIORITIES = [ + { + priorityName: PROMPTING_WORKSPACES, + queueName: PROMPTING_WORKSPACES_QUEUE, + before: CONFIGURING_WORKSPACES, + args: generator => generator.getArgsForPriority(PROMPTING_WORKSPACES), + }, + { + priorityName: CONFIGURING_WORKSPACES, + queueName: CONFIGURING_WORKSPACES_QUEUE, + before: LOADING_WORKSPACES, + args: generator => generator.getArgsForPriority(CONFIGURING_WORKSPACES), + }, + { + priorityName: LOADING_WORKSPACES, + queueName: LOADING_WORKSPACES_QUEUE, + before: PREPARING_WORKSPACES, + args: generator => generator.getArgsForPriority(LOADING_WORKSPACES), + }, + { + priorityName: PREPARING_WORKSPACES, + queueName: PREPARING_WORKSPACES_QUEUE, + before: DEFAULT, + args: generator => generator.getArgsForPriority(PREPARING_WORKSPACES), + }, +].reverse(); + +const WORKSPACES_QUEUES = { + PROMPTING_WORKSPACES_QUEUE, + CONFIGURING_WORKSPACES_QUEUE, + LOADING_WORKSPACES_QUEUE, + PREPARING_WORKSPACES_QUEUE, +}; + +export const WORKSPACES_PRIORITY_NAMES = { + PROMPTING_WORKSPACES, + CONFIGURING_WORKSPACES, + LOADING_WORKSPACES, + PREPARING_WORKSPACES, +}; + +export const PRIORITY_NAMES = { + ...PRIORITY_NAMES_BASE, + ...WORKSPACES_PRIORITY_NAMES, +}; + +export const QUEUES = { + ...QUEUES_BASE, + ...WORKSPACES_QUEUES, +}; + +export const PRIORITY_NAMES_LIST = [ + PRIORITY_NAMES.INITIALIZING, + PRIORITY_NAMES.PROMPTING, + PRIORITY_NAMES.CONFIGURING, + PRIORITY_NAMES.COMPOSING, + PRIORITY_NAMES.LOADING, + PRIORITY_NAMES.PREPARING, + PROMPTING_WORKSPACES, + CONFIGURING_WORKSPACES, + LOADING_WORKSPACES, + PREPARING_WORKSPACES, + PRIORITY_NAMES.DEFAULT, + PRIORITY_NAMES.WRITING, + PRIORITY_NAMES.POST_WRITING, + PRIORITY_NAMES_BASE.INSTALL, + PRIORITY_NAMES_BASE.POST_INSTALL, + PRIORITY_NAMES_BASE.END, +]; diff --git a/generators/base/__snapshots__/generator.spec.mts.snap b/generators/base/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/base/__snapshots__/generator.spec.mts.snap rename to generators/base/__snapshots__/generator.spec.ts.snap diff --git a/generators/base/api.d.mts b/generators/base/api.d.mts deleted file mode 100644 index 77a2c5565649..000000000000 --- a/generators/base/api.d.mts +++ /dev/null @@ -1,242 +0,0 @@ -import type { BaseOptions, BaseFeatures, ArgumentSpec, CliOptionSpec } from 'yeoman-generator'; -import type { SetOptional } from 'type-fest'; -import type CoreGenerator from '../base-core/index.mjs'; - -export type ApplicationWithConfig = { - config: { - [key: string]: string | boolean | number | string[]; - }; - entities: Record; -}; - -export type JHipsterGeneratorOptions = BaseOptions & { - /* cli options */ - commandName: string; - positionalArguments?: unknown[]; - - /* yeoman options */ - skipYoResolve?: boolean; - sharedData: any; - force?: boolean; - - /* base options */ - applicationId?: string; - applicationWithConfig?: ApplicationWithConfig; - /** - * @deprecated - */ - applicationWithEntities?: any; - creationTimestamp?: string; - ignoreErrors?: boolean; - ignoreNeedlesError?: boolean; - reproducible?: boolean; - reproducibleTests?: boolean; - skipPriorities?: string[]; - skipWriting?: boolean; - entities?: string[]; - disableBlueprints?: boolean; - - /* blueprint options */ - blueprints?: string; - blueprint?: any; - jhipsterContext?: any; - composeWithLocalBlueprint?: boolean; - - /* generate-blueprint options */ - localBlueprint?: boolean; - - /* jdl generator options */ - jdlFile?: string; - - /* application options */ - baseName?: string; - db?: string; - applicationType?: string; - skipUserManagement?: boolean; - skipDbChangelog?: boolean; - recreateInitialChangelog?: boolean; - - /* workspaces options */ - generateApplications?: boolean; - generateWorkspaces?: boolean; - generateWith?: string; - monorepository?: boolean; - workspaces?: boolean; - workspacesFolders?: string[]; -}; - -export type JHipsterGeneratorFeatures = BaseFeatures & { - priorityArgs?: boolean; - /** - * Wraps write context and shows removed fields and replacements if exists. - */ - jhipster7Migration?: boolean; - sbsBlueprint?: boolean; - checkBlueprint?: boolean; - /** - * Disable skipPriorities flag. - */ - disableSkipPriorities?: boolean; - /** - * Compose with bootstrap generator. - * - * Bootstrap generator adds support to: - * - multistep templates. - * - sort jhipster configuration json. - * - force jhipster configuration commit. - * - earlier prettier config commit for correct prettier. - * - prettier and eslint. - */ - jhipsterBootstrap?: boolean; - /** - * Store current version at .yo-rc.json. - * Defaults to true. - */ - storeJHipsterVersion?: boolean; - - /** - * Create transforms for commit. - */ - commitTransformFactory?: () => any; -}; - -// eslint-disable-next-line no-use-before-define -export type EditFileCallback = (this: Generator, content: string, filePath: string) => string; - -export type EditFileOptions = { create?: boolean; ignoreNonExisting?: boolean | string; assertModified?: boolean }; - -export type CascatedEditFileCallback = ( - ...callbacks: EditFileCallback[] -) => CascatedEditFileCallback; - -export type WriteFileTemplate = - | string - | ((this: Generator, data: DataType, filePath: string) => string) - | { - /** source file */ - sourceFile?: ((this: Generator, data: DataType) => string) | string; - /** destination file */ - destinationFile?: (this: Generator, destinationFile: DataType) => string | string; - /** @deprecated, use sourceFile instead */ - file?: ((this: Generator, data: DataType) => string) | string; - /** @deprecated, use destinationFile instead */ - renameTo?: ((this: Generator, data: DataType, filePath: string) => string) | string; - /** transforms (files processing) to be applied */ - transform?: boolean | (() => string)[]; - /** binary files skips ejs render, ejs extension and file transform */ - binary?: boolean; - /** ejs options. Refer to https://ejs.co/#docs */ - options?: Record; - override?: (this: Generator, data: DataType) => boolean; - }; - -export type WriteFileBlock = { - /** relative path were sources are placed */ - from?: ((this: Generator, data: DataType) => string) | string; - /** relative path were the files should be written, fallbacks to from/path */ - to?: ((this: Generator, data: DataType, filePath: string) => string) | string; - path?: ((this: Generator, data: DataType) => string) | string; - /** generate destinationFile based on sourceFile */ - renameTo?: ((this: Generator, data: DataType, filePath: string) => string) | string; - /** condition to enable to write the block */ - condition?: (this: Generator, data: DataType) => boolean | undefined; - /** transforms (files processing) to be applied */ - transform?: boolean | (() => string)[]; - templates: WriteFileTemplate[]; -}; - -export type WriteFileSection = Record[]>; - -export type WriteFileOptions = { - /** transforms (files processing) to be applied */ - transform?: EditFileCallback[]; - /** context to be used as template data */ - context?: DataType; - /** config passed to render methods */ - renderOptions?: Record; - /** - * path(s) to look for templates. - * Single absolute path or relative path(s) between the templates folder and template path. - */ - rootTemplatesPath?: string | string[]; -} & ( - | { - sections: WriteFileSection; - } - | { - /** templates to be written */ - templates: WriteFileTemplate; - } - | { - /** blocks to be written */ - blocks: WriteFileBlock[]; - } -); - -export type JHispterChoices = string[] | { value: string; name: string }[]; - -export type JHipsterOption = SetOptional & { - name?: string; - scope?: 'storage' | 'blueprint' | 'control' | 'generator'; - env?: string; - choices?: JHispterChoices; -}; - -export type ValidationResult = { - debug?: unknown; - info?: string | string[]; - warning?: string | string[]; - error?: string | string[]; -}; - -export type PromptSpec = { - type: 'input' | 'list' | 'confirm' | 'checkbox'; - message: string | ((any) => string); - when?: boolean | ((any) => boolean); - default?: any | ((any) => any); - filter?: any | ((any) => any); - transformer?: any | ((any) => any); -}; - -export type JHipsterArgumentConfig = SetOptional & { scope?: 'storage' | 'blueprint' | 'generator' }; - -export type ConfigSpec = { - description?: string; - choices?: JHispterChoices; - - cli?: SetOptional; - argument?: JHipsterArgumentConfig; - prompt?: PromptSpec | ((CoreGenerator) => PromptSpec); - scope?: 'storage' | 'blueprint' | 'generator'; - /** - * The callback receives the generator as input for 'generator' scope. - * The callback receives jhipsterConfigWithDefaults as input for 'storage' (default) scope. - * The callback receives blueprintStorage contents as input for 'blueprint' scope. - */ - default?: string | boolean | string[] | ((any) => string | boolean | string[]); -}; - -export type JHipsterArguments = Record; - -export type JHipsterOptions = Record; - -export type JHipsterConfigs = Record; - -export type JHipsterCommandDefinition = { - arguments?: JHipsterArguments; - options?: JHipsterOptions; - configs?: JHipsterConfigs; - /** - * Import options from a generator. - * @example ['server', 'jhipster-blueprint:server'] - */ - import?: string[]; - /** - * Override options from the generator been blueprinted. - */ - override?: boolean; - /** - * Load old options definition (yeoman's `this.options()`) from the generator. - */ - loadGeneratorOptions?: boolean; -}; diff --git a/generators/base/api.d.ts b/generators/base/api.d.ts new file mode 100644 index 000000000000..3bd4b6f1289d --- /dev/null +++ b/generators/base/api.d.ts @@ -0,0 +1,242 @@ +import type { BaseOptions, BaseFeatures, ArgumentSpec, CliOptionSpec } from 'yeoman-generator'; +import type { SetOptional } from 'type-fest'; +import type CoreGenerator from '../base-core/index.js'; + +export type ApplicationWithConfig = { + config: { + [key: string]: string | boolean | number | string[]; + }; + entities: Record; +}; + +export type JHipsterGeneratorOptions = BaseOptions & { + /* cli options */ + commandName: string; + positionalArguments?: unknown[]; + + /* yeoman options */ + skipYoResolve?: boolean; + sharedData: any; + force?: boolean; + + /* base options */ + applicationId?: string; + applicationWithConfig?: ApplicationWithConfig; + /** + * @deprecated + */ + applicationWithEntities?: any; + creationTimestamp?: string; + ignoreErrors?: boolean; + ignoreNeedlesError?: boolean; + reproducible?: boolean; + reproducibleTests?: boolean; + skipPriorities?: string[]; + skipWriting?: boolean; + entities?: string[]; + disableBlueprints?: boolean; + + /* blueprint options */ + blueprints?: string; + blueprint?: any; + jhipsterContext?: any; + composeWithLocalBlueprint?: boolean; + + /* generate-blueprint options */ + localBlueprint?: boolean; + + /* jdl generator options */ + jdlFile?: string; + + /* application options */ + baseName?: string; + db?: string; + applicationType?: string; + skipUserManagement?: boolean; + skipDbChangelog?: boolean; + recreateInitialChangelog?: boolean; + + /* workspaces options */ + generateApplications?: boolean; + generateWorkspaces?: boolean; + generateWith?: string; + monorepository?: boolean; + workspaces?: boolean; + workspacesFolders?: string[]; +}; + +export type JHipsterGeneratorFeatures = BaseFeatures & { + priorityArgs?: boolean; + /** + * Wraps write context and shows removed fields and replacements if exists. + */ + jhipster7Migration?: boolean; + sbsBlueprint?: boolean; + checkBlueprint?: boolean; + /** + * Disable skipPriorities flag. + */ + disableSkipPriorities?: boolean; + /** + * Compose with bootstrap generator. + * + * Bootstrap generator adds support to: + * - multistep templates. + * - sort jhipster configuration json. + * - force jhipster configuration commit. + * - earlier prettier config commit for correct prettier. + * - prettier and eslint. + */ + jhipsterBootstrap?: boolean; + /** + * Store current version at .yo-rc.json. + * Defaults to true. + */ + storeJHipsterVersion?: boolean; + + /** + * Create transforms for commit. + */ + commitTransformFactory?: () => any; +}; + +// eslint-disable-next-line no-use-before-define +export type EditFileCallback = (this: Generator, content: string, filePath: string) => string; + +export type EditFileOptions = { create?: boolean; ignoreNonExisting?: boolean | string; assertModified?: boolean }; + +export type CascatedEditFileCallback = ( + ...callbacks: EditFileCallback[] +) => CascatedEditFileCallback; + +export type WriteFileTemplate = + | string + | ((this: Generator, data: DataType, filePath: string) => string) + | { + /** source file */ + sourceFile?: ((this: Generator, data: DataType) => string) | string; + /** destination file */ + destinationFile?: (this: Generator, destinationFile: DataType) => string | string; + /** @deprecated, use sourceFile instead */ + file?: ((this: Generator, data: DataType) => string) | string; + /** @deprecated, use destinationFile instead */ + renameTo?: ((this: Generator, data: DataType, filePath: string) => string) | string; + /** transforms (files processing) to be applied */ + transform?: boolean | (() => string)[]; + /** binary files skips ejs render, ejs extension and file transform */ + binary?: boolean; + /** ejs options. Refer to https://ejs.co/#docs */ + options?: Record; + override?: (this: Generator, data: DataType) => boolean; + }; + +export type WriteFileBlock = { + /** relative path were sources are placed */ + from?: ((this: Generator, data: DataType) => string) | string; + /** relative path were the files should be written, fallbacks to from/path */ + to?: ((this: Generator, data: DataType, filePath: string) => string) | string; + path?: ((this: Generator, data: DataType) => string) | string; + /** generate destinationFile based on sourceFile */ + renameTo?: ((this: Generator, data: DataType, filePath: string) => string) | string; + /** condition to enable to write the block */ + condition?: (this: Generator, data: DataType) => boolean | undefined; + /** transforms (files processing) to be applied */ + transform?: boolean | (() => string)[]; + templates: WriteFileTemplate[]; +}; + +export type WriteFileSection = Record[]>; + +export type WriteFileOptions = { + /** transforms (files processing) to be applied */ + transform?: EditFileCallback[]; + /** context to be used as template data */ + context?: DataType; + /** config passed to render methods */ + renderOptions?: Record; + /** + * path(s) to look for templates. + * Single absolute path or relative path(s) between the templates folder and template path. + */ + rootTemplatesPath?: string | string[]; +} & ( + | { + sections: WriteFileSection; + } + | { + /** templates to be written */ + templates: WriteFileTemplate; + } + | { + /** blocks to be written */ + blocks: WriteFileBlock[]; + } +); + +export type JHispterChoices = string[] | { value: string; name: string }[]; + +export type JHipsterOption = SetOptional & { + name?: string; + scope?: 'storage' | 'blueprint' | 'control' | 'generator'; + env?: string; + choices?: JHispterChoices; +}; + +export type ValidationResult = { + debug?: unknown; + info?: string | string[]; + warning?: string | string[]; + error?: string | string[]; +}; + +export type PromptSpec = { + type: 'input' | 'list' | 'confirm' | 'checkbox'; + message: string | ((any) => string); + when?: boolean | ((any) => boolean); + default?: any | ((any) => any); + filter?: any | ((any) => any); + transformer?: any | ((any) => any); +}; + +export type JHipsterArgumentConfig = SetOptional & { scope?: 'storage' | 'blueprint' | 'generator' }; + +export type ConfigSpec = { + description?: string; + choices?: JHispterChoices; + + cli?: SetOptional; + argument?: JHipsterArgumentConfig; + prompt?: PromptSpec | ((CoreGenerator) => PromptSpec); + scope?: 'storage' | 'blueprint' | 'generator'; + /** + * The callback receives the generator as input for 'generator' scope. + * The callback receives jhipsterConfigWithDefaults as input for 'storage' (default) scope. + * The callback receives blueprintStorage contents as input for 'blueprint' scope. + */ + default?: string | boolean | string[] | ((any) => string | boolean | string[]); +}; + +export type JHipsterArguments = Record; + +export type JHipsterOptions = Record; + +export type JHipsterConfigs = Record; + +export type JHipsterCommandDefinition = { + arguments?: JHipsterArguments; + options?: JHipsterOptions; + configs?: JHipsterConfigs; + /** + * Import options from a generator. + * @example ['server', 'jhipster-blueprint:server'] + */ + import?: string[]; + /** + * Override options from the generator been blueprinted. + */ + override?: boolean; + /** + * Load old options definition (yeoman's `this.options()`) from the generator. + */ + loadGeneratorOptions?: boolean; +}; diff --git a/generators/base/blueprints.spec.mts b/generators/base/blueprints.spec.mts deleted file mode 100644 index 2893e38d2421..000000000000 --- a/generators/base/blueprints.spec.mts +++ /dev/null @@ -1,515 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import { esmocha, expect } from 'esmocha'; -import { RunResult } from 'yeoman-test'; -import { toHaveBeenCalledAfter } from 'jest-extended'; - -import { basicHelpers as helpers } from '../../test/support/index.mjs'; -import { packageJson } from '../../lib/index.mjs'; -import BaseGenerator from './index.mjs'; - -expect.extend({ toHaveBeenCalledAfter }); -const jhipsterVersion = packageJson.version; - -describe('generator - base - with blueprint', () => { - describe('generate application with a version-compatible blueprint', () => { - let runResult: RunResult; - before(async () => { - runResult = await helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('generator-jhipster-myblueprint', { - packageJson: { - dependencies: { - 'generator-jhipster': jhipsterVersion, - }, - }, - }) - .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - skipChecks: false, - blueprint: 'myblueprint', - }); - }); - - it('creates expected default files for server and angular', () => { - expect(runResult.mockedGenerators['jhipster-myblueprint:test-blueprint'].called); - }); - - it('blueprint version is saved in .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { blueprints: [{ name: 'generator-jhipster-myblueprint', version: '9.9.9' }] }, - }); - }); - }); - - describe('generate application with a conflicting version blueprint', () => { - it('throws an error', () => - expect( - helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('generator-jhipster-myblueprint', { - packageJson: { - dependencies: { - 'generator-jhipster': '1.1.1', - }, - }, - }) - .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) - .commitFiles() - .withJHipsterConfig() - .withOptions({ - skipChecks: false, - blueprint: 'myblueprint', - }), - ).rejects.toThrow(/targets JHipster v1.1.1 and is not compatible with this JHipster version/)); - }); - - describe('generating application with a git blueprint', () => { - it('should succeed', () => - helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('generator-jhipster-myblueprint', { - packageJson: { - dependencies: { - 'generator-jhipster': 'gitlab:jhipster/generator-jhipster#main', - }, - }, - }) - .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - skipChecks: false, - blueprint: 'myblueprint', - })); - }); - - describe('generate application with a peer version-compatible blueprint', () => { - let runResult: RunResult; - before(async () => { - runResult = await helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('generator-jhipster-myblueprint', { - packageJson: { - peerDependencies: { - 'generator-jhipster': '>=7.0.0-beta.0', - }, - }, - }) - .withOptions({ - skipChecks: false, - blueprint: 'myblueprint', - }); - }); - - it('blueprint version is saved in .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { blueprints: [{ name: 'generator-jhipster-myblueprint', version: '9.9.9' }] }, - }); - }); - }); - - describe('generate application with a peer conflicting version blueprint', () => { - it('throws an error', () => - expect(() => - helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('generator-jhipster-myblueprint', { - packageJson: { - peerDependencies: { - 'generator-jhipster': '1.1.1', - }, - }, - }) - .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - skipChecks: false, - blueprint: 'myblueprint', - }), - ).rejects.toThrow(/targets JHipster 1.1.1 and is not compatible with this JHipster version/)); - }); -}); - -describe('generator - base - with scoped blueprint', () => { - describe('generate monolith application with scoped blueprint', () => { - let runResult: RunResult; - before(async () => { - runResult = await helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('@jhipster/generator-jhipster-scoped-blueprint') - .withMockedGenerators(['@jhipster/jhipster-scoped-blueprint:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - blueprints: '@jhipster/generator-jhipster-scoped-blueprint', - }); - }); - - it('should compose with blueprint', () => { - expect(runResult.mockedGenerators['@jhipster/jhipster-scoped-blueprint:test-blueprint'].called).toBe(true); - }); - - it('blueprint version is saved in .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { blueprints: [{ name: '@jhipster/generator-jhipster-scoped-blueprint', version: '9.9.9' }] }, - }); - }); - }); -}); - -describe('generator - base - with blueprints disabled', () => { - describe('should not compose with blueprint', () => { - let runResult: RunResult; - before(async () => { - runResult = await helpers - .runTestBlueprintGenerator() - .withFakeTestBlueprint('@jhipster/generator-jhipster-scoped-blueprint') - .withMockedGenerators(['@jhipster/jhipster-scoped-blueprint:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - blueprints: '@jhipster/generator-jhipster-scoped-blueprint', - disableBlueprints: true, - }); - }); - - it('should compose with blueprint', () => { - expect(runResult.mockedGenerators['@jhipster/jhipster-scoped-blueprint:test-blueprint'].called).toBeFalsy; - }); - }); -}); - -describe('generator - base - with blueprint with constructor error', () => { - class BlueprintBlueprintedGenerator extends BaseGenerator { - constructor(args, opts, features) { - super(args, opts, features); - throw new Error('blueprint with error'); - } - } - - describe('generate monolith application with scoped blueprint', () => { - it('rejects the environment', async () => { - await expect( - helpers - .runTestBlueprintGenerator() - .withGenerators([[BlueprintBlueprintedGenerator, 'jhipster-throwing-constructor:test-blueprint']]) - .withJHipsterConfig() - .withOptions({ - blueprints: 'generator-jhipster-throwing-constructor', - }), - ).rejects.toThrow('blueprint with error'); - }); - }); -}); - -describe('generator - base - with multiple blueprints', () => { - describe('generate monolith application with scoped blueprint', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers - .runTestBlueprintGenerator() - .withMockedGenerators(['jhipster-blueprint1:test-blueprint', 'jhipster-blueprint2:test-blueprint']) - .withJHipsterConfig() - .withOptions({ - blueprints: 'generator-jhipster-blueprint1,generator-jhipster-blueprint2', - }); - }); - it('should compose with blueprints once', () => { - expect(runResult.mockedGenerators['jhipster-blueprint1:test-blueprint'].calledOnce); - expect(runResult.mockedGenerators['jhipster-blueprint2:test-blueprint'].calledOnce); - }); - }); -}); - -describe('generator - base - local blueprint', () => { - const BLUEPRINT_NS = 'jhipster:app'; - const BLUEPRINT_CONTENTS = `export async function createGenerator(env){ - const BaseGenerator = (await env.requireGenerator('${BLUEPRINT_NS}')); - return class extends BaseGenerator { - constructor(args, opts, features) { - super(args, opts, features); - } - - get [BaseGenerator.WRITING]() { - return { - write() { - this.writeDestination('local-blueprint.txt', 'This is a local blueprint'); - } - }; - } - }; - } - `; - - describe('generates application', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(BLUEPRINT_NS) - .withFiles({ '.blueprint/app/index.mjs': BLUEPRINT_CONTENTS }) - .commitFiles() - .withJHipsterConfig(); - }); - - it('creates expected default files', () => { - expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` -{ - ".blueprint/app/index.mjs": { - "stateCleared": "modified", - }, - ".yo-rc.json": { - "stateCleared": "modified", - }, - "local-blueprint.txt": { - "stateCleared": "modified", - }, -} -`); - }); - it('blueprint module and version are in package.json', () => { - runResult.assertFileContent('local-blueprint.txt', /This is a local blueprint/); - }); - }); -}); - -describe('generator - base-blueprint', () => { - const priorities = [ - 'initializing', - 'prompting', - 'configuring', - 'composing', - 'loading', - 'preparing', - 'default', - 'writing', - 'postWriting', - 'install', - 'end', - ]; - - const createPrioritiesFakes = (): Record => { - const mockedPriorities: Record = {}; - priorities.forEach(priority => { - mockedPriorities[priority] = esmocha.fn(); - }); - return mockedPriorities; - }; - - const createAllBlueprint = mockedPriorities => { - /** - * @class - * @extends {BaseGenerator} - */ - return class extends BaseGenerator { - get initializing() { - return { - mockedInitializing() { - mockedPriorities.initializing(); - }, - }; - } - - get [BaseGenerator.INITIALIZING]() { - return this.initializing; - } - - get prompting() { - return { - mockedPrompting() { - mockedPriorities.prompting(); - }, - }; - } - - get [BaseGenerator.PROMPTING]() { - return this.prompting; - } - - get configuring() { - return { - mockedConfiguring() { - mockedPriorities.configuring(); - }, - }; - } - - get [BaseGenerator.CONFIGURING]() { - return this.configuring; - } - - get composing() { - return { - mockedComposing() { - mockedPriorities.composing(); - }, - }; - } - - get [BaseGenerator.COMPOSING]() { - return this.composing; - } - - get loading() { - return { - mockedLoading() { - mockedPriorities.loading(); - }, - }; - } - - get [BaseGenerator.LOADING]() { - return this.loading; - } - - get preparing() { - return { - mockedPreparing() { - mockedPriorities.preparing(); - }, - }; - } - - get [BaseGenerator.PREPARING]() { - return this.preparing; - } - - get default() { - return { - mockedDefault() { - mockedPriorities.default(); - }, - }; - } - - get [BaseGenerator.DEFAULT]() { - return this.default; - } - - get writing() { - return { - mockedWriting() { - mockedPriorities.writing(); - }, - }; - } - - get [BaseGenerator.WRITING]() { - return this.writing; - } - - get postWriting() { - return { - mockedPostWriting() { - mockedPriorities.postWriting(); - }, - }; - } - - get [BaseGenerator.POST_WRITING]() { - return this.postWriting; - } - - get install() { - return { - mockedInstall() { - mockedPriorities.install(); - }, - }; - } - - get [BaseGenerator.INSTALL]() { - return this.install; - } - - get end() { - return { - mockedEnd() { - mockedPriorities.end(); - }, - }; - } - - get [BaseGenerator.END]() { - return this.end; - } - }; - }; - - describe('priorities', () => { - describe('when every priority has been implemented', () => { - let mockedPriorities: Record; - let mockBlueprintSubGen; - before(() => { - mockedPriorities = createPrioritiesFakes(); - mockBlueprintSubGen = createAllBlueprint(mockedPriorities); - return helpers.run(mockBlueprintSubGen); - }); - - priorities.forEach((priority, idx) => { - it(`should execute ${priority} once`, () => { - expect(mockedPriorities[priority]).toBeCalledTimes(1); - }); - if (idx > 0) { - const priorityBefore = priorities[idx - 1]; - it(`should execute ${priority} after ${priorityBefore} `, () => { - expect(mockedPriorities[priority]).toHaveBeenCalledAfter(mockedPriorities[priorityBefore]); - }); - } - }); - }); - - describe('when custom priorities are missing and the blueprint is sbs', () => { - let mockedPriorities; - let mockBlueprintSubGen; - before(() => { - mockedPriorities = createPrioritiesFakes(); - mockBlueprintSubGen = class extends createAllBlueprint(mockedPriorities) { - constructor(args, opts, features) { - super(args, opts, features); - this.sbsBlueprint = true; - } - - get [BaseGenerator.INITIALIZING]() { - return super.initializing; - } - - get [BaseGenerator.PROMPTING]() { - return super.prompting; - } - - get [BaseGenerator.CONFIGURING]() { - return super.configuring; - } - - get [BaseGenerator.DEFAULT]() { - return super.default; - } - - get [BaseGenerator.WRITING]() { - return super.writing; - } - - get [BaseGenerator.INSTALL]() { - return super.install; - } - - get [BaseGenerator.END]() { - return super.end; - } - }; - return helpers.create(mockBlueprintSubGen).run(); - }); - - priorities.forEach(priority => { - if (['composing', 'loading', 'preparing', 'postWriting'].includes(priority)) { - it(`should not execute ${priority}`, () => { - expect(mockedPriorities[priority]).not.toBeCalled(); - }); - } else { - it(`should execute ${priority} once`, () => { - expect(mockedPriorities[priority]).toBeCalledTimes(1); - }); - } - }); - }); - }); -}); diff --git a/generators/base/blueprints.spec.ts b/generators/base/blueprints.spec.ts new file mode 100644 index 000000000000..6e2be32b5c88 --- /dev/null +++ b/generators/base/blueprints.spec.ts @@ -0,0 +1,515 @@ +/* eslint-disable max-classes-per-file */ +import { esmocha, expect } from 'esmocha'; +import { RunResult } from 'yeoman-test'; +import { toHaveBeenCalledAfter } from 'jest-extended'; + +import { basicHelpers as helpers } from '../../test/support/index.js'; +import { packageJson } from '../../lib/index.js'; +import BaseGenerator from './index.js'; + +expect.extend({ toHaveBeenCalledAfter }); +const jhipsterVersion = packageJson.version; + +describe('generator - base - with blueprint', () => { + describe('generate application with a version-compatible blueprint', () => { + let runResult: RunResult; + before(async () => { + runResult = await helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('generator-jhipster-myblueprint', { + packageJson: { + dependencies: { + 'generator-jhipster': jhipsterVersion, + }, + }, + }) + .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + skipChecks: false, + blueprint: 'myblueprint', + }); + }); + + it('creates expected default files for server and angular', () => { + expect(runResult.mockedGenerators['jhipster-myblueprint:test-blueprint'].called); + }); + + it('blueprint version is saved in .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { blueprints: [{ name: 'generator-jhipster-myblueprint', version: '9.9.9' }] }, + }); + }); + }); + + describe('generate application with a conflicting version blueprint', () => { + it('throws an error', () => + expect( + helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('generator-jhipster-myblueprint', { + packageJson: { + dependencies: { + 'generator-jhipster': '1.1.1', + }, + }, + }) + .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) + .commitFiles() + .withJHipsterConfig() + .withOptions({ + skipChecks: false, + blueprint: 'myblueprint', + }), + ).rejects.toThrow(/targets JHipster v1.1.1 and is not compatible with this JHipster version/)); + }); + + describe('generating application with a git blueprint', () => { + it('should succeed', () => + helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('generator-jhipster-myblueprint', { + packageJson: { + dependencies: { + 'generator-jhipster': 'gitlab:jhipster/generator-jhipster#main', + }, + }, + }) + .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + skipChecks: false, + blueprint: 'myblueprint', + })); + }); + + describe('generate application with a peer version-compatible blueprint', () => { + let runResult: RunResult; + before(async () => { + runResult = await helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('generator-jhipster-myblueprint', { + packageJson: { + peerDependencies: { + 'generator-jhipster': '>=7.0.0-beta.0', + }, + }, + }) + .withOptions({ + skipChecks: false, + blueprint: 'myblueprint', + }); + }); + + it('blueprint version is saved in .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { blueprints: [{ name: 'generator-jhipster-myblueprint', version: '9.9.9' }] }, + }); + }); + }); + + describe('generate application with a peer conflicting version blueprint', () => { + it('throws an error', () => + expect(() => + helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('generator-jhipster-myblueprint', { + packageJson: { + peerDependencies: { + 'generator-jhipster': '1.1.1', + }, + }, + }) + .withMockedGenerators(['jhipster-myblueprint:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + skipChecks: false, + blueprint: 'myblueprint', + }), + ).rejects.toThrow(/targets JHipster 1.1.1 and is not compatible with this JHipster version/)); + }); +}); + +describe('generator - base - with scoped blueprint', () => { + describe('generate monolith application with scoped blueprint', () => { + let runResult: RunResult; + before(async () => { + runResult = await helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('@jhipster/generator-jhipster-scoped-blueprint') + .withMockedGenerators(['@jhipster/jhipster-scoped-blueprint:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + blueprints: '@jhipster/generator-jhipster-scoped-blueprint', + }); + }); + + it('should compose with blueprint', () => { + expect(runResult.mockedGenerators['@jhipster/jhipster-scoped-blueprint:test-blueprint'].called).toBe(true); + }); + + it('blueprint version is saved in .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { blueprints: [{ name: '@jhipster/generator-jhipster-scoped-blueprint', version: '9.9.9' }] }, + }); + }); + }); +}); + +describe('generator - base - with blueprints disabled', () => { + describe('should not compose with blueprint', () => { + let runResult: RunResult; + before(async () => { + runResult = await helpers + .runTestBlueprintGenerator() + .withFakeTestBlueprint('@jhipster/generator-jhipster-scoped-blueprint') + .withMockedGenerators(['@jhipster/jhipster-scoped-blueprint:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + blueprints: '@jhipster/generator-jhipster-scoped-blueprint', + disableBlueprints: true, + }); + }); + + it('should compose with blueprint', () => { + expect(runResult.mockedGenerators['@jhipster/jhipster-scoped-blueprint:test-blueprint'].called).toBeFalsy; + }); + }); +}); + +describe('generator - base - with blueprint with constructor error', () => { + class BlueprintBlueprintedGenerator extends BaseGenerator { + constructor(args, opts, features) { + super(args, opts, features); + throw new Error('blueprint with error'); + } + } + + describe('generate monolith application with scoped blueprint', () => { + it('rejects the environment', async () => { + await expect( + helpers + .runTestBlueprintGenerator() + .withGenerators([[BlueprintBlueprintedGenerator, 'jhipster-throwing-constructor:test-blueprint']]) + .withJHipsterConfig() + .withOptions({ + blueprints: 'generator-jhipster-throwing-constructor', + }), + ).rejects.toThrow('blueprint with error'); + }); + }); +}); + +describe('generator - base - with multiple blueprints', () => { + describe('generate monolith application with scoped blueprint', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers + .runTestBlueprintGenerator() + .withMockedGenerators(['jhipster-blueprint1:test-blueprint', 'jhipster-blueprint2:test-blueprint']) + .withJHipsterConfig() + .withOptions({ + blueprints: 'generator-jhipster-blueprint1,generator-jhipster-blueprint2', + }); + }); + it('should compose with blueprints once', () => { + expect(runResult.mockedGenerators['jhipster-blueprint1:test-blueprint'].calledOnce); + expect(runResult.mockedGenerators['jhipster-blueprint2:test-blueprint'].calledOnce); + }); + }); +}); + +describe('generator - base - local blueprint', () => { + const BLUEPRINT_NS = 'jhipster:app'; + const BLUEPRINT_CONTENTS = `export async function createGenerator(env){ + const BaseGenerator = (await env.requireGenerator('${BLUEPRINT_NS}')); + return class extends BaseGenerator { + constructor(args, opts, features) { + super(args, opts, features); + } + + get [BaseGenerator.WRITING]() { + return { + write() { + this.writeDestination('local-blueprint.txt', 'This is a local blueprint'); + } + }; + } + }; + } + `; + + describe('generates application', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(BLUEPRINT_NS) + .withFiles({ '.blueprint/app/index.mjs': BLUEPRINT_CONTENTS }) + .commitFiles() + .withJHipsterConfig(); + }); + + it('creates expected default files', () => { + expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` +{ + ".blueprint/app/index.mjs": { + "stateCleared": "modified", + }, + ".yo-rc.json": { + "stateCleared": "modified", + }, + "local-blueprint.txt": { + "stateCleared": "modified", + }, +} +`); + }); + it('blueprint module and version are in package.json', () => { + runResult.assertFileContent('local-blueprint.txt', /This is a local blueprint/); + }); + }); +}); + +describe('generator - base-blueprint', () => { + const priorities = [ + 'initializing', + 'prompting', + 'configuring', + 'composing', + 'loading', + 'preparing', + 'default', + 'writing', + 'postWriting', + 'install', + 'end', + ]; + + const createPrioritiesFakes = (): Record => { + const mockedPriorities: Record = {}; + priorities.forEach(priority => { + mockedPriorities[priority] = esmocha.fn(); + }); + return mockedPriorities; + }; + + const createAllBlueprint = mockedPriorities => { + /** + * @class + * @extends {BaseGenerator} + */ + return class extends BaseGenerator { + get initializing() { + return { + mockedInitializing() { + mockedPriorities.initializing(); + }, + }; + } + + get [BaseGenerator.INITIALIZING]() { + return this.initializing; + } + + get prompting() { + return { + mockedPrompting() { + mockedPriorities.prompting(); + }, + }; + } + + get [BaseGenerator.PROMPTING]() { + return this.prompting; + } + + get configuring() { + return { + mockedConfiguring() { + mockedPriorities.configuring(); + }, + }; + } + + get [BaseGenerator.CONFIGURING]() { + return this.configuring; + } + + get composing() { + return { + mockedComposing() { + mockedPriorities.composing(); + }, + }; + } + + get [BaseGenerator.COMPOSING]() { + return this.composing; + } + + get loading() { + return { + mockedLoading() { + mockedPriorities.loading(); + }, + }; + } + + get [BaseGenerator.LOADING]() { + return this.loading; + } + + get preparing() { + return { + mockedPreparing() { + mockedPriorities.preparing(); + }, + }; + } + + get [BaseGenerator.PREPARING]() { + return this.preparing; + } + + get default() { + return { + mockedDefault() { + mockedPriorities.default(); + }, + }; + } + + get [BaseGenerator.DEFAULT]() { + return this.default; + } + + get writing() { + return { + mockedWriting() { + mockedPriorities.writing(); + }, + }; + } + + get [BaseGenerator.WRITING]() { + return this.writing; + } + + get postWriting() { + return { + mockedPostWriting() { + mockedPriorities.postWriting(); + }, + }; + } + + get [BaseGenerator.POST_WRITING]() { + return this.postWriting; + } + + get install() { + return { + mockedInstall() { + mockedPriorities.install(); + }, + }; + } + + get [BaseGenerator.INSTALL]() { + return this.install; + } + + get end() { + return { + mockedEnd() { + mockedPriorities.end(); + }, + }; + } + + get [BaseGenerator.END]() { + return this.end; + } + }; + }; + + describe('priorities', () => { + describe('when every priority has been implemented', () => { + let mockedPriorities: Record; + let mockBlueprintSubGen; + before(() => { + mockedPriorities = createPrioritiesFakes(); + mockBlueprintSubGen = createAllBlueprint(mockedPriorities); + return helpers.run(mockBlueprintSubGen); + }); + + priorities.forEach((priority, idx) => { + it(`should execute ${priority} once`, () => { + expect(mockedPriorities[priority]).toBeCalledTimes(1); + }); + if (idx > 0) { + const priorityBefore = priorities[idx - 1]; + it(`should execute ${priority} after ${priorityBefore} `, () => { + expect(mockedPriorities[priority]).toHaveBeenCalledAfter(mockedPriorities[priorityBefore]); + }); + } + }); + }); + + describe('when custom priorities are missing and the blueprint is sbs', () => { + let mockedPriorities; + let mockBlueprintSubGen; + before(() => { + mockedPriorities = createPrioritiesFakes(); + mockBlueprintSubGen = class extends createAllBlueprint(mockedPriorities) { + constructor(args, opts, features) { + super(args, opts, features); + this.sbsBlueprint = true; + } + + get [BaseGenerator.INITIALIZING]() { + return super.initializing; + } + + get [BaseGenerator.PROMPTING]() { + return super.prompting; + } + + get [BaseGenerator.CONFIGURING]() { + return super.configuring; + } + + get [BaseGenerator.DEFAULT]() { + return super.default; + } + + get [BaseGenerator.WRITING]() { + return super.writing; + } + + get [BaseGenerator.INSTALL]() { + return super.install; + } + + get [BaseGenerator.END]() { + return super.end; + } + }; + return helpers.create(mockBlueprintSubGen).run(); + }); + + priorities.forEach(priority => { + if (['composing', 'loading', 'preparing', 'postWriting'].includes(priority)) { + it(`should not execute ${priority}`, () => { + expect(mockedPriorities[priority]).not.toBeCalled(); + }); + } else { + it(`should execute ${priority} once`, () => { + expect(mockedPriorities[priority]).toBeCalledTimes(1); + }); + } + }); + }); + }); +}); diff --git a/generators/base/command.mts b/generators/base/command.mts deleted file mode 100644 index 6eba08eea821..000000000000 --- a/generators/base/command.mts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - useVersionPlaceholders: { - description: 'replace mutable versions with placeholders', - type: Boolean, - env: 'VERSION_PLACEHOLDERS', - scope: 'generator', - hide: true, - }, - skipChecks: { - description: 'Check the status of the required tools', - type: Boolean, - scope: 'generator', - }, - experimental: { - description: - 'Enable experimental features. Please note that these features may be unstable and may undergo breaking changes at any time', - type: Boolean, - scope: 'generator', - }, - disableBlueprints: { - description: 'Disable blueprints support', - type: Boolean, - }, - debugEnabled: { - name: 'debug', - description: 'Enable debugger', - alias: 'd', - type: Boolean, - scope: 'generator', - }, - reproducible: { - description: 'Try to reproduce changelog', - type: Boolean, - scope: 'control', - }, - skipPrompts: { - description: 'Skip prompts', - type: Boolean, - }, - ignoreNeedlesError: { - description: 'Ignore needles failures', - type: Boolean, - hide: true, - }, - }, -}; - -export default command; diff --git a/generators/base/command.ts b/generators/base/command.ts new file mode 100644 index 000000000000..af8252fad57a --- /dev/null +++ b/generators/base/command.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + useVersionPlaceholders: { + description: 'replace mutable versions with placeholders', + type: Boolean, + env: 'VERSION_PLACEHOLDERS', + scope: 'generator', + hide: true, + }, + skipChecks: { + description: 'Check the status of the required tools', + type: Boolean, + scope: 'generator', + }, + experimental: { + description: + 'Enable experimental features. Please note that these features may be unstable and may undergo breaking changes at any time', + type: Boolean, + scope: 'generator', + }, + disableBlueprints: { + description: 'Disable blueprints support', + type: Boolean, + }, + debugEnabled: { + name: 'debug', + description: 'Enable debugger', + alias: 'd', + type: Boolean, + scope: 'generator', + }, + reproducible: { + description: 'Try to reproduce changelog', + type: Boolean, + scope: 'control', + }, + skipPrompts: { + description: 'Skip prompts', + type: Boolean, + }, + ignoreNeedlesError: { + description: 'Ignore needles failures', + type: Boolean, + hide: true, + }, + }, +}; + +export default command; diff --git a/generators/base/generator.mts b/generators/base/generator.mts deleted file mode 100644 index e21e03c1f177..000000000000 --- a/generators/base/generator.mts +++ /dev/null @@ -1,682 +0,0 @@ -/** - * Copyright 2013-2023 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 path from 'path'; -import chalk from 'chalk'; -import semver from 'semver'; -import * as _ from 'lodash-es'; - -import type { ComposeOptions } from 'yeoman-generator'; -import { packageJson } from '../../lib/index.mjs'; -import { packageNameToNamespace, removeFieldsWithNullishValues } from './support/index.mjs'; -import { mergeBlueprints, parseBluePrints, loadBlueprintsFromConfiguration, normalizeBlueprintName } from './internal/index.mjs'; -import { PRIORITY_NAMES } from './priorities.mjs'; -import { BaseGeneratorDefinition, GenericTaskGroup } from './tasks.mjs'; -import { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from './api.mjs'; -import CoreGenerator from '../base-core/index.mjs'; -import { LOCAL_BLUEPRINT_PACKAGE_NAMESPACE } from './support/constants.mjs'; -import { getConfigWithDefaults } from '../../jdl/index.js'; -import { loadStoredAppOptions } from '../app/support/index.mjs'; - -const { defaults } = _; - -/** - * Base class that contains blueprints support. - */ -export default class JHipsterBaseBlueprintGenerator< - Definition extends BaseGeneratorDefinition = BaseGeneratorDefinition, -> extends CoreGenerator { - fromBlueprint!: boolean; - sbsBlueprint?: boolean; - delegateToBlueprint?: boolean; - blueprintConfig?: Record; - jhipsterContext?: any; - - constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { - const { jhipsterContext, ...opts } = options ?? {}; - super(args, opts, features); - - if (this.options.help) { - return; - } - - loadStoredAppOptions.call(this); - - this.sbsBlueprint = this.features.sbsBlueprint ?? false; - this.fromBlueprint = this.rootGeneratorName() !== 'generator-jhipster'; - - if (this.fromBlueprint) { - this.blueprintStorage = this._getStorage(); - this.blueprintConfig = this.blueprintStorage.createProxy(); - - // jhipsterContext is the original generator - this.jhipsterContext = jhipsterContext; - - if (this.getFeatures().checkBlueprint) { - if (!this.jhipsterContext) { - throw new Error( - `This is a JHipster blueprint and should be used only like ${chalk.yellow( - `jhipster --blueprints ${this.options.namespace.split(':')[0]}`, - )}`, - ); - } - } - - try { - // Fallback to the original generator if the file does not exists in the blueprint. - const blueprintedTemplatePath = this.jhipsterTemplatePath(); - if (!this.jhipsterTemplatesFolders.includes(blueprintedTemplatePath)) { - this.jhipsterTemplatesFolders.push(blueprintedTemplatePath); - } - } catch (error) { - this.log.warn('Error adding current blueprint templates as alternative for JHipster templates.'); - this.log.log(error); - } - } - } - - /** - * Filter generator's tasks in case the blueprint should be responsible on queueing those tasks. - */ - delegateTasksToBlueprint(tasksGetter: () => TaskGroupType): TaskGroupType { - return this.delegateToBlueprint ? ({} as TaskGroupType) : tasksGetter(); - } - - /** - * Priority API stub for blueprints. - * - * Initializing priority is used to show logo and tasks related to preparing for prompts, like loading constants. - */ - get initializing(): GenericTaskGroup { - return this.asInitializingTaskGroup(this._initializing()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _initializing() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asInitializingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Prompting priority is used to prompt users for configuration values. - */ - get prompting(): GenericTaskGroup { - return this.asPromptingTaskGroup(this._prompting()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _prompting() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPromptingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Configuring priority is used to customize and validate the configuration. - */ - get configuring(): GenericTaskGroup { - return this.asConfiguringTaskGroup(this._configuring()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _configuring() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asConfiguringTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Composing should be used to compose with others generators. - */ - get composing(): GenericTaskGroup { - return this.asComposingTaskGroup(this._composing()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _composing() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asComposingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Loading should be used to load application configuration from jhipster configuration. - * Before this priority the configuration should be considered dirty, while each generator configures itself at configuring priority, another generator composed at composing priority can still change it. - */ - get loading(): GenericTaskGroup { - return this.asLoadingTaskGroup(this._loading()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _loading() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asLoadingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Preparing should be used to generate derived properties. - */ - get preparing(): GenericTaskGroup { - return this.asPreparingTaskGroup(this._preparing()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _preparing() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPreparingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Preparing should be used to generate derived properties. - */ - get postPreparing(): GenericTaskGroup { - return this.asPreparingTaskGroup({}); - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPostPreparingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Default priority should used as misc customizations. - */ - get default(): GenericTaskGroup { - return this.asDefaultTaskGroup(this._default()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _default() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asDefaultTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Writing priority should used to write files. - */ - get writing(): GenericTaskGroup { - return this.asWritingTaskGroup(this._writing()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _writing() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asWritingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * PostWriting priority should used to customize files. - */ - get postWriting(): GenericTaskGroup { - return this.asPostWritingTaskGroup(this._postWriting()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _postWriting() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPostWritingTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * Install priority should used to prepare the project. - */ - get install(): GenericTaskGroup { - return this.asInstallTaskGroup(this._install()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _install() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asInstallTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * PostWriting priority should used to customize files. - */ - get postInstall(): GenericTaskGroup { - return this.asPostInstallTaskGroup(this._postInstall()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _postInstall() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asPostInstallTaskGroup( - taskGroup: GenericTaskGroup, - ): GenericTaskGroup { - return taskGroup; - } - - /** - * Priority API stub for blueprints. - * - * End priority should used to say good bye and print instructions. - */ - get end(): GenericTaskGroup { - return this.asEndTaskGroup(this._end()); - } - - /** - * @deprecated - * Public API method used by the getter and also by Blueprints - */ - _end() { - return {}; - } - - /** - * Utility method to get typed objects for autocomplete. - */ - asEndTaskGroup(taskGroup: GenericTaskGroup): GenericTaskGroup { - return taskGroup; - } - - /** - * JHipster config with default values fallback - */ - get jhipsterConfigWithDefaults() { - const configWithDefaults = getConfigWithDefaults(removeFieldsWithNullishValues(this.config.getAll())); - defaults(configWithDefaults, { - skipFakeData: false, - skipCheckLengthOfIdentifier: false, - enableGradleEnterprise: false, - pages: [], - }); - return configWithDefaults; - } - - /** - * @protected - * Composes with blueprint generators, if any. - */ - protected async composeWithBlueprints(subGen: string, options?: ComposeOptions) { - this.delegateToBlueprint = false; - - if (this.options.disableBlueprints) { - return []; - } - - const control = this.sharedData.getControl(); - if (!control.blueprintConfigured) { - control.blueprintConfigured = true; - await this._configureBlueprints(); - } - - let blueprints = this.jhipsterConfig.blueprints || []; - if (this.options.composeWithLocalBlueprint) { - blueprints = blueprints.concat({ name: '@jhipster/local' }); - } - const composedBlueprints: any[] = []; - for (const blueprint of blueprints) { - const blueprintGenerator = await this._composeBlueprint(blueprint.name, subGen, options); - if (blueprintGenerator) { - composedBlueprints.push(blueprintGenerator); - if ((blueprintGenerator as any).sbsBlueprint) { - // If sbsBlueprint, add templatePath to the original generator templatesFolder. - this.jhipsterTemplatesFolders.unshift(blueprintGenerator.templatePath()); - } else { - // If the blueprints does not sets sbsBlueprint property, ignore normal workflow. - this.delegateToBlueprint = true; - this.checkBlueprintImplementsPriorities(blueprintGenerator); - } - } - } - return composedBlueprints; - } - - /** - * Check if the blueprint implements every priority implemented by the parent generator - * @param {BaseGenerator} blueprintGenerator - */ - private checkBlueprintImplementsPriorities(blueprintGenerator) { - const { taskPrefix: baseGeneratorTaskPrefix = '' } = this.features; - const { taskPrefix: blueprintTaskPrefix = '' } = blueprintGenerator.features; - // v8 remove deprecated priorities - const DEPRECATED_PRIORITIES = ['preConflicts']; - for (const priorityName of Object.values(PRIORITY_NAMES).filter(p => !DEPRECATED_PRIORITIES.includes(p))) { - const baseGeneratorPriorityName = `${baseGeneratorTaskPrefix}${priorityName}`; - if (baseGeneratorPriorityName in this) { - const blueprintPriorityName = `${blueprintTaskPrefix}${priorityName}`; - if (!Object.hasOwn(Object.getPrototypeOf(blueprintGenerator), blueprintPriorityName)) { - this.log.debug(`Priority ${blueprintPriorityName} not implemented at ${blueprintGenerator.options.namespace}.`); - } - } - } - } - - /** - * @private - * Configure blueprints. - */ - private async _configureBlueprints() { - let argvBlueprints = this.options.blueprints || ''; - // check for old single blueprint declaration - const blueprint = this.options.blueprint; - if (blueprint) { - this.log.warn('--blueprint option is deprecated. Please use --blueprints instead'); - if (!argvBlueprints.split(',').includes(blueprint)) { - argvBlueprints = `${blueprint},${argvBlueprints}`; - } - } - const blueprints = mergeBlueprints(parseBluePrints(argvBlueprints), loadBlueprintsFromConfiguration(this.config)); - - // EnvironmentBuilder already looks for blueprint when running from cli, this is required for tests. - // Can be removed once the tests uses EnvironmentBuilder. - const missingBlueprints = blueprints - .filter(blueprint => !this.env.isPackageRegistered(packageNameToNamespace(blueprint.name))) - .map(blueprint => blueprint.name); - if (missingBlueprints.length > 0) { - await this.env.lookup({ filterPaths: true, packagePatterns: missingBlueprints } as any); - } - - if (blueprints && blueprints.length > 0) { - blueprints.forEach(blueprint => { - blueprint.version = this._findBlueprintVersion(blueprint.name) || blueprint.version; - }); - this.jhipsterConfig.blueprints = blueprints; - } - - if (!this.skipChecks) { - const namespaces = blueprints.map(blueprint => packageNameToNamespace(blueprint.name)); - // Verify if the blueprints hava been registered. - const missing = namespaces.filter(namespace => !this.env.isPackageRegistered(namespace)); - if (missing && missing.length > 0) { - throw new Error(`Some blueprints were not found ${missing}, you should install them manually`); - } - } - } - - /** - * @private - * Compose external blueprint module - * @param {string} blueprint - name of the blueprint - * @param {string} subGen - sub generator - * @param {any} [extraOptions] - options to pass to blueprint generator - * @return {Generator|undefined} - */ - private async _composeBlueprint( - blueprint, - subGen, - extraOptions: ComposeOptions = {}, - ): Promise { - blueprint = normalizeBlueprintName(blueprint); - if (!this.skipChecks && blueprint !== LOCAL_BLUEPRINT_PACKAGE_NAMESPACE) { - this._checkBlueprint(blueprint); - } - - const generatorName = packageNameToNamespace(blueprint); - const generatorNamespace = `${generatorName}:${subGen}`; - if (!(await this.env.get(generatorNamespace))) { - this.log.debug( - `No blueprint found for blueprint ${chalk.yellow(blueprint)} and ${chalk.yellow(subGen)} with namespace ${chalk.yellow( - generatorNamespace, - )} subgenerator: falling back to default generator`, - ); - return undefined; - } - this.log.debug( - `Found blueprint ${chalk.yellow(blueprint)} and ${chalk.yellow(subGen)} with namespace ${chalk.yellow(generatorNamespace)}`, - ); - - const finalOptions: ComposeOptions = { - forwardOptions: true, - ...extraOptions, - generatorOptions: { - jhipsterContext: this, - ...extraOptions?.generatorOptions, - } as any, - }; - - const blueprintGenerator = await this.composeWith(generatorNamespace, finalOptions as any); - if (blueprintGenerator instanceof Error) { - throw blueprintGenerator; - } - (this as any)._debug(`Using blueprint ${chalk.yellow(blueprint)} for ${chalk.yellow(subGen)} subgenerator`); - return blueprintGenerator; - } - - /** - * @private - * Try to retrieve the package.json of the blueprint used, as an object. - * @param {string} blueprintPkgName - generator name - * @return {object} packageJson - retrieved package.json as an object or undefined if not found - */ - private _findBlueprintPackageJson(blueprintPkgName) { - const blueprintGeneratorName = packageNameToNamespace(blueprintPkgName); - const blueprintPackagePath = this.env.getPackagePath(blueprintGeneratorName); - if (!blueprintPackagePath) { - this.log.warn(`Could not retrieve packagePath of blueprint '${blueprintPkgName}'`); - return undefined; - } - const packageJsonFile = path.join(blueprintPackagePath, 'package.json'); - if (!fs.existsSync(packageJsonFile)) { - return undefined; - } - return JSON.parse(fs.readFileSync(packageJsonFile).toString()); - } - - /** - * @private - * Try to retrieve the version of the blueprint used. - * @param {string} blueprintPkgName - generator name - * @return {string} version - retrieved version or empty string if not found - */ - private _findBlueprintVersion(blueprintPkgName) { - const blueprintPackageJson = this._findBlueprintPackageJson(blueprintPkgName); - if (!blueprintPackageJson || !blueprintPackageJson.version) { - this.log.warn(`Could not retrieve version of blueprint '${blueprintPkgName}'`); - return undefined; - } - return blueprintPackageJson.version; - } - - /** - * @private - * Check if the generator specified as blueprint is installed. - * @param {string} blueprint - generator name - */ - protected _checkBlueprint(blueprint) { - if (blueprint === 'generator-jhipster') { - throw new Error(`You cannot use ${chalk.yellow(blueprint)} as the blueprint.`); - } - this._findBlueprintPackageJson(blueprint); - } - - /** - * @private - * Check if the generator specified as blueprint has a version compatible with current JHipster. - * @param {string} blueprintPkgName - generator name - */ - protected _checkJHipsterBlueprintVersion(blueprintPkgName) { - const blueprintPackageJson = this._findBlueprintPackageJson(blueprintPkgName); - if (!blueprintPackageJson) { - this.log.warn(`Could not retrieve version of JHipster declared by blueprint '${blueprintPkgName}'`); - return; - } - const mainGeneratorJhipsterVersion = packageJson.version; - const blueprintJhipsterVersion = blueprintPackageJson.dependencies && blueprintPackageJson.dependencies['generator-jhipster']; - if (blueprintJhipsterVersion) { - if (!semver.valid(blueprintJhipsterVersion) && !semver.validRange(blueprintJhipsterVersion)) { - this.log.verboseInfo(`Blueprint ${blueprintPkgName} contains generator-jhipster dependency with non comparable version`); - return; - } - if (semver.satisfies(mainGeneratorJhipsterVersion, blueprintJhipsterVersion, { includePrerelease: true })) { - return; - } - throw new Error( - `The installed ${chalk.yellow( - blueprintPkgName, - )} blueprint targets JHipster v${blueprintJhipsterVersion} and is not compatible with this JHipster version. Either update the blueprint or JHipster. You can also disable this check using --skip-checks at your own risk`, - ); - } - const blueprintPeerJhipsterVersion = - blueprintPackageJson.peerDependencies && blueprintPackageJson.peerDependencies['generator-jhipster']; - if (blueprintPeerJhipsterVersion) { - if (semver.satisfies(mainGeneratorJhipsterVersion, blueprintPeerJhipsterVersion, { includePrerelease: true })) { - return; - } - throw new Error( - `The installed ${chalk.yellow( - blueprintPkgName, - )} blueprint targets JHipster ${blueprintPeerJhipsterVersion} and is not compatible with this JHipster version. Either update the blueprint or JHipster. You can also disable this check using --skip-checks at your own risk`, - ); - } - this.log.warn(`Could not retrieve version of JHipster declared by blueprint '${blueprintPkgName}'`); - } -} diff --git a/generators/base/generator.spec.mts b/generators/base/generator.spec.mts deleted file mode 100644 index 0e2888dfec28..000000000000 --- a/generators/base/generator.spec.mts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect, esmocha } from 'esmocha'; -import lodash from 'lodash'; - -import EnvironmentBuilder from '../../cli/environment-builder.mjs'; -import BaseGenerator from './index.mjs'; -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { getCommandHelpOutput, shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(BaseGenerator); - describe('help', () => { - it('should print expected information', async () => { - expect(await getCommandHelpOutput()).toMatchSnapshot(); - }); - }); - - describe.skip('EnvironmentBuilder', () => { - let envBuilder; - before(() => { - envBuilder = EnvironmentBuilder.createDefaultBuilder(); - }); - it(`should be registered as jhipster:${generator} at yeoman-environment`, async () => { - expect(await envBuilder.getEnvironment().get(`jhipster:${generator}`)).toBe(BaseGenerator); - }); - }); - - describe('skipPriorities', () => { - const initializing = esmocha.fn(); - const prompting = esmocha.fn(); - const writing = esmocha.fn(); - const postWriting = esmocha.fn(); - - class CustomGenerator extends BaseGenerator { - get [BaseGenerator.INITIALIZING]() { - initializing(); - return {}; - } - - get [BaseGenerator.PROMPTING]() { - prompting(); - return {}; - } - - get [BaseGenerator.WRITING]() { - writing(); - return {}; - } - - get [BaseGenerator.POST_WRITING]() { - postWriting(); - return {}; - } - } - - before(async () => { - await helpers.run(CustomGenerator).withOptions({ - skipPriorities: ['prompting', 'writing', 'postWriting'], - }); - }); - - it('should skip priorities', async () => { - expect(initializing).toBeCalled(); - expect(prompting).not.toBeCalled(); - expect(writing).not.toBeCalled(); - expect(postWriting).not.toBeCalled(); - }); - }); -}); diff --git a/generators/base/generator.spec.ts b/generators/base/generator.spec.ts new file mode 100644 index 000000000000..9e64a6d739a7 --- /dev/null +++ b/generators/base/generator.spec.ts @@ -0,0 +1,98 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect, esmocha } from 'esmocha'; +import lodash from 'lodash'; + +import EnvironmentBuilder from '../../cli/environment-builder.mjs'; +import BaseGenerator from './index.js'; +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { getCommandHelpOutput, shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(BaseGenerator); + describe('help', () => { + it('should print expected information', async () => { + expect(await getCommandHelpOutput()).toMatchSnapshot(); + }); + }); + + describe.skip('EnvironmentBuilder', () => { + let envBuilder; + before(() => { + envBuilder = EnvironmentBuilder.createDefaultBuilder(); + }); + it(`should be registered as jhipster:${generator} at yeoman-environment`, async () => { + expect(await envBuilder.getEnvironment().get(`jhipster:${generator}`)).toBe(BaseGenerator); + }); + }); + + describe('skipPriorities', () => { + const initializing = esmocha.fn(); + const prompting = esmocha.fn(); + const writing = esmocha.fn(); + const postWriting = esmocha.fn(); + + class CustomGenerator extends BaseGenerator { + get [BaseGenerator.INITIALIZING]() { + initializing(); + return {}; + } + + get [BaseGenerator.PROMPTING]() { + prompting(); + return {}; + } + + get [BaseGenerator.WRITING]() { + writing(); + return {}; + } + + get [BaseGenerator.POST_WRITING]() { + postWriting(); + return {}; + } + } + + before(async () => { + await helpers.run(CustomGenerator).withOptions({ + skipPriorities: ['prompting', 'writing', 'postWriting'], + }); + }); + + it('should skip priorities', async () => { + expect(initializing).toBeCalled(); + expect(prompting).not.toBeCalled(); + expect(writing).not.toBeCalled(); + expect(postWriting).not.toBeCalled(); + }); + }); +}); diff --git a/generators/base/generator.ts b/generators/base/generator.ts new file mode 100644 index 000000000000..74b7782e5fa4 --- /dev/null +++ b/generators/base/generator.ts @@ -0,0 +1,682 @@ +/** + * Copyright 2013-2023 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 path from 'path'; +import chalk from 'chalk'; +import semver from 'semver'; +import * as _ from 'lodash-es'; + +import type { ComposeOptions } from 'yeoman-generator'; +import { packageJson } from '../../lib/index.js'; +import { packageNameToNamespace, removeFieldsWithNullishValues } from './support/index.js'; +import { mergeBlueprints, parseBluePrints, loadBlueprintsFromConfiguration, normalizeBlueprintName } from './internal/index.js'; +import { PRIORITY_NAMES } from './priorities.js'; +import { BaseGeneratorDefinition, GenericTaskGroup } from './tasks.js'; +import { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from './api.js'; +import CoreGenerator from '../base-core/index.js'; +import { LOCAL_BLUEPRINT_PACKAGE_NAMESPACE } from './support/constants.js'; +import { getConfigWithDefaults } from '../../jdl/index.js'; +import { loadStoredAppOptions } from '../app/support/index.js'; + +const { defaults } = _; + +/** + * Base class that contains blueprints support. + */ +export default class JHipsterBaseBlueprintGenerator< + Definition extends BaseGeneratorDefinition = BaseGeneratorDefinition, +> extends CoreGenerator { + fromBlueprint!: boolean; + sbsBlueprint?: boolean; + delegateToBlueprint?: boolean; + blueprintConfig?: Record; + jhipsterContext?: any; + + constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { + const { jhipsterContext, ...opts } = options ?? {}; + super(args, opts, features); + + if (this.options.help) { + return; + } + + loadStoredAppOptions.call(this); + + this.sbsBlueprint = this.features.sbsBlueprint ?? false; + this.fromBlueprint = this.rootGeneratorName() !== 'generator-jhipster'; + + if (this.fromBlueprint) { + this.blueprintStorage = this._getStorage(); + this.blueprintConfig = this.blueprintStorage.createProxy(); + + // jhipsterContext is the original generator + this.jhipsterContext = jhipsterContext; + + if (this.getFeatures().checkBlueprint) { + if (!this.jhipsterContext) { + throw new Error( + `This is a JHipster blueprint and should be used only like ${chalk.yellow( + `jhipster --blueprints ${this.options.namespace.split(':')[0]}`, + )}`, + ); + } + } + + try { + // Fallback to the original generator if the file does not exists in the blueprint. + const blueprintedTemplatePath = this.jhipsterTemplatePath(); + if (!this.jhipsterTemplatesFolders.includes(blueprintedTemplatePath)) { + this.jhipsterTemplatesFolders.push(blueprintedTemplatePath); + } + } catch (error) { + this.log.warn('Error adding current blueprint templates as alternative for JHipster templates.'); + this.log.log(error); + } + } + } + + /** + * Filter generator's tasks in case the blueprint should be responsible on queueing those tasks. + */ + delegateTasksToBlueprint(tasksGetter: () => TaskGroupType): TaskGroupType { + return this.delegateToBlueprint ? ({} as TaskGroupType) : tasksGetter(); + } + + /** + * Priority API stub for blueprints. + * + * Initializing priority is used to show logo and tasks related to preparing for prompts, like loading constants. + */ + get initializing(): GenericTaskGroup { + return this.asInitializingTaskGroup(this._initializing()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _initializing() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asInitializingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Prompting priority is used to prompt users for configuration values. + */ + get prompting(): GenericTaskGroup { + return this.asPromptingTaskGroup(this._prompting()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _prompting() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPromptingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Configuring priority is used to customize and validate the configuration. + */ + get configuring(): GenericTaskGroup { + return this.asConfiguringTaskGroup(this._configuring()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _configuring() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asConfiguringTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Composing should be used to compose with others generators. + */ + get composing(): GenericTaskGroup { + return this.asComposingTaskGroup(this._composing()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _composing() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asComposingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Loading should be used to load application configuration from jhipster configuration. + * Before this priority the configuration should be considered dirty, while each generator configures itself at configuring priority, another generator composed at composing priority can still change it. + */ + get loading(): GenericTaskGroup { + return this.asLoadingTaskGroup(this._loading()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _loading() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asLoadingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Preparing should be used to generate derived properties. + */ + get preparing(): GenericTaskGroup { + return this.asPreparingTaskGroup(this._preparing()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _preparing() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPreparingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Preparing should be used to generate derived properties. + */ + get postPreparing(): GenericTaskGroup { + return this.asPreparingTaskGroup({}); + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPostPreparingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Default priority should used as misc customizations. + */ + get default(): GenericTaskGroup { + return this.asDefaultTaskGroup(this._default()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _default() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asDefaultTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Writing priority should used to write files. + */ + get writing(): GenericTaskGroup { + return this.asWritingTaskGroup(this._writing()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _writing() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asWritingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * PostWriting priority should used to customize files. + */ + get postWriting(): GenericTaskGroup { + return this.asPostWritingTaskGroup(this._postWriting()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _postWriting() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPostWritingTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * Install priority should used to prepare the project. + */ + get install(): GenericTaskGroup { + return this.asInstallTaskGroup(this._install()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _install() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asInstallTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * PostWriting priority should used to customize files. + */ + get postInstall(): GenericTaskGroup { + return this.asPostInstallTaskGroup(this._postInstall()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _postInstall() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asPostInstallTaskGroup( + taskGroup: GenericTaskGroup, + ): GenericTaskGroup { + return taskGroup; + } + + /** + * Priority API stub for blueprints. + * + * End priority should used to say good bye and print instructions. + */ + get end(): GenericTaskGroup { + return this.asEndTaskGroup(this._end()); + } + + /** + * @deprecated + * Public API method used by the getter and also by Blueprints + */ + _end() { + return {}; + } + + /** + * Utility method to get typed objects for autocomplete. + */ + asEndTaskGroup(taskGroup: GenericTaskGroup): GenericTaskGroup { + return taskGroup; + } + + /** + * JHipster config with default values fallback + */ + get jhipsterConfigWithDefaults() { + const configWithDefaults = getConfigWithDefaults(removeFieldsWithNullishValues(this.config.getAll())); + defaults(configWithDefaults, { + skipFakeData: false, + skipCheckLengthOfIdentifier: false, + enableGradleEnterprise: false, + pages: [], + }); + return configWithDefaults; + } + + /** + * @protected + * Composes with blueprint generators, if any. + */ + protected async composeWithBlueprints(subGen: string, options?: ComposeOptions) { + this.delegateToBlueprint = false; + + if (this.options.disableBlueprints) { + return []; + } + + const control = this.sharedData.getControl(); + if (!control.blueprintConfigured) { + control.blueprintConfigured = true; + await this._configureBlueprints(); + } + + let blueprints = this.jhipsterConfig.blueprints || []; + if (this.options.composeWithLocalBlueprint) { + blueprints = blueprints.concat({ name: '@jhipster/local' }); + } + const composedBlueprints: any[] = []; + for (const blueprint of blueprints) { + const blueprintGenerator = await this._composeBlueprint(blueprint.name, subGen, options); + if (blueprintGenerator) { + composedBlueprints.push(blueprintGenerator); + if ((blueprintGenerator as any).sbsBlueprint) { + // If sbsBlueprint, add templatePath to the original generator templatesFolder. + this.jhipsterTemplatesFolders.unshift(blueprintGenerator.templatePath()); + } else { + // If the blueprints does not sets sbsBlueprint property, ignore normal workflow. + this.delegateToBlueprint = true; + this.checkBlueprintImplementsPriorities(blueprintGenerator); + } + } + } + return composedBlueprints; + } + + /** + * Check if the blueprint implements every priority implemented by the parent generator + * @param {BaseGenerator} blueprintGenerator + */ + private checkBlueprintImplementsPriorities(blueprintGenerator) { + const { taskPrefix: baseGeneratorTaskPrefix = '' } = this.features; + const { taskPrefix: blueprintTaskPrefix = '' } = blueprintGenerator.features; + // v8 remove deprecated priorities + const DEPRECATED_PRIORITIES = ['preConflicts']; + for (const priorityName of Object.values(PRIORITY_NAMES).filter(p => !DEPRECATED_PRIORITIES.includes(p))) { + const baseGeneratorPriorityName = `${baseGeneratorTaskPrefix}${priorityName}`; + if (baseGeneratorPriorityName in this) { + const blueprintPriorityName = `${blueprintTaskPrefix}${priorityName}`; + if (!Object.hasOwn(Object.getPrototypeOf(blueprintGenerator), blueprintPriorityName)) { + this.log.debug(`Priority ${blueprintPriorityName} not implemented at ${blueprintGenerator.options.namespace}.`); + } + } + } + } + + /** + * @private + * Configure blueprints. + */ + private async _configureBlueprints() { + let argvBlueprints = this.options.blueprints || ''; + // check for old single blueprint declaration + const blueprint = this.options.blueprint; + if (blueprint) { + this.log.warn('--blueprint option is deprecated. Please use --blueprints instead'); + if (!argvBlueprints.split(',').includes(blueprint)) { + argvBlueprints = `${blueprint},${argvBlueprints}`; + } + } + const blueprints = mergeBlueprints(parseBluePrints(argvBlueprints), loadBlueprintsFromConfiguration(this.config)); + + // EnvironmentBuilder already looks for blueprint when running from cli, this is required for tests. + // Can be removed once the tests uses EnvironmentBuilder. + const missingBlueprints = blueprints + .filter(blueprint => !this.env.isPackageRegistered(packageNameToNamespace(blueprint.name))) + .map(blueprint => blueprint.name); + if (missingBlueprints.length > 0) { + await this.env.lookup({ filterPaths: true, packagePatterns: missingBlueprints } as any); + } + + if (blueprints && blueprints.length > 0) { + blueprints.forEach(blueprint => { + blueprint.version = this._findBlueprintVersion(blueprint.name) || blueprint.version; + }); + this.jhipsterConfig.blueprints = blueprints; + } + + if (!this.skipChecks) { + const namespaces = blueprints.map(blueprint => packageNameToNamespace(blueprint.name)); + // Verify if the blueprints hava been registered. + const missing = namespaces.filter(namespace => !this.env.isPackageRegistered(namespace)); + if (missing && missing.length > 0) { + throw new Error(`Some blueprints were not found ${missing}, you should install them manually`); + } + } + } + + /** + * @private + * Compose external blueprint module + * @param {string} blueprint - name of the blueprint + * @param {string} subGen - sub generator + * @param {any} [extraOptions] - options to pass to blueprint generator + * @return {Generator|undefined} + */ + private async _composeBlueprint( + blueprint, + subGen, + extraOptions: ComposeOptions = {}, + ): Promise { + blueprint = normalizeBlueprintName(blueprint); + if (!this.skipChecks && blueprint !== LOCAL_BLUEPRINT_PACKAGE_NAMESPACE) { + this._checkBlueprint(blueprint); + } + + const generatorName = packageNameToNamespace(blueprint); + const generatorNamespace = `${generatorName}:${subGen}`; + if (!(await this.env.get(generatorNamespace))) { + this.log.debug( + `No blueprint found for blueprint ${chalk.yellow(blueprint)} and ${chalk.yellow(subGen)} with namespace ${chalk.yellow( + generatorNamespace, + )} subgenerator: falling back to default generator`, + ); + return undefined; + } + this.log.debug( + `Found blueprint ${chalk.yellow(blueprint)} and ${chalk.yellow(subGen)} with namespace ${chalk.yellow(generatorNamespace)}`, + ); + + const finalOptions: ComposeOptions = { + forwardOptions: true, + ...extraOptions, + generatorOptions: { + jhipsterContext: this, + ...extraOptions?.generatorOptions, + } as any, + }; + + const blueprintGenerator = await this.composeWith(generatorNamespace, finalOptions as any); + if (blueprintGenerator instanceof Error) { + throw blueprintGenerator; + } + (this as any)._debug(`Using blueprint ${chalk.yellow(blueprint)} for ${chalk.yellow(subGen)} subgenerator`); + return blueprintGenerator; + } + + /** + * @private + * Try to retrieve the package.json of the blueprint used, as an object. + * @param {string} blueprintPkgName - generator name + * @return {object} packageJson - retrieved package.json as an object or undefined if not found + */ + private _findBlueprintPackageJson(blueprintPkgName) { + const blueprintGeneratorName = packageNameToNamespace(blueprintPkgName); + const blueprintPackagePath = this.env.getPackagePath(blueprintGeneratorName); + if (!blueprintPackagePath) { + this.log.warn(`Could not retrieve packagePath of blueprint '${blueprintPkgName}'`); + return undefined; + } + const packageJsonFile = path.join(blueprintPackagePath, 'package.json'); + if (!fs.existsSync(packageJsonFile)) { + return undefined; + } + return JSON.parse(fs.readFileSync(packageJsonFile).toString()); + } + + /** + * @private + * Try to retrieve the version of the blueprint used. + * @param {string} blueprintPkgName - generator name + * @return {string} version - retrieved version or empty string if not found + */ + private _findBlueprintVersion(blueprintPkgName) { + const blueprintPackageJson = this._findBlueprintPackageJson(blueprintPkgName); + if (!blueprintPackageJson || !blueprintPackageJson.version) { + this.log.warn(`Could not retrieve version of blueprint '${blueprintPkgName}'`); + return undefined; + } + return blueprintPackageJson.version; + } + + /** + * @private + * Check if the generator specified as blueprint is installed. + * @param {string} blueprint - generator name + */ + protected _checkBlueprint(blueprint) { + if (blueprint === 'generator-jhipster') { + throw new Error(`You cannot use ${chalk.yellow(blueprint)} as the blueprint.`); + } + this._findBlueprintPackageJson(blueprint); + } + + /** + * @private + * Check if the generator specified as blueprint has a version compatible with current JHipster. + * @param {string} blueprintPkgName - generator name + */ + protected _checkJHipsterBlueprintVersion(blueprintPkgName) { + const blueprintPackageJson = this._findBlueprintPackageJson(blueprintPkgName); + if (!blueprintPackageJson) { + this.log.warn(`Could not retrieve version of JHipster declared by blueprint '${blueprintPkgName}'`); + return; + } + const mainGeneratorJhipsterVersion = packageJson.version; + const blueprintJhipsterVersion = blueprintPackageJson.dependencies && blueprintPackageJson.dependencies['generator-jhipster']; + if (blueprintJhipsterVersion) { + if (!semver.valid(blueprintJhipsterVersion) && !semver.validRange(blueprintJhipsterVersion)) { + this.log.verboseInfo(`Blueprint ${blueprintPkgName} contains generator-jhipster dependency with non comparable version`); + return; + } + if (semver.satisfies(mainGeneratorJhipsterVersion, blueprintJhipsterVersion, { includePrerelease: true })) { + return; + } + throw new Error( + `The installed ${chalk.yellow( + blueprintPkgName, + )} blueprint targets JHipster v${blueprintJhipsterVersion} and is not compatible with this JHipster version. Either update the blueprint or JHipster. You can also disable this check using --skip-checks at your own risk`, + ); + } + const blueprintPeerJhipsterVersion = + blueprintPackageJson.peerDependencies && blueprintPackageJson.peerDependencies['generator-jhipster']; + if (blueprintPeerJhipsterVersion) { + if (semver.satisfies(mainGeneratorJhipsterVersion, blueprintPeerJhipsterVersion, { includePrerelease: true })) { + return; + } + throw new Error( + `The installed ${chalk.yellow( + blueprintPkgName, + )} blueprint targets JHipster ${blueprintPeerJhipsterVersion} and is not compatible with this JHipster version. Either update the blueprint or JHipster. You can also disable this check using --skip-checks at your own risk`, + ); + } + this.log.warn(`Could not retrieve version of JHipster declared by blueprint '${blueprintPkgName}'`); + } +} diff --git a/generators/base/index.mts b/generators/base/index.mts deleted file mode 100644 index aa0b6781f488..000000000000 --- a/generators/base/index.mts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/base/index.ts b/generators/base/index.ts new file mode 100644 index 000000000000..de7cd835a2e2 --- /dev/null +++ b/generators/base/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/base/internal/blueprint.mjs b/generators/base/internal/blueprint.js similarity index 100% rename from generators/base/internal/blueprint.mjs rename to generators/base/internal/blueprint.js diff --git a/generators/base/internal/blueprint.spec.mts b/generators/base/internal/blueprint.spec.mts deleted file mode 100644 index 4c2bd101d3e6..000000000000 --- a/generators/base/internal/blueprint.spec.mts +++ /dev/null @@ -1,142 +0,0 @@ -import assert from 'assert'; -import { expect } from 'esmocha'; -import { mergeBlueprints, normalizeBlueprintName, parseBluePrints, removeBlueprintDuplicates } from './blueprint.mjs'; - -describe('generator - base - internal - blueprint', () => { - describe('::parseBluePrints', () => { - it('does nothing if an array', () => { - const expected = [{ name: 'generator-jhipster-foo', version: 'latest' }]; - const actual = parseBluePrints(expected); - assert.deepStrictEqual(actual, expected); - }); - it('returns a array if empty string', () => { - const expected = []; - const actual = parseBluePrints(''); - assert.deepStrictEqual(actual, expected); - }); - it('returns a array if not string', () => { - assert.deepStrictEqual(parseBluePrints(), []); - }); - it('adds generator-jhipster prefix if it is absent', () => { - const expected = [{ name: 'generator-jhipster-foo' }]; - const actual = parseBluePrints('foo'); - assert.deepStrictEqual(actual, expected); - }); - it('keeps generator-jhipster prefix if it is present', () => { - const expected = [{ name: 'generator-jhipster-foo', version: '1.0.1' }]; - const actual = parseBluePrints('generator-jhipster-foo@1.0.1'); - assert.deepStrictEqual(actual, expected); - }); - it('adds generator-jhipster prefix to scoped package and extracts version', () => { - const expected = [{ name: '@corp/generator-jhipster-foo', version: '1.0.1' }]; - const actual = parseBluePrints('@corp/foo@1.0.1'); - assert.deepStrictEqual(actual, expected); - }); - it('parses comma separated list', () => { - const expected = [ - { name: 'generator-jhipster-foo' }, - { name: 'generator-jhipster-bar', version: '1.0.1' }, - { name: '@corp/generator-jhipster-foo' }, - ]; - const actual = parseBluePrints('foo,bar@1.0.1,@corp/foo'); - assert.deepStrictEqual(actual, expected); - }); - }); - describe('::mergeBlueprints', () => { - describe('not passing arguments', () => { - it('returns a empty array', () => { - const expected = []; - const actual = mergeBlueprints(); - assert.deepStrictEqual(actual, expected); - }); - }); - describe('passing undefined', () => { - it('throws an error', done => { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mergeBlueprints(undefined as any); - } catch (error) { - assert.equal(error.message, 'Only arrays are supported.'); - done(); - } - }); - }); - describe('passing array and undefined', () => { - const argumentsToPass = [[], undefined]; - it('throws an error', done => { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (mergeBlueprints as any)(...argumentsToPass); - } catch (error) { - assert.equal(error.message, 'Only arrays are supported.'); - done(); - } - }); - }); - describe('passing unique blueprints', () => { - const argumentsToPass = [ - [{ name: 'generator-jhipster-foo', version: 'latest' }], - [{ name: 'generator-jhipster-bar', version: '1.0.1' }], - ]; - it('returns them concatenated', () => { - const expected = [ - { name: 'generator-jhipster-foo', version: 'latest' }, - { name: 'generator-jhipster-bar', version: '1.0.1' }, - ]; - const actual = mergeBlueprints(...argumentsToPass); - assert.deepStrictEqual(actual, expected); - }); - }); - describe('passing non unique blueprints', () => { - it('prefers prior version', () => { - const argumentsToPass = [ - [ - { name: 'generator-jhipster-foo', version: 'latest' }, - { name: 'generator-jhipster-bar', version: '1.0.1' }, - ], - [{ name: 'generator-jhipster-foo', version: '1.0.1' }], - ]; - const expected = [ - { name: 'generator-jhipster-foo', version: 'latest' }, - { name: 'generator-jhipster-bar', version: '1.0.1' }, - ]; - const actual = mergeBlueprints(...argumentsToPass); - assert.deepStrictEqual(actual, expected); - }); - it('uses later version when prior version is not defined', () => { - const argumentsToPass = [ - [{ name: 'generator-jhipster-foo' }, { name: 'generator-jhipster-bar', version: '1.0.1' }], - [{ name: 'generator-jhipster-foo', version: '1.0.1' }], - ]; - const expected = [ - { name: 'generator-jhipster-foo', version: '1.0.1' }, - { name: 'generator-jhipster-bar', version: '1.0.1' }, - ]; - const actual = mergeBlueprints(...argumentsToPass); - assert.deepStrictEqual(actual, expected); - }); - }); - }); - describe('::removeBlueprintDuplicates', () => { - it('keeps blueprints with undefined version', () => { - const argumentsToPass = [{ name: 'generator-jhipster-foo' }]; - const expected = [{ name: 'generator-jhipster-foo' }]; - const actual = removeBlueprintDuplicates(argumentsToPass); - assert.deepStrictEqual(actual, expected); - }); - }); - describe('::normalizeBlueprintName', () => { - it('adds generator-jhipster prefix if it is absent', () => { - const generatorName = normalizeBlueprintName('foo'); - expect(generatorName).toBe('generator-jhipster-foo'); - }); - it('keeps generator-jhipster prefix if it is present', () => { - const generatorName = normalizeBlueprintName('generator-jhipster-foo'); - expect(generatorName).toBe('generator-jhipster-foo'); - }); - it('adds generator-jhipster prefix for scoped package', () => { - const generatorName = normalizeBlueprintName('@corp/foo'); - expect(generatorName).toBe('@corp/generator-jhipster-foo'); - }); - }); -}); diff --git a/generators/base/internal/blueprint.spec.ts b/generators/base/internal/blueprint.spec.ts new file mode 100644 index 000000000000..b00a4885442f --- /dev/null +++ b/generators/base/internal/blueprint.spec.ts @@ -0,0 +1,142 @@ +import assert from 'assert'; +import { expect } from 'esmocha'; +import { mergeBlueprints, normalizeBlueprintName, parseBluePrints, removeBlueprintDuplicates } from './blueprint.js'; + +describe('generator - base - internal - blueprint', () => { + describe('::parseBluePrints', () => { + it('does nothing if an array', () => { + const expected = [{ name: 'generator-jhipster-foo', version: 'latest' }]; + const actual = parseBluePrints(expected); + assert.deepStrictEqual(actual, expected); + }); + it('returns a array if empty string', () => { + const expected = []; + const actual = parseBluePrints(''); + assert.deepStrictEqual(actual, expected); + }); + it('returns a array if not string', () => { + assert.deepStrictEqual(parseBluePrints(), []); + }); + it('adds generator-jhipster prefix if it is absent', () => { + const expected = [{ name: 'generator-jhipster-foo' }]; + const actual = parseBluePrints('foo'); + assert.deepStrictEqual(actual, expected); + }); + it('keeps generator-jhipster prefix if it is present', () => { + const expected = [{ name: 'generator-jhipster-foo', version: '1.0.1' }]; + const actual = parseBluePrints('generator-jhipster-foo@1.0.1'); + assert.deepStrictEqual(actual, expected); + }); + it('adds generator-jhipster prefix to scoped package and extracts version', () => { + const expected = [{ name: '@corp/generator-jhipster-foo', version: '1.0.1' }]; + const actual = parseBluePrints('@corp/foo@1.0.1'); + assert.deepStrictEqual(actual, expected); + }); + it('parses comma separated list', () => { + const expected = [ + { name: 'generator-jhipster-foo' }, + { name: 'generator-jhipster-bar', version: '1.0.1' }, + { name: '@corp/generator-jhipster-foo' }, + ]; + const actual = parseBluePrints('foo,bar@1.0.1,@corp/foo'); + assert.deepStrictEqual(actual, expected); + }); + }); + describe('::mergeBlueprints', () => { + describe('not passing arguments', () => { + it('returns a empty array', () => { + const expected = []; + const actual = mergeBlueprints(); + assert.deepStrictEqual(actual, expected); + }); + }); + describe('passing undefined', () => { + it('throws an error', done => { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mergeBlueprints(undefined as any); + } catch (error) { + assert.equal(error.message, 'Only arrays are supported.'); + done(); + } + }); + }); + describe('passing array and undefined', () => { + const argumentsToPass = [[], undefined]; + it('throws an error', done => { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mergeBlueprints as any)(...argumentsToPass); + } catch (error) { + assert.equal(error.message, 'Only arrays are supported.'); + done(); + } + }); + }); + describe('passing unique blueprints', () => { + const argumentsToPass = [ + [{ name: 'generator-jhipster-foo', version: 'latest' }], + [{ name: 'generator-jhipster-bar', version: '1.0.1' }], + ]; + it('returns them concatenated', () => { + const expected = [ + { name: 'generator-jhipster-foo', version: 'latest' }, + { name: 'generator-jhipster-bar', version: '1.0.1' }, + ]; + const actual = mergeBlueprints(...argumentsToPass); + assert.deepStrictEqual(actual, expected); + }); + }); + describe('passing non unique blueprints', () => { + it('prefers prior version', () => { + const argumentsToPass = [ + [ + { name: 'generator-jhipster-foo', version: 'latest' }, + { name: 'generator-jhipster-bar', version: '1.0.1' }, + ], + [{ name: 'generator-jhipster-foo', version: '1.0.1' }], + ]; + const expected = [ + { name: 'generator-jhipster-foo', version: 'latest' }, + { name: 'generator-jhipster-bar', version: '1.0.1' }, + ]; + const actual = mergeBlueprints(...argumentsToPass); + assert.deepStrictEqual(actual, expected); + }); + it('uses later version when prior version is not defined', () => { + const argumentsToPass = [ + [{ name: 'generator-jhipster-foo' }, { name: 'generator-jhipster-bar', version: '1.0.1' }], + [{ name: 'generator-jhipster-foo', version: '1.0.1' }], + ]; + const expected = [ + { name: 'generator-jhipster-foo', version: '1.0.1' }, + { name: 'generator-jhipster-bar', version: '1.0.1' }, + ]; + const actual = mergeBlueprints(...argumentsToPass); + assert.deepStrictEqual(actual, expected); + }); + }); + }); + describe('::removeBlueprintDuplicates', () => { + it('keeps blueprints with undefined version', () => { + const argumentsToPass = [{ name: 'generator-jhipster-foo' }]; + const expected = [{ name: 'generator-jhipster-foo' }]; + const actual = removeBlueprintDuplicates(argumentsToPass); + assert.deepStrictEqual(actual, expected); + }); + }); + describe('::normalizeBlueprintName', () => { + it('adds generator-jhipster prefix if it is absent', () => { + const generatorName = normalizeBlueprintName('foo'); + expect(generatorName).toBe('generator-jhipster-foo'); + }); + it('keeps generator-jhipster prefix if it is present', () => { + const generatorName = normalizeBlueprintName('generator-jhipster-foo'); + expect(generatorName).toBe('generator-jhipster-foo'); + }); + it('adds generator-jhipster prefix for scoped package', () => { + const generatorName = normalizeBlueprintName('@corp/foo'); + expect(generatorName).toBe('@corp/generator-jhipster-foo'); + }); + }); +}); diff --git a/generators/base/internal/index.mts b/generators/base/internal/index.mts deleted file mode 100644 index 2bc11cfac788..000000000000 --- a/generators/base/internal/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './blueprint.mjs'; diff --git a/generators/base/internal/index.ts b/generators/base/internal/index.ts new file mode 100644 index 000000000000..e6eeb79cab29 --- /dev/null +++ b/generators/base/internal/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './blueprint.js'; diff --git a/generators/base/priorities.mjs b/generators/base/priorities.js similarity index 100% rename from generators/base/priorities.mjs rename to generators/base/priorities.js diff --git a/generators/base/shared-data.mts b/generators/base/shared-data.mts deleted file mode 100644 index ee2aee24f373..000000000000 --- a/generators/base/shared-data.mts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { type BaseApplication } from '../base-application/types.mjs'; -import { type Control } from './types.mjs'; - -const { defaults } = _; - -export default class SharedData { - _storage: any; - - constructor(storage, initialControl: Partial = {}) { - if (!storage) { - throw new Error('Storage is required for SharedData'); - } - // Backward compatibility sharedData - this._storage = storage; - - defaults(this._storage, { - sharedDeployment: {}, - sharedWorkspaces: {}, - sharedEntities: {}, - sharedApplication: {}, - sharedSource: {}, - control: initialControl, - props: {}, - }); - this._storage.sharedApplication.nodeDependencies = this._storage.sharedApplication.nodeDependencies ?? {}; - } - - getSource() { - return this._storage.sharedSource; - } - - getControl(): Control { - return this._storage.control; - } - - getApplication(): ApplicationType { - if (!this._storage.sharedApplication) throw new Error('Shared application not loaded'); - return this._storage.sharedApplication; - } - - setEntity(entityName, entity) { - this._storage.sharedEntities[entityName] = entity; - } - - hasEntity(entityName) { - return Boolean(this._storage.sharedEntities[entityName]); - } - - getEntity(entityName) { - const entity = this._storage.sharedEntities[entityName]; - if (!entity) { - throw new Error(`Entity definition not loaded for ${entityName}`); - } - return entity; - } - - getEntities(entityNames = Object.keys(this._storage.sharedEntities)) { - return entityNames.map(entityName => ({ entityName, entity: this.getEntity(entityName) })); - } - - getEntitiesMap() { - return this._storage.sharedEntities; - } -} diff --git a/generators/base/shared-data.ts b/generators/base/shared-data.ts new file mode 100644 index 000000000000..61082c61e33e --- /dev/null +++ b/generators/base/shared-data.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { type BaseApplication } from '../base-application/types.js'; +import { type Control } from './types.js'; + +const { defaults } = _; + +export default class SharedData { + _storage: any; + + constructor(storage, initialControl: Partial = {}) { + if (!storage) { + throw new Error('Storage is required for SharedData'); + } + // Backward compatibility sharedData + this._storage = storage; + + defaults(this._storage, { + sharedDeployment: {}, + sharedWorkspaces: {}, + sharedEntities: {}, + sharedApplication: {}, + sharedSource: {}, + control: initialControl, + props: {}, + }); + this._storage.sharedApplication.nodeDependencies = this._storage.sharedApplication.nodeDependencies ?? {}; + } + + getSource() { + return this._storage.sharedSource; + } + + getControl(): Control { + return this._storage.control; + } + + getApplication(): ApplicationType { + if (!this._storage.sharedApplication) throw new Error('Shared application not loaded'); + return this._storage.sharedApplication; + } + + setEntity(entityName, entity) { + this._storage.sharedEntities[entityName] = entity; + } + + hasEntity(entityName) { + return Boolean(this._storage.sharedEntities[entityName]); + } + + getEntity(entityName) { + const entity = this._storage.sharedEntities[entityName]; + if (!entity) { + throw new Error(`Entity definition not loaded for ${entityName}`); + } + return entity; + } + + getEntities(entityNames = Object.keys(this._storage.sharedEntities)) { + return entityNames.map(entityName => ({ entityName, entity: this.getEntity(entityName) })); + } + + getEntitiesMap() { + return this._storage.sharedEntities; + } +} diff --git a/generators/base/support/basename.spec.mts b/generators/base/support/basename.spec.mts deleted file mode 100644 index ac60548630a4..000000000000 --- a/generators/base/support/basename.spec.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, describe, it } from 'esmocha'; -import { getFrontendAppName } from './basename.mjs'; - -describe('generator > base', () => { - describe('getFrontendAppName', () => { - describe('when called with name having App', () => { - it('returns the frontend app name', () => { - expect(getFrontendAppName({ baseName: 'myAmazingApp' })).toBe('myAmazingApp'); - }); - }); - describe('when called with name', () => { - it('returns the frontend app name with the App suffix added', () => { - expect(getFrontendAppName({ baseName: 'myAwesomeProject' })).toBe('myAwesomeProjectApp'); - }); - }); - describe('when called with name starting with a digit', () => { - it('returns the default frontend app name - App', () => { - expect(getFrontendAppName({ baseName: '1derful' })).toBe('App'); - }); - }); - }); -}); diff --git a/generators/base/support/basename.spec.ts b/generators/base/support/basename.spec.ts new file mode 100644 index 000000000000..1d1a96385a2f --- /dev/null +++ b/generators/base/support/basename.spec.ts @@ -0,0 +1,22 @@ +import { expect, describe, it } from 'esmocha'; +import { getFrontendAppName } from './basename.js'; + +describe('generator > base', () => { + describe('getFrontendAppName', () => { + describe('when called with name having App', () => { + it('returns the frontend app name', () => { + expect(getFrontendAppName({ baseName: 'myAmazingApp' })).toBe('myAmazingApp'); + }); + }); + describe('when called with name', () => { + it('returns the frontend app name with the App suffix added', () => { + expect(getFrontendAppName({ baseName: 'myAwesomeProject' })).toBe('myAwesomeProjectApp'); + }); + }); + describe('when called with name starting with a digit', () => { + it('returns the default frontend app name - App', () => { + expect(getFrontendAppName({ baseName: '1derful' })).toBe('App'); + }); + }); + }); +}); diff --git a/generators/base/support/basename.mts b/generators/base/support/basename.ts similarity index 100% rename from generators/base/support/basename.mts rename to generators/base/support/basename.ts diff --git a/generators/base/support/config.spec.mts b/generators/base/support/config.spec.mts deleted file mode 100644 index cdb5bef83372..000000000000 --- a/generators/base/support/config.spec.mts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from 'esmocha'; -import { removeFieldsWithNullishValues } from './config.mjs'; - -describe('generator - base - support - config', () => { - describe('deepCleanup', () => { - it('should cleanup objects', () => { - expect(removeFieldsWithNullishValues({ foo: 'bar', foo2: undefined, foo3: null })).toMatchObject({ foo: 'bar' }); - }); - it('should cleanup property objects', () => { - expect(removeFieldsWithNullishValues({ nested: { foo: 'bar', foo2: undefined, foo3: null } })).toMatchObject({ - nested: { foo: 'bar' }, - }); - }); - it('should cleanup property arrays', () => { - expect(removeFieldsWithNullishValues({ foo: ['bar', undefined, null] })).toMatchObject({ foo: ['bar'] }); - }); - }); -}); diff --git a/generators/base/support/config.spec.ts b/generators/base/support/config.spec.ts new file mode 100644 index 000000000000..50c6199d8071 --- /dev/null +++ b/generators/base/support/config.spec.ts @@ -0,0 +1,18 @@ +import { expect } from 'esmocha'; +import { removeFieldsWithNullishValues } from './config.js'; + +describe('generator - base - support - config', () => { + describe('deepCleanup', () => { + it('should cleanup objects', () => { + expect(removeFieldsWithNullishValues({ foo: 'bar', foo2: undefined, foo3: null })).toMatchObject({ foo: 'bar' }); + }); + it('should cleanup property objects', () => { + expect(removeFieldsWithNullishValues({ nested: { foo: 'bar', foo2: undefined, foo3: null } })).toMatchObject({ + nested: { foo: 'bar' }, + }); + }); + it('should cleanup property arrays', () => { + expect(removeFieldsWithNullishValues({ foo: ['bar', undefined, null] })).toMatchObject({ foo: ['bar'] }); + }); + }); +}); diff --git a/generators/base/support/config.mts b/generators/base/support/config.ts similarity index 100% rename from generators/base/support/config.mts rename to generators/base/support/config.ts diff --git a/generators/base/support/configuration-helpers/options.mjs b/generators/base/support/configuration-helpers/options.js similarity index 100% rename from generators/base/support/configuration-helpers/options.mjs rename to generators/base/support/configuration-helpers/options.js diff --git a/generators/base/support/constants.mts b/generators/base/support/constants.ts similarity index 100% rename from generators/base/support/constants.mts rename to generators/base/support/constants.ts diff --git a/generators/base/support/contents.spec.mts b/generators/base/support/contents.spec.mts deleted file mode 100644 index 97831b6a34b5..000000000000 --- a/generators/base/support/contents.spec.mts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect } from 'esmocha'; - -import { normalizeLineEndings, stripMargin } from './contents.mjs'; - -describe('generator - base - support - contents', () => { - describe('stripMargin', () => { - it('should produce correct output without margin', () => { - const entityFolderName = 'entityFolderName'; - const entityFileName = 'entityFileName'; - const content = `|export * from './${entityFolderName}/${entityFileName}-update.component'; - |export * from './${entityFolderName}/${entityFileName}-delete-dialog.component'; - |export * from './${entityFolderName}/${entityFileName}-detail.component'; - |export * from './${entityFolderName}/${entityFileName}.component'; - |export * from './${entityFolderName}/${entityFileName}.state';`; - const out = `export * from './entityFolderName/entityFileName-update.component'; -export * from './entityFolderName/entityFileName-delete-dialog.component'; -export * from './entityFolderName/entityFileName-detail.component'; -export * from './entityFolderName/entityFileName.component'; -export * from './entityFolderName/entityFileName.state';`; - expect(stripMargin(content)).toEqual(out); - }); - it('should produce correct indented output without margin', () => { - const routerName = 'routerName'; - const enableTranslation = true; - const content = `|
  • - | - | ${routerName} - | - |
  • `; - const out = `
  • - - routerName - -
  • `; - expect(stripMargin(content)).toEqual(out); - }); - }); - - describe('::normalizeLineEndings', () => { - it('should convert \\r\\n to \\n', () => { - expect(normalizeLineEndings('a\r\ncrlf\r\nfile\r\nwith\nlf\nlines\r\n', '\r\n')).toBe('a\r\ncrlf\r\nfile\r\nwith\r\nlf\r\nlines\r\n'); - }); - it('should convert \\n to \\r\\n', () => { - expect(normalizeLineEndings('a\r\ncrlf\r\nfile\r\nwith\nlf\nlines\r\n', '\n')).toBe('a\ncrlf\nfile\nwith\nlf\nlines\n'); - }); - }); -}); diff --git a/generators/base/support/contents.spec.ts b/generators/base/support/contents.spec.ts new file mode 100644 index 000000000000..74be02fa6603 --- /dev/null +++ b/generators/base/support/contents.spec.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2013-2023 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 { expect } from 'esmocha'; + +import { normalizeLineEndings, stripMargin } from './contents.js'; + +describe('generator - base - support - contents', () => { + describe('stripMargin', () => { + it('should produce correct output without margin', () => { + const entityFolderName = 'entityFolderName'; + const entityFileName = 'entityFileName'; + const content = `|export * from './${entityFolderName}/${entityFileName}-update.component'; + |export * from './${entityFolderName}/${entityFileName}-delete-dialog.component'; + |export * from './${entityFolderName}/${entityFileName}-detail.component'; + |export * from './${entityFolderName}/${entityFileName}.component'; + |export * from './${entityFolderName}/${entityFileName}.state';`; + const out = `export * from './entityFolderName/entityFileName-update.component'; +export * from './entityFolderName/entityFileName-delete-dialog.component'; +export * from './entityFolderName/entityFileName-detail.component'; +export * from './entityFolderName/entityFileName.component'; +export * from './entityFolderName/entityFileName.state';`; + expect(stripMargin(content)).toEqual(out); + }); + it('should produce correct indented output without margin', () => { + const routerName = 'routerName'; + const enableTranslation = true; + const content = `|
  • + | + | ${routerName} + | + |
  • `; + const out = `
  • + + routerName + +
  • `; + expect(stripMargin(content)).toEqual(out); + }); + }); + + describe('::normalizeLineEndings', () => { + it('should convert \\r\\n to \\n', () => { + expect(normalizeLineEndings('a\r\ncrlf\r\nfile\r\nwith\nlf\nlines\r\n', '\r\n')).toBe('a\r\ncrlf\r\nfile\r\nwith\r\nlf\r\nlines\r\n'); + }); + it('should convert \\n to \\r\\n', () => { + expect(normalizeLineEndings('a\r\ncrlf\r\nfile\r\nwith\nlf\nlines\r\n', '\n')).toBe('a\ncrlf\nfile\nwith\nlf\nlines\n'); + }); + }); +}); diff --git a/generators/base/support/contents.mts b/generators/base/support/contents.ts similarity index 100% rename from generators/base/support/contents.mts rename to generators/base/support/contents.ts diff --git a/generators/base/support/faker.mts b/generators/base/support/faker.mts deleted file mode 100644 index 27bcf1903f21..000000000000 --- a/generators/base/support/faker.mts +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-disable max-classes-per-file */ -/** - * Copyright 2013-2023 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 { Faker, base, en } from '@faker-js/faker'; -import Randexp from 'randexp'; - -import { languageToJavaLanguage } from '../../languages/support/index.mjs'; - -class RandexpWithFaker extends Randexp { - faker: Faker; - - constructor(regexp: string | RegExp, flags: string | undefined, faker: Faker) { - super(regexp, flags); - this.max = 5; - this.faker = faker; - if (this.faker === undefined) { - throw new Error('Faker is required'); - } - // In order to have consistent results with RandExp, the RNG is seeded. - this.randInt = (from: number, to?: number): number => { - return faker.number.int({ min: from, max: to }); - }; - } -} - -class FakerWithRandexp extends Faker { - createRandexp(regexp: string | RegExp, flags?: string) { - return new RandexpWithFaker(regexp, flags, this); - } -} - -/** - * Create a faker instance. - * @param nativeLanguage - native language - * @returns Faker instance - */ -// eslint-disable-next-line import/prefer-default-export -export async function createFaker(nativeLanguage = 'en') { - nativeLanguage = languageToJavaLanguage(nativeLanguage); - let locale; - // Faker >=6 doesn't exports locales by itself, it exports a faker instance with the locale. - // We need a Faker instance for each entity, to build additional fake instances, use the locale from the exported localized faker instance. - // See https://github.com/faker-js/faker/pull/642 - try { - // eslint-disable-next-line import/no-dynamic-require, quotes - locale = (await import(`@faker-js/faker`))[nativeLanguage]; - } catch (error) { - // Faker not implemented for the native language, fallback to en. - // eslint-disable-next-line import/no-unresolved, import/no-dynamic-require - locale = (await import('@faker-js/faker')).en; - } - - const faker = new FakerWithRandexp({ - locale: [locale, base, en], - }); - faker.createRandexp = (pattern, m) => new RandexpWithFaker(pattern, m, faker); - return faker; -} diff --git a/generators/base/support/faker.ts b/generators/base/support/faker.ts new file mode 100644 index 000000000000..2c01af4fe358 --- /dev/null +++ b/generators/base/support/faker.ts @@ -0,0 +1,74 @@ +/* eslint-disable max-classes-per-file */ +/** + * Copyright 2013-2023 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 { Faker, base, en } from '@faker-js/faker'; +import Randexp from 'randexp'; + +import { languageToJavaLanguage } from '../../languages/support/index.js'; + +class RandexpWithFaker extends Randexp { + faker: Faker; + + constructor(regexp: string | RegExp, flags: string | undefined, faker: Faker) { + super(regexp, flags); + this.max = 5; + this.faker = faker; + if (this.faker === undefined) { + throw new Error('Faker is required'); + } + // In order to have consistent results with RandExp, the RNG is seeded. + this.randInt = (from: number, to?: number): number => { + return faker.number.int({ min: from, max: to }); + }; + } +} + +class FakerWithRandexp extends Faker { + createRandexp(regexp: string | RegExp, flags?: string) { + return new RandexpWithFaker(regexp, flags, this); + } +} + +/** + * Create a faker instance. + * @param nativeLanguage - native language + * @returns Faker instance + */ +// eslint-disable-next-line import/prefer-default-export +export async function createFaker(nativeLanguage = 'en') { + nativeLanguage = languageToJavaLanguage(nativeLanguage); + let locale; + // Faker >=6 doesn't exports locales by itself, it exports a faker instance with the locale. + // We need a Faker instance for each entity, to build additional fake instances, use the locale from the exported localized faker instance. + // See https://github.com/faker-js/faker/pull/642 + try { + // eslint-disable-next-line import/no-dynamic-require, quotes + locale = (await import(`@faker-js/faker`))[nativeLanguage]; + } catch (error) { + // Faker not implemented for the native language, fallback to en. + // eslint-disable-next-line import/no-unresolved, import/no-dynamic-require + locale = (await import('@faker-js/faker')).en; + } + + const faker = new FakerWithRandexp({ + locale: [locale, base, en], + }); + faker.createRandexp = (pattern, m) => new RandexpWithFaker(pattern, m, faker); + return faker; +} diff --git a/generators/base/support/hipster.mts b/generators/base/support/hipster.mts deleted file mode 100644 index 8c0ff5f3fb2d..000000000000 --- a/generators/base/support/hipster.mts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 { stringHashCode } from './string.mjs'; - -/** - * get a hipster based on the applications name. - * @param baseName of application - */ -export default function getHipster(baseName: string): string { - const hash = stringHashCode(baseName); - - switch (hash % 4) { - case 0: - return 'jhipster_family_member_0'; - case 1: - return 'jhipster_family_member_1'; - case 2: - return 'jhipster_family_member_2'; - case 3: - return 'jhipster_family_member_3'; - default: - return 'jhipster_family_member_0'; - } -} diff --git a/generators/base/support/hipster.ts b/generators/base/support/hipster.ts new file mode 100644 index 000000000000..252254484be4 --- /dev/null +++ b/generators/base/support/hipster.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 { stringHashCode } from './string.js'; + +/** + * get a hipster based on the applications name. + * @param baseName of application + */ +export default function getHipster(baseName: string): string { + const hash = stringHashCode(baseName); + + switch (hash % 4) { + case 0: + return 'jhipster_family_member_0'; + case 1: + return 'jhipster_family_member_1'; + case 2: + return 'jhipster_family_member_2'; + case 3: + return 'jhipster_family_member_3'; + default: + return 'jhipster_family_member_0'; + } +} diff --git a/generators/base/support/index.mts b/generators/base/support/index.mts deleted file mode 100644 index 9758b964c217..000000000000 --- a/generators/base/support/index.mts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013-2023 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 './basename.mjs'; -export * from './configuration-helpers/options.mjs'; -export * from './config.mjs'; -export * from './contents.mjs'; -export * from './faker.mjs'; -export { default as getHipster } from './hipster.mjs'; -export { default as createJHipster7Context } from './jhipster7-context.mjs'; -export * from './logger.mjs'; -export * from './namespace.mjs'; -export * from './needles.mjs'; -export * from './path.mjs'; -export { default as httpsGet } from './remote.mjs'; -export * from './secret.mjs'; -export * from './string.mjs'; -export * from './timestamp.mjs'; -export * from './write-files.mjs'; diff --git a/generators/base/support/index.ts b/generators/base/support/index.ts new file mode 100644 index 000000000000..b189393169d0 --- /dev/null +++ b/generators/base/support/index.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2013-2023 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 './basename.js'; +export * from './configuration-helpers/options.js'; +export * from './config.js'; +export * from './contents.js'; +export * from './faker.js'; +export { default as getHipster } from './hipster.js'; +export { default as createJHipster7Context } from './jhipster7-context.js'; +export * from './logger.js'; +export * from './namespace.js'; +export * from './needles.js'; +export * from './path.js'; +export { default as httpsGet } from './remote.js'; +export * from './secret.js'; +export * from './string.js'; +export * from './timestamp.js'; +export * from './write-files.js'; diff --git a/generators/base/support/jhipster7-context.mjs b/generators/base/support/jhipster7-context.js similarity index 100% rename from generators/base/support/jhipster7-context.mjs rename to generators/base/support/jhipster7-context.js diff --git a/generators/base/support/logger.mts b/generators/base/support/logger.ts similarity index 100% rename from generators/base/support/logger.mts rename to generators/base/support/logger.ts diff --git a/generators/base/support/namespace.mts b/generators/base/support/namespace.ts similarity index 100% rename from generators/base/support/namespace.mts rename to generators/base/support/namespace.ts diff --git a/generators/base/support/needles.mts b/generators/base/support/needles.mts deleted file mode 100644 index 53b4d7a1171f..000000000000 --- a/generators/base/support/needles.mts +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Copyright 2013-2023 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 assert from 'assert'; -import * as _ from 'lodash-es'; -import escapeStringRegexp from 'escape-string-regexp'; -import CoreGenerator from '../../base-core/index.mjs'; -import { CascatedEditFileCallback, EditFileCallback } from '../api.mjs'; -import { joinCallbacks } from './write-files.mjs'; - -const { kebabCase } = _; - -export type NeedleInsertion = { - needle: string; - /** - * Content to add. - */ - contentToAdd: string | string[] | ((content: string, options: { needleIndent: number; indentPrefix: string }) => string); - contentToCheck?: string | RegExp; - /** - * check existing content ignoring white spaces and new lines. - */ - ignoreWhitespaces?: boolean; - /** - * throw error if needle was not found - */ - optional?: boolean; - /** - * Detect and apply indent - */ - autoIndent?: boolean; -}; - -type NeedleFileInsertion = NeedleInsertion & { - /** - * Path to file. - * The generator context must be passed. - */ - filePath?: string; - /** - * Common needle prefix - */ - needlesPrefix?: string; -}; - -type NeedleContentInsertion = NeedleInsertion & { - content: string; -}; - -/** - * Change spaces sequences and '>' to allow any number of spaces or new line prefix - */ -export const convertToPrettierExpressions = (str: string): string => { - return str.replace(/\s+/g, '([\\s\n]*)').replace(/>+/g, '(\n?[\\s]*)>'); -}; - -/** - * Check if contentToCheck existing in content - * - * @param contentToCheck - * @param content - * @param [ignoreWhitespaces=true] - */ -export const checkContentIn = (contentToCheck: string | RegExp, content, ignoreWhitespaces = true) => { - assert(content, 'content is required'); - assert(contentToCheck, 'contentToCheck is required'); - - let re: RegExp; - if (typeof contentToCheck === 'string') { - const pattern = ignoreWhitespaces - ? convertToPrettierExpressions(escapeStringRegexp(contentToCheck)) - : contentToCheck - .split('\n') - .map(line => `\\s*${escapeStringRegexp(line)}`) - .join('\n'); - re = new RegExp(pattern); - } else { - re = contentToCheck; - } - return re.test(content); -}; - -/** - * Write content before needle applying indentation - * - * @param args - * @returns null if needle was not found, new content otherwise - */ -export const insertContentBeforeNeedle = ({ content, contentToAdd, needle, autoIndent = true }: NeedleContentInsertion): string | null => { - assert(needle, 'needle is required'); - assert(content, 'content is required'); - assert(contentToAdd, 'contentToAdd is required'); - - needle = needle.includes('jhipster-needle-') ? needle : `jhipster-needle-${needle}`; - - let regexp = new RegExp(`(?://|` : undefined, resource].filter(i => i).join('\n'), + }), + ); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); + } + + get default() { + return this.asDefaultTaskGroup({ + insight({ application }) { + statistics.sendSubGenEvent('generator', GENERATOR_CLIENT, { + app: { + clientFramework: application.clientFramework, + enableTranslation: application.enableTranslation, + nativeLanguage: application.nativeLanguage, + languages: application.languages, + }, + }); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); + } + + // Public API method used by the getter and also by Blueprints + get writing() { + return this.asWritingTaskGroup({ + webappFakeDataSeed({ application: { clientFramework } }) { + this.resetEntitiesFakeData(clientFramework); + }, + writeCommonFiles, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.asWritingTaskGroup(this.delegateTasksToBlueprint(() => this.writing)); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + async writeEnumerationFiles({ application, entities }) { + if (!application.webappEnumerationsDir || ![ANGULAR, VUE, REACT].includes(application.clientFramework)) { + return; + } + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + await addEnumerationFiles.call(this, { application, entity }); + } + }, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.asWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.writingEntities)); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + packageJsonScripts({ application }) { + if (![ANGULAR, VUE, REACT].includes(application.clientFramework)) { + return; + } + const packageJsonStorage = this.createStorage(this.destinationPath(application.clientRootDir, 'package.json')); + const scriptsStorage = packageJsonStorage.createStorage('scripts'); + + const packageJsonConfigStorage = packageJsonStorage.createStorage('config').createProxy(); + if (process.env.JHI_PROFILE) { + packageJsonConfigStorage.default_environment = process.env.JHI_PROFILE.includes('dev') ? 'dev' : 'prod'; + } + + const devDependencies = packageJsonStorage.createStorage('devDependencies'); + devDependencies.set('wait-on', application.nodeDependencies['wait-on']); + devDependencies.set('concurrently', application.nodeDependencies.concurrently); + + if (application.clientFrameworkReact) { + scriptsStorage.set('ci:frontend:test', 'npm run webapp:build:$npm_package_config_default_environment && npm run test-ci'); + } else { + scriptsStorage.set('ci:frontend:build', 'npm run webapp:build:$npm_package_config_default_environment'); + scriptsStorage.set('ci:frontend:test', 'npm run ci:frontend:build && npm test'); + } + }, + + microfrontend({ application, source }) { + if (!application.microfrontend || ![ANGULAR, VUE, REACT].includes(application.clientFramework)) { + return; + } + if (application.clientFrameworkAngular) { + const conditional = application.applicationTypeMicroservice ? "targetOptions.target === 'serve' ? {} : " : ''; + source.addWebpackConfig({ + config: `${conditional}require('./webpack.microfrontend')(config, options, targetOptions)`, + }); + } else if (application.clientFrameworkVue || application.clientFrameworkReact) { + source.addWebpackConfig({ config: "require('./webpack.microfrontend')({ serve: options.env.WEBPACK_SERVE })" }); + } else { + throw new Error(`Client framework ${application.clientFramework} doesn't support microfrontends`); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/client/generator.mjs b/generators/client/generator.mjs deleted file mode 100644 index 5202f9f945bc..000000000000 --- a/generators/client/generator.mjs +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; - -import { askForClientTheme, askForClientThemeVariant } from './prompts.mjs'; -import { writeFiles as writeCommonFiles } from './files-common.mjs'; - -import { addEnumerationFiles } from './entity-files.mjs'; - -import { LOGIN_REGEX_JS } from '../generator-constants.mjs'; -import statistics from '../statistics.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_CYPRESS, GENERATOR_COMMON, GENERATOR_CLIENT } from '../generator-list.mjs'; - -import { testFrameworkTypes, clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { createNeedleCallback } from '../base/support/index.mjs'; -import { loadStoredAppOptions } from '../app/support/index.mjs'; -import command from './command.mjs'; - -const { ANGULAR, VUE, REACT, NO: CLIENT_FRAMEWORK_NO } = clientFrameworkTypes; -const { CYPRESS } = testFrameworkTypes; - -export default class JHipsterClientGenerator extends BaseApplicationGenerator { - command = command; - - async beforeQueue() { - loadStoredAppOptions.call(this); - - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_CLIENT); - } - - if (!this.delegateToBlueprint) { - // TODO depend on GENERATOR_BOOTSTRAP_APPLICATION_CLIENT. - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - await this.dependsOnJHipster(GENERATOR_COMMON); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadOptions() { - this.parseJHipsterCommand(this.command); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return this.asPromptingTaskGroup({ - async prompting({ control }) { - if (control.existingProject && this.options.askAnswered !== true) return; - await this.prompt(this.prepareQuestions(this.command.configs)); - }, - askForClientTheme, - askForClientThemeVariant, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.asPromptingTaskGroup(this.delegateTasksToBlueprint(() => this.prompting)); - } - - get configuring() { - return this.asConfiguringTaskGroup({ - applyNoFramework() { - const { clientFramework } = this.jhipsterConfigWithDefaults; - if (clientFramework === CLIENT_FRAMEWORK_NO) { - this.jhipsterConfig.skipClient = true; - this.cancelCancellableTasks(); - } - }, - mergeTestConfig() { - if (this.jhipsterConfig.clientTestFrameworks) { - this.jhipsterConfig.testFrameworks = [ - ...new Set([...(this.jhipsterConfig.testFrameworks ?? []), ...this.jhipsterConfig.clientTestFrameworks]), - ]; - delete this.jhipsterConfig.clientTestFrameworks; - } - }, - upgradeAngular() { - if (this.jhipsterConfig.clientFramework === 'angularX') { - this.jhipsterConfig.clientFramework = ANGULAR; - } - }, - - configureDevServerPort() { - if (this.jhipsterConfig.devServerPort !== undefined) return; - - const { clientFramework, applicationIndex } = this.jhipsterConfigWithDefaults; - const devServerBasePort = clientFramework === ANGULAR ? 4200 : 9060; - let devServerPort; - - if (applicationIndex !== undefined) { - devServerPort = devServerBasePort + applicationIndex; - } else if (!devServerPort) { - devServerPort = devServerBasePort; - } - - this.jhipsterConfig.devServerPort = devServerPort; - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.asConfiguringTaskGroup(this.delegateTasksToBlueprint(() => this.configuring)); - } - - get composing() { - return this.asComposingTaskGroup({ - async composing() { - const { clientFramework, testFrameworks } = this.jhipsterConfigWithDefaults; - if ([ANGULAR, VUE, REACT].includes(clientFramework)) { - await this.composeWithJHipster(clientFramework); - } - if (Array.isArray(testFrameworks) && testFrameworks.includes(CYPRESS)) { - await this.composeWithJHipster(GENERATOR_CYPRESS); - } - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.asComposingTaskGroup(this.delegateTasksToBlueprint(() => this.composing)); - } - - get loading() { - return this.asLoadingTaskGroup({ - loadSharedConfig({ application }) { - // TODO v8 rename to nodePackageManager; - application.clientPackageManager = 'npm'; - }, - - loadPackageJson({ application }) { - // Load common client package.json into packageJson - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_CLIENT, 'resources', 'package.json'), - ); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.asLoadingTaskGroup(this.delegateTasksToBlueprint(() => this.loading)); - } - - // Public API method used by the getter and also by Blueprints - get preparing() { - return this.asPreparingTaskGroup({ - microservice({ application }) { - if (application.applicationTypeMicroservice) { - application.withAdminUi = false; - } - }, - - prepareForTemplates({ application }) { - application.webappLoginRegExp = LOGIN_REGEX_JS; - }, - - addExternalResource({ application, source }) { - if (![ANGULAR, VUE, REACT].includes(application.clientFramework)) { - return; - } - source.addExternalResourceToRoot = ({ resource, comment }) => - this.editFile( - `${application.clientSrcDir}index.html`, - createNeedleCallback({ - needle: 'add-resources-to-root', - contentToAdd: [comment ? `` : undefined, resource].filter(i => i).join('\n'), - }), - ); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); - } - - get default() { - return this.asDefaultTaskGroup({ - insight({ application }) { - statistics.sendSubGenEvent('generator', GENERATOR_CLIENT, { - app: { - clientFramework: application.clientFramework, - enableTranslation: application.enableTranslation, - nativeLanguage: application.nativeLanguage, - languages: application.languages, - }, - }); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); - } - - // Public API method used by the getter and also by Blueprints - get writing() { - return this.asWritingTaskGroup({ - webappFakeDataSeed({ application: { clientFramework } }) { - this.resetEntitiesFakeData(clientFramework); - }, - writeCommonFiles, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.asWritingTaskGroup(this.delegateTasksToBlueprint(() => this.writing)); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - async writeEnumerationFiles({ application, entities }) { - if (!application.webappEnumerationsDir || ![ANGULAR, VUE, REACT].includes(application.clientFramework)) { - return; - } - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - await addEnumerationFiles.call(this, { application, entity }); - } - }, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.asWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.writingEntities)); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - packageJsonScripts({ application }) { - if (![ANGULAR, VUE, REACT].includes(application.clientFramework)) { - return; - } - const packageJsonStorage = this.createStorage(this.destinationPath(application.clientRootDir, 'package.json')); - const scriptsStorage = packageJsonStorage.createStorage('scripts'); - - const packageJsonConfigStorage = packageJsonStorage.createStorage('config').createProxy(); - if (process.env.JHI_PROFILE) { - packageJsonConfigStorage.default_environment = process.env.JHI_PROFILE.includes('dev') ? 'dev' : 'prod'; - } - - const devDependencies = packageJsonStorage.createStorage('devDependencies'); - devDependencies.set('wait-on', application.nodeDependencies['wait-on']); - devDependencies.set('concurrently', application.nodeDependencies.concurrently); - - if (application.clientFrameworkReact) { - scriptsStorage.set('ci:frontend:test', 'npm run webapp:build:$npm_package_config_default_environment && npm run test-ci'); - } else { - scriptsStorage.set('ci:frontend:build', 'npm run webapp:build:$npm_package_config_default_environment'); - scriptsStorage.set('ci:frontend:test', 'npm run ci:frontend:build && npm test'); - } - }, - - microfrontend({ application, source }) { - if (!application.microfrontend || ![ANGULAR, VUE, REACT].includes(application.clientFramework)) { - return; - } - if (application.clientFrameworkAngular) { - const conditional = application.applicationTypeMicroservice ? "targetOptions.target === 'serve' ? {} : " : ''; - source.addWebpackConfig({ - config: `${conditional}require('./webpack.microfrontend')(config, options, targetOptions)`, - }); - } else if (application.clientFrameworkVue || application.clientFrameworkReact) { - source.addWebpackConfig({ config: "require('./webpack.microfrontend')({ serve: options.env.WEBPACK_SERVE })" }); - } else { - throw new Error(`Client framework ${application.clientFramework} doesn't support microfrontends`); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/client/generator.spec.mts b/generators/client/generator.spec.mts deleted file mode 100644 index 7c2666639ccc..000000000000 --- a/generators/client/generator.spec.mts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import assert from 'assert'; -import lodash from 'lodash'; -import { expect } from 'esmocha'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { defaultHelpers as helpers, checkEnforcements, result } from '../../test/support/index.mjs'; -import { testFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_CLIENT } from '../generator-list.mjs'; - -const { snakeCase } = lodash; -const { CYPRESS } = testFrameworkTypes; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({ client: true }, GENERATOR_CLIENT); - - describe('composing', () => { - const mockedComposedGenerators = ['jhipster:common', 'jhipster:languages', 'jhipster:cypress']; - - describe('with translation disabled', () => { - let runResult; - const options = { enableTranslation: false }; - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(options) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - after(() => runResult.cleanup()); - - it('should compose with jhipster:common', () => { - assert(runResult.mockedGenerators['jhipster:common'].calledOnce); - }); - it('should compose with jhipster:languages', () => { - assert.equal(runResult.mockedGenerators['jhipster:languages'].callCount, 1); - }); - }); - - describe('with translation enabled', () => { - let runResult; - const options = { enableTranslation: true }; - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(options) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - after(() => runResult.cleanup()); - - it('should compose with jhipster:common', () => { - assert(runResult.mockedGenerators['jhipster:common'].calledOnce); - }); - it('should compose with jhipster:languages', () => { - assert.equal(runResult.mockedGenerators['jhipster:languages'].callCount, 1); - }); - }); - - describe('without cypress', () => { - let runResult; - const options = { testFrameworks: [] }; - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(options) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - after(() => runResult.cleanup()); - - it('should compose with jhipster:common', () => { - assert(runResult.mockedGenerators['jhipster:common'].calledOnce); - }); - it('should compose with jhipster:languages', () => { - assert(runResult.mockedGenerators['jhipster:languages'].calledOnce); - }); - it('should not compose with jhipster:cypress', () => { - assert.equal(runResult.mockedGenerators['jhipster:cypress'].callCount, 0); - }); - }); - - describe('with cypress', () => { - let runResult; - const options = { testFrameworks: [CYPRESS] }; - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(options) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - after(() => runResult.cleanup()); - - it('should compose with jhipster:common', () => { - assert(runResult.mockedGenerators['jhipster:common'].calledOnce); - }); - it('should compose with jhipster:languages', () => { - assert(runResult.mockedGenerators['jhipster:languages'].calledOnce); - }); - it('should compose with jhipster:cypress', () => { - assert(runResult.mockedGenerators['jhipster:cypress'].calledOnce); - }); - }); - }); - - describe('with microservices', () => { - const mockedComposedGenerators = [ - 'jhipster:common', - 'jhipster:languages', - 'jhipster:cypress', - 'jhipster:angular', - 'jhipster:react', - 'jhipster:vue', - ]; - const options = { applicationType: 'microservice' }; - before(async () => { - await helpers - .run(generatorFile) - .withJHipsterConfig(options) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - it('should compose with jhipster:common', () => { - assert(result.mockedGenerators['jhipster:common'].calledOnce); - }); - it('should compose with jhipster:languages', () => { - assert(result.mockedGenerators['jhipster:languages'].notCalled); - }); - it('should compose with jhipster:cypress', () => { - assert(result.mockedGenerators['jhipster:cypress'].notCalled); - }); - it('should compose with jhipster:angular', () => { - assert(result.mockedGenerators['jhipster:angular'].notCalled); - }); - it('should compose with jhipster:react', () => { - assert(result.mockedGenerators['jhipster:react'].notCalled); - }); - it('should compose with jhipster:vue', () => { - assert(result.mockedGenerators['jhipster:vue'].notCalled); - }); - }); -}); diff --git a/generators/client/generator.spec.ts b/generators/client/generator.spec.ts new file mode 100644 index 000000000000..b388c7654229 --- /dev/null +++ b/generators/client/generator.spec.ts @@ -0,0 +1,179 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import assert from 'assert'; +import lodash from 'lodash'; +import { expect } from 'esmocha'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { defaultHelpers as helpers, checkEnforcements, result } from '../../test/support/index.js'; +import { testFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_CLIENT } from '../generator-list.js'; + +const { snakeCase } = lodash; +const { CYPRESS } = testFrameworkTypes; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({ client: true }, GENERATOR_CLIENT); + + describe('composing', () => { + const mockedComposedGenerators = ['jhipster:common', 'jhipster:languages', 'jhipster:cypress']; + + describe('with translation disabled', () => { + let runResult; + const options = { enableTranslation: false }; + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(options) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + after(() => runResult.cleanup()); + + it('should compose with jhipster:common', () => { + assert(runResult.mockedGenerators['jhipster:common'].calledOnce); + }); + it('should compose with jhipster:languages', () => { + assert.equal(runResult.mockedGenerators['jhipster:languages'].callCount, 1); + }); + }); + + describe('with translation enabled', () => { + let runResult; + const options = { enableTranslation: true }; + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(options) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + after(() => runResult.cleanup()); + + it('should compose with jhipster:common', () => { + assert(runResult.mockedGenerators['jhipster:common'].calledOnce); + }); + it('should compose with jhipster:languages', () => { + assert.equal(runResult.mockedGenerators['jhipster:languages'].callCount, 1); + }); + }); + + describe('without cypress', () => { + let runResult; + const options = { testFrameworks: [] }; + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(options) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + after(() => runResult.cleanup()); + + it('should compose with jhipster:common', () => { + assert(runResult.mockedGenerators['jhipster:common'].calledOnce); + }); + it('should compose with jhipster:languages', () => { + assert(runResult.mockedGenerators['jhipster:languages'].calledOnce); + }); + it('should not compose with jhipster:cypress', () => { + assert.equal(runResult.mockedGenerators['jhipster:cypress'].callCount, 0); + }); + }); + + describe('with cypress', () => { + let runResult; + const options = { testFrameworks: [CYPRESS] }; + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(options) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + after(() => runResult.cleanup()); + + it('should compose with jhipster:common', () => { + assert(runResult.mockedGenerators['jhipster:common'].calledOnce); + }); + it('should compose with jhipster:languages', () => { + assert(runResult.mockedGenerators['jhipster:languages'].calledOnce); + }); + it('should compose with jhipster:cypress', () => { + assert(runResult.mockedGenerators['jhipster:cypress'].calledOnce); + }); + }); + }); + + describe('with microservices', () => { + const mockedComposedGenerators = [ + 'jhipster:common', + 'jhipster:languages', + 'jhipster:cypress', + 'jhipster:angular', + 'jhipster:react', + 'jhipster:vue', + ]; + const options = { applicationType: 'microservice' }; + before(async () => { + await helpers + .run(generatorFile) + .withJHipsterConfig(options) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + it('should compose with jhipster:common', () => { + assert(result.mockedGenerators['jhipster:common'].calledOnce); + }); + it('should compose with jhipster:languages', () => { + assert(result.mockedGenerators['jhipster:languages'].notCalled); + }); + it('should compose with jhipster:cypress', () => { + assert(result.mockedGenerators['jhipster:cypress'].notCalled); + }); + it('should compose with jhipster:angular', () => { + assert(result.mockedGenerators['jhipster:angular'].notCalled); + }); + it('should compose with jhipster:react', () => { + assert(result.mockedGenerators['jhipster:react'].notCalled); + }); + it('should compose with jhipster:vue', () => { + assert(result.mockedGenerators['jhipster:vue'].notCalled); + }); + }); +}); diff --git a/generators/client/index.mts b/generators/client/index.mts deleted file mode 100644 index fde8c7b918f0..000000000000 --- a/generators/client/index.mts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2013-2023 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 { GeneratorDefinition } from '../base-application/generator.mjs'; - -export { default } from './generator.mjs'; -export { default as command } from './command.mjs'; -export { files as commonFiles } from './files-common.mjs'; - -export type SourceType = { - addEntitiesToClient: (arg1: GeneratorDefinition['postWritingEntitiesTaskParam']) => void; -}; diff --git a/generators/client/index.ts b/generators/client/index.ts new file mode 100644 index 000000000000..3c286f47683b --- /dev/null +++ b/generators/client/index.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2013-2023 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 { GeneratorDefinition } from '../base-application/generator.js'; + +export { default } from './generator.js'; +export { default as command } from './command.js'; +export { files as commonFiles } from './files-common.js'; + +export type SourceType = { + addEntitiesToClient: (arg1: GeneratorDefinition['postWritingEntitiesTaskParam']) => void; +}; diff --git a/generators/client/needle-api/needle-client-vue.mts b/generators/client/needle-api/needle-client-vue.mts deleted file mode 100644 index ebf51da233ac..000000000000 --- a/generators/client/needle-api/needle-client-vue.mts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import * as _ from 'lodash-es'; -import needleClientBase from './needle-client.mjs'; -import { stripMargin } from '../../base/support/index.mjs'; -import { createNeedleCallback } from '../../base/support/needles.mjs'; - -export default class extends needleClientBase { - addEntityToMenu( - routerName: string, - enableTranslation: boolean, - entityTranslationKeyMenu: string, - entityTranslationValue: string = _.startCase(routerName), - ) { - const ignoreNonExisting = - this.generator.sharedData.getControl().ignoreNeedlesError && - `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; - const filePath = `${this.clientSrcDir}/app/entities/entities-menu.vue`; - - const menuI18nTitle = enableTranslation ? ` v-text="$t('global.menu.entities.${entityTranslationKeyMenu}')"` : ''; - const entityEntry = - // prettier-ignore - stripMargin( - `| -| -| ${entityTranslationValue} -| `); - - this.generator.editFile( - filePath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-entity-to-menu', - contentToAdd: entityEntry, - ignoreWhitespaces: true, - contentToCheck: ``, - autoIndent: false, - }), - ); - } - - addEntityToRouterImport(entityName: string, fileName: string, folderName: string, readOnly: string) { - const ignoreNonExisting = - this.generator.sharedData.getControl().ignoreNeedlesError && - `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to router entities import.\n')}`; - const filePath = `${this.clientSrcDir}/app/router/entities.ts`; - - let entityEntry; - if (!readOnly) { - // prettier-ignore - entityEntry = stripMargin( - `|// prettier-ignore - |const ${entityName} = () => import('@/entities/${folderName}/${fileName}.vue'); - |// prettier-ignore - |const ${entityName}Update = () => import('@/entities/${folderName}/${fileName}-update.vue'); - |// prettier-ignore - |const ${entityName}Details = () => import('@/entities/${folderName}/${fileName}-details.vue');` - ); - } else { - // prettier-ignore - entityEntry = stripMargin( - `|// prettier-ignore - |const ${entityName} = () => import('@/entities/${folderName}/${fileName}.vue'); - |// prettier-ignore - |const ${entityName}Details = () => import('@/entities/${folderName}/${fileName}-details.vue');` - ); - } - - this.generator.editFile( - filePath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-entity-to-router-import', - contentToAdd: entityEntry, - ignoreWhitespaces: true, - contentToCheck: `import('@/entities/${folderName}/${fileName}.vue');`, - autoIndent: false, - }), - ); - } - - addEntityToRouter(entityInstance: string, entityName: string, entityFileName: string, readOnly: boolean) { - const ignoreNonExisting = - this.generator.sharedData.getControl().ignoreNeedlesError && - `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to router entities.\n')}`; - const filePath = `${this.clientSrcDir}/app/router/entities.ts`; - - let entityEntry; - if (!readOnly) { - // prettier-ignore - entityEntry = stripMargin( - `|{ - | path: '${entityFileName}', - | name: '${entityName}', - | component: ${entityName}, - | meta: { authorities: [Authority.USER] } - | }, - | { - | path: '${entityFileName}/new', - | name: '${entityName}Create', - | component: ${entityName}Update, - | meta: { authorities: [Authority.USER] } - | }, - | { - | path: '${entityFileName}/:${entityInstance}Id/edit', - | name: '${entityName}Edit', - | component: ${entityName}Update, - | meta: { authorities: [Authority.USER] } - | }, - | { - | path: '${entityFileName}/:${entityInstance}Id/view', - | name: '${entityName}View', - | component: ${entityName}Details, - | meta: { authorities: [Authority.USER] } - | },` - ); - } else { - // prettier-ignore - entityEntry = stripMargin( - `|{ - | path: '/${entityFileName}', - | name: '${entityName}', - | component: ${entityName}, - | meta: { authorities: [Authority.USER] } - | }, - | { - | path: '/${entityFileName}/:${entityInstance}Id/view', - | name: '${entityName}View', - | component: ${entityName}Details, - | meta: { authorities: [Authority.USER] } - | },` - ); - } - - this.generator.editFile( - filePath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-entity-to-router', - contentToAdd: entityEntry, - ignoreWhitespaces: true, - contentToCheck: `path: '${entityFileName}'`, - autoIndent: false, - }), - ); - } - - addEntityServiceToMainImport(entityName: string, entityClass: string, entityFileName: string, entityFolderName: string) { - const errorMessage = `${chalk.yellow('Reference to entity ') + entityClass} ${chalk.yellow('not added to import in main.\n')}`; - const filePath = `${this.clientSrcDir}/app/main.ts`; - - // prettier-ignore - const entityEntry = stripMargin( - `import ${entityName}Service from '@/entities/${entityFolderName}/${entityFileName}.service';` - ); - - const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-main-import', entityEntry); - this.addBlockContentToFile(rewriteFileModel, errorMessage); - } - - addEntityServiceToMain(entityInstance: string, entityName: string) { - const errorMessage = `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to service in main.\n')}`; - const filePath = `${this.clientSrcDir}/app/main.ts`; - - // prettier-ignore - const entityEntry = stripMargin( - `${entityInstance}Service: () => new ${entityName}Service(),` - ); - - const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-main', entityEntry); - this.addBlockContentToFile(rewriteFileModel, errorMessage); - } - - addEntityServiceToEntitiesComponentImport(entityName: string, entityClass: string, entityFileName: string, entityFolderName: string) { - const errorMessage = `${chalk.yellow('Reference to entity ') + entityClass} ${chalk.yellow( - 'not added to import in entities component.\n', - )}`; - const filePath = `${this.clientSrcDir}/app/entities/entities.component.ts`; - - // prettier-ignore - const entityEntry = `import ${entityName}Service from './${entityFolderName}/${entityFileName}.service';`; - - const rewriteFileModel = this.generateFileModel( - filePath, - 'jhipster-needle-add-entity-service-to-entities-component-import', - entityEntry, - ); - this.addBlockContentToFile(rewriteFileModel, errorMessage); - } - - addEntityServiceToEntitiesComponent(entityInstance: string, entityName: string) { - const errorMessage = `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow( - 'not added to service in entities component.\n', - )}`; - const filePath = `${this.clientSrcDir}/app/entities/entities.component.ts`; - - // prettier-ignore - const entityEntry = `provide('${entityInstance}Service', () => new ${entityName}Service());`; - - const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-entities-component', entityEntry); - this.addBlockContentToFile(rewriteFileModel, errorMessage); - } -} diff --git a/generators/client/needle-api/needle-client-vue.ts b/generators/client/needle-api/needle-client-vue.ts new file mode 100644 index 000000000000..8aadd0cb7f28 --- /dev/null +++ b/generators/client/needle-api/needle-client-vue.ts @@ -0,0 +1,220 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import * as _ from 'lodash-es'; +import needleClientBase from './needle-client.js'; +import { stripMargin } from '../../base/support/index.js'; +import { createNeedleCallback } from '../../base/support/needles.js'; + +export default class extends needleClientBase { + addEntityToMenu( + routerName: string, + enableTranslation: boolean, + entityTranslationKeyMenu: string, + entityTranslationValue: string = _.startCase(routerName), + ) { + const ignoreNonExisting = + this.generator.sharedData.getControl().ignoreNeedlesError && + `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; + const filePath = `${this.clientSrcDir}/app/entities/entities-menu.vue`; + + const menuI18nTitle = enableTranslation ? ` v-text="$t('global.menu.entities.${entityTranslationKeyMenu}')"` : ''; + const entityEntry = + // prettier-ignore + stripMargin( + `| +| +| ${entityTranslationValue} +| `); + + this.generator.editFile( + filePath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-entity-to-menu', + contentToAdd: entityEntry, + ignoreWhitespaces: true, + contentToCheck: ``, + autoIndent: false, + }), + ); + } + + addEntityToRouterImport(entityName: string, fileName: string, folderName: string, readOnly: string) { + const ignoreNonExisting = + this.generator.sharedData.getControl().ignoreNeedlesError && + `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to router entities import.\n')}`; + const filePath = `${this.clientSrcDir}/app/router/entities.ts`; + + let entityEntry; + if (!readOnly) { + // prettier-ignore + entityEntry = stripMargin( + `|// prettier-ignore + |const ${entityName} = () => import('@/entities/${folderName}/${fileName}.vue'); + |// prettier-ignore + |const ${entityName}Update = () => import('@/entities/${folderName}/${fileName}-update.vue'); + |// prettier-ignore + |const ${entityName}Details = () => import('@/entities/${folderName}/${fileName}-details.vue');` + ); + } else { + // prettier-ignore + entityEntry = stripMargin( + `|// prettier-ignore + |const ${entityName} = () => import('@/entities/${folderName}/${fileName}.vue'); + |// prettier-ignore + |const ${entityName}Details = () => import('@/entities/${folderName}/${fileName}-details.vue');` + ); + } + + this.generator.editFile( + filePath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-entity-to-router-import', + contentToAdd: entityEntry, + ignoreWhitespaces: true, + contentToCheck: `import('@/entities/${folderName}/${fileName}.vue');`, + autoIndent: false, + }), + ); + } + + addEntityToRouter(entityInstance: string, entityName: string, entityFileName: string, readOnly: boolean) { + const ignoreNonExisting = + this.generator.sharedData.getControl().ignoreNeedlesError && + `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to router entities.\n')}`; + const filePath = `${this.clientSrcDir}/app/router/entities.ts`; + + let entityEntry; + if (!readOnly) { + // prettier-ignore + entityEntry = stripMargin( + `|{ + | path: '${entityFileName}', + | name: '${entityName}', + | component: ${entityName}, + | meta: { authorities: [Authority.USER] } + | }, + | { + | path: '${entityFileName}/new', + | name: '${entityName}Create', + | component: ${entityName}Update, + | meta: { authorities: [Authority.USER] } + | }, + | { + | path: '${entityFileName}/:${entityInstance}Id/edit', + | name: '${entityName}Edit', + | component: ${entityName}Update, + | meta: { authorities: [Authority.USER] } + | }, + | { + | path: '${entityFileName}/:${entityInstance}Id/view', + | name: '${entityName}View', + | component: ${entityName}Details, + | meta: { authorities: [Authority.USER] } + | },` + ); + } else { + // prettier-ignore + entityEntry = stripMargin( + `|{ + | path: '/${entityFileName}', + | name: '${entityName}', + | component: ${entityName}, + | meta: { authorities: [Authority.USER] } + | }, + | { + | path: '/${entityFileName}/:${entityInstance}Id/view', + | name: '${entityName}View', + | component: ${entityName}Details, + | meta: { authorities: [Authority.USER] } + | },` + ); + } + + this.generator.editFile( + filePath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-entity-to-router', + contentToAdd: entityEntry, + ignoreWhitespaces: true, + contentToCheck: `path: '${entityFileName}'`, + autoIndent: false, + }), + ); + } + + addEntityServiceToMainImport(entityName: string, entityClass: string, entityFileName: string, entityFolderName: string) { + const errorMessage = `${chalk.yellow('Reference to entity ') + entityClass} ${chalk.yellow('not added to import in main.\n')}`; + const filePath = `${this.clientSrcDir}/app/main.ts`; + + // prettier-ignore + const entityEntry = stripMargin( + `import ${entityName}Service from '@/entities/${entityFolderName}/${entityFileName}.service';` + ); + + const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-main-import', entityEntry); + this.addBlockContentToFile(rewriteFileModel, errorMessage); + } + + addEntityServiceToMain(entityInstance: string, entityName: string) { + const errorMessage = `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow('not added to service in main.\n')}`; + const filePath = `${this.clientSrcDir}/app/main.ts`; + + // prettier-ignore + const entityEntry = stripMargin( + `${entityInstance}Service: () => new ${entityName}Service(),` + ); + + const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-main', entityEntry); + this.addBlockContentToFile(rewriteFileModel, errorMessage); + } + + addEntityServiceToEntitiesComponentImport(entityName: string, entityClass: string, entityFileName: string, entityFolderName: string) { + const errorMessage = `${chalk.yellow('Reference to entity ') + entityClass} ${chalk.yellow( + 'not added to import in entities component.\n', + )}`; + const filePath = `${this.clientSrcDir}/app/entities/entities.component.ts`; + + // prettier-ignore + const entityEntry = `import ${entityName}Service from './${entityFolderName}/${entityFileName}.service';`; + + const rewriteFileModel = this.generateFileModel( + filePath, + 'jhipster-needle-add-entity-service-to-entities-component-import', + entityEntry, + ); + this.addBlockContentToFile(rewriteFileModel, errorMessage); + } + + addEntityServiceToEntitiesComponent(entityInstance: string, entityName: string) { + const errorMessage = `${chalk.yellow('Reference to entity ') + entityName} ${chalk.yellow( + 'not added to service in entities component.\n', + )}`; + const filePath = `${this.clientSrcDir}/app/entities/entities.component.ts`; + + // prettier-ignore + const entityEntry = `provide('${entityInstance}Service', () => new ${entityName}Service());`; + + const rewriteFileModel = this.generateFileModel(filePath, 'jhipster-needle-add-entity-service-to-entities-component', entityEntry); + this.addBlockContentToFile(rewriteFileModel, errorMessage); + } +} diff --git a/generators/client/needle-api/needle-client.mts b/generators/client/needle-api/needle-client.mts deleted file mode 100644 index 14d0e6d66604..000000000000 --- a/generators/client/needle-api/needle-client.mts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2013-2023 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 needleBase from '../../needle-base.mjs'; - -export default class extends needleBase { - addStyle(style: string, comment: string, filePath: string, needle: string) { - const content = this._mergeStyleAndComment(style, comment); - - this.addBlockContentToFile( - { file: filePath, needle, splicable: content, regexp: `\n${style}\n`, prettierAware: true }, - 'Style not added to JHipster app.\n', - ); - } - - _mergeStyleAndComment(style: string, comment: string) { - let styleBlock = ''; - - if (comment) { - styleBlock += '/* ==========================================================================\n'; - styleBlock += `${comment}\n`; - styleBlock += '========================================================================== */\n'; - } - styleBlock += `${style}\n`; - - return styleBlock; - } -} diff --git a/generators/client/needle-api/needle-client.ts b/generators/client/needle-api/needle-client.ts new file mode 100644 index 000000000000..79582e97d764 --- /dev/null +++ b/generators/client/needle-api/needle-client.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2013-2023 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 needleBase from '../../needle-base.js'; + +export default class extends needleBase { + addStyle(style: string, comment: string, filePath: string, needle: string) { + const content = this._mergeStyleAndComment(style, comment); + + this.addBlockContentToFile( + { file: filePath, needle, splicable: content, regexp: `\n${style}\n`, prettierAware: true }, + 'Style not added to JHipster app.\n', + ); + } + + _mergeStyleAndComment(style: string, comment: string) { + let styleBlock = ''; + + if (comment) { + styleBlock += '/* ==========================================================================\n'; + styleBlock += `${comment}\n`; + styleBlock += '========================================================================== */\n'; + } + styleBlock += `${style}\n`; + + return styleBlock; + } +} diff --git a/generators/client/needle-client.spec.mts b/generators/client/needle-client.spec.mts deleted file mode 100644 index 216a77264a6b..000000000000 --- a/generators/client/needle-client.spec.mts +++ /dev/null @@ -1,47 +0,0 @@ -import ClientGenerator from '../../generators/client/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { dryRunHelpers as helpers, result as runResult, getGenerator } from '../../test/support/index.mjs'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends ClientGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - if (!this.jhipsterContext) { - throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); - } - - this.sbsBlueprint = true; - } - - get [ClientGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - // @ts-ignore - async additionalResource({ source }) { - source.addExternalResourceToRoot({ - resource: '', - comment: 'Comment added by JHipster API', - }); - }, - }); - } -}; - -describe('needle API Client: JHipster client generator with blueprint', () => { - before(async () => { - await helpers - .run(getGenerator('client')) - .withJHipsterConfig({ - skipServer: true, - }) - .withOptions({ - blueprint: 'myblueprint', - }) - .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']]); - }); - - it('Assert index.html contain the comment and the resource added', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}index.html`, ''); - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}index.html`, ''); - }); -}); diff --git a/generators/client/needle-client.spec.ts b/generators/client/needle-client.spec.ts new file mode 100644 index 000000000000..9f0c141138f5 --- /dev/null +++ b/generators/client/needle-client.spec.ts @@ -0,0 +1,47 @@ +import ClientGenerator from '../../generators/client/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import { dryRunHelpers as helpers, result as runResult, getGenerator } from '../../test/support/index.js'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends ClientGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + if (!this.jhipsterContext) { + throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); + } + + this.sbsBlueprint = true; + } + + get [ClientGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + // @ts-ignore + async additionalResource({ source }) { + source.addExternalResourceToRoot({ + resource: '', + comment: 'Comment added by JHipster API', + }); + }, + }); + } +}; + +describe('needle API Client: JHipster client generator with blueprint', () => { + before(async () => { + await helpers + .run(getGenerator('client')) + .withJHipsterConfig({ + skipServer: true, + }) + .withOptions({ + blueprint: 'myblueprint', + }) + .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']]); + }); + + it('Assert index.html contain the comment and the resource added', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}index.html`, ''); + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}index.html`, ''); + }); +}); diff --git a/generators/client/prompts.js b/generators/client/prompts.js new file mode 100644 index 000000000000..a57e1971e4dd --- /dev/null +++ b/generators/client/prompts.js @@ -0,0 +1,137 @@ +/** + * Copyright 2013-2023 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 { httpsGet } from '../base/support/index.js'; + +export async function askForClientTheme({ control }) { + if (control.existingProject && !this.options.askAnswered) return; + + const config = this.jhipsterConfigWithDefaults; + await this.prompt( + { + type: 'list', + name: 'clientTheme', + when: () => ['angular', 'react', 'vue'].includes(config.clientFramework), + message: 'Would you like to use a Bootswatch theme (https://bootswatch.com/)?', + choices: async () => { + const bootswatchChoices = await retrieveOnlineBootswatchThemes(this).catch(errorMessage => { + this.log.warn(errorMessage); + return retrieveLocalBootswatchThemes(); + }); + return [ + { + value: 'none', + name: 'Default JHipster', + }, + ...bootswatchChoices, + ]; + }, + default: config.clientTheme, + }, + this.config, + ); +} + +export async function askForClientThemeVariant({ control }) { + if (control.existingProject && !this.options.askAnswered) return; + if ((this.jhipsterConfig.clientTheme ?? 'none') === 'none') { + return; + } + + const config = this.jhipsterConfigWithDefaults; + await this.prompt( + { + type: 'list', + name: 'clientThemeVariant', + when: () => !this.jhipsterConfig.skipClient, + message: 'Choose a Bootswatch variant navbar theme (https://bootswatch.com/)?', + choices: [ + { value: 'primary', name: 'Primary' }, + { value: 'dark', name: 'Dark' }, + { value: 'light', name: 'Light' }, + ], + default: config.clientThemeVariant, + }, + this.config, + ); +} + +async function retrieveOnlineBootswatchThemes(generator) { + return _retrieveBootswatchThemes(generator, true); +} + +async function retrieveLocalBootswatchThemes(generator) { + return _retrieveBootswatchThemes(generator, false); +} + +async function _retrieveBootswatchThemes(generator, useApi) { + const errorMessage = 'Could not fetch bootswatch themes from API. Using default ones.'; + if (!useApi) { + return [ + { value: 'cerulean', name: 'Cerulean' }, + { value: 'cosmo', name: 'Cosmo' }, + { value: 'cyborg', name: 'Cyborg' }, + { value: 'darkly', name: 'Darkly' }, + { value: 'flatly', name: 'Flatly' }, + { value: 'journal', name: 'Journal' }, + { value: 'litera', name: 'Litera' }, + { value: 'lumen', name: 'Lumen' }, + { value: 'lux', name: 'Lux' }, + { value: 'materia', name: 'Materia' }, + { value: 'minty', name: 'Minty' }, + { value: 'morph', name: 'Morph' }, + { value: 'pulse', name: 'Pulse' }, + { value: 'quartz', name: 'Quartz' }, + { value: 'sandstone', name: 'Sandstone' }, + { value: 'simplex', name: 'Simplex' }, + { value: 'sketchy', name: 'Sketchy' }, + { value: 'slate', name: 'Slate' }, + { value: 'solar', name: 'Solar' }, + { value: 'spacelab', name: 'Spacelab' }, + { value: 'superhero', name: 'Superhero' }, + { value: 'united', name: 'United' }, + { value: 'vapor', name: 'Vapor' }, + { value: 'yeti', name: 'Yeti' }, + { value: 'zephyr', name: 'Zephyr' }, + ]; + } + + return new Promise((resolve, reject) => { + httpsGet( + 'https://bootswatch.com/api/5.json', + + body => { + try { + const { themes } = JSON.parse(body); + + const bootswatchChoices = themes.map(theme => ({ + value: theme.name.toLowerCase(), + name: theme.name, + })); + + resolve(bootswatchChoices); + } catch (err) { + reject(errorMessage); + } + }, + () => { + reject(errorMessage); + }, + ); + }); +} diff --git a/generators/client/prompts.mjs b/generators/client/prompts.mjs deleted file mode 100644 index bb2fc7467f98..000000000000 --- a/generators/client/prompts.mjs +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright 2013-2023 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 { httpsGet } from '../base/support/index.mjs'; - -export async function askForClientTheme({ control }) { - if (control.existingProject && !this.options.askAnswered) return; - - const config = this.jhipsterConfigWithDefaults; - await this.prompt( - { - type: 'list', - name: 'clientTheme', - when: () => ['angular', 'react', 'vue'].includes(config.clientFramework), - message: 'Would you like to use a Bootswatch theme (https://bootswatch.com/)?', - choices: async () => { - const bootswatchChoices = await retrieveOnlineBootswatchThemes(this).catch(errorMessage => { - this.log.warn(errorMessage); - return retrieveLocalBootswatchThemes(); - }); - return [ - { - value: 'none', - name: 'Default JHipster', - }, - ...bootswatchChoices, - ]; - }, - default: config.clientTheme, - }, - this.config, - ); -} - -export async function askForClientThemeVariant({ control }) { - if (control.existingProject && !this.options.askAnswered) return; - if ((this.jhipsterConfig.clientTheme ?? 'none') === 'none') { - return; - } - - const config = this.jhipsterConfigWithDefaults; - await this.prompt( - { - type: 'list', - name: 'clientThemeVariant', - when: () => !this.jhipsterConfig.skipClient, - message: 'Choose a Bootswatch variant navbar theme (https://bootswatch.com/)?', - choices: [ - { value: 'primary', name: 'Primary' }, - { value: 'dark', name: 'Dark' }, - { value: 'light', name: 'Light' }, - ], - default: config.clientThemeVariant, - }, - this.config, - ); -} - -async function retrieveOnlineBootswatchThemes(generator) { - return _retrieveBootswatchThemes(generator, true); -} - -async function retrieveLocalBootswatchThemes(generator) { - return _retrieveBootswatchThemes(generator, false); -} - -async function _retrieveBootswatchThemes(generator, useApi) { - const errorMessage = 'Could not fetch bootswatch themes from API. Using default ones.'; - if (!useApi) { - return [ - { value: 'cerulean', name: 'Cerulean' }, - { value: 'cosmo', name: 'Cosmo' }, - { value: 'cyborg', name: 'Cyborg' }, - { value: 'darkly', name: 'Darkly' }, - { value: 'flatly', name: 'Flatly' }, - { value: 'journal', name: 'Journal' }, - { value: 'litera', name: 'Litera' }, - { value: 'lumen', name: 'Lumen' }, - { value: 'lux', name: 'Lux' }, - { value: 'materia', name: 'Materia' }, - { value: 'minty', name: 'Minty' }, - { value: 'morph', name: 'Morph' }, - { value: 'pulse', name: 'Pulse' }, - { value: 'quartz', name: 'Quartz' }, - { value: 'sandstone', name: 'Sandstone' }, - { value: 'simplex', name: 'Simplex' }, - { value: 'sketchy', name: 'Sketchy' }, - { value: 'slate', name: 'Slate' }, - { value: 'solar', name: 'Solar' }, - { value: 'spacelab', name: 'Spacelab' }, - { value: 'superhero', name: 'Superhero' }, - { value: 'united', name: 'United' }, - { value: 'vapor', name: 'Vapor' }, - { value: 'yeti', name: 'Yeti' }, - { value: 'zephyr', name: 'Zephyr' }, - ]; - } - - return new Promise((resolve, reject) => { - httpsGet( - 'https://bootswatch.com/api/5.json', - - body => { - try { - const { themes } = JSON.parse(body); - - const bootswatchChoices = themes.map(theme => ({ - value: theme.name.toLowerCase(), - name: theme.name, - })); - - resolve(bootswatchChoices); - } catch (err) { - reject(errorMessage); - } - }, - () => { - reject(errorMessage); - }, - ); - }); -} diff --git a/generators/client/prompts.spec.mts b/generators/client/prompts.spec.mts deleted file mode 100644 index d66f7f99d6f8..000000000000 --- a/generators/client/prompts.spec.mts +++ /dev/null @@ -1,56 +0,0 @@ -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { - applicationTypes, - databaseTypes, - cacheTypes, - authenticationTypes, - testFrameworkTypes, - clientFrameworkTypes, - buildToolTypes, -} from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_APP } from '../generator-list.mjs'; - -const { MONOLITH } = applicationTypes; -const { H2_DISK, MYSQL, SQL } = databaseTypes; -const { EHCACHE } = cacheTypes; -const { JWT } = authenticationTypes; -const { CYPRESS } = testFrameworkTypes; -const { ANGULAR } = clientFrameworkTypes; -const { MAVEN } = buildToolTypes; - -const mockedComposedGenerators = ['jhipster:common', 'jhipster:server', 'jhipster:languages', 'jhipster:entity']; - -describe('generator - client - prompts', () => { - describe('clientTestFrameworks prompt', () => { - describe('with cypress value', () => { - let runResult; - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_APP) - .withAnswers({ - baseName: 'sampleMysql', - packageName: 'com.mycompany.myapp', - applicationType: MONOLITH, - databaseType: SQL, - devDatabaseType: H2_DISK, - prodDatabaseType: MYSQL, - cacheProvider: EHCACHE, - authenticationType: JWT, - enableTranslation: true, - nativeLanguage: 'en', - languages: ['en', 'fr'], - clientTestFrameworks: [CYPRESS], - buildTool: MAVEN, - clientFramework: ANGULAR, - clientTheme: 'none', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedComposedGenerators); - }); - - it('should write testFrameworks with cypress value to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { testFrameworks: [CYPRESS] } }); - }); - }); - }); -}); diff --git a/generators/client/prompts.spec.ts b/generators/client/prompts.spec.ts new file mode 100644 index 000000000000..cab0f178298d --- /dev/null +++ b/generators/client/prompts.spec.ts @@ -0,0 +1,56 @@ +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { + applicationTypes, + databaseTypes, + cacheTypes, + authenticationTypes, + testFrameworkTypes, + clientFrameworkTypes, + buildToolTypes, +} from '../../jdl/jhipster/index.js'; +import { GENERATOR_APP } from '../generator-list.js'; + +const { MONOLITH } = applicationTypes; +const { H2_DISK, MYSQL, SQL } = databaseTypes; +const { EHCACHE } = cacheTypes; +const { JWT } = authenticationTypes; +const { CYPRESS } = testFrameworkTypes; +const { ANGULAR } = clientFrameworkTypes; +const { MAVEN } = buildToolTypes; + +const mockedComposedGenerators = ['jhipster:common', 'jhipster:server', 'jhipster:languages', 'jhipster:entity']; + +describe('generator - client - prompts', () => { + describe('clientTestFrameworks prompt', () => { + describe('with cypress value', () => { + let runResult; + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_APP) + .withAnswers({ + baseName: 'sampleMysql', + packageName: 'com.mycompany.myapp', + applicationType: MONOLITH, + databaseType: SQL, + devDatabaseType: H2_DISK, + prodDatabaseType: MYSQL, + cacheProvider: EHCACHE, + authenticationType: JWT, + enableTranslation: true, + nativeLanguage: 'en', + languages: ['en', 'fr'], + clientTestFrameworks: [CYPRESS], + buildTool: MAVEN, + clientFramework: ANGULAR, + clientTheme: 'none', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedComposedGenerators); + }); + + it('should write testFrameworks with cypress value to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { testFrameworks: [CYPRESS] } }); + }); + }); + }); +}); diff --git a/generators/client/support/config.mts b/generators/client/support/config.mts deleted file mode 100644 index 0a42070ab975..000000000000 --- a/generators/client/support/config.mts +++ /dev/null @@ -1,38 +0,0 @@ -import { getFrontendAppName } from '../../base/support/basename.mjs'; -import { CLIENT_MAIN_SRC_DIR, CLIENT_TEST_SRC_DIR } from '../../generator-constants.mjs'; - -/** - * Load client configs into application. - */ -export const loadClientConfig = ({ config, application }: { config: any; application: any }) => { - (application as any).clientPackageManager = config.clientPackageManager; - application.clientFramework = config.clientFramework; - (application as any).clientTheme = config.clientTheme; - (application as any).clientThemeVariant = config.clientThemeVariant; - (application as any).devServerPort = config.devServerPort; - - (application as any).clientRootDir = config.clientRootDir ?? ''; - (application as any).clientSrcDir = config.clientSrcDir ?? `${application.clientRootDir}${CLIENT_MAIN_SRC_DIR}`; - (application as any).clientTestDir = config.clientTestDir ?? `${application.clientRootDir}${CLIENT_TEST_SRC_DIR}`; -}; - -/** - * Load client derived properties. - */ -export const loadDerivedClientConfig = ({ application }: { application: any }) => { - if ((application as any).microfrontend === undefined) { - if ((application as any).applicationTypeMicroservice) { - (application as any).microfrontend = application.clientFrameworkAny; - } else if ((application as any).applicationTypeGateway) { - (application as any).microfrontend = (application as any).microfrontends && (application as any).microfrontends.length > 0; - } - } - (application as any).clientThemeNone = (application as any).clientTheme === 'none'; - (application as any).clientThemePrimary = (application as any).clientThemeVariant === 'primary'; - (application as any).clientThemeLight = (application as any).clientThemeVariant === 'light'; - (application as any).clientThemeDark = (application as any).clientThemeVariant === 'dark'; - - if ((application as any).baseName) { - (application as any).frontendAppName = getFrontendAppName({ baseName: (application as any).baseName }); - } -}; diff --git a/generators/client/support/config.ts b/generators/client/support/config.ts new file mode 100644 index 000000000000..f219cd2c8fe3 --- /dev/null +++ b/generators/client/support/config.ts @@ -0,0 +1,38 @@ +import { getFrontendAppName } from '../../base/support/basename.js'; +import { CLIENT_MAIN_SRC_DIR, CLIENT_TEST_SRC_DIR } from '../../generator-constants.js'; + +/** + * Load client configs into application. + */ +export const loadClientConfig = ({ config, application }: { config: any; application: any }) => { + (application as any).clientPackageManager = config.clientPackageManager; + application.clientFramework = config.clientFramework; + (application as any).clientTheme = config.clientTheme; + (application as any).clientThemeVariant = config.clientThemeVariant; + (application as any).devServerPort = config.devServerPort; + + (application as any).clientRootDir = config.clientRootDir ?? ''; + (application as any).clientSrcDir = config.clientSrcDir ?? `${application.clientRootDir}${CLIENT_MAIN_SRC_DIR}`; + (application as any).clientTestDir = config.clientTestDir ?? `${application.clientRootDir}${CLIENT_TEST_SRC_DIR}`; +}; + +/** + * Load client derived properties. + */ +export const loadDerivedClientConfig = ({ application }: { application: any }) => { + if ((application as any).microfrontend === undefined) { + if ((application as any).applicationTypeMicroservice) { + (application as any).microfrontend = application.clientFrameworkAny; + } else if ((application as any).applicationTypeGateway) { + (application as any).microfrontend = (application as any).microfrontends && (application as any).microfrontends.length > 0; + } + } + (application as any).clientThemeNone = (application as any).clientTheme === 'none'; + (application as any).clientThemePrimary = (application as any).clientThemeVariant === 'primary'; + (application as any).clientThemeLight = (application as any).clientThemeVariant === 'light'; + (application as any).clientThemeDark = (application as any).clientThemeVariant === 'dark'; + + if ((application as any).baseName) { + (application as any).frontendAppName = getFrontendAppName({ baseName: (application as any).baseName }); + } +}; diff --git a/generators/client/support/entity-definition.js b/generators/client/support/entity-definition.js new file mode 100644 index 000000000000..ff8745d16353 --- /dev/null +++ b/generators/client/support/entity-definition.js @@ -0,0 +1,150 @@ +/** + * Copyright 2013-2023 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 getTypescriptKeyType from './types-utils.js'; + +import { fieldTypes, validations, clientFrameworkTypes } from '../../../jdl/jhipster/index.js'; + +const dbTypes = fieldTypes; +const { + Validations: { REQUIRED }, +} = validations; + +const { + STRING: TYPE_STRING, + INTEGER: TYPE_INTEGER, + LONG: TYPE_LONG, + BIG_DECIMAL: TYPE_BIG_DECIMAL, + FLOAT: TYPE_FLOAT, + DOUBLE: TYPE_DOUBLE, + UUID: TYPE_UUID, + BOOLEAN: TYPE_BOOLEAN, + LOCAL_DATE: TYPE_LOCAL_DATE, + ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, + INSTANT: TYPE_INSTANT, + DURATION: TYPE_DURATION, +} = dbTypes.CommonDBTypes; + +const TYPE_BYTES = dbTypes.RelationalOnlyDBTypes.BYTES; +const TYPE_BYTE_BUFFER = dbTypes.RelationalOnlyDBTypes.BYTE_BUFFER; +const { ANGULAR, VUE } = clientFrameworkTypes; + +/** + * @private + * Generate Entity Client Field Declarations + * + * @param {string} primaryKey - primary key definition + * @param {Array|Object} fields - array of fields + * @param {Array|Object} relationships - array of relationships + * @param {string} dto - dto + * @param [customDateType] + * @param {boolean} embedded - either the actual entity is embedded or not + * @param { string} clientFramework + * @returns variablesWithTypes: Array + */ +const generateEntityClientFields = ( + primaryKey, + fields, + relationships, + dto, + customDateType = 'dayjs.Dayjs', + embedded = false, + clientFramework = ANGULAR, +) => { + const variablesWithTypes = []; + if (!embedded && primaryKey) { + const tsKeyType = getTypescriptKeyType(primaryKey); + if (clientFramework === VUE) { + variablesWithTypes.push(`id?: ${tsKeyType}`); + } + } + fields.forEach(field => { + const fieldType = field.fieldType; + const fieldName = field.fieldName; + const nullable = !field.id && field.nullable; + let tsType = 'any'; + if (field.fieldIsEnum) { + tsType = `keyof typeof ${fieldType}`; + } else if (fieldType === TYPE_BOOLEAN) { + tsType = 'boolean'; + } else if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(fieldType)) { + tsType = 'number'; + } else if ([TYPE_STRING, TYPE_UUID, TYPE_DURATION, TYPE_BYTES, TYPE_BYTE_BUFFER].includes(fieldType)) { + tsType = 'string'; + if ([TYPE_BYTES, TYPE_BYTE_BUFFER].includes(fieldType) && field.fieldTypeBlobContent !== 'text') { + variablesWithTypes.push(`${fieldName}ContentType?: ${nullable ? 'string | null' : 'string'}`); + } + } else if ([TYPE_LOCAL_DATE, TYPE_INSTANT, TYPE_ZONED_DATE_TIME].includes(fieldType)) { + tsType = customDateType; + } + if (nullable) { + tsType += ' | null'; + } + variablesWithTypes.push(`${fieldName}?: ${tsType}`); + }); + + relationships.forEach(relationship => { + let fieldType; + let fieldName; + const nullable = !relationship.relationshipValidateRules || !relationship.relationshipValidateRules.includes(REQUIRED); + const relationshipType = relationship.relationshipType; + if (relationshipType === 'one-to-many' || relationshipType === 'many-to-many') { + fieldType = `I${relationship.otherEntityAngularName}[]`; + fieldName = relationship.relationshipFieldNamePlural; + } else { + fieldType = `I${relationship.otherEntityAngularName}`; + fieldName = relationship.relationshipFieldName; + } + if (nullable) { + fieldType += ' | null'; + } + variablesWithTypes.push(`${fieldName}?: ${fieldType}`); + }); + return variablesWithTypes; +}; + +/** + * @private + * Generate a test entity, according to the type + * + * @param references + * @param {number} [index] - index of the primary key sample, pass undefined for a random key. + */ +export const generateTestEntity = (references, index = 'random') => { + const random = index === 'random'; + const entries = references + .map(reference => { + if (random && reference.field) { + const field = reference.field; + const fakeData = field.generateFakeData('json-serializable'); + if (reference.field.fieldWithContentType) { + return [ + [reference.name, fakeData], + [field.contentTypeFieldName, 'unknown'], + ]; + } + return [[reference.name, fakeData]]; + } + return [[reference.name, this.generateTestEntityId(reference.type, index, false)]]; + }) + .flat(); + return Object.fromEntries(entries); +}; + +export default generateEntityClientFields; diff --git a/generators/client/support/entity-definition.mjs b/generators/client/support/entity-definition.mjs deleted file mode 100644 index a5a6ece4f399..000000000000 --- a/generators/client/support/entity-definition.mjs +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright 2013-2023 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 getTypescriptKeyType from './types-utils.mjs'; - -import { fieldTypes, validations, clientFrameworkTypes } from '../../../jdl/jhipster/index.mjs'; - -const dbTypes = fieldTypes; -const { - Validations: { REQUIRED }, -} = validations; - -const { - STRING: TYPE_STRING, - INTEGER: TYPE_INTEGER, - LONG: TYPE_LONG, - BIG_DECIMAL: TYPE_BIG_DECIMAL, - FLOAT: TYPE_FLOAT, - DOUBLE: TYPE_DOUBLE, - UUID: TYPE_UUID, - BOOLEAN: TYPE_BOOLEAN, - LOCAL_DATE: TYPE_LOCAL_DATE, - ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, - INSTANT: TYPE_INSTANT, - DURATION: TYPE_DURATION, -} = dbTypes.CommonDBTypes; - -const TYPE_BYTES = dbTypes.RelationalOnlyDBTypes.BYTES; -const TYPE_BYTE_BUFFER = dbTypes.RelationalOnlyDBTypes.BYTE_BUFFER; -const { ANGULAR, VUE } = clientFrameworkTypes; - -/** - * @private - * Generate Entity Client Field Declarations - * - * @param {string} primaryKey - primary key definition - * @param {Array|Object} fields - array of fields - * @param {Array|Object} relationships - array of relationships - * @param {string} dto - dto - * @param [customDateType] - * @param {boolean} embedded - either the actual entity is embedded or not - * @param { string} clientFramework - * @returns variablesWithTypes: Array - */ -const generateEntityClientFields = ( - primaryKey, - fields, - relationships, - dto, - customDateType = 'dayjs.Dayjs', - embedded = false, - clientFramework = ANGULAR, -) => { - const variablesWithTypes = []; - if (!embedded && primaryKey) { - const tsKeyType = getTypescriptKeyType(primaryKey); - if (clientFramework === VUE) { - variablesWithTypes.push(`id?: ${tsKeyType}`); - } - } - fields.forEach(field => { - const fieldType = field.fieldType; - const fieldName = field.fieldName; - const nullable = !field.id && field.nullable; - let tsType = 'any'; - if (field.fieldIsEnum) { - tsType = `keyof typeof ${fieldType}`; - } else if (fieldType === TYPE_BOOLEAN) { - tsType = 'boolean'; - } else if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(fieldType)) { - tsType = 'number'; - } else if ([TYPE_STRING, TYPE_UUID, TYPE_DURATION, TYPE_BYTES, TYPE_BYTE_BUFFER].includes(fieldType)) { - tsType = 'string'; - if ([TYPE_BYTES, TYPE_BYTE_BUFFER].includes(fieldType) && field.fieldTypeBlobContent !== 'text') { - variablesWithTypes.push(`${fieldName}ContentType?: ${nullable ? 'string | null' : 'string'}`); - } - } else if ([TYPE_LOCAL_DATE, TYPE_INSTANT, TYPE_ZONED_DATE_TIME].includes(fieldType)) { - tsType = customDateType; - } - if (nullable) { - tsType += ' | null'; - } - variablesWithTypes.push(`${fieldName}?: ${tsType}`); - }); - - relationships.forEach(relationship => { - let fieldType; - let fieldName; - const nullable = !relationship.relationshipValidateRules || !relationship.relationshipValidateRules.includes(REQUIRED); - const relationshipType = relationship.relationshipType; - if (relationshipType === 'one-to-many' || relationshipType === 'many-to-many') { - fieldType = `I${relationship.otherEntityAngularName}[]`; - fieldName = relationship.relationshipFieldNamePlural; - } else { - fieldType = `I${relationship.otherEntityAngularName}`; - fieldName = relationship.relationshipFieldName; - } - if (nullable) { - fieldType += ' | null'; - } - variablesWithTypes.push(`${fieldName}?: ${fieldType}`); - }); - return variablesWithTypes; -}; - -/** - * @private - * Generate a test entity, according to the type - * - * @param references - * @param {number} [index] - index of the primary key sample, pass undefined for a random key. - */ -export const generateTestEntity = (references, index = 'random') => { - const random = index === 'random'; - const entries = references - .map(reference => { - if (random && reference.field) { - const field = reference.field; - const fakeData = field.generateFakeData('json-serializable'); - if (reference.field.fieldWithContentType) { - return [ - [reference.name, fakeData], - [field.contentTypeFieldName, 'unknown'], - ]; - } - return [[reference.name, fakeData]]; - } - return [[reference.name, this.generateTestEntityId(reference.type, index, false)]]; - }) - .flat(); - return Object.fromEntries(entries); -}; - -export default generateEntityClientFields; diff --git a/generators/client/support/files.mts b/generators/client/support/files.mts deleted file mode 100644 index 3304b868a1b5..000000000000 --- a/generators/client/support/files.mts +++ /dev/null @@ -1,79 +0,0 @@ -import type { WriteFileBlock } from '../../base/api.mjs'; -import type CoreGenerator from '../../base-core/index.mjs'; -import { CLIENT_MAIN_SRC_DIR, CLIENT_TEST_SRC_DIR } from '../../generator-constants.mjs'; - -export const replaceEntityFilePath = (data: any, filepath: string) => - filepath - .replace(/_entityFolder_/, data.entityFolderName) - .replace(/_entityFile_/, data.entityFileName) - .replace(/_entityModel_/, data.entityModelFileName); - -const CLIENT_TEMPLATES_SRC_DIR = CLIENT_MAIN_SRC_DIR; - -type RelativeWriteFileBlock = WriteFileBlock & { relativePath?: string }; - -export function clientRootTemplatesBlock(blockOrRelativePath?: string): Pick; -export function clientRootTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function clientRootTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { - return clientBlock({ - srcPath: '', - destProperty: 'clientRootDir', - blockOrRelativePath, - }); -} - -export function clientSrcTemplatesBlock(blockOrRelativePath?: string): Pick; -export function clientSrcTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function clientSrcTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { - return clientBlock({ - srcPath: CLIENT_TEMPLATES_SRC_DIR, - destProperty: 'clientSrcDir', - blockOrRelativePath, - }); -} - -export function clientApplicationTemplatesBlock(blockOrRelativePath?: string): Pick; -export function clientApplicationTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function clientApplicationTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { - return clientBlock({ - srcPath: CLIENT_TEMPLATES_SRC_DIR, - relativeToSrc: 'app/', - destProperty: 'clientSrcDir', - blockOrRelativePath, - }); -} - -export function clientTestTemplatesBlock(blockOrRelativePath?: string): Pick; -export function clientTestTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function clientTestTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { - return clientBlock({ - srcPath: CLIENT_TEST_SRC_DIR, - destProperty: 'clientTestDir', - blockOrRelativePath, - }); -} - -function clientBlock({ - srcPath, - destProperty, - blockOrRelativePath = '', - relativeToSrc = '', -}: { - srcPath: string; - destProperty: string; - blockOrRelativePath: string | RelativeWriteFileBlock; - relativeToSrc?: string; -}): WriteFileBlock | Pick { - const block: RelativeWriteFileBlock | undefined = typeof blockOrRelativePath !== 'string' ? blockOrRelativePath : undefined; - const blockRenameTo = typeof block?.renameTo === 'function' ? block.renameTo : undefined; - const relativePath: string = typeof blockOrRelativePath === 'string' ? blockOrRelativePath : blockOrRelativePath.relativePath ?? ''; - return { - path: `${srcPath}${relativeToSrc}${relativePath}`, - ...block, - renameTo(this: CoreGenerator, data: any, filePath: string) { - return `${data[destProperty]}${relativeToSrc}${replaceEntityFilePath(data, relativePath) ?? ''}${ - replaceEntityFilePath(data, blockRenameTo?.call?.(this, data, filePath) ?? filePath) ?? '' - }`; - }, - }; -} diff --git a/generators/client/support/files.ts b/generators/client/support/files.ts new file mode 100644 index 000000000000..64e73d48427e --- /dev/null +++ b/generators/client/support/files.ts @@ -0,0 +1,79 @@ +import type { WriteFileBlock } from '../../base/api.js'; +import type CoreGenerator from '../../base-core/index.js'; +import { CLIENT_MAIN_SRC_DIR, CLIENT_TEST_SRC_DIR } from '../../generator-constants.js'; + +export const replaceEntityFilePath = (data: any, filepath: string) => + filepath + .replace(/_entityFolder_/, data.entityFolderName) + .replace(/_entityFile_/, data.entityFileName) + .replace(/_entityModel_/, data.entityModelFileName); + +const CLIENT_TEMPLATES_SRC_DIR = CLIENT_MAIN_SRC_DIR; + +type RelativeWriteFileBlock = WriteFileBlock & { relativePath?: string }; + +export function clientRootTemplatesBlock(blockOrRelativePath?: string): Pick; +export function clientRootTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function clientRootTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { + return clientBlock({ + srcPath: '', + destProperty: 'clientRootDir', + blockOrRelativePath, + }); +} + +export function clientSrcTemplatesBlock(blockOrRelativePath?: string): Pick; +export function clientSrcTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function clientSrcTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { + return clientBlock({ + srcPath: CLIENT_TEMPLATES_SRC_DIR, + destProperty: 'clientSrcDir', + blockOrRelativePath, + }); +} + +export function clientApplicationTemplatesBlock(blockOrRelativePath?: string): Pick; +export function clientApplicationTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function clientApplicationTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { + return clientBlock({ + srcPath: CLIENT_TEMPLATES_SRC_DIR, + relativeToSrc: 'app/', + destProperty: 'clientSrcDir', + blockOrRelativePath, + }); +} + +export function clientTestTemplatesBlock(blockOrRelativePath?: string): Pick; +export function clientTestTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function clientTestTemplatesBlock(blockOrRelativePath: string | RelativeWriteFileBlock = ''): Partial { + return clientBlock({ + srcPath: CLIENT_TEST_SRC_DIR, + destProperty: 'clientTestDir', + blockOrRelativePath, + }); +} + +function clientBlock({ + srcPath, + destProperty, + blockOrRelativePath = '', + relativeToSrc = '', +}: { + srcPath: string; + destProperty: string; + blockOrRelativePath: string | RelativeWriteFileBlock; + relativeToSrc?: string; +}): WriteFileBlock | Pick { + const block: RelativeWriteFileBlock | undefined = typeof blockOrRelativePath !== 'string' ? blockOrRelativePath : undefined; + const blockRenameTo = typeof block?.renameTo === 'function' ? block.renameTo : undefined; + const relativePath: string = typeof blockOrRelativePath === 'string' ? blockOrRelativePath : blockOrRelativePath.relativePath ?? ''; + return { + path: `${srcPath}${relativeToSrc}${relativePath}`, + ...block, + renameTo(this: CoreGenerator, data: any, filePath: string) { + return `${data[destProperty]}${relativeToSrc}${replaceEntityFilePath(data, relativePath) ?? ''}${ + replaceEntityFilePath(data, blockRenameTo?.call?.(this, data, filePath) ?? filePath) ?? '' + }`; + }, + }; +} diff --git a/generators/client/support/index.mts b/generators/client/support/index.mts deleted file mode 100644 index 2e3807fca2d9..000000000000 --- a/generators/client/support/index.mts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2013-2023 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 './config.mjs'; -export { default as getTypescriptKeyType, getTypescriptType } from './types-utils.mjs'; -export { default as generateEntityClientFields } from './entity-definition.mjs'; -export { default as prepareEntity } from './prepare-entity.mjs'; -export * from './prepare-entity.mjs'; -export { default as prepareField } from './prepare-field.mjs'; -export * from './template-utils.mjs'; -export * from './update-languages.mjs'; -export * from './files.mjs'; diff --git a/generators/client/support/index.ts b/generators/client/support/index.ts new file mode 100644 index 000000000000..5bd29dcdbdc0 --- /dev/null +++ b/generators/client/support/index.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2013-2023 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 './config.js'; +export { default as getTypescriptKeyType, getTypescriptType } from './types-utils.js'; +export { default as generateEntityClientFields } from './entity-definition.js'; +export { default as prepareEntity } from './prepare-entity.js'; +export * from './prepare-entity.js'; +export { default as prepareField } from './prepare-field.js'; +export * from './template-utils.js'; +export * from './update-languages.js'; +export * from './files.js'; diff --git a/generators/client/support/prepare-entity.js b/generators/client/support/prepare-entity.js new file mode 100644 index 000000000000..a48d14f0b7b1 --- /dev/null +++ b/generators/client/support/prepare-entity.js @@ -0,0 +1,27 @@ +/** + * Copyright 2013-2023 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 getTypescriptKeyType from './types-utils.js'; + +export default function prepareEntity() {} + +export function preparePostEntityClientDerivedProperties(entity) { + if (entity.primaryKey) { + entity.tsKeyType = getTypescriptKeyType(entity.primaryKey.type); + } +} diff --git a/generators/client/support/prepare-entity.mjs b/generators/client/support/prepare-entity.mjs deleted file mode 100644 index 001f9c17eebe..000000000000 --- a/generators/client/support/prepare-entity.mjs +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2013-2023 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 getTypescriptKeyType from './types-utils.mjs'; - -export default function prepareEntity() {} - -export function preparePostEntityClientDerivedProperties(entity) { - if (entity.primaryKey) { - entity.tsKeyType = getTypescriptKeyType(entity.primaryKey.type); - } -} diff --git a/generators/client/support/prepare-field.mjs b/generators/client/support/prepare-field.js similarity index 100% rename from generators/client/support/prepare-field.mjs rename to generators/client/support/prepare-field.js diff --git a/generators/client/support/template-utils.js b/generators/client/support/template-utils.js new file mode 100644 index 000000000000..5aed7eeed83f --- /dev/null +++ b/generators/client/support/template-utils.js @@ -0,0 +1,206 @@ +/** + * Copyright 2013-2023 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 path from 'path'; + +import { fieldTypes, clientFrameworkTypes } from '../../../jdl/jhipster/index.js'; +import { getEntryIfTypeOrTypeAttribute } from './types-utils.js'; + +const { STRING: TYPE_STRING, UUID: TYPE_UUID } = fieldTypes.CommonDBTypes; +const { ANGULAR, VUE } = clientFrameworkTypes; + +/** + * @private + * Generate Entity Client Imports + * + * @param {Array|Object} relationships - array of relationships + * @param {string} dto - dto + * @param {string} clientFramework the client framework, 'angular', 'vue' or 'react'. + * @returns typeImports: Map + */ +export const generateEntityClientImports = (relationships, dto, clientFramework) => { + const typeImports = new Map(); + relationships.forEach(relationship => { + const otherEntityAngularName = relationship.otherEntityAngularName; + const importType = `I${otherEntityAngularName}`; + let importPath; + if (relationship.otherEntity?.builtInUser) { + importPath = clientFramework === ANGULAR ? 'app/entities/user/user.model' : 'app/shared/model/user.model'; + } else { + importPath = + clientFramework === ANGULAR + ? `app/entities/${relationship.otherEntityClientRootFolder}${relationship.otherEntityFolderName}/${relationship.otherEntityFileName}.model` + : `app/shared/model/${relationship.otherEntityClientRootFolder}${relationship.otherEntityFileName}.model`; + } + typeImports.set(importType, importPath); + }); + return typeImports; +}; + +/** + * @private + * Generate Entity Client Enum Imports + * + * @param {Array|Object} fields - array of the entity fields + * @param {string} clientFramework the client framework, 'angular' or 'react'. + * @returns typeImports: Map + */ +export const generateEntityClientEnumImports = (fields, clientFramework) => { + const typeImports = new Map(); + const uniqueEnums = {}; + fields.forEach(field => { + const { enumFileName, fieldType } = field; + if (field.fieldIsEnum && (!uniqueEnums[fieldType] || (uniqueEnums[fieldType] && field.fieldValues.length !== 0))) { + const importType = `${fieldType}`; + const basePath = clientFramework === VUE ? '@' : 'app'; + const modelPath = clientFramework === ANGULAR ? 'entities' : 'shared/model'; + const importPath = `${basePath}/${modelPath}/enumerations/${enumFileName}.model`; + uniqueEnums[fieldType] = field.fieldType; + typeImports.set(importType, importPath); + } + }); + return typeImports; +}; + +/** + * @private + * Generate a primary key, according to the type + * + * @param {any} primaryKey - primary key definition + * @param {number} index - the index of the primary key, currently it's possible to generate 2 values, index = 0 - first key (default), otherwise second key + * @param {boolean} [wrapped=true] - wrapped values for required types. + */ + +export const generateTestEntityId = (primaryKey, index = 0, wrapped = true) => { + primaryKey = getEntryIfTypeOrTypeAttribute(primaryKey); + let value; + if (primaryKey === TYPE_STRING) { + value = index === 0 ? 'ABC' : 'CBA'; + } else if (primaryKey === TYPE_UUID) { + value = index === 0 ? '9fec3727-3421-4967-b213-ba36557ca194' : '1361f429-3817-4123-8ee3-fdf8943310b2'; + } else { + value = index === 0 ? 123 : 456; + } + if (wrapped && [TYPE_UUID, TYPE_STRING].includes(primaryKey)) { + return `'${value}'`; + } + return value; +}; + +/** + * @private + * Generate a test entity, according to the type + * + * @param references + * @param {number} [index] - index of the primary key sample, pass undefined for a random key. + */ +export const generateTestEntity = (references, index = 'random') => { + const random = index === 'random'; + const entries = references + .map(reference => { + if (random && reference.field) { + const field = reference.field; + const { fieldWithContentType, contentTypeFieldName } = field; + const fakeData = field.generateFakeData('json-serializable'); + if (fieldWithContentType) { + return [ + [reference.name, fakeData], + [contentTypeFieldName, 'unknown'], + ]; + } + return [[reference.name, fakeData]]; + } + return [[reference.name, generateTestEntityId(reference.type, index, false)]]; + }) + .flat(); + return Object.fromEntries(entries); +}; + +/** + * Generate a test entity, according to the references + * + * @param references + * @param additionalFields + * @return {String} test sample + */ +export const generateTypescriptTestEntity = (references, additionalFields = {}) => { + const entries = references + .map(reference => { + if (reference.field) { + const field = reference.field; + const { fieldIsEnum, fieldTypeTimed, fieldTypeLocalDate, fieldWithContentType, fieldName, contentTypeFieldName } = field; + + const fakeData = field.generateFakeData('ts'); + if (fieldWithContentType) { + return [ + [fieldName, fakeData], + [contentTypeFieldName, "'unknown'"], + ]; + } + if (fieldIsEnum) { + return [[fieldName, fakeData]]; + } + if (fieldTypeTimed || fieldTypeLocalDate) { + return [[fieldName, `dayjs(${fakeData})`]]; + } + return [[fieldName, fakeData]]; + } + return [[reference.name, generateTestEntityId(reference.type, 'random', false)]]; + }) + .flat(); + return `{ + ${[...entries, ...Object.entries(additionalFields)].map(([key, value]) => `${key}: ${value}`).join(',\n ')} +}`; +}; +/** + * Generate a test entity for the PK references (when the PK is a composite key) + * + * @param {any} primaryKey - primary key definition. + * @param {number} [index] - index of the primary key sample, pass undefined for a random key. + */ +export const generateTestEntityPrimaryKey = (primaryKey, index) => { + return JSON.stringify( + generateTestEntity( + primaryKey.fields.map(f => f.reference), + index, + ), + ); +}; + +/** + * @private + * Get a parent folder path addition for entity + * @param {string} clientRootFolder + */ +export const getEntityParentPathAddition = clientRootFolder => { + if (!clientRootFolder) { + return ''; + } + const relative = path.relative(`/app/entities/${clientRootFolder}/`, '/app/entities/'); + if (relative.includes('app')) { + // Relative path outside angular base dir. + throw new Error(` + "clientRootFolder outside app base dir '${clientRootFolder}'" +`); + } + const entityFolderPathAddition = relative.replace(/[/|\\]?..[/|\\]entities/, '').replace('entities', '..'); + if (!entityFolderPathAddition) { + return ''; + } + return `${entityFolderPathAddition}/`; +}; diff --git a/generators/client/support/template-utils.mjs b/generators/client/support/template-utils.mjs deleted file mode 100644 index 0dc751a39bf7..000000000000 --- a/generators/client/support/template-utils.mjs +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2013-2023 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 path from 'path'; - -import { fieldTypes, clientFrameworkTypes } from '../../../jdl/jhipster/index.mjs'; -import { getEntryIfTypeOrTypeAttribute } from './types-utils.mjs'; - -const { STRING: TYPE_STRING, UUID: TYPE_UUID } = fieldTypes.CommonDBTypes; -const { ANGULAR, VUE } = clientFrameworkTypes; - -/** - * @private - * Generate Entity Client Imports - * - * @param {Array|Object} relationships - array of relationships - * @param {string} dto - dto - * @param {string} clientFramework the client framework, 'angular', 'vue' or 'react'. - * @returns typeImports: Map - */ -export const generateEntityClientImports = (relationships, dto, clientFramework) => { - const typeImports = new Map(); - relationships.forEach(relationship => { - const otherEntityAngularName = relationship.otherEntityAngularName; - const importType = `I${otherEntityAngularName}`; - let importPath; - if (relationship.otherEntity?.builtInUser) { - importPath = clientFramework === ANGULAR ? 'app/entities/user/user.model' : 'app/shared/model/user.model'; - } else { - importPath = - clientFramework === ANGULAR - ? `app/entities/${relationship.otherEntityClientRootFolder}${relationship.otherEntityFolderName}/${relationship.otherEntityFileName}.model` - : `app/shared/model/${relationship.otherEntityClientRootFolder}${relationship.otherEntityFileName}.model`; - } - typeImports.set(importType, importPath); - }); - return typeImports; -}; - -/** - * @private - * Generate Entity Client Enum Imports - * - * @param {Array|Object} fields - array of the entity fields - * @param {string} clientFramework the client framework, 'angular' or 'react'. - * @returns typeImports: Map - */ -export const generateEntityClientEnumImports = (fields, clientFramework) => { - const typeImports = new Map(); - const uniqueEnums = {}; - fields.forEach(field => { - const { enumFileName, fieldType } = field; - if (field.fieldIsEnum && (!uniqueEnums[fieldType] || (uniqueEnums[fieldType] && field.fieldValues.length !== 0))) { - const importType = `${fieldType}`; - const basePath = clientFramework === VUE ? '@' : 'app'; - const modelPath = clientFramework === ANGULAR ? 'entities' : 'shared/model'; - const importPath = `${basePath}/${modelPath}/enumerations/${enumFileName}.model`; - uniqueEnums[fieldType] = field.fieldType; - typeImports.set(importType, importPath); - } - }); - return typeImports; -}; - -/** - * @private - * Generate a primary key, according to the type - * - * @param {any} primaryKey - primary key definition - * @param {number} index - the index of the primary key, currently it's possible to generate 2 values, index = 0 - first key (default), otherwise second key - * @param {boolean} [wrapped=true] - wrapped values for required types. - */ - -export const generateTestEntityId = (primaryKey, index = 0, wrapped = true) => { - primaryKey = getEntryIfTypeOrTypeAttribute(primaryKey); - let value; - if (primaryKey === TYPE_STRING) { - value = index === 0 ? 'ABC' : 'CBA'; - } else if (primaryKey === TYPE_UUID) { - value = index === 0 ? '9fec3727-3421-4967-b213-ba36557ca194' : '1361f429-3817-4123-8ee3-fdf8943310b2'; - } else { - value = index === 0 ? 123 : 456; - } - if (wrapped && [TYPE_UUID, TYPE_STRING].includes(primaryKey)) { - return `'${value}'`; - } - return value; -}; - -/** - * @private - * Generate a test entity, according to the type - * - * @param references - * @param {number} [index] - index of the primary key sample, pass undefined for a random key. - */ -export const generateTestEntity = (references, index = 'random') => { - const random = index === 'random'; - const entries = references - .map(reference => { - if (random && reference.field) { - const field = reference.field; - const { fieldWithContentType, contentTypeFieldName } = field; - const fakeData = field.generateFakeData('json-serializable'); - if (fieldWithContentType) { - return [ - [reference.name, fakeData], - [contentTypeFieldName, 'unknown'], - ]; - } - return [[reference.name, fakeData]]; - } - return [[reference.name, generateTestEntityId(reference.type, index, false)]]; - }) - .flat(); - return Object.fromEntries(entries); -}; - -/** - * Generate a test entity, according to the references - * - * @param references - * @param additionalFields - * @return {String} test sample - */ -export const generateTypescriptTestEntity = (references, additionalFields = {}) => { - const entries = references - .map(reference => { - if (reference.field) { - const field = reference.field; - const { fieldIsEnum, fieldTypeTimed, fieldTypeLocalDate, fieldWithContentType, fieldName, contentTypeFieldName } = field; - - const fakeData = field.generateFakeData('ts'); - if (fieldWithContentType) { - return [ - [fieldName, fakeData], - [contentTypeFieldName, "'unknown'"], - ]; - } - if (fieldIsEnum) { - return [[fieldName, fakeData]]; - } - if (fieldTypeTimed || fieldTypeLocalDate) { - return [[fieldName, `dayjs(${fakeData})`]]; - } - return [[fieldName, fakeData]]; - } - return [[reference.name, generateTestEntityId(reference.type, 'random', false)]]; - }) - .flat(); - return `{ - ${[...entries, ...Object.entries(additionalFields)].map(([key, value]) => `${key}: ${value}`).join(',\n ')} -}`; -}; -/** - * Generate a test entity for the PK references (when the PK is a composite key) - * - * @param {any} primaryKey - primary key definition. - * @param {number} [index] - index of the primary key sample, pass undefined for a random key. - */ -export const generateTestEntityPrimaryKey = (primaryKey, index) => { - return JSON.stringify( - generateTestEntity( - primaryKey.fields.map(f => f.reference), - index, - ), - ); -}; - -/** - * @private - * Get a parent folder path addition for entity - * @param {string} clientRootFolder - */ -export const getEntityParentPathAddition = clientRootFolder => { - if (!clientRootFolder) { - return ''; - } - const relative = path.relative(`/app/entities/${clientRootFolder}/`, '/app/entities/'); - if (relative.includes('app')) { - // Relative path outside angular base dir. - throw new Error(` - "clientRootFolder outside app base dir '${clientRootFolder}'" -`); - } - const entityFolderPathAddition = relative.replace(/[/|\\]?..[/|\\]entities/, '').replace('entities', '..'); - if (!entityFolderPathAddition) { - return ''; - } - return `${entityFolderPathAddition}/`; -}; diff --git a/generators/client/support/template-utils.spec.js b/generators/client/support/template-utils.spec.js new file mode 100644 index 000000000000..e3691bf6ce8a --- /dev/null +++ b/generators/client/support/template-utils.spec.js @@ -0,0 +1,118 @@ +import path from 'path'; +import { expect } from 'chai'; + +import { entityOptions } from '../../../jdl/jhipster/index.js'; + +import { generateEntityClientImports, generateTestEntityId, getEntityParentPathAddition } from './template-utils.js'; + +const { MapperTypes } = entityOptions; + +const NO_DTO = MapperTypes.NO; + +describe('generator - client - support - template-utils', () => { + describe('generateEntityClientImports', () => { + describe('with relationships from or to the User', () => { + const relationships = [ + { + otherEntityAngularName: 'User', + }, + { + otherEntityAngularName: 'AnEntity', + }, + ]; + describe('when called with 2 distinct relationships without dto option', () => { + it('return a Map with 2 imports', () => { + const imports = generateEntityClientImports(relationships, NO_DTO); + expect(imports).to.have.all.keys('IUser', 'IAnEntity'); + expect(imports.size).to.eql(relationships.length); + }); + }); + describe('when called with 2 identical relationships without dto option', () => { + const relationships = [ + { + otherEntityAngularName: 'User', + }, + { + otherEntityAngularName: 'User', + }, + ]; + it('return a Map with 1 import', () => { + const imports = generateEntityClientImports(relationships, NO_DTO); + expect(imports).to.have.key('IUser'); + expect(imports.size).to.eql(1); + }); + }); + }); + }); + + describe('generateTestEntityId', () => { + describe('when called with int', () => { + it('return 123', () => { + expect(generateTestEntityId('int')).to.equal(123); + }); + }); + describe('when called with String', () => { + it("return 'ABC'", () => { + expect(generateTestEntityId('String')).to.equal("'ABC'"); + }); + }); + describe('when called with UUID', () => { + it("return '9fec3727-3421-4967-b213-ba36557ca194'", () => { + expect(generateTestEntityId('UUID')).to.equal("'9fec3727-3421-4967-b213-ba36557ca194'"); + }); + }); + }); + + describe('getEntityParentPathAddition', () => { + describe('when passing /', () => { + it('returns an empty string', () => { + expect(getEntityParentPathAddition('/')).to.equal(''); + }); + }); + describe('when passing /foo/', () => { + it('returns ../', () => { + expect(getEntityParentPathAddition('/foo/')).to.equal('../'); + }); + }); + describe('when passing undefined', () => { + it('returns an empty string', () => { + expect(getEntityParentPathAddition()).to.equal(''); + }); + }); + describe('when passing empty', () => { + it('returns an empty string', () => { + expect(getEntityParentPathAddition('')).to.equal(''); + }); + }); + describe('when passing foo', () => { + it('returns ../', () => { + expect(getEntityParentPathAddition('foo')).to.equal('../'); + }); + }); + describe('when passing foo/bar', () => { + it('returns ../../', () => { + expect(getEntityParentPathAddition('foo/bar')).to.equal(`..${path.sep}../`); + }); + }); + describe('when passing ../foo', () => { + it('returns an empty string', () => { + expect(getEntityParentPathAddition('../foo')).to.equal(''); + }); + }); + describe('when passing ../foo/bar', () => { + it('returns ../', () => { + expect(getEntityParentPathAddition('../foo/bar')).to.equal('../'); + }); + }); + describe('when passing ../foo/bar/foo2', () => { + it('returns ../../', () => { + expect(getEntityParentPathAddition('../foo/bar/foo2')).to.equal(`..${path.sep}../`); + }); + }); + describe('when passing ../../foo', () => { + it('throw an error', () => { + expect(() => getEntityParentPathAddition('../../foo')).to.throw(); + }); + }); + }); +}); diff --git a/generators/client/support/template-utils.spec.mjs b/generators/client/support/template-utils.spec.mjs deleted file mode 100644 index 0a7602557de4..000000000000 --- a/generators/client/support/template-utils.spec.mjs +++ /dev/null @@ -1,118 +0,0 @@ -import path from 'path'; -import { expect } from 'chai'; - -import { entityOptions } from '../../../jdl/jhipster/index.mjs'; - -import { generateEntityClientImports, generateTestEntityId, getEntityParentPathAddition } from './template-utils.mjs'; - -const { MapperTypes } = entityOptions; - -const NO_DTO = MapperTypes.NO; - -describe('generator - client - support - template-utils', () => { - describe('generateEntityClientImports', () => { - describe('with relationships from or to the User', () => { - const relationships = [ - { - otherEntityAngularName: 'User', - }, - { - otherEntityAngularName: 'AnEntity', - }, - ]; - describe('when called with 2 distinct relationships without dto option', () => { - it('return a Map with 2 imports', () => { - const imports = generateEntityClientImports(relationships, NO_DTO); - expect(imports).to.have.all.keys('IUser', 'IAnEntity'); - expect(imports.size).to.eql(relationships.length); - }); - }); - describe('when called with 2 identical relationships without dto option', () => { - const relationships = [ - { - otherEntityAngularName: 'User', - }, - { - otherEntityAngularName: 'User', - }, - ]; - it('return a Map with 1 import', () => { - const imports = generateEntityClientImports(relationships, NO_DTO); - expect(imports).to.have.key('IUser'); - expect(imports.size).to.eql(1); - }); - }); - }); - }); - - describe('generateTestEntityId', () => { - describe('when called with int', () => { - it('return 123', () => { - expect(generateTestEntityId('int')).to.equal(123); - }); - }); - describe('when called with String', () => { - it("return 'ABC'", () => { - expect(generateTestEntityId('String')).to.equal("'ABC'"); - }); - }); - describe('when called with UUID', () => { - it("return '9fec3727-3421-4967-b213-ba36557ca194'", () => { - expect(generateTestEntityId('UUID')).to.equal("'9fec3727-3421-4967-b213-ba36557ca194'"); - }); - }); - }); - - describe('getEntityParentPathAddition', () => { - describe('when passing /', () => { - it('returns an empty string', () => { - expect(getEntityParentPathAddition('/')).to.equal(''); - }); - }); - describe('when passing /foo/', () => { - it('returns ../', () => { - expect(getEntityParentPathAddition('/foo/')).to.equal('../'); - }); - }); - describe('when passing undefined', () => { - it('returns an empty string', () => { - expect(getEntityParentPathAddition()).to.equal(''); - }); - }); - describe('when passing empty', () => { - it('returns an empty string', () => { - expect(getEntityParentPathAddition('')).to.equal(''); - }); - }); - describe('when passing foo', () => { - it('returns ../', () => { - expect(getEntityParentPathAddition('foo')).to.equal('../'); - }); - }); - describe('when passing foo/bar', () => { - it('returns ../../', () => { - expect(getEntityParentPathAddition('foo/bar')).to.equal(`..${path.sep}../`); - }); - }); - describe('when passing ../foo', () => { - it('returns an empty string', () => { - expect(getEntityParentPathAddition('../foo')).to.equal(''); - }); - }); - describe('when passing ../foo/bar', () => { - it('returns ../', () => { - expect(getEntityParentPathAddition('../foo/bar')).to.equal('../'); - }); - }); - describe('when passing ../foo/bar/foo2', () => { - it('returns ../../', () => { - expect(getEntityParentPathAddition('../foo/bar/foo2')).to.equal(`..${path.sep}../`); - }); - }); - describe('when passing ../../foo', () => { - it('throw an error', () => { - expect(() => getEntityParentPathAddition('../../foo')).to.throw(); - }); - }); - }); -}); diff --git a/generators/client/support/types-utils.js b/generators/client/support/types-utils.js new file mode 100644 index 000000000000..f43c28933ddd --- /dev/null +++ b/generators/client/support/types-utils.js @@ -0,0 +1,82 @@ +/** + * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.js'; +import { fieldIsEnum } from '../../base-application/support/index.js'; + +const { + BOOLEAN: TYPE_BOOLEAN, + ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, + INSTANT: TYPE_INSTANT, + LOCAL_DATE: TYPE_LOCAL_DATE, + INTEGER: TYPE_INTEGER, + LONG: TYPE_LONG, + BIG_DECIMAL: TYPE_BIG_DECIMAL, + FLOAT: TYPE_FLOAT, + DOUBLE: TYPE_DOUBLE, +} = fieldTypes.CommonDBTypes; + +/** + * return the input if it's a type, otherwise returns the type attribute of the input + * @param key + * @returns {*} + */ +export const getEntryIfTypeOrTypeAttribute = key => { + if (typeof key === 'object') { + return key.type; + } + return key; +}; +/** + * @private + * Find key type for Typescript + * + * @param {string | object} primaryKey - primary key definition + * @returns {string} primary key type in Typescript + */ +const getTypescriptKeyType = primaryKey => { + if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(getEntryIfTypeOrTypeAttribute(primaryKey))) { + return 'number'; + } + return 'string'; +}; + +/** + * @private + * Find type for Typescript + * + * @param {string} fieldType - field type + * @returns {string} field type in Typescript + */ +export const getTypescriptType = fieldType => { + if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(fieldType)) { + return 'number'; + } + if ([TYPE_LOCAL_DATE, TYPE_ZONED_DATE_TIME, TYPE_INSTANT].includes(fieldType)) { + return 'dayjs.Dayjs'; + } + if ([TYPE_BOOLEAN].includes(fieldType)) { + return 'boolean'; + } + if (fieldIsEnum(fieldType)) { + return fieldType; + } + return 'string'; +}; + +export default getTypescriptKeyType; diff --git a/generators/client/support/types-utils.mjs b/generators/client/support/types-utils.mjs deleted file mode 100644 index efbd573a7518..000000000000 --- a/generators/client/support/types-utils.mjs +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.mjs'; -import { fieldIsEnum } from '../../base-application/support/index.mjs'; - -const { - BOOLEAN: TYPE_BOOLEAN, - ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, - INSTANT: TYPE_INSTANT, - LOCAL_DATE: TYPE_LOCAL_DATE, - INTEGER: TYPE_INTEGER, - LONG: TYPE_LONG, - BIG_DECIMAL: TYPE_BIG_DECIMAL, - FLOAT: TYPE_FLOAT, - DOUBLE: TYPE_DOUBLE, -} = fieldTypes.CommonDBTypes; - -/** - * return the input if it's a type, otherwise returns the type attribute of the input - * @param key - * @returns {*} - */ -export const getEntryIfTypeOrTypeAttribute = key => { - if (typeof key === 'object') { - return key.type; - } - return key; -}; -/** - * @private - * Find key type for Typescript - * - * @param {string | object} primaryKey - primary key definition - * @returns {string} primary key type in Typescript - */ -const getTypescriptKeyType = primaryKey => { - if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(getEntryIfTypeOrTypeAttribute(primaryKey))) { - return 'number'; - } - return 'string'; -}; - -/** - * @private - * Find type for Typescript - * - * @param {string} fieldType - field type - * @returns {string} field type in Typescript - */ -export const getTypescriptType = fieldType => { - if ([TYPE_INTEGER, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_BIG_DECIMAL].includes(fieldType)) { - return 'number'; - } - if ([TYPE_LOCAL_DATE, TYPE_ZONED_DATE_TIME, TYPE_INSTANT].includes(fieldType)) { - return 'dayjs.Dayjs'; - } - if ([TYPE_BOOLEAN].includes(fieldType)) { - return 'boolean'; - } - if (fieldIsEnum(fieldType)) { - return fieldType; - } - return 'string'; -}; - -export default getTypescriptKeyType; diff --git a/generators/client/support/types-utils.spec.js b/generators/client/support/types-utils.spec.js new file mode 100644 index 000000000000..8512bd0e9fed --- /dev/null +++ b/generators/client/support/types-utils.spec.js @@ -0,0 +1,39 @@ +import { expect } from 'esmocha'; + +import { fieldTypes } from '../../../jdl/jhipster/index.js'; + +import { getTypescriptType } from './types-utils.js'; + +const { CommonDBTypes } = fieldTypes; + +describe('generator - client - support - type-utils', () => { + describe('getTypescriptType', () => { + describe('when called with sql DB name', () => { + it('return SQL', () => { + expect(Object.fromEntries(Object.values(CommonDBTypes).map(dbType => [dbType, getTypescriptType(dbType)]))).toMatchInlineSnapshot(` +{ + "AnyBlob": "string", + "BigDecimal": "number", + "Blob": "string", + "Boolean": "boolean", + "ByteBuffer": "string", + "Double": "number", + "Duration": "string", + "Enum": "Enum", + "Float": "number", + "ImageBlob": "string", + "Instant": "dayjs.Dayjs", + "Integer": "number", + "LocalDate": "dayjs.Dayjs", + "Long": "number", + "String": "string", + "TextBlob": "string", + "UUID": "string", + "ZonedDateTime": "dayjs.Dayjs", + "byte[]": "string", +} +`); + }); + }); + }); +}); diff --git a/generators/client/support/types-utils.spec.mjs b/generators/client/support/types-utils.spec.mjs deleted file mode 100644 index 4ca268521d2a..000000000000 --- a/generators/client/support/types-utils.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { expect } from 'esmocha'; - -import { fieldTypes } from '../../../jdl/jhipster/index.mjs'; - -import { getTypescriptType } from './types-utils.mjs'; - -const { CommonDBTypes } = fieldTypes; - -describe('generator - client - support - type-utils', () => { - describe('getTypescriptType', () => { - describe('when called with sql DB name', () => { - it('return SQL', () => { - expect(Object.fromEntries(Object.values(CommonDBTypes).map(dbType => [dbType, getTypescriptType(dbType)]))).toMatchInlineSnapshot(` -{ - "AnyBlob": "string", - "BigDecimal": "number", - "Blob": "string", - "Boolean": "boolean", - "ByteBuffer": "string", - "Double": "number", - "Duration": "string", - "Enum": "Enum", - "Float": "number", - "ImageBlob": "string", - "Instant": "dayjs.Dayjs", - "Integer": "number", - "LocalDate": "dayjs.Dayjs", - "Long": "number", - "String": "string", - "TextBlob": "string", - "UUID": "string", - "ZonedDateTime": "dayjs.Dayjs", - "byte[]": "string", -} -`); - }); - }); - }); -}); diff --git a/generators/client/support/update-languages.mts b/generators/client/support/update-languages.mts deleted file mode 100644 index db923d06929a..000000000000 --- a/generators/client/support/update-languages.mts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2013-2023 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 { CommonClientServerApplication } from '../../base-application/types.mjs'; -import BaseGenerator from '../../base/index.mjs'; - -export type UpdateClientLanguagesTaskParam = { application: CommonClientServerApplication & { enableTranslation: true }; control?: any }; - -/** - * Update DayJS Locales. - * - * @param application - * @param configurationFile - * @param commonjs - */ -// eslint-disable-next-line import/prefer-default-export -export function updateLanguagesInDayjsConfigurationTask( - this: BaseGenerator, - { application, control = {} }: UpdateClientLanguagesTaskParam, - { configurationFile, commonjs = false }: { configurationFile: string; commonjs?: boolean }, -): void { - const { languagesDefinition = [] } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - - const uniqueDayjsLocales = [...new Map(languagesDefinition.map(v => [v.dayjsLocale, v])).values()]; - const newContent = uniqueDayjsLocales.reduce( - (content, language) => `${content}import 'dayjs/${commonjs ? '' : 'esm/'}locale/${language.dayjsLocale}'\n`, - '// jhipster-needle-i18n-language-dayjs-imports - JHipster will import languages from dayjs here\n', - ); - - this.editFile(configurationFile, { ignoreNonExisting }, content => - content.replace(/\/\/ jhipster-needle-i18n-language-dayjs-imports[\s\S]+?(?=\/\/ DAYJS CONFIGURATION)/g, `${newContent}\n`), - ); -} diff --git a/generators/client/support/update-languages.ts b/generators/client/support/update-languages.ts new file mode 100644 index 000000000000..c391c8d6fb9b --- /dev/null +++ b/generators/client/support/update-languages.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2013-2023 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 { CommonClientServerApplication } from '../../base-application/types.js'; +import BaseGenerator from '../../base/index.js'; + +export type UpdateClientLanguagesTaskParam = { application: CommonClientServerApplication & { enableTranslation: true }; control?: any }; + +/** + * Update DayJS Locales. + * + * @param application + * @param configurationFile + * @param commonjs + */ +// eslint-disable-next-line import/prefer-default-export +export function updateLanguagesInDayjsConfigurationTask( + this: BaseGenerator, + { application, control = {} }: UpdateClientLanguagesTaskParam, + { configurationFile, commonjs = false }: { configurationFile: string; commonjs?: boolean }, +): void { + const { languagesDefinition = [] } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + + const uniqueDayjsLocales = [...new Map(languagesDefinition.map(v => [v.dayjsLocale, v])).values()]; + const newContent = uniqueDayjsLocales.reduce( + (content, language) => `${content}import 'dayjs/${commonjs ? '' : 'esm/'}locale/${language.dayjsLocale}'\n`, + '// jhipster-needle-i18n-language-dayjs-imports - JHipster will import languages from dayjs here\n', + ); + + this.editFile(configurationFile, { ignoreNonExisting }, content => + content.replace(/\/\/ jhipster-needle-i18n-language-dayjs-imports[\s\S]+?(?=\/\/ DAYJS CONFIGURATION)/g, `${newContent}\n`), + ); +} diff --git a/generators/client/types.d.mts b/generators/client/types.d.mts deleted file mode 100644 index 8b2fadfd777d..000000000000 --- a/generators/client/types.d.mts +++ /dev/null @@ -1,40 +0,0 @@ -import type { addIconImport, addItemToMenu, addRoute } from '../angular/support/needles.mts'; -import type { AngularApplication } from '../angular/types.mjs'; -import type { OptionWithDerivedProperties } from '../base-application/application-options.mjs'; -import type { CypressApplication } from '../cypress/types.mjs'; - -type ClientFrameworkType = ['no', 'angular', 'react', 'vue', 'svelte']; - -type ClientFrameworkApplication = OptionWithDerivedProperties<'clientFramework', ClientFrameworkType>; - -export type ClientApplication = ClientFrameworkApplication & - AngularApplication & - CypressApplication & { - withAdminUi: boolean; - webappLoginRegExp: string; - webappEnumerationsDir?: string; - }; - -export type ClientResources = { - /** - * resources added to root file. - */ - resource: string; - /** - * comment to add before resources content. - */ - comment?: string; -}; -export type ClientSourceType = { - /** - * Add external resources to root file(index.html). - */ - addExternalResourceToRoot?(resources: ClientResources): void; - addIconImport?(args: Parameters[0]): void; - addAdminRoute?(args: Omit[0], 'needle'>): void; - addItemToAdminMenu?(args: Omit[0], 'needle' | 'enableTranslation' | 'jhiPrefix'>): void; - /** - * Add webpack config. - */ - addWebpackConfig?(args: { config: string }); -}; diff --git a/generators/client/types.d.ts b/generators/client/types.d.ts new file mode 100644 index 000000000000..43d4d1ca5b74 --- /dev/null +++ b/generators/client/types.d.ts @@ -0,0 +1,40 @@ +import type { addIconImport, addItemToMenu, addRoute } from '../angular/support/needles.js'; +import type { AngularApplication } from '../angular/types.js'; +import type { OptionWithDerivedProperties } from '../base-application/application-options.js'; +import type { CypressApplication } from '../cypress/types.js'; + +type ClientFrameworkType = ['no', 'angular', 'react', 'vue', 'svelte']; + +type ClientFrameworkApplication = OptionWithDerivedProperties<'clientFramework', ClientFrameworkType>; + +export type ClientApplication = ClientFrameworkApplication & + AngularApplication & + CypressApplication & { + withAdminUi: boolean; + webappLoginRegExp: string; + webappEnumerationsDir?: string; + }; + +export type ClientResources = { + /** + * resources added to root file. + */ + resource: string; + /** + * comment to add before resources content. + */ + comment?: string; +}; +export type ClientSourceType = { + /** + * Add external resources to root file(index.html). + */ + addExternalResourceToRoot?(resources: ClientResources): void; + addIconImport?(args: Parameters[0]): void; + addAdminRoute?(args: Omit[0], 'needle'>): void; + addItemToAdminMenu?(args: Omit[0], 'needle' | 'enableTranslation' | 'jhiPrefix'>): void; + /** + * Add webpack config. + */ + addWebpackConfig?(args: { config: string }); +}; diff --git a/generators/common/__snapshots__/generator.spec.mjs.snap b/generators/common/__snapshots__/generator.spec.js.snap similarity index 100% rename from generators/common/__snapshots__/generator.spec.mjs.snap rename to generators/common/__snapshots__/generator.spec.js.snap diff --git a/generators/common/command.mts b/generators/common/command.mts deleted file mode 100644 index 6a01edae925c..000000000000 --- a/generators/common/command.mts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION_BASE } from '../generator-list.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - prettierTabWidth: { - description: 'Default tab width for prettier', - type: Number, - scope: 'storage', - }, - skipCommitHook: { - description: 'Skip adding husky commit hooks', - type: Boolean, - scope: 'storage', - }, - }, - import: [GENERATOR_BOOTSTRAP_APPLICATION_BASE], -}; - -export default command; diff --git a/generators/common/command.ts b/generators/common/command.ts new file mode 100644 index 000000000000..3c5ff3c3b464 --- /dev/null +++ b/generators/common/command.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION_BASE } from '../generator-list.js'; + +const command: JHipsterCommandDefinition = { + options: { + prettierTabWidth: { + description: 'Default tab width for prettier', + type: Number, + scope: 'storage', + }, + skipCommitHook: { + description: 'Skip adding husky commit hooks', + type: Boolean, + scope: 'storage', + }, + }, + import: [GENERATOR_BOOTSTRAP_APPLICATION_BASE], +}; + +export default command; diff --git a/generators/common/files.mjs b/generators/common/files.js similarity index 100% rename from generators/common/files.mjs rename to generators/common/files.js diff --git a/generators/common/generator.js b/generators/common/generator.js new file mode 100644 index 000000000000..a2d07220ba95 --- /dev/null +++ b/generators/common/generator.js @@ -0,0 +1,274 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import { isFileStateModified } from 'mem-fs-editor/state'; +import BaseApplicationGenerator from '../base-application/index.js'; + +import { writeFiles, prettierConfigFiles } from './files.js'; +import { + MAIN_DIR, + TEST_DIR, + SERVER_MAIN_RES_DIR, + JHIPSTER_DOCUMENTATION_URL, + JHIPSTER_DOCUMENTATION_ARCHIVE_PATH, +} from '../generator-constants.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_COMMON, GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_GIT } from '../generator-list.js'; +import command from './command.js'; +import { createPrettierTransform } from '../bootstrap/support/prettier-support.js'; +import { loadStoredAppOptions } from '../app/support/index.js'; + +const { REACT, ANGULAR } = clientFrameworkTypes; + +export default class CommonGenerator extends BaseApplicationGenerator { + command = command; + + async beforeQueue() { + loadStoredAppOptions.call(this); + + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_COMMON); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + await this.dependsOnJHipster(GENERATOR_GIT); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadOptions() { + this.parseJHipsterCommand(this.command); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return this.asPromptingTaskGroup({ + async prompting({ control }) { + if (control.existingProject && this.options.askAnswered !== true) return; + await this.prompt(this.prepareQuestions(this.command.configs)); + }, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.asPromptingTaskGroup(this.delegateTasksToBlueprint(() => this.prompting)); + } + + // Public API method used by the getter and also by Blueprints + get configuring() { + return { + async configureMonorepository() { + if (this.jhipsterConfig.monorepository) return; + + const git = this.createGit(); + if ((await git.checkIsRepo()) && !(await git.checkIsRepo('root'))) { + this.jhipsterConfig.monorepository = true; + } + }, + configureCommitHook() { + if (this.jhipsterConfig.monorepository) { + this.jhipsterConfig.skipCommitHook = true; + } + }, + }; + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get configuringEachEntity() { + return this.asConfiguringEachEntityTaskGroup({ + migrateEntity({ entityConfig, entityStorage }) { + for (const field of entityConfig.fields) { + if (field.javadoc) { + field.documentation = field.javadoc; + delete field.javadoc; + } + if (field.fieldTypeJavadoc) { + field.fieldTypeDocumentation = field.fieldTypeJavadoc; + delete field.fieldTypeJavadoc; + } + } + for (const relationship of entityConfig.relationships) { + if (relationship.javadoc) { + relationship.documentation = relationship.javadoc; + delete relationship.javadoc; + } + } + if (entityConfig.javadoc) { + entityConfig.documentation = entityConfig.javadoc; + delete entityConfig.javadoc; + } else { + entityStorage.save(); + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.configuringEachEntity); + } + + // Public API method used by the getter and also by Blueprints + get loading() { + return this.asLoadingTaskGroup({ + loadPackageJson({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster(GENERATOR_COMMON, 'resources', 'package.json'), + ); + }, + + loadConfig({ applicationDefaults }) { + applicationDefaults({ + prettierTabWidth: this.jhipsterConfig.prettierTabWidth ?? 2, + }); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + // Public API method used by the getter and also by Blueprints + get preparing() { + return this.asPreparingTaskGroup({ + setupConstants({ application }) { + // Make constants available in templates + application.MAIN_DIR = MAIN_DIR; + application.TEST_DIR = TEST_DIR; + application.SERVER_MAIN_RES_DIR = SERVER_MAIN_RES_DIR; + application.ANGULAR = ANGULAR; + application.REACT = REACT; + + // Make documentation URL available in templates + application.DOCUMENTATION_URL = JHIPSTER_DOCUMENTATION_URL; + application.DOCUMENTATION_ARCHIVE_PATH = JHIPSTER_DOCUMENTATION_ARCHIVE_PATH; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get default() { + return this.asDefaultTaskGroup({ + async formatSonarProperties() { + this.queueTransformStream( + { + name: 'prettifying sonar-project.properties', + filter: file => + isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && file.path.endsWith('sonar-project.properties'), + refresh: false, + }, + await createPrettierTransform.call(this, { extensions: 'properties', prettierProperties: true }), + ); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); + } + + // Public API method used by the getter and also by Blueprints + get writing() { + return { + cleanup({ application }) { + if (this.isJhipsterVersionLessThan('7.1.1')) { + if (!application.skipCommitHook) { + this.removeFile('.huskyrc'); + } + } + if (this.isJhipsterVersionLessThan('7.6.1')) { + if (application.skipClient) { + this.removeFile('npmw'); + this.removeFile('npmw.cmd'); + } + } + if (this.isJhipsterVersionLessThan('8.0.0-rc.2')) { + if (!application.skipCommitHook) { + this.removeFile('.lintstagedrc.js'); + } + } + }, + writePrettierConfig({ application }) { + return this.writeFiles({ + sections: prettierConfigFiles, + context: application, + }); + }, + ...writeFiles(), + }; + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addJHipsterDependencies({ application }) { + if (application.skipJhipsterDependencies) return; + + this.packageJson.merge({ + devDependencies: { + 'generator-jhipster': application.jhipsterVersion, + ...Object.fromEntries(application.blueprints.map(blueprint => [blueprint.name, blueprint.version])), + }, + }); + }, + addCommitHookDependencies({ application }) { + if (application.skipCommitHook) return; + this.packageJson.merge({ + scripts: { + prepare: 'husky install', + }, + devDependencies: { + husky: application.nodeDependencies.husky, + 'lint-staged': application.nodeDependencies['lint-staged'], + prettier: application.nodeDependencies.prettier, + 'prettier-plugin-packagejson': application.nodeDependencies['prettier-plugin-packagejson'], + }, + }); + if (application.backendTypeJavaAny) { + this.packageJson.merge({ + devDependencies: { + 'prettier-plugin-java': application.nodeDependencies['prettier-plugin-java'], + }, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/common/generator.mjs b/generators/common/generator.mjs deleted file mode 100644 index fc50882d35db..000000000000 --- a/generators/common/generator.mjs +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import { isFileStateModified } from 'mem-fs-editor/state'; -import BaseApplicationGenerator from '../base-application/index.mjs'; - -import { writeFiles, prettierConfigFiles } from './files.mjs'; -import { - MAIN_DIR, - TEST_DIR, - SERVER_MAIN_RES_DIR, - JHIPSTER_DOCUMENTATION_URL, - JHIPSTER_DOCUMENTATION_ARCHIVE_PATH, -} from '../generator-constants.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_COMMON, GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_GIT } from '../generator-list.mjs'; -import command from './command.mjs'; -import { createPrettierTransform } from '../bootstrap/support/prettier-support.mjs'; -import { loadStoredAppOptions } from '../app/support/index.mjs'; - -const { REACT, ANGULAR } = clientFrameworkTypes; - -export default class CommonGenerator extends BaseApplicationGenerator { - command = command; - - async beforeQueue() { - loadStoredAppOptions.call(this); - - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_COMMON); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - await this.dependsOnJHipster(GENERATOR_GIT); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadOptions() { - this.parseJHipsterCommand(this.command); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return this.asPromptingTaskGroup({ - async prompting({ control }) { - if (control.existingProject && this.options.askAnswered !== true) return; - await this.prompt(this.prepareQuestions(this.command.configs)); - }, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.asPromptingTaskGroup(this.delegateTasksToBlueprint(() => this.prompting)); - } - - // Public API method used by the getter and also by Blueprints - get configuring() { - return { - async configureMonorepository() { - if (this.jhipsterConfig.monorepository) return; - - const git = this.createGit(); - if ((await git.checkIsRepo()) && !(await git.checkIsRepo('root'))) { - this.jhipsterConfig.monorepository = true; - } - }, - configureCommitHook() { - if (this.jhipsterConfig.monorepository) { - this.jhipsterConfig.skipCommitHook = true; - } - }, - }; - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get configuringEachEntity() { - return this.asConfiguringEachEntityTaskGroup({ - migrateEntity({ entityConfig, entityStorage }) { - for (const field of entityConfig.fields) { - if (field.javadoc) { - field.documentation = field.javadoc; - delete field.javadoc; - } - if (field.fieldTypeJavadoc) { - field.fieldTypeDocumentation = field.fieldTypeJavadoc; - delete field.fieldTypeJavadoc; - } - } - for (const relationship of entityConfig.relationships) { - if (relationship.javadoc) { - relationship.documentation = relationship.javadoc; - delete relationship.javadoc; - } - } - if (entityConfig.javadoc) { - entityConfig.documentation = entityConfig.javadoc; - delete entityConfig.javadoc; - } else { - entityStorage.save(); - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.configuringEachEntity); - } - - // Public API method used by the getter and also by Blueprints - get loading() { - return this.asLoadingTaskGroup({ - loadPackageJson({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_COMMON, 'resources', 'package.json'), - ); - }, - - loadConfig({ applicationDefaults }) { - applicationDefaults({ - prettierTabWidth: this.jhipsterConfig.prettierTabWidth ?? 2, - }); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - // Public API method used by the getter and also by Blueprints - get preparing() { - return this.asPreparingTaskGroup({ - setupConstants({ application }) { - // Make constants available in templates - application.MAIN_DIR = MAIN_DIR; - application.TEST_DIR = TEST_DIR; - application.SERVER_MAIN_RES_DIR = SERVER_MAIN_RES_DIR; - application.ANGULAR = ANGULAR; - application.REACT = REACT; - - // Make documentation URL available in templates - application.DOCUMENTATION_URL = JHIPSTER_DOCUMENTATION_URL; - application.DOCUMENTATION_ARCHIVE_PATH = JHIPSTER_DOCUMENTATION_ARCHIVE_PATH; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get default() { - return this.asDefaultTaskGroup({ - async formatSonarProperties() { - this.queueTransformStream( - { - name: 'prettifying sonar-project.properties', - filter: file => - isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && file.path.endsWith('sonar-project.properties'), - refresh: false, - }, - await createPrettierTransform.call(this, { extensions: 'properties', prettierProperties: true }), - ); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); - } - - // Public API method used by the getter and also by Blueprints - get writing() { - return { - cleanup({ application }) { - if (this.isJhipsterVersionLessThan('7.1.1')) { - if (!application.skipCommitHook) { - this.removeFile('.huskyrc'); - } - } - if (this.isJhipsterVersionLessThan('7.6.1')) { - if (application.skipClient) { - this.removeFile('npmw'); - this.removeFile('npmw.cmd'); - } - } - if (this.isJhipsterVersionLessThan('8.0.0-rc.2')) { - if (!application.skipCommitHook) { - this.removeFile('.lintstagedrc.js'); - } - } - }, - writePrettierConfig({ application }) { - return this.writeFiles({ - sections: prettierConfigFiles, - context: application, - }); - }, - ...writeFiles(), - }; - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addJHipsterDependencies({ application }) { - if (application.skipJhipsterDependencies) return; - - this.packageJson.merge({ - devDependencies: { - 'generator-jhipster': application.jhipsterVersion, - ...Object.fromEntries(application.blueprints.map(blueprint => [blueprint.name, blueprint.version])), - }, - }); - }, - addCommitHookDependencies({ application }) { - if (application.skipCommitHook) return; - this.packageJson.merge({ - scripts: { - prepare: 'husky install', - }, - devDependencies: { - husky: application.nodeDependencies.husky, - 'lint-staged': application.nodeDependencies['lint-staged'], - prettier: application.nodeDependencies.prettier, - 'prettier-plugin-packagejson': application.nodeDependencies['prettier-plugin-packagejson'], - }, - }); - if (application.backendTypeJavaAny) { - this.packageJson.merge({ - devDependencies: { - 'prettier-plugin-java': application.nodeDependencies['prettier-plugin-java'], - }, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/common/generator.spec.js b/generators/common/generator.spec.js new file mode 100644 index 000000000000..acec7f6d6fea --- /dev/null +++ b/generators/common/generator.spec.js @@ -0,0 +1,80 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { defaultHelpers as helpers, basicHelpers, runResult, checkEnforcements } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { GENERATOR_COMMON } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.js'); + +const mockedGenerators = ['jhipster:git']; + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({ client: true }, GENERATOR_COMMON); + + describe('with', () => { + describe('default config', () => { + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig().withMockedGenerators(mockedGenerators); + }); + + it('should succeed', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + + it('should add generator-jhipster to package.json', () => { + runResult.assertFileContent('package.json', 'generator-jhipster'); + }); + }); + describe('Custom prettier', () => { + before(async () => { + await basicHelpers + .run(generatorFile) + .withJHipsterConfig({ + prettierTabWidth: 10, + }) + .withMockedGenerators(mockedGenerators); + }); + + it('writes custom .prettierrc', () => { + runResult.assertFileContent('.prettierrc', /tabWidth: 10/); + }); + + it('uses custom prettier formatting to js file', () => { + runResult.assertFileContent('.lintstagedrc.cjs', / {10}'{/); + }); + }); + }); +}); diff --git a/generators/common/generator.spec.mjs b/generators/common/generator.spec.mjs deleted file mode 100644 index d3c5eb164ba6..000000000000 --- a/generators/common/generator.spec.mjs +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { defaultHelpers as helpers, basicHelpers, runResult, checkEnforcements } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { GENERATOR_COMMON } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mjs'); - -const mockedGenerators = ['jhipster:git']; - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({ client: true }, GENERATOR_COMMON); - - describe('with', () => { - describe('default config', () => { - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig().withMockedGenerators(mockedGenerators); - }); - - it('should succeed', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - - it('should add generator-jhipster to package.json', () => { - runResult.assertFileContent('package.json', 'generator-jhipster'); - }); - }); - describe('Custom prettier', () => { - before(async () => { - await basicHelpers - .run(generatorFile) - .withJHipsterConfig({ - prettierTabWidth: 10, - }) - .withMockedGenerators(mockedGenerators); - }); - - it('writes custom .prettierrc', () => { - runResult.assertFileContent('.prettierrc', /tabWidth: 10/); - }); - - it('uses custom prettier formatting to js file', () => { - runResult.assertFileContent('.lintstagedrc.cjs', / {10}'{/); - }); - }); - }); -}); diff --git a/generators/common/index.mts b/generators/common/index.mts deleted file mode 100644 index d1d7b7c03b8c..000000000000 --- a/generators/common/index.mts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2013-2023 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 { BaseApplicationGeneratorDefinition } from '../base-application/tasks.mjs'; -import { type Entity } from '../base-application/index.mjs'; -import { ClientServerApplication } from './types.mjs'; - -export { default } from './generator.mjs'; -export { default as command } from './command.mjs'; -export { commonFiles as files } from './files.mjs'; - -// TODO move to ./generator.mts -type ApplicationDefinition = { - applicationType: ClientServerApplication; - entityType: Entity; - sourceType: Record any>; -}; - -// TODO move to ./generator.mts -export type GeneratorDefinition = - BaseApplicationGeneratorDefinition; diff --git a/generators/common/index.ts b/generators/common/index.ts new file mode 100644 index 000000000000..d539a3c5d3c8 --- /dev/null +++ b/generators/common/index.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2013-2023 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 { BaseApplicationGeneratorDefinition } from '../base-application/tasks.js'; +import { type Entity } from '../base-application/index.js'; +import { ClientServerApplication } from './types.js'; + +export { default } from './generator.js'; +export { default as command } from './command.js'; +export { commonFiles as files } from './files.js'; + +// TODO move to ./generator.mts +type ApplicationDefinition = { + applicationType: ClientServerApplication; + entityType: Entity; + sourceType: Record any>; +}; + +// TODO move to ./generator.mts +export type GeneratorDefinition = + BaseApplicationGeneratorDefinition; diff --git a/generators/common/types.d.mts b/generators/common/types.d.mts deleted file mode 100644 index d881fd0f54a6..000000000000 --- a/generators/common/types.d.mts +++ /dev/null @@ -1,4 +0,0 @@ -import type { ClientApplication } from '../client/types.mjs'; -import type { SpringBootApplication } from '../server/types.mjs'; - -export type ClientServerApplication = ClientApplication & SpringBootApplication; diff --git a/generators/common/types.d.ts b/generators/common/types.d.ts new file mode 100644 index 000000000000..048492838e27 --- /dev/null +++ b/generators/common/types.d.ts @@ -0,0 +1,4 @@ +import type { ClientApplication } from '../client/types.js'; +import type { SpringBootApplication } from '../server/types.js'; + +export type ClientServerApplication = ClientApplication & SpringBootApplication; diff --git a/generators/cucumber/__snapshots__/generator.spec.mts.snap b/generators/cucumber/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/cucumber/__snapshots__/generator.spec.mts.snap rename to generators/cucumber/__snapshots__/generator.spec.ts.snap diff --git a/generators/cucumber/cleanup.mts b/generators/cucumber/cleanup.mts deleted file mode 100644 index 016b84383f2b..000000000000 --- a/generators/cucumber/cleanup.mts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupTask(this: Generator, { application }: any) { - if (this.isJhipsterVersionLessThan('7.4.2')) { - this.removeFile(`${application.javaPackageTestDir}cucumber.properties`); - this.removeFile(`${application.srcTestJava}features/gitkeep`); - this.removeFile(`${application.srcTestJava}features/user/user.feature`); - } -} diff --git a/generators/cucumber/cleanup.ts b/generators/cucumber/cleanup.ts new file mode 100644 index 000000000000..df4129e07c93 --- /dev/null +++ b/generators/cucumber/cleanup.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupTask(this: Generator, { application }: any) { + if (this.isJhipsterVersionLessThan('7.4.2')) { + this.removeFile(`${application.javaPackageTestDir}cucumber.properties`); + this.removeFile(`${application.srcTestJava}features/gitkeep`); + this.removeFile(`${application.srcTestJava}features/user/user.feature`); + } +} diff --git a/generators/cucumber/files.mts b/generators/cucumber/files.mts deleted file mode 100644 index e3ae5cea093f..000000000000 --- a/generators/cucumber/files.mts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { moveToJavaPackageTestDir } from '../server/support/index.mjs'; -import { SERVER_TEST_SRC_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.mjs'; -import { WriteFileSection } from '../base/api.mjs'; -import { CommonClientServerApplication } from '../base-application/types.mjs'; - -const cucumberFiles: WriteFileSection = { - cucumberFiles: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - // Create Cucumber test files - 'cucumber/CucumberIT.java', - 'cucumber/stepdefs/StepDefs.java', - 'cucumber/CucumberTestContextConfiguration.java', - ], - }, - { - path: `${SERVER_TEST_RES_DIR}_package_/`, - renameTo: (data, filename) => `${data.srcTestResources}${data.packageFolder}${filename}`, - templates: ['cucumber/gitkeep'], - }, - { - condition: generator => generator.generateUserManagement && !generator.databaseTypeMongodb && !generator.databaseTypeCassandra, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['cucumber/stepdefs/UserStepDefs.java'], - }, - { - condition: generator => generator.generateUserManagement && !generator.databaseTypeMongodb && !generator.databaseTypeCassandra, - path: `${SERVER_TEST_RES_DIR}_package_/`, - renameTo: (data, filename) => `${data.srcTestResources}${data.packageFolder}${filename}`, - templates: ['cucumber/user.feature'], - }, - ], -}; - -export default async function writeTask(this: Generator, { application }) { - await this.writeFiles({ - sections: cucumberFiles, - context: application, - }); -} diff --git a/generators/cucumber/files.ts b/generators/cucumber/files.ts new file mode 100644 index 000000000000..43c9a3facccc --- /dev/null +++ b/generators/cucumber/files.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { moveToJavaPackageTestDir } from '../server/support/index.js'; +import { SERVER_TEST_SRC_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.js'; +import { WriteFileSection } from '../base/api.js'; +import { CommonClientServerApplication } from '../base-application/types.js'; + +const cucumberFiles: WriteFileSection = { + cucumberFiles: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + // Create Cucumber test files + 'cucumber/CucumberIT.java', + 'cucumber/stepdefs/StepDefs.java', + 'cucumber/CucumberTestContextConfiguration.java', + ], + }, + { + path: `${SERVER_TEST_RES_DIR}_package_/`, + renameTo: (data, filename) => `${data.srcTestResources}${data.packageFolder}${filename}`, + templates: ['cucumber/gitkeep'], + }, + { + condition: generator => generator.generateUserManagement && !generator.databaseTypeMongodb && !generator.databaseTypeCassandra, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['cucumber/stepdefs/UserStepDefs.java'], + }, + { + condition: generator => generator.generateUserManagement && !generator.databaseTypeMongodb && !generator.databaseTypeCassandra, + path: `${SERVER_TEST_RES_DIR}_package_/`, + renameTo: (data, filename) => `${data.srcTestResources}${data.packageFolder}${filename}`, + templates: ['cucumber/user.feature'], + }, + ], +}; + +export default async function writeTask(this: Generator, { application }) { + await this.writeFiles({ + sections: cucumberFiles, + context: application, + }); +} diff --git a/generators/cucumber/generator.mts b/generators/cucumber/generator.mts deleted file mode 100644 index c5a87b6612ff..000000000000 --- a/generators/cucumber/generator.mts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_CUCUMBER, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; - -export default class CucumberGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_CUCUMBER); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - if (application.buildToolMaven) { - source.addMavenDefinition?.({ - dependencies: [ - { groupId: 'io.cucumber', artifactId: 'cucumber-junit-platform-engine', scope: 'test' }, - { groupId: 'io.cucumber', artifactId: 'cucumber-java', scope: 'test' }, - { groupId: 'io.cucumber', artifactId: 'cucumber-spring', scope: 'test' }, - { groupId: 'org.junit.platform', artifactId: 'junit-platform-console', scope: 'test' }, - { groupId: 'org.testng', artifactId: 'testng', scope: 'test' }, - ], - plugins: [{ groupId: 'org.apache.maven.plugins', artifactId: 'maven-antrun-plugin' }], - pluginManagement: [ - { - groupId: 'org.apache.maven.plugins', - artifactId: 'maven-antrun-plugin', - additionalContent: ` - - - - prepare cucumber feature files - integration-test - - run - - - - - - ${ - application.reactive - ? ` - ` - : '' - } - - - - - - - - - -`, - }, - ], - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/cucumber/generator.spec.mts b/generators/cucumber/generator.spec.mts deleted file mode 100644 index b1a5630c793e..000000000000 --- a/generators/cucumber/generator.spec.mts +++ /dev/null @@ -1,35 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { defaultHelpers as helpers, result } from '../../test/support/index.mjs'; - -import { GENERATOR_CUCUMBER } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('with default config', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_CUCUMBER).withJHipsterConfig({ testFrameworks: ['cucumber'] }); - }); - - it('should match files snapshot', () => { - expect(result.getSnapshot()).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/cucumber/generator.spec.ts b/generators/cucumber/generator.spec.ts new file mode 100644 index 000000000000..2970a01c2b77 --- /dev/null +++ b/generators/cucumber/generator.spec.ts @@ -0,0 +1,35 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { defaultHelpers as helpers, result } from '../../test/support/index.js'; + +import { GENERATOR_CUCUMBER } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('with default config', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_CUCUMBER).withJHipsterConfig({ testFrameworks: ['cucumber'] }); + }); + + it('should match files snapshot', () => { + expect(result.getSnapshot()).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/cucumber/generator.ts b/generators/cucumber/generator.ts new file mode 100644 index 000000000000..d7fc97f255f4 --- /dev/null +++ b/generators/cucumber/generator.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_CUCUMBER, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; + +export default class CucumberGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_CUCUMBER); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + if (application.buildToolMaven) { + source.addMavenDefinition?.({ + dependencies: [ + { groupId: 'io.cucumber', artifactId: 'cucumber-junit-platform-engine', scope: 'test' }, + { groupId: 'io.cucumber', artifactId: 'cucumber-java', scope: 'test' }, + { groupId: 'io.cucumber', artifactId: 'cucumber-spring', scope: 'test' }, + { groupId: 'org.junit.platform', artifactId: 'junit-platform-console', scope: 'test' }, + { groupId: 'org.testng', artifactId: 'testng', scope: 'test' }, + ], + plugins: [{ groupId: 'org.apache.maven.plugins', artifactId: 'maven-antrun-plugin' }], + pluginManagement: [ + { + groupId: 'org.apache.maven.plugins', + artifactId: 'maven-antrun-plugin', + additionalContent: ` + + + + prepare cucumber feature files + integration-test + + run + + + + + + ${ + application.reactive + ? ` + ` + : '' + } + + + + + + + + + +`, + }, + ], + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/cucumber/index.mts b/generators/cucumber/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/cucumber/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/cucumber/index.ts b/generators/cucumber/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/cucumber/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/cypress/__snapshots__/generator.spec.mts.snap b/generators/cypress/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/cypress/__snapshots__/generator.spec.mts.snap rename to generators/cypress/__snapshots__/generator.spec.ts.snap diff --git a/generators/cypress/command.mts b/generators/cypress/command.mts deleted file mode 100644 index f530c52361f1..000000000000 --- a/generators/cypress/command.mts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - cypressCoverage: { - description: 'Enable Cypress code coverage report generation', - type: Boolean, - scope: 'storage', - }, - cypressAudit: { - description: 'Enable cypress-audit/lighthouse report generation', - type: Boolean, - scope: 'storage', - }, - }, -}; - -export default command; diff --git a/generators/cypress/command.ts b/generators/cypress/command.ts new file mode 100644 index 000000000000..6f547c8e342e --- /dev/null +++ b/generators/cypress/command.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + cypressCoverage: { + description: 'Enable Cypress code coverage report generation', + type: Boolean, + scope: 'storage', + }, + cypressAudit: { + description: 'Enable cypress-audit/lighthouse report generation', + type: Boolean, + scope: 'storage', + }, + }, +}; + +export default command; diff --git a/generators/cypress/files.mts b/generators/cypress/files.mts deleted file mode 100644 index 4eb201cf37dc..000000000000 --- a/generators/cypress/files.mts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright 2013-2023 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 { CLIENT_TEST_SRC_DIR } from '../generator-constants.mjs'; - -import type { WriteFileSection } from '../base/api.mjs'; -import type CypressGenerator from './generator.mjs'; -import type { CypressApplication } from './types.mjs'; -import { type Entity } from '../base-application/index.mjs'; -import type { CommonClientServerApplication } from '../base-application/types.mjs'; -import { clientRootTemplatesBlock } from '../client/support/index.mjs'; - -const CYPRESS_TEMPLATE_SOURCE_DIR = `${CLIENT_TEST_SRC_DIR}cypress/`; - -export const cypressFiles: WriteFileSection = { - common: [ - { - templates: ['README.md.jhi.cypress'], - }, - clientRootTemplatesBlock({ - templates: ['cypress.config.ts'], - }), - ], - clientTestFw: [ - { - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: [ - '.eslintrc.json', - 'fixtures/integration-test.png', - 'plugins/index.ts', - 'e2e/administration/administration.cy.ts', - 'support/commands.ts', - 'support/navbar.ts', - 'support/index.ts', - 'support/entity.ts', - 'support/management.ts', - 'tsconfig.json', - ], - }, - { - condition: generator => !generator.authenticationTypeOauth2, - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: ['e2e/account/login-page.cy.ts'], - }, - { - condition: generator => Boolean(generator.generateUserManagement), - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: [ - 'e2e/account/register-page.cy.ts', - 'e2e/account/settings-page.cy.ts', - 'e2e/account/password-page.cy.ts', - 'e2e/account/reset-password-page.cy.ts', - 'support/account.ts', - ], - }, - { - condition: generator => generator.authenticationTypeOauth2, - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: ['support/oauth2.ts'], - }, - ], - audit: [ - { - condition: generator => generator.cypressAudit, - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: ['e2e/lighthouse.audits.ts'], - }, - clientRootTemplatesBlock({ - condition: generator => generator.cypressAudit, - templates: ['cypress-audits.config.ts'], - }), - ], - coverage: [ - { - condition: generator => generator.cypressCoverage, - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, - templates: ['plugins/global.d.ts'], - }, - ], -}; - -export const cypressEntityFiles: WriteFileSection = { - testsCypress: [ - { - condition: ctx => !ctx.builtIn && !ctx.embedded, - path: CYPRESS_TEMPLATE_SOURCE_DIR, - renameTo: ctx => `${ctx.cypressDir}e2e/entity/${ctx.entityFileName}.cy.ts`, - templates: ['e2e/entity/_entity_.cy.ts'], - }, - ], -}; diff --git a/generators/cypress/files.ts b/generators/cypress/files.ts new file mode 100644 index 000000000000..59cb20b3c6bb --- /dev/null +++ b/generators/cypress/files.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2013-2023 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 { CLIENT_TEST_SRC_DIR } from '../generator-constants.js'; + +import type { WriteFileSection } from '../base/api.js'; +import type CypressGenerator from './generator.js'; +import type { CypressApplication } from './types.js'; +import { type Entity } from '../base-application/index.js'; +import type { CommonClientServerApplication } from '../base-application/types.js'; +import { clientRootTemplatesBlock } from '../client/support/index.js'; + +const CYPRESS_TEMPLATE_SOURCE_DIR = `${CLIENT_TEST_SRC_DIR}cypress/`; + +export const cypressFiles: WriteFileSection = { + common: [ + { + templates: ['README.md.jhi.cypress'], + }, + clientRootTemplatesBlock({ + templates: ['cypress.config.ts'], + }), + ], + clientTestFw: [ + { + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: [ + '.eslintrc.json', + 'fixtures/integration-test.png', + 'plugins/index.ts', + 'e2e/administration/administration.cy.ts', + 'support/commands.ts', + 'support/navbar.ts', + 'support/index.ts', + 'support/entity.ts', + 'support/management.ts', + 'tsconfig.json', + ], + }, + { + condition: generator => !generator.authenticationTypeOauth2, + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: ['e2e/account/login-page.cy.ts'], + }, + { + condition: generator => Boolean(generator.generateUserManagement), + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: [ + 'e2e/account/register-page.cy.ts', + 'e2e/account/settings-page.cy.ts', + 'e2e/account/password-page.cy.ts', + 'e2e/account/reset-password-page.cy.ts', + 'support/account.ts', + ], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: ['support/oauth2.ts'], + }, + ], + audit: [ + { + condition: generator => generator.cypressAudit, + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: ['e2e/lighthouse.audits.ts'], + }, + clientRootTemplatesBlock({ + condition: generator => generator.cypressAudit, + templates: ['cypress-audits.config.ts'], + }), + ], + coverage: [ + { + condition: generator => generator.cypressCoverage, + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: (ctx, file) => `${ctx.cypressDir}${file}`, + templates: ['plugins/global.d.ts'], + }, + ], +}; + +export const cypressEntityFiles: WriteFileSection = { + testsCypress: [ + { + condition: ctx => !ctx.builtIn && !ctx.embedded, + path: CYPRESS_TEMPLATE_SOURCE_DIR, + renameTo: ctx => `${ctx.cypressDir}e2e/entity/${ctx.entityFileName}.cy.ts`, + templates: ['e2e/entity/_entity_.cy.ts'], + }, + ], +}; diff --git a/generators/cypress/generator.mts b/generators/cypress/generator.mts deleted file mode 100644 index 80320ffc1b1a..000000000000 --- a/generators/cypress/generator.mts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright 2013-2023 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 { stringHashCode, createFaker } from '../base/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { cypressFiles, cypressEntityFiles } from './files.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { GENERATOR_CYPRESS, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; - -import { generateTestEntity as entityWithFakeValues } from '../client/support/index.mjs'; - -const { ANGULAR } = clientFrameworkTypes; - -export default class CypressGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_CYPRESS); - } - - if (!this.delegateToBlueprint) { - // TODO depend on GENERATOR_BOOTSTRAP_APPLICATION_CLIENT. - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get prompting() { - return this.asPromptingTaskGroup({ - async askForCypressOptions({ control }) { - if (control.existingProject && !this.options.askAnswered) return; - await (this.prompt as any)( - [ - { - when: this.jhipsterConfig?.clientFramework === ANGULAR, - type: 'confirm', - name: 'cypressCoverage', - message: 'Would you like to generate code coverage for Cypress tests? [Experimental]', - }, - { - type: 'confirm', - name: 'cypressAudit', - message: 'Would you like to audit Cypress tests?', - }, - ], - this.config, - ); - }, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get loading() { - return this.asLoadingTaskGroup({ - prepareForTemplates({ application }) { - const { cypressAudit = true, cypressCoverage = false } = this.jhipsterConfig as any; - application.cypressAudit = cypressAudit; - application.cypressCoverage = cypressCoverage; - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application }) { - application.cypressDir = application.cypressDir ?? application.clientTestDir ? `${application.clientTestDir}cypress/` : 'cypress'; - application.cypressTemporaryDir = - application.cypressTemporaryDir ?? application.temporaryDir ? `${application.temporaryDir}cypress/` : '.cypress/'; - application.cypressBootstrapEntities = application.cypressBootstrapEntities ?? true; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get preparingEachEntity() { - return this.asPreparingEachEntityTaskGroup({ - prepareForTemplates({ entity }) { - this._.defaults(entity, { workaroundEntityCannotBeEmpty: false, workaroundInstantReactiveMariaDB: false }); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntity); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanup({ application: { authenticationTypeOauth2, generateUserManagement, cypressDir } }) { - if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { - this.removeFile(`${cypressDir}support/keycloak-oauth2.ts`); - this.removeFile(`${cypressDir}fixtures/users/user.json`); - } - if (this.isJhipsterVersionLessThan('7.8.2')) { - this.removeFile('cypress.json'); - this.removeFile('cypress-audits.json'); - - this.removeFile(`${cypressDir}integration/administration/administration.spec.ts`); - this.removeFile(`${cypressDir}integration/lighthouse.audits.ts`); - if (!authenticationTypeOauth2) { - this.removeFile(`${cypressDir}integration/account/login-page.spec.ts`); - } - if (generateUserManagement) { - this.removeFile(`${cypressDir}integration/account/register-page.spec.ts`); - this.removeFile(`${cypressDir}integration/account/settings-page.spec.ts`); - this.removeFile(`${cypressDir}integration/account/password-page.spec.ts`); - this.removeFile(`${cypressDir}integration/account/reset-password-page.spec.ts`); - } - } - }, - async writeFiles({ application }) { - const faker = await createFaker(); - faker.seed(stringHashCode(application.baseName)); - const context = { ...application, faker } as any; - return this.writeFiles({ - sections: cypressFiles, - context, - }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - cleanupCypressEntityFiles({ application: { cypressDir }, entities }) { - for (const entity of entities) { - if (this.isJhipsterVersionLessThan('7.8.2')) { - this.removeFile(`${cypressDir}integration/entity/${entity.entityFileName}.spec.ts`); - } - } - }, - - async writeCypressEntityFiles({ application, entities }) { - for (const entity of entities) { - const context = { ...application, ...entity } as any; - await this.writeFiles({ - sections: cypressEntityFiles, - context, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - loadPackageJson({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster('client', 'resources', 'package.json'), - ); - }, - - configure({ application }) { - const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); - clientPackageJson.merge({ - devDependencies: { - 'eslint-plugin-cypress': application.nodeDependencies['eslint-plugin-cypress'], - }, - scripts: { - e2e: 'npm run e2e:cypress:headed --', - 'e2e:headless': 'npm run e2e:cypress --', - 'e2e:cypress:headed': 'npm run e2e:cypress -- --headed', - 'e2e:cypress': 'cypress run --e2e --browser chrome', - 'e2e:cypress:record': 'npm run e2e:cypress -- --record', - cypress: 'cypress open --e2e', - }, - }); - }, - - configureAudits({ application }) { - if (!application.cypressAudit) return; - const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); - clientPackageJson.merge({ - devDependencies: { - lighthouse: application.nodeDependencies.lighthouse, - 'cypress-audit': application.nodeDependencies['cypress-audit'], - }, - scripts: { - 'cypress:audits': 'cypress open --e2e --config-file cypress-audits.config.js', - 'e2e:cypress:audits:headless': 'npm run e2e:cypress -- --config-file cypress-audits.config.js', - 'e2e:cypress:audits': - // eslint-disable-next-line no-template-curly-in-string - 'cypress run --e2e --browser chrome --config-file cypress-audits.config.js', - }, - }); - }, - configureCoverage({ application, source }) { - const { cypressCoverage, clientFrameworkAngular, dasherizedBaseName } = application; - if (!cypressCoverage) return; - const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); - clientPackageJson.merge({ - devDependencies: { - '@cypress/code-coverage': application.nodeDependencies['@cypress/code-coverage'], - 'babel-loader': application.nodeDependencies['babel-loader'], - 'babel-plugin-istanbul': application.nodeDependencies['babel-plugin-istanbul'], - nyc: application.nodeDependencies.nyc, - }, - scripts: { - 'clean-coverage': 'rimraf .nyc_output coverage', - 'pree2e:cypress:coverage': 'npm run clean-coverage && npm run ci:server:await', - 'e2e:cypress:coverage': 'npm run e2e:cypress:headed', - 'poste2e:cypress:coverage': 'nyc report', - 'prewebapp:instrumenter': 'npm run clean-www && npm run clean-coverage', - 'webapp:instrumenter': 'ng build --configuration instrumenter', - }, - }); - if (clientFrameworkAngular) { - // Add 'ng build --configuration instrumenter' support - this.createStorage('angular.json').setPath(`projects.${dasherizedBaseName}.architect.build.configurations.instrumenter`, {}); - source.addWebpackConfig?.({ - config: `targetOptions.configuration === 'instrumenter' - ? { - module: { - rules: [ - { - test: /\\.(js|ts)$/, - use: [ - { - loader: 'babel-loader', - options: { - plugins: ['istanbul'], - }, - } - ], - enforce: 'post', - include: path.resolve(__dirname, '../${CLIENT_MAIN_SRC_DIR}'), - exclude: [/\\.(e2e|spec)\\.ts$/, /node_modules/, /(ngfactory|ngstyle)\\.js/], - }, - ], - }, - } - : {}`, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - generateTestEntity(references) { - return entityWithFakeValues(references); - } -} diff --git a/generators/cypress/generator.spec.mts b/generators/cypress/generator.spec.mts deleted file mode 100644 index ad2ac6a1237e..000000000000 --- a/generators/cypress/generator.spec.mts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright 2013-2023 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 path, { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; -import { clientFrameworkTypes, testFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { - fromMatrix, - extendMatrix, - AuthenticationTypeMatrix, - checkEnforcements, - defaultHelpers as helpers, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; -import { GENERATOR_CYPRESS } from '../generator-list.mjs'; - -const { CYPRESS } = testFrameworkTypes; -const { ANGULAR, REACT, VUE } = clientFrameworkTypes; -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -const generatorPath = path.join(__dirname, 'index.mts'); - -const e2eMatrix = extendMatrix( - fromMatrix({ - ...AuthenticationTypeMatrix, - cypressAudit: [false, true], - }), - { - clientFramework: [ANGULAR, REACT, VUE], - withAdminUi: [false, true], - cypressCoverage: [false, true], - clientRootDir: [undefined, { value: 'clientRoot/' }, { value: '' }], - }, -); - -const e2eSamples = Object.fromEntries( - Object.entries(e2eMatrix).map(([name, sample]) => [ - name, - { - ...sample, - testFrameworks: [CYPRESS], - }, - ]), -); -const entities = [ - { - name: 'EntityA', - changelogDate: '20220129025419', - }, -]; - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({ client: true }, GENERATOR_CYPRESS); - - it('samples matrix should match snapshot', () => { - expect(e2eSamples).toMatchSnapshot(); - }); - - Object.entries(e2eSamples).forEach(([name, sampleConfig]) => { - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers.run(generatorPath).withJHipsterConfig(sampleConfig, entities); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - it('contains cypress testFramework', () => { - runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { testFrameworks: [CYPRESS] } }); - }); - - describe('withAdminUi', () => { - const { applicationType, withAdminUi, clientRootDir = '' } = sampleConfig; - const generateAdminUi = applicationType !== 'microservice' && withAdminUi; - - if (applicationType !== 'microservice') { - const adminUiRoutingTitle = generateAdminUi ? 'should generate admin routing' : 'should not generate admin routing'; - it(adminUiRoutingTitle, () => { - const assertion = (...args) => - generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args); - - assertion( - `${clientRootDir}src/test/javascript/cypress/e2e/administration/administration.cy.ts`, - ' metricsPageHeadingSelector,\n' + - ' healthPageHeadingSelector,\n' + - ' logsPageHeadingSelector,\n' + - ' configurationPageHeadingSelector,', - ); - - assertion( - `${clientRootDir}src/test/javascript/cypress/e2e/administration/administration.cy.ts`, - " describe('/metrics', () => {\n" + - " it('should load the page', () => {\n" + - " cy.clickOnAdminMenuItem('metrics');\n" + - " cy.get(metricsPageHeadingSelector).should('be.visible');\n" + - ' });\n' + - ' });\n' + - '\n' + - " describe('/health', () => {\n" + - " it('should load the page', () => {\n" + - " cy.clickOnAdminMenuItem('health');\n" + - " cy.get(healthPageHeadingSelector).should('be.visible');\n" + - ' });\n' + - ' });\n' + - '\n' + - " describe('/logs', () => {\n" + - " it('should load the page', () => {\n" + - " cy.clickOnAdminMenuItem('logs');\n" + - " cy.get(logsPageHeadingSelector).should('be.visible');\n" + - ' });\n' + - ' });\n' + - '\n' + - " describe('/configuration', () => {\n" + - " it('should load the page', () => {\n" + - " cy.clickOnAdminMenuItem('configuration');\n" + - " cy.get(configurationPageHeadingSelector).should('be.visible');\n" + - ' });\n' + - ' });', - ); - - assertion( - `${clientRootDir}src/test/javascript/cypress/support/commands.ts`, - 'export const metricsPageHeadingSelector = \'[data-cy="metricsPageHeading"]\';\n' + - 'export const healthPageHeadingSelector = \'[data-cy="healthPageHeading"]\';\n' + - 'export const logsPageHeadingSelector = \'[data-cy="logsPageHeading"]\';\n' + - 'export const configurationPageHeadingSelector = \'[data-cy="configurationPageHeading"]\';', - ); - }); - } - }); - }); - }); -}); diff --git a/generators/cypress/generator.spec.ts b/generators/cypress/generator.spec.ts new file mode 100644 index 000000000000..05a7b254c504 --- /dev/null +++ b/generators/cypress/generator.spec.ts @@ -0,0 +1,166 @@ +/** + * Copyright 2013-2023 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 path, { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; +import { clientFrameworkTypes, testFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { + fromMatrix, + extendMatrix, + AuthenticationTypeMatrix, + checkEnforcements, + defaultHelpers as helpers, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; +import { GENERATOR_CYPRESS } from '../generator-list.js'; + +const { CYPRESS } = testFrameworkTypes; +const { ANGULAR, REACT, VUE } = clientFrameworkTypes; +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +const generatorPath = path.join(__dirname, 'index.ts'); + +const e2eMatrix = extendMatrix( + fromMatrix({ + ...AuthenticationTypeMatrix, + cypressAudit: [false, true], + }), + { + clientFramework: [ANGULAR, REACT, VUE], + withAdminUi: [false, true], + cypressCoverage: [false, true], + clientRootDir: [undefined, { value: 'clientRoot/' }, { value: '' }], + }, +); + +const e2eSamples = Object.fromEntries( + Object.entries(e2eMatrix).map(([name, sample]) => [ + name, + { + ...sample, + testFrameworks: [CYPRESS], + }, + ]), +); +const entities = [ + { + name: 'EntityA', + changelogDate: '20220129025419', + }, +]; + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({ client: true }, GENERATOR_CYPRESS); + + it('samples matrix should match snapshot', () => { + expect(e2eSamples).toMatchSnapshot(); + }); + + Object.entries(e2eSamples).forEach(([name, sampleConfig]) => { + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers.run(generatorPath).withJHipsterConfig(sampleConfig, entities); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + it('contains cypress testFramework', () => { + runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { testFrameworks: [CYPRESS] } }); + }); + + describe('withAdminUi', () => { + const { applicationType, withAdminUi, clientRootDir = '' } = sampleConfig; + const generateAdminUi = applicationType !== 'microservice' && withAdminUi; + + if (applicationType !== 'microservice') { + const adminUiRoutingTitle = generateAdminUi ? 'should generate admin routing' : 'should not generate admin routing'; + it(adminUiRoutingTitle, () => { + const assertion = (...args) => + generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args); + + assertion( + `${clientRootDir}src/test/javascript/cypress/e2e/administration/administration.cy.ts`, + ' metricsPageHeadingSelector,\n' + + ' healthPageHeadingSelector,\n' + + ' logsPageHeadingSelector,\n' + + ' configurationPageHeadingSelector,', + ); + + assertion( + `${clientRootDir}src/test/javascript/cypress/e2e/administration/administration.cy.ts`, + " describe('/metrics', () => {\n" + + " it('should load the page', () => {\n" + + " cy.clickOnAdminMenuItem('metrics');\n" + + " cy.get(metricsPageHeadingSelector).should('be.visible');\n" + + ' });\n' + + ' });\n' + + '\n' + + " describe('/health', () => {\n" + + " it('should load the page', () => {\n" + + " cy.clickOnAdminMenuItem('health');\n" + + " cy.get(healthPageHeadingSelector).should('be.visible');\n" + + ' });\n' + + ' });\n' + + '\n' + + " describe('/logs', () => {\n" + + " it('should load the page', () => {\n" + + " cy.clickOnAdminMenuItem('logs');\n" + + " cy.get(logsPageHeadingSelector).should('be.visible');\n" + + ' });\n' + + ' });\n' + + '\n' + + " describe('/configuration', () => {\n" + + " it('should load the page', () => {\n" + + " cy.clickOnAdminMenuItem('configuration');\n" + + " cy.get(configurationPageHeadingSelector).should('be.visible');\n" + + ' });\n' + + ' });', + ); + + assertion( + `${clientRootDir}src/test/javascript/cypress/support/commands.ts`, + 'export const metricsPageHeadingSelector = \'[data-cy="metricsPageHeading"]\';\n' + + 'export const healthPageHeadingSelector = \'[data-cy="healthPageHeading"]\';\n' + + 'export const logsPageHeadingSelector = \'[data-cy="logsPageHeading"]\';\n' + + 'export const configurationPageHeadingSelector = \'[data-cy="configurationPageHeading"]\';', + ); + }); + } + }); + }); + }); +}); diff --git a/generators/cypress/generator.ts b/generators/cypress/generator.ts new file mode 100644 index 000000000000..f09ba6de1275 --- /dev/null +++ b/generators/cypress/generator.ts @@ -0,0 +1,280 @@ +/** + * Copyright 2013-2023 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 { stringHashCode, createFaker } from '../base/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { cypressFiles, cypressEntityFiles } from './files.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import { GENERATOR_CYPRESS, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; + +import { generateTestEntity as entityWithFakeValues } from '../client/support/index.js'; + +const { ANGULAR } = clientFrameworkTypes; + +export default class CypressGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_CYPRESS); + } + + if (!this.delegateToBlueprint) { + // TODO depend on GENERATOR_BOOTSTRAP_APPLICATION_CLIENT. + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get prompting() { + return this.asPromptingTaskGroup({ + async askForCypressOptions({ control }) { + if (control.existingProject && !this.options.askAnswered) return; + await (this.prompt as any)( + [ + { + when: this.jhipsterConfig?.clientFramework === ANGULAR, + type: 'confirm', + name: 'cypressCoverage', + message: 'Would you like to generate code coverage for Cypress tests? [Experimental]', + }, + { + type: 'confirm', + name: 'cypressAudit', + message: 'Would you like to audit Cypress tests?', + }, + ], + this.config, + ); + }, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get loading() { + return this.asLoadingTaskGroup({ + prepareForTemplates({ application }) { + const { cypressAudit = true, cypressCoverage = false } = this.jhipsterConfig as any; + application.cypressAudit = cypressAudit; + application.cypressCoverage = cypressCoverage; + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application }) { + application.cypressDir = application.cypressDir ?? application.clientTestDir ? `${application.clientTestDir}cypress/` : 'cypress'; + application.cypressTemporaryDir = + application.cypressTemporaryDir ?? application.temporaryDir ? `${application.temporaryDir}cypress/` : '.cypress/'; + application.cypressBootstrapEntities = application.cypressBootstrapEntities ?? true; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get preparingEachEntity() { + return this.asPreparingEachEntityTaskGroup({ + prepareForTemplates({ entity }) { + this._.defaults(entity, { workaroundEntityCannotBeEmpty: false, workaroundInstantReactiveMariaDB: false }); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntity); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanup({ application: { authenticationTypeOauth2, generateUserManagement, cypressDir } }) { + if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { + this.removeFile(`${cypressDir}support/keycloak-oauth2.ts`); + this.removeFile(`${cypressDir}fixtures/users/user.json`); + } + if (this.isJhipsterVersionLessThan('7.8.2')) { + this.removeFile('cypress.json'); + this.removeFile('cypress-audits.json'); + + this.removeFile(`${cypressDir}integration/administration/administration.spec.ts`); + this.removeFile(`${cypressDir}integration/lighthouse.audits.ts`); + if (!authenticationTypeOauth2) { + this.removeFile(`${cypressDir}integration/account/login-page.spec.ts`); + } + if (generateUserManagement) { + this.removeFile(`${cypressDir}integration/account/register-page.spec.ts`); + this.removeFile(`${cypressDir}integration/account/settings-page.spec.ts`); + this.removeFile(`${cypressDir}integration/account/password-page.spec.ts`); + this.removeFile(`${cypressDir}integration/account/reset-password-page.spec.ts`); + } + } + }, + async writeFiles({ application }) { + const faker = await createFaker(); + faker.seed(stringHashCode(application.baseName)); + const context = { ...application, faker } as any; + return this.writeFiles({ + sections: cypressFiles, + context, + }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + cleanupCypressEntityFiles({ application: { cypressDir }, entities }) { + for (const entity of entities) { + if (this.isJhipsterVersionLessThan('7.8.2')) { + this.removeFile(`${cypressDir}integration/entity/${entity.entityFileName}.spec.ts`); + } + } + }, + + async writeCypressEntityFiles({ application, entities }) { + for (const entity of entities) { + const context = { ...application, ...entity } as any; + await this.writeFiles({ + sections: cypressEntityFiles, + context, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + loadPackageJson({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster('client', 'resources', 'package.json'), + ); + }, + + configure({ application }) { + const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); + clientPackageJson.merge({ + devDependencies: { + 'eslint-plugin-cypress': application.nodeDependencies['eslint-plugin-cypress'], + }, + scripts: { + e2e: 'npm run e2e:cypress:headed --', + 'e2e:headless': 'npm run e2e:cypress --', + 'e2e:cypress:headed': 'npm run e2e:cypress -- --headed', + 'e2e:cypress': 'cypress run --e2e --browser chrome', + 'e2e:cypress:record': 'npm run e2e:cypress -- --record', + cypress: 'cypress open --e2e', + }, + }); + }, + + configureAudits({ application }) { + if (!application.cypressAudit) return; + const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); + clientPackageJson.merge({ + devDependencies: { + lighthouse: application.nodeDependencies.lighthouse, + 'cypress-audit': application.nodeDependencies['cypress-audit'], + }, + scripts: { + 'cypress:audits': 'cypress open --e2e --config-file cypress-audits.config.js', + 'e2e:cypress:audits:headless': 'npm run e2e:cypress -- --config-file cypress-audits.config.js', + 'e2e:cypress:audits': + // eslint-disable-next-line no-template-curly-in-string + 'cypress run --e2e --browser chrome --config-file cypress-audits.config.js', + }, + }); + }, + configureCoverage({ application, source }) { + const { cypressCoverage, clientFrameworkAngular, dasherizedBaseName } = application; + if (!cypressCoverage) return; + const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); + clientPackageJson.merge({ + devDependencies: { + '@cypress/code-coverage': application.nodeDependencies['@cypress/code-coverage'], + 'babel-loader': application.nodeDependencies['babel-loader'], + 'babel-plugin-istanbul': application.nodeDependencies['babel-plugin-istanbul'], + nyc: application.nodeDependencies.nyc, + }, + scripts: { + 'clean-coverage': 'rimraf .nyc_output coverage', + 'pree2e:cypress:coverage': 'npm run clean-coverage && npm run ci:server:await', + 'e2e:cypress:coverage': 'npm run e2e:cypress:headed', + 'poste2e:cypress:coverage': 'nyc report', + 'prewebapp:instrumenter': 'npm run clean-www && npm run clean-coverage', + 'webapp:instrumenter': 'ng build --configuration instrumenter', + }, + }); + if (clientFrameworkAngular) { + // Add 'ng build --configuration instrumenter' support + this.createStorage('angular.json').setPath(`projects.${dasherizedBaseName}.architect.build.configurations.instrumenter`, {}); + source.addWebpackConfig?.({ + config: `targetOptions.configuration === 'instrumenter' + ? { + module: { + rules: [ + { + test: /\\.(js|ts)$/, + use: [ + { + loader: 'babel-loader', + options: { + plugins: ['istanbul'], + }, + } + ], + enforce: 'post', + include: path.resolve(__dirname, '../${CLIENT_MAIN_SRC_DIR}'), + exclude: [/\\.(e2e|spec)\\.ts$/, /node_modules/, /(ngfactory|ngstyle)\\.js/], + }, + ], + }, + } + : {}`, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + generateTestEntity(references) { + return entityWithFakeValues(references); + } +} diff --git a/generators/cypress/index.mts b/generators/cypress/index.mts deleted file mode 100644 index a61ac18c9513..000000000000 --- a/generators/cypress/index.mts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; -export * from './generator.mjs'; -export { cypressFiles, cypressEntityFiles } from './files.mjs'; diff --git a/generators/cypress/index.ts b/generators/cypress/index.ts new file mode 100644 index 000000000000..caab017c4c22 --- /dev/null +++ b/generators/cypress/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; +export * from './generator.js'; +export { cypressFiles, cypressEntityFiles } from './files.js'; diff --git a/generators/cypress/types.d.mts b/generators/cypress/types.d.ts similarity index 100% rename from generators/cypress/types.d.mts rename to generators/cypress/types.d.ts diff --git a/generators/docker-compose/__snapshots__/docker-compose.spec.mts.snap b/generators/docker-compose/__snapshots__/docker-compose.spec.ts.snap similarity index 100% rename from generators/docker-compose/__snapshots__/docker-compose.spec.mts.snap rename to generators/docker-compose/__snapshots__/docker-compose.spec.ts.snap diff --git a/generators/docker-compose/command.mts b/generators/docker-compose/command.mts deleted file mode 100644 index 9bc21259616f..000000000000 --- a/generators/docker-compose/command.mts +++ /dev/null @@ -1,13 +0,0 @@ -import { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - arguments: { - appsFolders: { - type: Array, - description: 'Application folders', - }, - }, - options: {}, -}; - -export default command; diff --git a/generators/docker-compose/command.ts b/generators/docker-compose/command.ts new file mode 100644 index 000000000000..d4cf73b0f409 --- /dev/null +++ b/generators/docker-compose/command.ts @@ -0,0 +1,13 @@ +import { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + arguments: { + appsFolders: { + type: Array, + description: 'Application folders', + }, + }, + options: {}, +}; + +export default command; diff --git a/generators/docker-compose/docker-compose.spec.mts b/generators/docker-compose/docker-compose.spec.mts deleted file mode 100644 index 0711b03282ad..000000000000 --- a/generators/docker-compose/docker-compose.spec.mts +++ /dev/null @@ -1,547 +0,0 @@ -import { expect } from 'esmocha'; - -import monitoringTypes from '../../jdl/jhipster/monitoring-types.js'; -import applicationTypes from '../../jdl/jhipster/application-types.js'; -import { GENERATOR_DOCKER_COMPOSE } from '../generator-list.mjs'; -import { defaultHelpers as helpers, getGenerator, runResult } from '../../test/support/index.mjs'; - -const { PROMETHEUS } = monitoringTypes; -const { MICROSERVICE, MONOLITH } = applicationTypes; - -const NO_MONITORING = monitoringTypes.NO; - -const expectedFiles = { - dockercompose: ['docker-compose.yml', 'central-server-config/application.yml'], - prometheus: ['prometheus-conf/alert_rules.yml', 'prometheus-conf/prometheus.yml', 'alertmanager-conf/config.yml'], - monolith: ['docker-compose.yml'], -}; - -describe('generator - Docker Compose', () => { - describe('only gateway', () => { - let runResult; - const chosenApps = ['01-gateway']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: NO_MONITORING, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('only one microservice', () => { - let runResult; - const chosenApps = ['02-mysql']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: NO_MONITORING, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('one microservice and a directory path without a trailing slash', () => { - let runResult; - const chosenApps = ['02-mysql']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: '.', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: NO_MONITORING, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('Correct the directory path by appending a trailing slash', () => { - runResult.assertFileContent('.yo-rc.json', '"directoryPath": "./"'); - }); - }); - - describe('gateway and one microservice, without monitoring', () => { - const chosenApps = ['01-gateway', '02-mysql']; - before(async () => { - await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: NO_MONITORING, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and one microservice', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('no prometheus files', () => { - runResult.assertNoFile(expectedFiles.prometheus); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and one microservice, with curator', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('no prometheus files', () => { - runResult.assertNoFile(expectedFiles.prometheus); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and one microservice, with prometheus', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: PROMETHEUS, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates expected prometheus files', () => { - runResult.assertFile(expectedFiles.prometheus); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and multi microservices', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and multi microservices, with 1 mongodb cluster', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: ['04-mongo'], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and 1 microservice, with Cassandra', () => { - let runResult; - const chosenApps = ['01-gateway', '05-cassandra']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('monolith', () => { - let runResult; - const chosenApps = ['08-monolith']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MONOLITH, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.monolith); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and multi microservices using oauth2', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '10-couchbase', '07-mariadb']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot({ - 'realm-config/jhipster-realm.json': { - contents: expect.any(String), - stateCleared: 'modified', - }, - }); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and multi microservices, with couchbase', () => { - let runResult; - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '10-couchbase', '07-mariadb']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('gateway and 1 microservice, with 1 couchbase cluster', () => { - let runResult; - const chosenApps = ['01-gateway', '10-couchbase']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MICROSERVICE, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: ['10-couchbase'], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.dockercompose); - }); - it('creates consul content', () => { - runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); - }); - it('creates compose file without container_name, external_links, links', () => { - runResult.assertNoFileContent('docker-compose.yml', /container_name:/); - runResult.assertNoFileContent('docker-compose.yml', /external_links:/); - runResult.assertNoFileContent('docker-compose.yml', /links:/); - }); - }); - - describe('oracle monolith', () => { - let runResult; - const chosenApps = ['12-oracle']; - before(async () => { - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) - .withAnswers({ - deploymentApplicationType: MONOLITH, - directoryPath: './', - appsFolders: chosenApps, - clusteredDbApps: [], - monitoring: NO_MONITORING, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.monolith); - }); - }); -}); diff --git a/generators/docker-compose/docker-compose.spec.ts b/generators/docker-compose/docker-compose.spec.ts new file mode 100644 index 000000000000..200817e3af89 --- /dev/null +++ b/generators/docker-compose/docker-compose.spec.ts @@ -0,0 +1,547 @@ +import { expect } from 'esmocha'; + +import monitoringTypes from '../../jdl/jhipster/monitoring-types.js'; +import applicationTypes from '../../jdl/jhipster/application-types.js'; +import { GENERATOR_DOCKER_COMPOSE } from '../generator-list.js'; +import { defaultHelpers as helpers, getGenerator, runResult } from '../../test/support/index.js'; + +const { PROMETHEUS } = monitoringTypes; +const { MICROSERVICE, MONOLITH } = applicationTypes; + +const NO_MONITORING = monitoringTypes.NO; + +const expectedFiles = { + dockercompose: ['docker-compose.yml', 'central-server-config/application.yml'], + prometheus: ['prometheus-conf/alert_rules.yml', 'prometheus-conf/prometheus.yml', 'alertmanager-conf/config.yml'], + monolith: ['docker-compose.yml'], +}; + +describe('generator - Docker Compose', () => { + describe('only gateway', () => { + let runResult; + const chosenApps = ['01-gateway']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: NO_MONITORING, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('only one microservice', () => { + let runResult; + const chosenApps = ['02-mysql']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: NO_MONITORING, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('one microservice and a directory path without a trailing slash', () => { + let runResult; + const chosenApps = ['02-mysql']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: '.', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: NO_MONITORING, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('Correct the directory path by appending a trailing slash', () => { + runResult.assertFileContent('.yo-rc.json', '"directoryPath": "./"'); + }); + }); + + describe('gateway and one microservice, without monitoring', () => { + const chosenApps = ['01-gateway', '02-mysql']; + before(async () => { + await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: NO_MONITORING, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and one microservice', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('no prometheus files', () => { + runResult.assertNoFile(expectedFiles.prometheus); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and one microservice, with curator', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('no prometheus files', () => { + runResult.assertNoFile(expectedFiles.prometheus); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and one microservice, with prometheus', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: PROMETHEUS, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates expected prometheus files', () => { + runResult.assertFile(expectedFiles.prometheus); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and multi microservices', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and multi microservices, with 1 mongodb cluster', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: ['04-mongo'], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and 1 microservice, with Cassandra', () => { + let runResult; + const chosenApps = ['01-gateway', '05-cassandra']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + // runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('monolith', () => { + let runResult; + const chosenApps = ['08-monolith']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MONOLITH, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.monolith); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and multi microservices using oauth2', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '10-couchbase', '07-mariadb']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot({ + 'realm-config/jhipster-realm.json': { + contents: expect.any(String), + stateCleared: 'modified', + }, + }); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and multi microservices, with couchbase', () => { + let runResult; + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '10-couchbase', '07-mariadb']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('gateway and 1 microservice, with 1 couchbase cluster', () => { + let runResult; + const chosenApps = ['01-gateway', '10-couchbase']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MICROSERVICE, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: ['10-couchbase'], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.dockercompose); + }); + it('creates consul content', () => { + runResult.assertFileContent('docker-compose.yml', /SPRING_CLOUD_CONSUL_HOST=consul/); + }); + it('creates compose file without container_name, external_links, links', () => { + runResult.assertNoFileContent('docker-compose.yml', /container_name:/); + runResult.assertNoFileContent('docker-compose.yml', /external_links:/); + runResult.assertNoFileContent('docker-compose.yml', /links:/); + }); + }); + + describe('oracle monolith', () => { + let runResult; + const chosenApps = ['12-oracle']; + before(async () => { + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_DOCKER_COMPOSE)) + .withAnswers({ + deploymentApplicationType: MONOLITH, + directoryPath: './', + appsFolders: chosenApps, + clusteredDbApps: [], + monitoring: NO_MONITORING, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.monolith); + }); + }); +}); diff --git a/generators/docker-compose/files.js b/generators/docker-compose/files.js new file mode 100644 index 000000000000..afa2e23e43ee --- /dev/null +++ b/generators/docker-compose/files.js @@ -0,0 +1,60 @@ +/** + * Copyright 2013-2023 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 { applicationTypes, authenticationTypes, monitoringTypes } from '../../jdl/jhipster/index.js'; + +const { PROMETHEUS } = monitoringTypes; +const { MICROSERVICE } = applicationTypes; +const { OAUTH2 } = authenticationTypes; + +// eslint-disable-next-line import/prefer-default-export +export function writeFiles() { + return { + cleanup() { + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile('realm-config/jhipster-users-0.json'); + } + }, + + writeDockerCompose({ deployment }) { + this.writeFile('docker-compose.yml.ejs', 'docker-compose.yml', deployment); + this.writeFile('README-DOCKER-COMPOSE.md.ejs', 'README-DOCKER-COMPOSE.md', deployment); + }, + + writeRegistryFiles({ deployment }) { + if (deployment.serviceDiscoveryAny) { + this.writeFile('central-server-config/application.yml.ejs', 'central-server-config/application.yml', deployment); + } + }, + + writeKeycloakFiles({ deployment }) { + if (deployment.authenticationType === OAUTH2 && deployment.applicationType !== MICROSERVICE) { + this.writeFile('realm-config/keycloak-health-check.sh', 'realm-config/keycloak-health-check.sh', deployment); + this.writeFile('realm-config/jhipster-realm.json.ejs', 'realm-config/jhipster-realm.json', deployment); + } + }, + + writePrometheusFiles({ deployment }) { + if (deployment.monitoring !== PROMETHEUS) return; + + this.writeFile('prometheus-conf/prometheus.yml.ejs', 'prometheus-conf/prometheus.yml', deployment); + this.writeFile('prometheus-conf/alert_rules.yml.ejs', 'prometheus-conf/alert_rules.yml', deployment); + this.writeFile('alertmanager-conf/config.yml.ejs', 'alertmanager-conf/config.yml', deployment); + }, + }; +} diff --git a/generators/docker-compose/files.mjs b/generators/docker-compose/files.mjs deleted file mode 100644 index 3936dfbc263c..000000000000 --- a/generators/docker-compose/files.mjs +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2013-2023 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 { applicationTypes, authenticationTypes, monitoringTypes } from '../../jdl/jhipster/index.mjs'; - -const { PROMETHEUS } = monitoringTypes; -const { MICROSERVICE } = applicationTypes; -const { OAUTH2 } = authenticationTypes; - -// eslint-disable-next-line import/prefer-default-export -export function writeFiles() { - return { - cleanup() { - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile('realm-config/jhipster-users-0.json'); - } - }, - - writeDockerCompose({ deployment }) { - this.writeFile('docker-compose.yml.ejs', 'docker-compose.yml', deployment); - this.writeFile('README-DOCKER-COMPOSE.md.ejs', 'README-DOCKER-COMPOSE.md', deployment); - }, - - writeRegistryFiles({ deployment }) { - if (deployment.serviceDiscoveryAny) { - this.writeFile('central-server-config/application.yml.ejs', 'central-server-config/application.yml', deployment); - } - }, - - writeKeycloakFiles({ deployment }) { - if (deployment.authenticationType === OAUTH2 && deployment.applicationType !== MICROSERVICE) { - this.writeFile('realm-config/keycloak-health-check.sh', 'realm-config/keycloak-health-check.sh', deployment); - this.writeFile('realm-config/jhipster-realm.json.ejs', 'realm-config/jhipster-realm.json', deployment); - } - }, - - writePrometheusFiles({ deployment }) { - if (deployment.monitoring !== PROMETHEUS) return; - - this.writeFile('prometheus-conf/prometheus.yml.ejs', 'prometheus-conf/prometheus.yml', deployment); - this.writeFile('prometheus-conf/alert_rules.yml.ejs', 'prometheus-conf/alert_rules.yml', deployment); - this.writeFile('alertmanager-conf/config.yml.ejs', 'alertmanager-conf/config.yml', deployment); - }, - }; -} diff --git a/generators/docker-compose/generator.js b/generators/docker-compose/generator.js new file mode 100644 index 000000000000..127c3a1d5f5a --- /dev/null +++ b/generators/docker-compose/generator.js @@ -0,0 +1,543 @@ +/** + * Copyright 2013-2023 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 { existsSync } from 'fs'; +import pathjs from 'path'; +import chalk from 'chalk'; +import jsyaml from 'js-yaml'; +import normalize from 'normalize-path'; +import * as _ from 'lodash-es'; + +import BaseWorkspacesGenerator from '../base-workspaces/index.js'; + +import { writeFiles } from './files.js'; +import { deploymentOptions, monitoringTypes, serviceDiscoveryTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_BOOTSTRAP_WORKSPACES, GENERATOR_DOCKER_COMPOSE } from '../generator-list.js'; +import { stringHashCode, createFaker, convertSecretToBase64, createBase64Secret } from '../base/support/index.js'; +import { checkDocker } from '../base-workspaces/internal/docker-base.js'; +import { loadDockerDependenciesTask } from '../base-workspaces/internal/index.js'; +import statistics from '../statistics.js'; +import command from './command.js'; +import { loadDerivedPlatformConfig, loadPlatformConfig } from '../server/support/index.js'; + +const { PROMETHEUS, NO: NO_MONITORING } = monitoringTypes; +const { CONSUL, EUREKA, NO: NO_SERVICE_DISCOVERY } = serviceDiscoveryTypes; +const { Options: DeploymentOptions } = deploymentOptions; +const { defaults } = _; + +/* eslint-disable consistent-return */ +/** + * @class + * @extends {import('../base/index.js')} + */ +export default class DockerComposeGenerator extends BaseWorkspacesGenerator { + existingDeployment; + + async beforeQueue() { + this.parseJHipsterArguments(command.arguments); + if (this.appsFolders?.length > 0) { + this.jhipsterConfig.appsFolders = this.appsFolders; + } + + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_WORKSPACES); + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_DOCKER_COMPOSE); + } + } + + get initializing() { + return { + sayHello() { + this.log.log(chalk.white(`${chalk.bold('🐳')} Welcome to the JHipster Docker Compose Sub-Generator ${chalk.bold('🐳')}`)); + this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); + }, + + parseOptions() { + this.parseJHipsterOptions(command.options); + }, + checkDocker, + async checkDockerCompose() { + if (this.skipChecks) return; + + const { exitCode } = await this.spawnCommand('docker compose version', { reject: false, stdio: 'pipe' }); + if (exitCode !== 0) { + throw new Error(`Docker Compose V2 is not installed on your computer. + Read https://docs.docker.com/compose/install/ +`); + } + }, + }; + } + + get [BaseWorkspacesGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get loading() { + return { + loadWorkspacesConfig() { + this.loadWorkspacesConfig(); + }, + }; + } + + get [BaseWorkspacesGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get promptingWorkspaces() { + return { + async askForMonitoring({ workspaces }) { + if (workspaces.existingWorkspaces && !this.options.askAnswered) return; + + await this.askForMonitoring(); + }, + async askForClustersMode({ workspaces, applications }) { + if (workspaces.existingWorkspaces && !this.options.askAnswered) return; + + await this.askForClustersMode({ applications }); + }, + async askForServiceDiscovery({ workspaces, applications }) { + if (workspaces.existingWorkspaces && !this.options.askAnswered) return; + + await this.askForServiceDiscovery({ applications }); + }, + }; + } + + get [BaseWorkspacesGenerator.PROMPTING_WORKSPACES]() { + return this.delegateTasksToBlueprint(() => this.promptingWorkspaces); + } + + get configuringWorkspaces() { + return { + configureBaseDeployment({ applications }) { + this.jhipsterConfig.jwtSecretKey = this.jhipsterConfig.jwtSecretKey ?? createBase64Secret(this.options.reproducibleTests); + if (applications.some(app => app.serviceDiscoveryEureka)) { + this.jhipsterConfig.adminPassword = this.jhipsterConfig.adminPassword ?? 'admin'; + } + }, + }; + } + + get [BaseWorkspacesGenerator.CONFIGURING_WORKSPACES]() { + return this.delegateTasksToBlueprint(() => this.configuringWorkspaces); + } + + get loadingWorkspaces() { + return { + loadBaseDeployment({ deployment }) { + deployment.jwtSecretKey = this.jhipsterConfig.jwtSecretKey; + + loadDockerDependenciesTask.call(this, { context: deployment }); + }, + loadPlatformConfig({ deployment }) { + this.loadDeploymentConfig({ deployment }); + }, + }; + } + + get [BaseWorkspacesGenerator.LOADING_WORKSPACES]() { + return this.delegateTasksToBlueprint(() => this.loadingWorkspaces); + } + + get preparingWorkspaces() { + return { + prepareDeployment({ deployment, applications }) { + this.prepareDeploymentDerivedProperties({ deployment, applications }); + }, + }; + } + + get [BaseWorkspacesGenerator.PREPARING_WORKSPACES]() { + return this.delegateTasksToBlueprint(() => this.preparingWorkspaces); + } + + get default() { + return { + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_DOCKER_COMPOSE); + }, + async setAppsYaml({ workspaces, deployment, applications }) { + const faker = await createFaker(); + + deployment.keycloakRedirectUris = ''; + deployment.appsYaml = applications.map(appConfig => { + const lowercaseBaseName = appConfig.baseName.toLowerCase(); + appConfig.clusteredDb = deployment.clusteredDbApps?.includes(appConfig.appFolder); + const parentConfiguration = {}; + const path = this.destinationPath(workspaces.directoryPath, appConfig.appFolder); + // Add application configuration + const yaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/app.yml`)); + const yamlConfig = yaml.services.app; + if (yamlConfig.depends_on) { + yamlConfig.depends_on = Object.fromEntries( + Object.entries(yamlConfig.depends_on).map(([serviceName, config]) => { + if (['keycloak', 'jhipster-registry', 'consul'].includes(serviceName)) { + return [serviceName, config]; + } + return [`${lowercaseBaseName}-${serviceName}`, config]; + }), + ); + } + if (appConfig.applicationTypeGateway || appConfig.applicationTypeMonolith) { + if (deployment.keycloakSecrets === undefined && appConfig.authenticationTypeOauth2) { + faker.seed(stringHashCode(appConfig.baseName)); + deployment.keycloakSecrets = Array.from(Array(6), () => faker.string.uuid()); + } + deployment.keycloakRedirectUris += `"http://localhost:${appConfig.composePort}/*", "https://localhost:${appConfig.composePort}/*", `; + if (appConfig.devServerPort !== undefined) { + deployment.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPort}/*", `; + } + // Split ports by ":" and take last 2 elements to skip the hostname/IP if present + const ports = yamlConfig.ports[0].split(':').slice(-2); + ports[0] = appConfig.composePort; + yamlConfig.ports[0] = ports.join(':'); + } + + if (yamlConfig.environment) { + yamlConfig.environment = yamlConfig.environment.map(envOption => { + // Doesn't applies to keycloak, jhipster-registry and consul. + // docker-compose changes the container name to `${lowercaseBaseName}-${databaseType}`. + // we need to update the environment urls to the new container host. + [ + 'SPRING_R2DBC_URL', + 'SPRING_DATASOURCE_URL', + 'SPRING_LIQUIBASE_URL', + 'SPRING_NEO4J_URI', + 'SPRING_DATA_MONGODB_URI', + 'JHIPSTER_CACHE_REDIS_SERVER', + 'SPRING_ELASTICSEARCH_URIS', + ].forEach(varName => { + if (envOption.startsWith(varName)) { + envOption = envOption + .replace('://', `://${lowercaseBaseName}-`) + .replace('oracle:thin:@', `oracle:thin:@${lowercaseBaseName}-`); + } + }); + ['JHIPSTER_CACHE_MEMCACHED_SERVERS', 'SPRING_COUCHBASE_CONNECTION_STRING', 'SPRING_CASSANDRA_CONTACTPOINTS'].forEach( + varName => { + if (envOption.startsWith(varName)) { + envOption = envOption.replace(`${varName}=`, `${varName}=${lowercaseBaseName}-`); + } + }, + ); + return envOption; + }); + } + + if (appConfig.applicationTypeMonolith && deployment.monitoring === PROMETHEUS) { + yamlConfig.environment.push('JHIPSTER_LOGGING_LOGSTASH_ENABLED=false'); + yamlConfig.environment.push('MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true'); + } + + if (deployment.serviceDiscoveryType === EUREKA) { + // Set the JHipster Registry password + yamlConfig.environment.push(`JHIPSTER_REGISTRY_PASSWORD=${deployment.adminPassword}`); + } + + const hasNoServiceDiscovery = !deployment.serviceDiscoveryType && deployment.serviceDiscoveryType !== NO_SERVICE_DISCOVERY; + if (hasNoServiceDiscovery && appConfig.skipClient) { + yamlConfig.environment.push('SERVER_PORT=80'); // to simplify service resolution in docker/k8s + } + + parentConfiguration[lowercaseBaseName] = yamlConfig; + + // Add database configuration + if (appConfig.databaseTypeAny && !appConfig.prodDatabaseTypeOracle) { + const database = appConfig.databaseTypeSql ? appConfig.prodDatabaseType : appConfig.databaseType; + const relativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/docker`)); + const databaseYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${database}.yml`)); + const databaseServiceName = `${lowercaseBaseName}-${database}`; + let databaseYamlConfig = databaseYaml.services[database]; + // Don't export database ports + delete databaseYamlConfig.ports; + + if (appConfig.databaseTypeCassandra) { + // migration service config + const cassandraMigrationYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/cassandra-migration.yml`)); + const cassandraMigrationConfig = cassandraMigrationYaml.services[`${database}-migration`]; + cassandraMigrationConfig.build.context = relativePath; + const cqlFilesRelativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/resources/config/cql`)); + cassandraMigrationConfig.volumes[0] = `${cqlFilesRelativePath}:/cql:ro`; + + parentConfiguration[`${databaseServiceName}-migration`] = cassandraMigrationConfig; + } + + if (appConfig.databaseTypeCouchbase) { + databaseYamlConfig.build.context = relativePath; + } + + if (appConfig.clusteredDb) { + const clusterDbYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${database}-cluster.yml`)); + const dbNodeConfig = clusterDbYaml.services[`${database}-node`]; + dbNodeConfig.build.context = relativePath; + databaseYamlConfig = clusterDbYaml.services[database]; + delete databaseYamlConfig.ports; + if (appConfig.databaseTypeCouchbase) { + databaseYamlConfig.build.context = relativePath; + } + parentConfiguration[`${databaseServiceName}-node`] = dbNodeConfig; + if (appConfig.databaseTypeMongodb) { + parentConfiguration[`${databaseServiceName}-config`] = clusterDbYaml.services[`${database}-config`]; + } + } + + parentConfiguration[databaseServiceName] = databaseYamlConfig; + } + if (appConfig.searchEngineElasticsearch) { + // Add search engine configuration + const searchEngine = appConfig.searchEngine; + const searchEngineYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${searchEngine}.yml`)); + const searchEngineConfig = searchEngineYaml.services[searchEngine]; + delete searchEngineConfig.ports; + parentConfiguration[`${lowercaseBaseName}-${searchEngine}`] = searchEngineConfig; + } + // Add Memcached support + if (appConfig.cacheProviderMemcached) { + const memcachedYaml = jsyaml.load(this.readDestination(`${path}/src/main/docker/memcached.yml`)); + const memcachedConfig = memcachedYaml.services.memcached; + delete memcachedConfig.ports; + parentConfiguration[`${lowercaseBaseName}-memcached`] = memcachedConfig; + } + + // Add Redis support + if (appConfig.cacheProviderRedis) { + const redisYaml = jsyaml.load(this.readDestination(`${path}/src/main/docker/redis.yml`)); + const redisConfig = redisYaml.services.redis; + delete redisConfig.ports; + parentConfiguration[`${lowercaseBaseName}-redis`] = redisConfig; + } + // Expose authenticationType + deployment.authenticationType = appConfig.authenticationType; + + // Dump the file + let yamlString = jsyaml.dump(parentConfiguration, { indent: 2, lineWidth: -1 }); + + // Add extra indentation for each lines + const yamlArray = yamlString.split('\n'); + for (let j = 0; j < yamlArray.length; j++) { + yamlArray[j] = ` ${yamlArray[j]}`; + } + yamlString = yamlArray.join('\n'); + return yamlString; + }); + }, + }; + } + + get [BaseWorkspacesGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return writeFiles(); + } + + get [BaseWorkspacesGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get end() { + return { + end({ workspaces, applications }) { + this.checkApplicationsDockerImages({ workspaces, applications }); + + this.log.verboseInfo(`You can launch all your infrastructure by running : ${chalk.cyan('docker compose up -d')}`); + const uiApplications = applications.filter( + app => (app.applicationTypeGateway || app.applicationTypeMonolith) && app.clientFrameworkAny, + ); + if (uiApplications.length > 0) { + this.log.log('\nYour applications will be accessible on these URLs:'); + for (const application of uiApplications) { + this.log.verboseInfo(`\t- ${application.baseName}: http://localhost:${application.composePort}`); + } + this.log.log('\n'); + } + }, + }; + } + + get [BaseWorkspacesGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + checkApplicationsDockerImages({ workspaces, applications }) { + this.log.log('\nChecking Docker images in applications directories...'); + + let imagePath = ''; + let runCommand = ''; + let hasWarning = false; + let warningMessage = 'To generate the missing Docker image(s), please run:\n'; + applications.forEach(application => { + if (application.buildToolGradle) { + imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, 'build/jib-cache'); + runCommand = `./gradlew bootJar -Pprod jibDockerBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''}`; + } else if (application.buildToolMaven) { + imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, '/target/jib-cache'); + runCommand = `./mvnw -ntp -Pprod verify jib:dockerBuild${process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : ''}`; + } + if (!existsSync(imagePath)) { + hasWarning = true; + warningMessage += ` ${chalk.cyan(runCommand)} in ${this.destinationPath(workspaces.directoryPath, application.appFolder)}\n`; + } + }); + if (hasWarning) { + this.log.warn('Docker Compose configuration generated, but no Jib cache found'); + this.log.warn('If you forgot to generate the Docker image for this application, please run:'); + this.log.log(chalk.red(warningMessage)); + } else { + this.log.verboseInfo(`${chalk.bold.green('Docker Compose configuration successfully generated!')}`); + } + } + + get deploymentConfigWithDefaults() { + return defaults({}, this.jhipsterConfig, DeploymentOptions.defaults(this.jhipsterConfig.deploymentType)); + } + + loadDeploymentConfig({ deployment }) { + const config = this.deploymentConfigWithDefaults; + deployment.clusteredDbApps = config.clusteredDbApps; + deployment.adminPassword = config.adminPassword; + deployment.jwtSecretKey = config.jwtSecretKey; + loadPlatformConfig({ config, application: deployment }); + loadDerivedPlatformConfig({ application: deployment }); + } + + prepareDeploymentDerivedProperties({ deployment, applications }) { + if (deployment.adminPassword) { + deployment.adminPasswordBase64 = convertSecretToBase64(deployment.adminPassword); + } + deployment.usesOauth2 = applications.some(appConfig => appConfig.authenticationTypeOauth2); + deployment.useKafka = applications.some(appConfig => appConfig.messageBrokerKafka); + deployment.usePulsar = applications.some(appConfig => appConfig.messageBrokerPulsar); + deployment.useMemcached = applications.some(appConfig => appConfig.cacheProviderMemcached); + deployment.useRedis = applications.some(appConfig => appConfig.cacheProviderRedis); + deployment.includesApplicationTypeGateway = applications.some(appConfig => appConfig.applicationTypeGateway); + deployment.entryPort = 8080; + + deployment.appConfigs = applications; + deployment.applications = applications; + } + + async askForMonitoring() { + await this.prompt( + [ + { + type: 'list', + name: 'monitoring', + message: 'Do you want to setup monitoring for your applications ?', + choices: [ + { + value: NO_MONITORING, + name: 'No', + }, + { + value: PROMETHEUS, + name: 'Yes, for metrics only with Prometheus', + }, + ], + default: NO_MONITORING, + }, + ], + this.config, + ); + } + + async askForClustersMode({ applications }) { + const clusteredDbApps = applications.filter(app => app.databaseTypeMongodb || app.databaseTypeCouchbase).map(app => app.appFolder); + if (clusteredDbApps.length === 0) return; + + await this.prompt( + [ + { + type: 'checkbox', + name: 'clusteredDbApps', + message: 'Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)?', + choices: clusteredDbApps, + default: clusteredDbApps, + }, + ], + this.config, + ); + } + + async askForServiceDiscovery({ applications }) { + const serviceDiscoveryEnabledApps = applications.filter(app => app.serviceDiscoveryAny); + if (serviceDiscoveryEnabledApps.length === 0) { + this.jhipsterConfig.serviceDiscoveryType = NO_SERVICE_DISCOVERY; + return; + } + + if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryConsul)) { + this.jhipsterConfig.serviceDiscoveryType = CONSUL; + this.log.log(chalk.green('Consul detected as the service discovery and configuration provider used by your apps')); + } else if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryEureka)) { + this.jhipsterConfig.serviceDiscoveryType = EUREKA; + this.log.log(chalk.green('JHipster registry detected as the service discovery and configuration provider used by your apps')); + } else { + this.log.warn( + chalk.yellow('Unable to determine the service discovery and configuration provider to use from your apps configuration.'), + ); + this.log.verboseInfo('Your service discovery enabled apps:'); + serviceDiscoveryEnabledApps.forEach(app => { + this.log.verboseInfo(` -${app.baseName} (${app.serviceDiscoveryType})`); + }); + + await this.prompt( + [ + { + type: 'list', + name: 'serviceDiscoveryType', + message: 'Which Service Discovery registry and Configuration server would you like to use ?', + choices: [ + { + value: CONSUL, + name: 'Consul', + }, + { + value: EUREKA, + name: 'JHipster Registry', + }, + { + value: NO_SERVICE_DISCOVERY, + name: 'No Service Discovery and Configuration', + }, + ], + default: CONSUL, + }, + ], + this.config, + ); + } + if (this.jhipsterConfig.serviceDiscoveryType === EUREKA) { + await this.prompt( + [ + { + type: 'input', + name: 'adminPassword', + message: 'Enter the admin password used to secure the JHipster Registry', + default: 'admin', + validate: input => (input.length < 5 ? 'The password must have at least 5 characters' : true), + }, + ], + this.config, + ); + } + } +} diff --git a/generators/docker-compose/generator.mjs b/generators/docker-compose/generator.mjs deleted file mode 100644 index 3109e16d3656..000000000000 --- a/generators/docker-compose/generator.mjs +++ /dev/null @@ -1,543 +0,0 @@ -/** - * Copyright 2013-2023 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 { existsSync } from 'fs'; -import pathjs from 'path'; -import chalk from 'chalk'; -import jsyaml from 'js-yaml'; -import normalize from 'normalize-path'; -import * as _ from 'lodash-es'; - -import BaseWorkspacesGenerator from '../base-workspaces/index.mjs'; - -import { writeFiles } from './files.mjs'; -import { deploymentOptions, monitoringTypes, serviceDiscoveryTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_BOOTSTRAP_WORKSPACES, GENERATOR_DOCKER_COMPOSE } from '../generator-list.mjs'; -import { stringHashCode, createFaker, convertSecretToBase64, createBase64Secret } from '../base/support/index.mjs'; -import { checkDocker } from '../base-workspaces/internal/docker-base.mjs'; -import { loadDockerDependenciesTask } from '../base-workspaces/internal/index.mjs'; -import statistics from '../statistics.mjs'; -import command from './command.mjs'; -import { loadDerivedPlatformConfig, loadPlatformConfig } from '../server/support/index.mjs'; - -const { PROMETHEUS, NO: NO_MONITORING } = monitoringTypes; -const { CONSUL, EUREKA, NO: NO_SERVICE_DISCOVERY } = serviceDiscoveryTypes; -const { Options: DeploymentOptions } = deploymentOptions; -const { defaults } = _; - -/* eslint-disable consistent-return */ -/** - * @class - * @extends {import('../base/index.mjs')} - */ -export default class DockerComposeGenerator extends BaseWorkspacesGenerator { - existingDeployment; - - async beforeQueue() { - this.parseJHipsterArguments(command.arguments); - if (this.appsFolders?.length > 0) { - this.jhipsterConfig.appsFolders = this.appsFolders; - } - - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_WORKSPACES); - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_DOCKER_COMPOSE); - } - } - - get initializing() { - return { - sayHello() { - this.log.log(chalk.white(`${chalk.bold('🐳')} Welcome to the JHipster Docker Compose Sub-Generator ${chalk.bold('🐳')}`)); - this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); - }, - - parseOptions() { - this.parseJHipsterOptions(command.options); - }, - checkDocker, - async checkDockerCompose() { - if (this.skipChecks) return; - - const { exitCode } = await this.spawnCommand('docker compose version', { reject: false, stdio: 'pipe' }); - if (exitCode !== 0) { - throw new Error(`Docker Compose V2 is not installed on your computer. - Read https://docs.docker.com/compose/install/ -`); - } - }, - }; - } - - get [BaseWorkspacesGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get loading() { - return { - loadWorkspacesConfig() { - this.loadWorkspacesConfig(); - }, - }; - } - - get [BaseWorkspacesGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get promptingWorkspaces() { - return { - async askForMonitoring({ workspaces }) { - if (workspaces.existingWorkspaces && !this.options.askAnswered) return; - - await this.askForMonitoring(); - }, - async askForClustersMode({ workspaces, applications }) { - if (workspaces.existingWorkspaces && !this.options.askAnswered) return; - - await this.askForClustersMode({ applications }); - }, - async askForServiceDiscovery({ workspaces, applications }) { - if (workspaces.existingWorkspaces && !this.options.askAnswered) return; - - await this.askForServiceDiscovery({ applications }); - }, - }; - } - - get [BaseWorkspacesGenerator.PROMPTING_WORKSPACES]() { - return this.delegateTasksToBlueprint(() => this.promptingWorkspaces); - } - - get configuringWorkspaces() { - return { - configureBaseDeployment({ applications }) { - this.jhipsterConfig.jwtSecretKey = this.jhipsterConfig.jwtSecretKey ?? createBase64Secret(this.options.reproducibleTests); - if (applications.some(app => app.serviceDiscoveryEureka)) { - this.jhipsterConfig.adminPassword = this.jhipsterConfig.adminPassword ?? 'admin'; - } - }, - }; - } - - get [BaseWorkspacesGenerator.CONFIGURING_WORKSPACES]() { - return this.delegateTasksToBlueprint(() => this.configuringWorkspaces); - } - - get loadingWorkspaces() { - return { - loadBaseDeployment({ deployment }) { - deployment.jwtSecretKey = this.jhipsterConfig.jwtSecretKey; - - loadDockerDependenciesTask.call(this, { context: deployment }); - }, - loadPlatformConfig({ deployment }) { - this.loadDeploymentConfig({ deployment }); - }, - }; - } - - get [BaseWorkspacesGenerator.LOADING_WORKSPACES]() { - return this.delegateTasksToBlueprint(() => this.loadingWorkspaces); - } - - get preparingWorkspaces() { - return { - prepareDeployment({ deployment, applications }) { - this.prepareDeploymentDerivedProperties({ deployment, applications }); - }, - }; - } - - get [BaseWorkspacesGenerator.PREPARING_WORKSPACES]() { - return this.delegateTasksToBlueprint(() => this.preparingWorkspaces); - } - - get default() { - return { - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_DOCKER_COMPOSE); - }, - async setAppsYaml({ workspaces, deployment, applications }) { - const faker = await createFaker(); - - deployment.keycloakRedirectUris = ''; - deployment.appsYaml = applications.map(appConfig => { - const lowercaseBaseName = appConfig.baseName.toLowerCase(); - appConfig.clusteredDb = deployment.clusteredDbApps?.includes(appConfig.appFolder); - const parentConfiguration = {}; - const path = this.destinationPath(workspaces.directoryPath, appConfig.appFolder); - // Add application configuration - const yaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/app.yml`)); - const yamlConfig = yaml.services.app; - if (yamlConfig.depends_on) { - yamlConfig.depends_on = Object.fromEntries( - Object.entries(yamlConfig.depends_on).map(([serviceName, config]) => { - if (['keycloak', 'jhipster-registry', 'consul'].includes(serviceName)) { - return [serviceName, config]; - } - return [`${lowercaseBaseName}-${serviceName}`, config]; - }), - ); - } - if (appConfig.applicationTypeGateway || appConfig.applicationTypeMonolith) { - if (deployment.keycloakSecrets === undefined && appConfig.authenticationTypeOauth2) { - faker.seed(stringHashCode(appConfig.baseName)); - deployment.keycloakSecrets = Array.from(Array(6), () => faker.string.uuid()); - } - deployment.keycloakRedirectUris += `"http://localhost:${appConfig.composePort}/*", "https://localhost:${appConfig.composePort}/*", `; - if (appConfig.devServerPort !== undefined) { - deployment.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPort}/*", `; - } - // Split ports by ":" and take last 2 elements to skip the hostname/IP if present - const ports = yamlConfig.ports[0].split(':').slice(-2); - ports[0] = appConfig.composePort; - yamlConfig.ports[0] = ports.join(':'); - } - - if (yamlConfig.environment) { - yamlConfig.environment = yamlConfig.environment.map(envOption => { - // Doesn't applies to keycloak, jhipster-registry and consul. - // docker-compose changes the container name to `${lowercaseBaseName}-${databaseType}`. - // we need to update the environment urls to the new container host. - [ - 'SPRING_R2DBC_URL', - 'SPRING_DATASOURCE_URL', - 'SPRING_LIQUIBASE_URL', - 'SPRING_NEO4J_URI', - 'SPRING_DATA_MONGODB_URI', - 'JHIPSTER_CACHE_REDIS_SERVER', - 'SPRING_ELASTICSEARCH_URIS', - ].forEach(varName => { - if (envOption.startsWith(varName)) { - envOption = envOption - .replace('://', `://${lowercaseBaseName}-`) - .replace('oracle:thin:@', `oracle:thin:@${lowercaseBaseName}-`); - } - }); - ['JHIPSTER_CACHE_MEMCACHED_SERVERS', 'SPRING_COUCHBASE_CONNECTION_STRING', 'SPRING_CASSANDRA_CONTACTPOINTS'].forEach( - varName => { - if (envOption.startsWith(varName)) { - envOption = envOption.replace(`${varName}=`, `${varName}=${lowercaseBaseName}-`); - } - }, - ); - return envOption; - }); - } - - if (appConfig.applicationTypeMonolith && deployment.monitoring === PROMETHEUS) { - yamlConfig.environment.push('JHIPSTER_LOGGING_LOGSTASH_ENABLED=false'); - yamlConfig.environment.push('MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true'); - } - - if (deployment.serviceDiscoveryType === EUREKA) { - // Set the JHipster Registry password - yamlConfig.environment.push(`JHIPSTER_REGISTRY_PASSWORD=${deployment.adminPassword}`); - } - - const hasNoServiceDiscovery = !deployment.serviceDiscoveryType && deployment.serviceDiscoveryType !== NO_SERVICE_DISCOVERY; - if (hasNoServiceDiscovery && appConfig.skipClient) { - yamlConfig.environment.push('SERVER_PORT=80'); // to simplify service resolution in docker/k8s - } - - parentConfiguration[lowercaseBaseName] = yamlConfig; - - // Add database configuration - if (appConfig.databaseTypeAny && !appConfig.prodDatabaseTypeOracle) { - const database = appConfig.databaseTypeSql ? appConfig.prodDatabaseType : appConfig.databaseType; - const relativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/docker`)); - const databaseYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${database}.yml`)); - const databaseServiceName = `${lowercaseBaseName}-${database}`; - let databaseYamlConfig = databaseYaml.services[database]; - // Don't export database ports - delete databaseYamlConfig.ports; - - if (appConfig.databaseTypeCassandra) { - // migration service config - const cassandraMigrationYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/cassandra-migration.yml`)); - const cassandraMigrationConfig = cassandraMigrationYaml.services[`${database}-migration`]; - cassandraMigrationConfig.build.context = relativePath; - const cqlFilesRelativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/resources/config/cql`)); - cassandraMigrationConfig.volumes[0] = `${cqlFilesRelativePath}:/cql:ro`; - - parentConfiguration[`${databaseServiceName}-migration`] = cassandraMigrationConfig; - } - - if (appConfig.databaseTypeCouchbase) { - databaseYamlConfig.build.context = relativePath; - } - - if (appConfig.clusteredDb) { - const clusterDbYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${database}-cluster.yml`)); - const dbNodeConfig = clusterDbYaml.services[`${database}-node`]; - dbNodeConfig.build.context = relativePath; - databaseYamlConfig = clusterDbYaml.services[database]; - delete databaseYamlConfig.ports; - if (appConfig.databaseTypeCouchbase) { - databaseYamlConfig.build.context = relativePath; - } - parentConfiguration[`${databaseServiceName}-node`] = dbNodeConfig; - if (appConfig.databaseTypeMongodb) { - parentConfiguration[`${databaseServiceName}-config`] = clusterDbYaml.services[`${database}-config`]; - } - } - - parentConfiguration[databaseServiceName] = databaseYamlConfig; - } - if (appConfig.searchEngineElasticsearch) { - // Add search engine configuration - const searchEngine = appConfig.searchEngine; - const searchEngineYaml = jsyaml.load(this.fs.read(`${path}/src/main/docker/${searchEngine}.yml`)); - const searchEngineConfig = searchEngineYaml.services[searchEngine]; - delete searchEngineConfig.ports; - parentConfiguration[`${lowercaseBaseName}-${searchEngine}`] = searchEngineConfig; - } - // Add Memcached support - if (appConfig.cacheProviderMemcached) { - const memcachedYaml = jsyaml.load(this.readDestination(`${path}/src/main/docker/memcached.yml`)); - const memcachedConfig = memcachedYaml.services.memcached; - delete memcachedConfig.ports; - parentConfiguration[`${lowercaseBaseName}-memcached`] = memcachedConfig; - } - - // Add Redis support - if (appConfig.cacheProviderRedis) { - const redisYaml = jsyaml.load(this.readDestination(`${path}/src/main/docker/redis.yml`)); - const redisConfig = redisYaml.services.redis; - delete redisConfig.ports; - parentConfiguration[`${lowercaseBaseName}-redis`] = redisConfig; - } - // Expose authenticationType - deployment.authenticationType = appConfig.authenticationType; - - // Dump the file - let yamlString = jsyaml.dump(parentConfiguration, { indent: 2, lineWidth: -1 }); - - // Add extra indentation for each lines - const yamlArray = yamlString.split('\n'); - for (let j = 0; j < yamlArray.length; j++) { - yamlArray[j] = ` ${yamlArray[j]}`; - } - yamlString = yamlArray.join('\n'); - return yamlString; - }); - }, - }; - } - - get [BaseWorkspacesGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return writeFiles(); - } - - get [BaseWorkspacesGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get end() { - return { - end({ workspaces, applications }) { - this.checkApplicationsDockerImages({ workspaces, applications }); - - this.log.verboseInfo(`You can launch all your infrastructure by running : ${chalk.cyan('docker compose up -d')}`); - const uiApplications = applications.filter( - app => (app.applicationTypeGateway || app.applicationTypeMonolith) && app.clientFrameworkAny, - ); - if (uiApplications.length > 0) { - this.log.log('\nYour applications will be accessible on these URLs:'); - for (const application of uiApplications) { - this.log.verboseInfo(`\t- ${application.baseName}: http://localhost:${application.composePort}`); - } - this.log.log('\n'); - } - }, - }; - } - - get [BaseWorkspacesGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - checkApplicationsDockerImages({ workspaces, applications }) { - this.log.log('\nChecking Docker images in applications directories...'); - - let imagePath = ''; - let runCommand = ''; - let hasWarning = false; - let warningMessage = 'To generate the missing Docker image(s), please run:\n'; - applications.forEach(application => { - if (application.buildToolGradle) { - imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, 'build/jib-cache'); - runCommand = `./gradlew bootJar -Pprod jibDockerBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''}`; - } else if (application.buildToolMaven) { - imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, '/target/jib-cache'); - runCommand = `./mvnw -ntp -Pprod verify jib:dockerBuild${process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : ''}`; - } - if (!existsSync(imagePath)) { - hasWarning = true; - warningMessage += ` ${chalk.cyan(runCommand)} in ${this.destinationPath(workspaces.directoryPath, application.appFolder)}\n`; - } - }); - if (hasWarning) { - this.log.warn('Docker Compose configuration generated, but no Jib cache found'); - this.log.warn('If you forgot to generate the Docker image for this application, please run:'); - this.log.log(chalk.red(warningMessage)); - } else { - this.log.verboseInfo(`${chalk.bold.green('Docker Compose configuration successfully generated!')}`); - } - } - - get deploymentConfigWithDefaults() { - return defaults({}, this.jhipsterConfig, DeploymentOptions.defaults(this.jhipsterConfig.deploymentType)); - } - - loadDeploymentConfig({ deployment }) { - const config = this.deploymentConfigWithDefaults; - deployment.clusteredDbApps = config.clusteredDbApps; - deployment.adminPassword = config.adminPassword; - deployment.jwtSecretKey = config.jwtSecretKey; - loadPlatformConfig({ config, application: deployment }); - loadDerivedPlatformConfig({ application: deployment }); - } - - prepareDeploymentDerivedProperties({ deployment, applications }) { - if (deployment.adminPassword) { - deployment.adminPasswordBase64 = convertSecretToBase64(deployment.adminPassword); - } - deployment.usesOauth2 = applications.some(appConfig => appConfig.authenticationTypeOauth2); - deployment.useKafka = applications.some(appConfig => appConfig.messageBrokerKafka); - deployment.usePulsar = applications.some(appConfig => appConfig.messageBrokerPulsar); - deployment.useMemcached = applications.some(appConfig => appConfig.cacheProviderMemcached); - deployment.useRedis = applications.some(appConfig => appConfig.cacheProviderRedis); - deployment.includesApplicationTypeGateway = applications.some(appConfig => appConfig.applicationTypeGateway); - deployment.entryPort = 8080; - - deployment.appConfigs = applications; - deployment.applications = applications; - } - - async askForMonitoring() { - await this.prompt( - [ - { - type: 'list', - name: 'monitoring', - message: 'Do you want to setup monitoring for your applications ?', - choices: [ - { - value: NO_MONITORING, - name: 'No', - }, - { - value: PROMETHEUS, - name: 'Yes, for metrics only with Prometheus', - }, - ], - default: NO_MONITORING, - }, - ], - this.config, - ); - } - - async askForClustersMode({ applications }) { - const clusteredDbApps = applications.filter(app => app.databaseTypeMongodb || app.databaseTypeCouchbase).map(app => app.appFolder); - if (clusteredDbApps.length === 0) return; - - await this.prompt( - [ - { - type: 'checkbox', - name: 'clusteredDbApps', - message: 'Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)?', - choices: clusteredDbApps, - default: clusteredDbApps, - }, - ], - this.config, - ); - } - - async askForServiceDiscovery({ applications }) { - const serviceDiscoveryEnabledApps = applications.filter(app => app.serviceDiscoveryAny); - if (serviceDiscoveryEnabledApps.length === 0) { - this.jhipsterConfig.serviceDiscoveryType = NO_SERVICE_DISCOVERY; - return; - } - - if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryConsul)) { - this.jhipsterConfig.serviceDiscoveryType = CONSUL; - this.log.log(chalk.green('Consul detected as the service discovery and configuration provider used by your apps')); - } else if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryEureka)) { - this.jhipsterConfig.serviceDiscoveryType = EUREKA; - this.log.log(chalk.green('JHipster registry detected as the service discovery and configuration provider used by your apps')); - } else { - this.log.warn( - chalk.yellow('Unable to determine the service discovery and configuration provider to use from your apps configuration.'), - ); - this.log.verboseInfo('Your service discovery enabled apps:'); - serviceDiscoveryEnabledApps.forEach(app => { - this.log.verboseInfo(` -${app.baseName} (${app.serviceDiscoveryType})`); - }); - - await this.prompt( - [ - { - type: 'list', - name: 'serviceDiscoveryType', - message: 'Which Service Discovery registry and Configuration server would you like to use ?', - choices: [ - { - value: CONSUL, - name: 'Consul', - }, - { - value: EUREKA, - name: 'JHipster Registry', - }, - { - value: NO_SERVICE_DISCOVERY, - name: 'No Service Discovery and Configuration', - }, - ], - default: CONSUL, - }, - ], - this.config, - ); - } - if (this.jhipsterConfig.serviceDiscoveryType === EUREKA) { - await this.prompt( - [ - { - type: 'input', - name: 'adminPassword', - message: 'Enter the admin password used to secure the JHipster Registry', - default: 'admin', - validate: input => (input.length < 5 ? 'The password must have at least 5 characters' : true), - }, - ], - this.config, - ); - } - } -} diff --git a/generators/docker-compose/generator.spec.js b/generators/docker-compose/generator.spec.js new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/docker-compose/generator.spec.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/docker-compose/generator.spec.mjs b/generators/docker-compose/generator.spec.mjs deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/docker-compose/generator.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/docker-compose/index.mts b/generators/docker-compose/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/docker-compose/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/docker-compose/index.ts b/generators/docker-compose/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/docker-compose/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/docker/__snapshots__/generator.spec.mts.snap b/generators/docker/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/docker/__snapshots__/generator.spec.mts.snap rename to generators/docker/__snapshots__/generator.spec.ts.snap diff --git a/generators/docker/__test-support/service-discovery-matcher.mts b/generators/docker/__test-support/service-discovery-matcher.mts deleted file mode 100644 index ee67f91e1b7e..000000000000 --- a/generators/docker/__test-support/service-discovery-matcher.mts +++ /dev/null @@ -1,42 +0,0 @@ -import type { RunResult } from 'yeoman-test'; - -import { JAVA_DOCKER_DIR } from '../../generator-constants.mjs'; -import { matchWrittenConfig, matchWrittenFiles } from '../../../test/support/matcher.mjs'; - -const expectedEurekaFiles = () => { - return [ - `${JAVA_DOCKER_DIR}central-server-config/localhost-config/application.yml`, - `${JAVA_DOCKER_DIR}central-server-config/docker-config/application.yml`, - `${JAVA_DOCKER_DIR}jhipster-registry.yml`, - ]; -}; - -const desiredEurekaConfig = { - 'generator-jhipster': { - serviceDiscoveryType: 'eureka', - }, -}; - -const expectedConsulFiles = () => { - return [ - `${JAVA_DOCKER_DIR}central-server-config/application.yml`, - `${JAVA_DOCKER_DIR}consul.yml`, - `${JAVA_DOCKER_DIR}config/git2consul.json`, - ]; -}; - -const desiredConsulConfig = { - 'generator-jhipster': { - serviceDiscoveryType: 'consul', - }, -}; - -export const matchEureka = (resultGetter: () => RunResult, shouldMatch: boolean) => { - matchWrittenFiles('eureka', resultGetter, expectedEurekaFiles, shouldMatch); - matchWrittenConfig('eureka', resultGetter, desiredEurekaConfig, shouldMatch); -}; - -export const matchConsul = (resultGetter: () => RunResult, shouldMatch: boolean) => { - matchWrittenFiles('consul', resultGetter, expectedConsulFiles, shouldMatch); - matchWrittenConfig('consul', resultGetter, desiredConsulConfig, shouldMatch); -}; diff --git a/generators/docker/__test-support/service-discovery-matcher.ts b/generators/docker/__test-support/service-discovery-matcher.ts new file mode 100644 index 000000000000..5fc0adf05fae --- /dev/null +++ b/generators/docker/__test-support/service-discovery-matcher.ts @@ -0,0 +1,42 @@ +import type { RunResult } from 'yeoman-test'; + +import { JAVA_DOCKER_DIR } from '../../generator-constants.js'; +import { matchWrittenConfig, matchWrittenFiles } from '../../../test/support/matcher.js'; + +const expectedEurekaFiles = () => { + return [ + `${JAVA_DOCKER_DIR}central-server-config/localhost-config/application.yml`, + `${JAVA_DOCKER_DIR}central-server-config/docker-config/application.yml`, + `${JAVA_DOCKER_DIR}jhipster-registry.yml`, + ]; +}; + +const desiredEurekaConfig = { + 'generator-jhipster': { + serviceDiscoveryType: 'eureka', + }, +}; + +const expectedConsulFiles = () => { + return [ + `${JAVA_DOCKER_DIR}central-server-config/application.yml`, + `${JAVA_DOCKER_DIR}consul.yml`, + `${JAVA_DOCKER_DIR}config/git2consul.json`, + ]; +}; + +const desiredConsulConfig = { + 'generator-jhipster': { + serviceDiscoveryType: 'consul', + }, +}; + +export const matchEureka = (resultGetter: () => RunResult, shouldMatch: boolean) => { + matchWrittenFiles('eureka', resultGetter, expectedEurekaFiles, shouldMatch); + matchWrittenConfig('eureka', resultGetter, desiredEurekaConfig, shouldMatch); +}; + +export const matchConsul = (resultGetter: () => RunResult, shouldMatch: boolean) => { + matchWrittenFiles('consul', resultGetter, expectedConsulFiles, shouldMatch); + matchWrittenConfig('consul', resultGetter, desiredConsulConfig, shouldMatch); +}; diff --git a/generators/docker/constants.mjs b/generators/docker/constants.js similarity index 100% rename from generators/docker/constants.mjs rename to generators/docker/constants.js diff --git a/generators/docker/files.js b/generators/docker/files.js new file mode 100644 index 000000000000..2c815852c860 --- /dev/null +++ b/generators/docker/files.js @@ -0,0 +1,186 @@ +import { TEMPLATES_DOCKER_DIR } from '../generator-constants.js'; + +const renameTo = (ctx, filepath) => `${ctx.dockerServicesDir}${filepath}`.replace('/_eureka_', '').replace('/_consul_', ''); + +// eslint-disable-next-line import/prefer-default-export +export const dockerFiles = { + sqlDatabasesFiles: [ + { + condition: ctx => ctx.dockerServices.some(service => ['postgresql', 'mariadb', 'mysql', 'mssql'].includes(service)), + templates: [ + { + sourceFile: ctx => `${TEMPLATES_DOCKER_DIR}${ctx.prodDatabaseType}.yml`, + destinationFile: ctx => `${ctx.dockerServicesDir}${ctx.prodDatabaseType}.yml`, + }, + ], + }, + { + condition: ctx => ctx.dockerServices.includes('mysql'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + transform: false, + templates: ['config/mysql/my.cnf'], + }, + { + condition: ctx => ctx.dockerServices.includes('mariadb'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + transform: false, + templates: ['config/mariadb/my.cnf'], + }, + ], + couchbaseFiles: [ + { + condition: ctx => ctx.dockerServices.includes('couchbase'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['couchbase.yml', 'couchbase-cluster.yml', 'couchbase/Couchbase.Dockerfile', 'couchbase/scripts/configure-node.sh'], + }, + ], + mongodbFiles: [ + { + condition: ctx => ctx.dockerServices.includes('mongodb'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['mongodb.yml', 'mongodb-cluster.yml', 'mongodb/MongoDB.Dockerfile', 'mongodb/scripts/init_replicaset.js'], + }, + ], + cassandraFiles: [ + { + condition: ctx => ctx.dockerServices.includes('cassandra'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: [ + // docker-compose files + 'cassandra.yml', + 'cassandra-cluster.yml', + 'cassandra-migration.yml', + // dockerfiles + 'cassandra/Cassandra-Migration.Dockerfile', + // scripts + 'cassandra/scripts/autoMigrate.sh', + 'cassandra/scripts/execute-cql.sh', + ], + }, + ], + neo4jFiles: [ + { + condition: ctx => ctx.dockerServices.includes('neo4j'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['neo4j.yml'], + }, + ], + cacheProvideFiles: [ + { + condition: generator => generator.dockerServices.includes('hazelcast'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['hazelcast-management-center.yml'], + }, + { + condition: generator => generator.dockerServices.includes('memcached'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['memcached.yml'], + }, + { + condition: generator => generator.dockerServices.includes('redis'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['redis.yml', 'redis-cluster.yml', 'redis/Redis-Cluster.Dockerfile', 'redis/connectRedisCluster.sh'], + }, + ], + searchDiscoveryFiles: [ + { + condition: generator => generator.serviceDiscoveryAny, + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['central-server-config/README.md'], + }, + { + condition: generator => generator.dockerServices.includes('consul'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['consul.yml', 'config/git2consul.json', 'central-server-config/_consul_/application.yml'], + }, + { + condition: generator => generator.dockerServices.includes('eureka'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: [ + 'jhipster-registry.yml', + 'central-server-config/_eureka_/docker-config/application.yml', + 'central-server-config/_eureka_/localhost-config/application.yml', + ], + }, + ], + applicationFiles: [ + { + path: TEMPLATES_DOCKER_DIR, + condition: ctx => ctx.dockerServices.includes('app'), + renameTo, + templates: ['app.yml'], + }, + { + path: TEMPLATES_DOCKER_DIR, + condition: ctx => ctx.backendTypeJavaAny, + renameTo, + templates: [ + 'jhipster-control-center.yml', + 'monitoring.yml', + 'grafana/provisioning/dashboards/dashboard.yml', + 'grafana/provisioning/dashboards/JVM.json', + 'grafana/provisioning/datasources/datasource.yml', + ], + }, + { + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['sonar.yml', 'prometheus/prometheus.yml'], + }, + { + condition: generator => generator.dockerServices.includes('elasticsearch'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['elasticsearch.yml'], + }, + { + condition: generator => generator.dockerServices.includes('kafka'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['kafka.yml'], + }, + { + condition: generator => generator.dockerServices.includes('pulsar'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['pulsar.yml'], + }, + { + condition: generator => !!generator.dockerServices.includes('swagger-editor'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['swagger-editor.yml'], + }, + { + condition: generator => generator.dockerServices.includes('keycloak'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['keycloak.yml', 'realm-config/jhipster-realm.json'], + }, + { + condition: generator => generator.dockerServices.includes('keycloak'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + transform: false, + templates: ['realm-config/keycloak-health-check.sh'], + }, + { + condition: generator => generator.dockerServices.includes('zipkin'), + path: TEMPLATES_DOCKER_DIR, + renameTo, + templates: ['zipkin.yml'], + }, + ], +}; diff --git a/generators/docker/files.mjs b/generators/docker/files.mjs deleted file mode 100644 index 59641fd7252e..000000000000 --- a/generators/docker/files.mjs +++ /dev/null @@ -1,186 +0,0 @@ -import { TEMPLATES_DOCKER_DIR } from '../generator-constants.mjs'; - -const renameTo = (ctx, filepath) => `${ctx.dockerServicesDir}${filepath}`.replace('/_eureka_', '').replace('/_consul_', ''); - -// eslint-disable-next-line import/prefer-default-export -export const dockerFiles = { - sqlDatabasesFiles: [ - { - condition: ctx => ctx.dockerServices.some(service => ['postgresql', 'mariadb', 'mysql', 'mssql'].includes(service)), - templates: [ - { - sourceFile: ctx => `${TEMPLATES_DOCKER_DIR}${ctx.prodDatabaseType}.yml`, - destinationFile: ctx => `${ctx.dockerServicesDir}${ctx.prodDatabaseType}.yml`, - }, - ], - }, - { - condition: ctx => ctx.dockerServices.includes('mysql'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - transform: false, - templates: ['config/mysql/my.cnf'], - }, - { - condition: ctx => ctx.dockerServices.includes('mariadb'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - transform: false, - templates: ['config/mariadb/my.cnf'], - }, - ], - couchbaseFiles: [ - { - condition: ctx => ctx.dockerServices.includes('couchbase'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['couchbase.yml', 'couchbase-cluster.yml', 'couchbase/Couchbase.Dockerfile', 'couchbase/scripts/configure-node.sh'], - }, - ], - mongodbFiles: [ - { - condition: ctx => ctx.dockerServices.includes('mongodb'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['mongodb.yml', 'mongodb-cluster.yml', 'mongodb/MongoDB.Dockerfile', 'mongodb/scripts/init_replicaset.js'], - }, - ], - cassandraFiles: [ - { - condition: ctx => ctx.dockerServices.includes('cassandra'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: [ - // docker-compose files - 'cassandra.yml', - 'cassandra-cluster.yml', - 'cassandra-migration.yml', - // dockerfiles - 'cassandra/Cassandra-Migration.Dockerfile', - // scripts - 'cassandra/scripts/autoMigrate.sh', - 'cassandra/scripts/execute-cql.sh', - ], - }, - ], - neo4jFiles: [ - { - condition: ctx => ctx.dockerServices.includes('neo4j'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['neo4j.yml'], - }, - ], - cacheProvideFiles: [ - { - condition: generator => generator.dockerServices.includes('hazelcast'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['hazelcast-management-center.yml'], - }, - { - condition: generator => generator.dockerServices.includes('memcached'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['memcached.yml'], - }, - { - condition: generator => generator.dockerServices.includes('redis'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['redis.yml', 'redis-cluster.yml', 'redis/Redis-Cluster.Dockerfile', 'redis/connectRedisCluster.sh'], - }, - ], - searchDiscoveryFiles: [ - { - condition: generator => generator.serviceDiscoveryAny, - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['central-server-config/README.md'], - }, - { - condition: generator => generator.dockerServices.includes('consul'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['consul.yml', 'config/git2consul.json', 'central-server-config/_consul_/application.yml'], - }, - { - condition: generator => generator.dockerServices.includes('eureka'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: [ - 'jhipster-registry.yml', - 'central-server-config/_eureka_/docker-config/application.yml', - 'central-server-config/_eureka_/localhost-config/application.yml', - ], - }, - ], - applicationFiles: [ - { - path: TEMPLATES_DOCKER_DIR, - condition: ctx => ctx.dockerServices.includes('app'), - renameTo, - templates: ['app.yml'], - }, - { - path: TEMPLATES_DOCKER_DIR, - condition: ctx => ctx.backendTypeJavaAny, - renameTo, - templates: [ - 'jhipster-control-center.yml', - 'monitoring.yml', - 'grafana/provisioning/dashboards/dashboard.yml', - 'grafana/provisioning/dashboards/JVM.json', - 'grafana/provisioning/datasources/datasource.yml', - ], - }, - { - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['sonar.yml', 'prometheus/prometheus.yml'], - }, - { - condition: generator => generator.dockerServices.includes('elasticsearch'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['elasticsearch.yml'], - }, - { - condition: generator => generator.dockerServices.includes('kafka'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['kafka.yml'], - }, - { - condition: generator => generator.dockerServices.includes('pulsar'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['pulsar.yml'], - }, - { - condition: generator => !!generator.dockerServices.includes('swagger-editor'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['swagger-editor.yml'], - }, - { - condition: generator => generator.dockerServices.includes('keycloak'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['keycloak.yml', 'realm-config/jhipster-realm.json'], - }, - { - condition: generator => generator.dockerServices.includes('keycloak'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - transform: false, - templates: ['realm-config/keycloak-health-check.sh'], - }, - { - condition: generator => generator.dockerServices.includes('zipkin'), - path: TEMPLATES_DOCKER_DIR, - renameTo, - templates: ['zipkin.yml'], - }, - ], -}; diff --git a/generators/docker/generator.js b/generators/docker/generator.js new file mode 100644 index 000000000000..562c49270646 --- /dev/null +++ b/generators/docker/generator.js @@ -0,0 +1,318 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable camelcase */ +import * as _ from 'lodash-es'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { createDockerComposeFile, createDockerExtendedServices } from '../docker/support/index.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION_SERVER, GENERATOR_DOCKER } from '../generator-list.js'; +import { dockerFiles } from './files.js'; +import { SERVICE_COMPLETED_SUCCESSFULLY, SERVICE_HEALTHY } from './constants.js'; +import { stringHashCode, createFaker } from '../base/support/index.js'; +import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.js'; + +const { intersection } = _; + +export default class DockerGenerator extends BaseApplicationGenerator { + hasServicesFile = false; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_DOCKER); + } + + if (!this.delegateToBlueprint) { + // TODO depend on GENERATOR_BOOTSTRAP_APPLICATION_SERVER. + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); + } + } + + get loading() { + return this.asLoadingTaskGroup({ + loading({ applicationDefaults }) { + applicationDefaults({ + dockerServices: [], + }); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.asLoadingTaskGroup(this.delegateTasksToBlueprint(() => this.loading)); + } + + get preparing() { + return this.asPreparingTaskGroup({ + dockerServices({ application }) { + if (application.backendTypeSpringBoot) { + application.dockerServices.push('app'); + } + if (application.authenticationTypeOauth2) { + application.dockerServices.push('keycloak'); + } + if (application.searchEngineElasticsearch) { + application.dockerServices.push('elasticsearch'); + } + if (application.messageBrokerKafka || application.messageBrokerPulsar) { + application.dockerServices.push(application.messageBroker); + } + if (application.serviceDiscoveryConsul || application.serviceDiscoveryEureka) { + application.dockerServices.push(application.serviceDiscoveryType); + } + if (application.serviceDiscoveryAny || application.applicationTypeGateway || application.applicationTypeMicroservice) { + application.dockerServices.push('zipkin'); + } + if (application.enableSwaggerCodegen) { + application.dockerServices.push('swagger-editor'); + } + if (application.cacheProviderMemcached || application.cacheProviderRedis || application.cacheProviderHazelcast) { + application.dockerServices.push(application.cacheProvider); + } + if ( + application.databaseTypeCassandra || + application.databaseTypeCouchbase || + application.databaseTypeMongodb || + application.databaseTypeNeo4j + ) { + application.dockerServices.push(application.databaseType); + } + if ( + application.prodDatabaseTypePostgresql || + application.prodDatabaseTypeMariadb || + application.prodDatabaseTypeMysql || + application.prodDatabaseTypeMssql + ) { + application.dockerServices.push(application.prodDatabaseType); + } + }, + addAppServices({ application, source }) { + const writeInitialServicesFile = () => { + if (!this.hasServicesFile) { + const dockerFile = createDockerComposeFile(application.lowercaseBaseName); + this.writeDestination(`${application.dockerServicesDir}services.yml`, dockerFile); + } + this.hasServicesFile = true; + }; + + source.addDockerExtendedServiceToApplicationAndServices = (...services) => { + const extendedServices = createDockerExtendedServices(...services); + writeInitialServicesFile(); + this.mergeDestinationYaml(`${application.dockerServicesDir}services.yml`, extendedServices); + this.mergeDestinationYaml(`${application.dockerServicesDir}app.yml`, extendedServices); + }; + + source.addDockerExtendedServiceToServices = (...services) => { + const extendedServices = createDockerExtendedServices(...services); + writeInitialServicesFile(); + this.mergeDestinationYaml(`${application.dockerServicesDir}services.yml`, extendedServices); + }; + + source.addDockerDependencyToApplication = (...services) => { + this.mergeDestinationYaml(`${application.dockerServicesDir}app.yml`, { + services: { + app: { + depends_on: Object.fromEntries( + services.map(({ serviceName, condition }) => [ + serviceName, + { + condition, + }, + ]), + ), + }, + }, + }); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.preparing; + } + + get writing() { + return this.asWritingTaskGroup({ + async writeDockerFiles({ application }) { + if (application.authenticationTypeOauth2) { + const faker = await createFaker(); + faker.seed(stringHashCode(application.baseName)); + application.keycloakSecrets = Array.from(Array(6), () => faker.string.uuid()); + } + await this.writeFiles({ + sections: dockerFiles, + context: application, + }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.writing; + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + async dockerServices({ application, source }) { + if (application.dockerServices.includes('cassandra')) { + const serviceName = application.databaseType; + source.addDockerExtendedServiceToApplicationAndServices( + { serviceName }, + { serviceFile: './cassandra.yml', serviceName: 'cassandra-migration' }, + ); + source.addDockerDependencyToApplication( + { serviceName, condition: SERVICE_HEALTHY }, + { serviceName: 'cassandra-migration', condition: SERVICE_COMPLETED_SUCCESSFULLY }, + ); + } + + for (const serviceName of intersection( + ['couchbase', 'mongodb', 'neo4j', 'postgresql', 'mysql', 'mariadb', 'mssql', 'elasticsearch', 'keycloak'], + application.dockerServices, + )) { + source.addDockerExtendedServiceToApplicationAndServices({ serviceName }); + source.addDockerDependencyToApplication({ serviceName, condition: SERVICE_HEALTHY }); + } + + for (const serviceName of application.dockerServices.filter(service => ['redis', 'memcached', 'pulsar'].includes(service))) { + source.addDockerExtendedServiceToApplicationAndServices({ serviceName }); + } + + if (application.dockerServices.includes('eureka')) { + const depends_on = application.authenticationTypeOauth2 + ? { + keycloak: { + condition: SERVICE_HEALTHY, + }, + } + : undefined; + source.addDockerExtendedServiceToApplicationAndServices({ + serviceName: 'jhipster-registry', + additionalConfig: { + depends_on, + }, + }); + + source.addDockerDependencyToApplication({ serviceName: 'jhipster-registry', condition: SERVICE_HEALTHY }); + } + if (application.dockerServices.includes('consul')) { + source.addDockerExtendedServiceToApplicationAndServices( + { serviceName: 'consul' }, + { serviceFile: './consul.yml', serviceName: 'consul-config-loader' }, + ); + } + if (application.dockerServices.includes('kafka')) { + source.addDockerExtendedServiceToApplicationAndServices( + { serviceName: 'kafka' }, + { serviceFile: './kafka.yml', serviceName: 'zookeeper' }, + ); + } + }, + + packageJsonScripts({ application }) { + const scriptsStorage = this.packageJson.createStorage('scripts'); + const { databaseType, databaseTypeSql, prodDatabaseType, prodDatabaseTypeNo, prodDatabaseTypeOracle } = application; + let postServicesSleep; + + if (databaseTypeSql) { + if (prodDatabaseTypeNo || prodDatabaseTypeOracle) { + scriptsStorage.set( + 'docker:db:up', + `echo "Docker for db ${prodDatabaseType} not configured for application ${application.baseName}"`, + ); + } else { + const dockerFile = `${application.dockerServicesDir}${prodDatabaseType}.yml`; + scriptsStorage.set({ + 'docker:db:up': `docker compose -f ${dockerFile} up --wait`, + 'docker:db:down': `docker compose -f ${dockerFile} down -v`, + }); + } + } else { + const dockerFile = `${application.dockerServicesDir}${databaseType}.yml`; + if (this.fs.exists(this.destinationPath(dockerFile))) { + scriptsStorage.set({ + 'docker:db:up': `docker compose -f ${dockerFile} up --wait`, + 'docker:db:down': `docker compose -f ${dockerFile} down -v`, + }); + if (application.databaseTypeCassandra) { + // Wait for migration + postServicesSleep = 10; + } + } else { + scriptsStorage.set( + 'docker:db:up', + `echo "Docker for db ${databaseType} not configured for application ${application.baseName}"`, + ); + } + } + + ['keycloak', 'elasticsearch', 'kafka', 'consul', 'redis', 'memcached', 'jhipster-registry'].forEach(dockerConfig => { + const dockerFile = `${application.dockerServicesDir}${dockerConfig}.yml`; + if (this.fs.exists(this.destinationPath(dockerFile))) { + scriptsStorage.set(`docker:${dockerConfig}:up`, `docker compose -f ${dockerFile} up --wait`); + scriptsStorage.set(`docker:${dockerConfig}:down`, `docker compose -f ${dockerFile} down -v`); + } + }); + + if (postServicesSleep) { + scriptsStorage.set({ + 'postservices:up': `sleep ${postServicesSleep}`, + }); + } + if (this.hasServicesFile) { + scriptsStorage.set({ + 'services:up': `docker compose -f ${application.dockerServicesDir}services.yml up --wait`, + 'ci:e2e:teardown:docker': `docker compose -f ${application.dockerServicesDir}services.yml down -v && docker ps -a`, + }); + } + scriptsStorage.set({ + 'app:up': `docker compose -f ${application.dockerServicesDir}app.yml up --wait`, + 'ci:e2e:prepare:docker': 'npm run services:up --if-present && docker ps -a', + 'ci:e2e:prepare': 'npm run ci:e2e:prepare:docker', + 'ci:e2e:teardown': 'npm run ci:e2e:teardown:docker --if-present', + }); + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateToBlueprint ? {} : this.postWriting); + } + + /** + * @private + * Returns the JDBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getJDBCUrl(databaseType, options = {}) { + return getJdbcUrl(databaseType, options); + } + + /** + * @private + * Returns the R2DBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getR2DBCUrl(databaseType, options = {}) { + return getR2dbcUrl(databaseType, options); + } +} diff --git a/generators/docker/generator.mjs b/generators/docker/generator.mjs deleted file mode 100644 index 189240a7bb1a..000000000000 --- a/generators/docker/generator.mjs +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable camelcase */ -import * as _ from 'lodash-es'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { createDockerComposeFile, createDockerExtendedServices } from '../docker/support/index.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION_SERVER, GENERATOR_DOCKER } from '../generator-list.mjs'; -import { dockerFiles } from './files.mjs'; -import { SERVICE_COMPLETED_SUCCESSFULLY, SERVICE_HEALTHY } from './constants.mjs'; -import { stringHashCode, createFaker } from '../base/support/index.mjs'; -import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.mjs'; - -const { intersection } = _; - -export default class DockerGenerator extends BaseApplicationGenerator { - hasServicesFile = false; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_DOCKER); - } - - if (!this.delegateToBlueprint) { - // TODO depend on GENERATOR_BOOTSTRAP_APPLICATION_SERVER. - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); - } - } - - get loading() { - return this.asLoadingTaskGroup({ - loading({ applicationDefaults }) { - applicationDefaults({ - dockerServices: [], - }); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.asLoadingTaskGroup(this.delegateTasksToBlueprint(() => this.loading)); - } - - get preparing() { - return this.asPreparingTaskGroup({ - dockerServices({ application }) { - if (application.backendTypeSpringBoot) { - application.dockerServices.push('app'); - } - if (application.authenticationTypeOauth2) { - application.dockerServices.push('keycloak'); - } - if (application.searchEngineElasticsearch) { - application.dockerServices.push('elasticsearch'); - } - if (application.messageBrokerKafka || application.messageBrokerPulsar) { - application.dockerServices.push(application.messageBroker); - } - if (application.serviceDiscoveryConsul || application.serviceDiscoveryEureka) { - application.dockerServices.push(application.serviceDiscoveryType); - } - if (application.serviceDiscoveryAny || application.applicationTypeGateway || application.applicationTypeMicroservice) { - application.dockerServices.push('zipkin'); - } - if (application.enableSwaggerCodegen) { - application.dockerServices.push('swagger-editor'); - } - if (application.cacheProviderMemcached || application.cacheProviderRedis || application.cacheProviderHazelcast) { - application.dockerServices.push(application.cacheProvider); - } - if ( - application.databaseTypeCassandra || - application.databaseTypeCouchbase || - application.databaseTypeMongodb || - application.databaseTypeNeo4j - ) { - application.dockerServices.push(application.databaseType); - } - if ( - application.prodDatabaseTypePostgresql || - application.prodDatabaseTypeMariadb || - application.prodDatabaseTypeMysql || - application.prodDatabaseTypeMssql - ) { - application.dockerServices.push(application.prodDatabaseType); - } - }, - addAppServices({ application, source }) { - const writeInitialServicesFile = () => { - if (!this.hasServicesFile) { - const dockerFile = createDockerComposeFile(application.lowercaseBaseName); - this.writeDestination(`${application.dockerServicesDir}services.yml`, dockerFile); - } - this.hasServicesFile = true; - }; - - source.addDockerExtendedServiceToApplicationAndServices = (...services) => { - const extendedServices = createDockerExtendedServices(...services); - writeInitialServicesFile(); - this.mergeDestinationYaml(`${application.dockerServicesDir}services.yml`, extendedServices); - this.mergeDestinationYaml(`${application.dockerServicesDir}app.yml`, extendedServices); - }; - - source.addDockerExtendedServiceToServices = (...services) => { - const extendedServices = createDockerExtendedServices(...services); - writeInitialServicesFile(); - this.mergeDestinationYaml(`${application.dockerServicesDir}services.yml`, extendedServices); - }; - - source.addDockerDependencyToApplication = (...services) => { - this.mergeDestinationYaml(`${application.dockerServicesDir}app.yml`, { - services: { - app: { - depends_on: Object.fromEntries( - services.map(({ serviceName, condition }) => [ - serviceName, - { - condition, - }, - ]), - ), - }, - }, - }); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.preparing; - } - - get writing() { - return this.asWritingTaskGroup({ - async writeDockerFiles({ application }) { - if (application.authenticationTypeOauth2) { - const faker = await createFaker(); - faker.seed(stringHashCode(application.baseName)); - application.keycloakSecrets = Array.from(Array(6), () => faker.string.uuid()); - } - await this.writeFiles({ - sections: dockerFiles, - context: application, - }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.writing; - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - async dockerServices({ application, source }) { - if (application.dockerServices.includes('cassandra')) { - const serviceName = application.databaseType; - source.addDockerExtendedServiceToApplicationAndServices( - { serviceName }, - { serviceFile: './cassandra.yml', serviceName: 'cassandra-migration' }, - ); - source.addDockerDependencyToApplication( - { serviceName, condition: SERVICE_HEALTHY }, - { serviceName: 'cassandra-migration', condition: SERVICE_COMPLETED_SUCCESSFULLY }, - ); - } - - for (const serviceName of intersection( - ['couchbase', 'mongodb', 'neo4j', 'postgresql', 'mysql', 'mariadb', 'mssql', 'elasticsearch', 'keycloak'], - application.dockerServices, - )) { - source.addDockerExtendedServiceToApplicationAndServices({ serviceName }); - source.addDockerDependencyToApplication({ serviceName, condition: SERVICE_HEALTHY }); - } - - for (const serviceName of application.dockerServices.filter(service => ['redis', 'memcached', 'pulsar'].includes(service))) { - source.addDockerExtendedServiceToApplicationAndServices({ serviceName }); - } - - if (application.dockerServices.includes('eureka')) { - const depends_on = application.authenticationTypeOauth2 - ? { - keycloak: { - condition: SERVICE_HEALTHY, - }, - } - : undefined; - source.addDockerExtendedServiceToApplicationAndServices({ - serviceName: 'jhipster-registry', - additionalConfig: { - depends_on, - }, - }); - - source.addDockerDependencyToApplication({ serviceName: 'jhipster-registry', condition: SERVICE_HEALTHY }); - } - if (application.dockerServices.includes('consul')) { - source.addDockerExtendedServiceToApplicationAndServices( - { serviceName: 'consul' }, - { serviceFile: './consul.yml', serviceName: 'consul-config-loader' }, - ); - } - if (application.dockerServices.includes('kafka')) { - source.addDockerExtendedServiceToApplicationAndServices( - { serviceName: 'kafka' }, - { serviceFile: './kafka.yml', serviceName: 'zookeeper' }, - ); - } - }, - - packageJsonScripts({ application }) { - const scriptsStorage = this.packageJson.createStorage('scripts'); - const { databaseType, databaseTypeSql, prodDatabaseType, prodDatabaseTypeNo, prodDatabaseTypeOracle } = application; - let postServicesSleep; - - if (databaseTypeSql) { - if (prodDatabaseTypeNo || prodDatabaseTypeOracle) { - scriptsStorage.set( - 'docker:db:up', - `echo "Docker for db ${prodDatabaseType} not configured for application ${application.baseName}"`, - ); - } else { - const dockerFile = `${application.dockerServicesDir}${prodDatabaseType}.yml`; - scriptsStorage.set({ - 'docker:db:up': `docker compose -f ${dockerFile} up --wait`, - 'docker:db:down': `docker compose -f ${dockerFile} down -v`, - }); - } - } else { - const dockerFile = `${application.dockerServicesDir}${databaseType}.yml`; - if (this.fs.exists(this.destinationPath(dockerFile))) { - scriptsStorage.set({ - 'docker:db:up': `docker compose -f ${dockerFile} up --wait`, - 'docker:db:down': `docker compose -f ${dockerFile} down -v`, - }); - if (application.databaseTypeCassandra) { - // Wait for migration - postServicesSleep = 10; - } - } else { - scriptsStorage.set( - 'docker:db:up', - `echo "Docker for db ${databaseType} not configured for application ${application.baseName}"`, - ); - } - } - - ['keycloak', 'elasticsearch', 'kafka', 'consul', 'redis', 'memcached', 'jhipster-registry'].forEach(dockerConfig => { - const dockerFile = `${application.dockerServicesDir}${dockerConfig}.yml`; - if (this.fs.exists(this.destinationPath(dockerFile))) { - scriptsStorage.set(`docker:${dockerConfig}:up`, `docker compose -f ${dockerFile} up --wait`); - scriptsStorage.set(`docker:${dockerConfig}:down`, `docker compose -f ${dockerFile} down -v`); - } - }); - - if (postServicesSleep) { - scriptsStorage.set({ - 'postservices:up': `sleep ${postServicesSleep}`, - }); - } - if (this.hasServicesFile) { - scriptsStorage.set({ - 'services:up': `docker compose -f ${application.dockerServicesDir}services.yml up --wait`, - 'ci:e2e:teardown:docker': `docker compose -f ${application.dockerServicesDir}services.yml down -v && docker ps -a`, - }); - } - scriptsStorage.set({ - 'app:up': `docker compose -f ${application.dockerServicesDir}app.yml up --wait`, - 'ci:e2e:prepare:docker': 'npm run services:up --if-present && docker ps -a', - 'ci:e2e:prepare': 'npm run ci:e2e:prepare:docker', - 'ci:e2e:teardown': 'npm run ci:e2e:teardown:docker --if-present', - }); - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateToBlueprint ? {} : this.postWriting); - } - - /** - * @private - * Returns the JDBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getJDBCUrl(databaseType, options = {}) { - return getJdbcUrl(databaseType, options); - } - - /** - * @private - * Returns the R2DBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getR2DBCUrl(databaseType, options = {}) { - return getR2dbcUrl(databaseType, options); - } -} diff --git a/generators/docker/generator.spec.mts b/generators/docker/generator.spec.mts deleted file mode 100644 index 54f9d229d785..000000000000 --- a/generators/docker/generator.spec.mts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { buildSamplesFromMatrix, extendFilteredMatrix, extendMatrix } from '../../test/support/matrix-utils.mjs'; -import { defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import { matchElasticSearchDocker } from '../spring-data-elasticsearch/__test-support/elastic-search-matcher.mjs'; -import { matchConsul, matchEureka } from './__test-support/service-discovery-matcher.mjs'; - -import { databaseTypes, searchEngineTypes, serviceDiscoveryTypes, cacheTypes } from '../../jdl/jhipster/index.mjs'; -import { buildServerMatrix } from '../../test/support/server-samples.mjs'; -import { MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_NO, MESSAGE_BROKER_PULSAR } from '../server/options/message-broker.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const { CASSANDRA, COUCHBASE, MONGODB, NEO4J, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; -const { NO: NO_SEARCH_ENGINE, ELASTICSEARCH } = searchEngineTypes; -const { NO: NO_SERVICE_DISCOVERY, EUREKA, CONSUL } = serviceDiscoveryTypes; -const { NO: NO_CACHE, REDIS, MEMCACHED, HAZELCAST } = cacheTypes; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const NO_SQL = [CASSANDRA, COUCHBASE, MONGODB, NEO4J]; - -let matrix = buildServerMatrix(); - -matrix = extendMatrix(matrix, { - prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE, ...NO_SQL], -}); - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -Object.entries(matrix).forEach(([_name, config]) => { - if (NO_SQL.includes(config.prodDatabaseType)) { - config.databaseType = config.prodDatabaseType; - delete config.prodDatabaseType; - } -}); - -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], -}); - -matrix = extendFilteredMatrix(matrix, ({ reactive }) => !reactive, { - cacheProvider: [NO_CACHE, REDIS, MEMCACHED, HAZELCAST], -}); - -const testSamples = buildSamplesFromMatrix(matrix); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { searchEngine, serviceDiscoveryType } = sampleConfig; - - describe(name, () => { - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig(sampleConfig); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('should match app.yml snapshot', () => { - expect(runResult.getSnapshot('**/app.yml')).toMatchSnapshot(); - }); - describe('searchEngine', () => { - const elasticsearch = searchEngine === ELASTICSEARCH; - matchElasticSearchDocker(() => runResult, elasticsearch); - }); - describe('serviceDiscoveryType', () => { - matchEureka(() => runResult, serviceDiscoveryType === EUREKA); - matchConsul(() => runResult, serviceDiscoveryType === CONSUL); - }); - }); - - describe(`custom path for ${name}`, () => { - before(async () => { - await helpers - .run(generatorFile) - .withSharedApplication({ - dockerServicesDir: 'foo/', - }) - .withJHipsterConfig(sampleConfig); - }); - - it('should not generate any file inside src/', () => { - expect(Object.keys(runResult.getStateSnapshot('**/src/**')).length).toBe(0); - }); - }); - }); -}); diff --git a/generators/docker/generator.spec.ts b/generators/docker/generator.spec.ts new file mode 100644 index 000000000000..bcff807b86fc --- /dev/null +++ b/generators/docker/generator.spec.ts @@ -0,0 +1,122 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { buildSamplesFromMatrix, extendFilteredMatrix, extendMatrix } from '../../test/support/matrix-utils.js'; +import { defaultHelpers as helpers, runResult } from '../../test/support/index.js'; +import { matchElasticSearchDocker } from '../spring-data-elasticsearch/__test-support/elastic-search-matcher.js'; +import { matchConsul, matchEureka } from './__test-support/service-discovery-matcher.js'; + +import { databaseTypes, searchEngineTypes, serviceDiscoveryTypes, cacheTypes } from '../../jdl/jhipster/index.js'; +import { buildServerMatrix } from '../../test/support/server-samples.js'; +import { MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_NO, MESSAGE_BROKER_PULSAR } from '../server/options/message-broker.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const { CASSANDRA, COUCHBASE, MONGODB, NEO4J, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; +const { NO: NO_SEARCH_ENGINE, ELASTICSEARCH } = searchEngineTypes; +const { NO: NO_SERVICE_DISCOVERY, EUREKA, CONSUL } = serviceDiscoveryTypes; +const { NO: NO_CACHE, REDIS, MEMCACHED, HAZELCAST } = cacheTypes; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const NO_SQL = [CASSANDRA, COUCHBASE, MONGODB, NEO4J]; + +let matrix = buildServerMatrix(); + +matrix = extendMatrix(matrix, { + prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE, ...NO_SQL], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +Object.entries(matrix).forEach(([_name, config]) => { + if (NO_SQL.includes(config.prodDatabaseType)) { + config.databaseType = config.prodDatabaseType; + delete config.prodDatabaseType; + } +}); + +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], +}); + +matrix = extendFilteredMatrix(matrix, ({ reactive }) => !reactive, { + cacheProvider: [NO_CACHE, REDIS, MEMCACHED, HAZELCAST], +}); + +const testSamples = buildSamplesFromMatrix(matrix); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { searchEngine, serviceDiscoveryType } = sampleConfig; + + describe(name, () => { + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig(sampleConfig); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('should match app.yml snapshot', () => { + expect(runResult.getSnapshot('**/app.yml')).toMatchSnapshot(); + }); + describe('searchEngine', () => { + const elasticsearch = searchEngine === ELASTICSEARCH; + matchElasticSearchDocker(() => runResult, elasticsearch); + }); + describe('serviceDiscoveryType', () => { + matchEureka(() => runResult, serviceDiscoveryType === EUREKA); + matchConsul(() => runResult, serviceDiscoveryType === CONSUL); + }); + }); + + describe(`custom path for ${name}`, () => { + before(async () => { + await helpers + .run(generatorFile) + .withSharedApplication({ + dockerServicesDir: 'foo/', + }) + .withJHipsterConfig(sampleConfig); + }); + + it('should not generate any file inside src/', () => { + expect(Object.keys(runResult.getStateSnapshot('**/src/**')).length).toBe(0); + }); + }); + }); +}); diff --git a/generators/docker/index.mts b/generators/docker/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/docker/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/docker/index.ts b/generators/docker/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/docker/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/docker/support/check-docker.js b/generators/docker/support/check-docker.js new file mode 100644 index 000000000000..448d0ea0ea41 --- /dev/null +++ b/generators/docker/support/check-docker.js @@ -0,0 +1,65 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; + +/** + * Check that Docker exists. + * @this {import('../../base-core/index.js').default} + */ +export const checkDocker = async function () { + if (this.abort || this.skipChecks) return; + const ret = await this.spawnCommand('docker -v', { reject: false, stdio: 'pipe' }); + if (ret.exitCode !== 0) { + this.log.error( + chalk.red( + `Docker version 1.10.0 or later is not installed on your computer. + Read http://docs.docker.com/engine/installation/#installation +`, + ), + ); + throw new Error(); + } + + const dockerVersion = ret.stdout.split(' ')[2].replace(/,/g, ''); + const dockerVersionMajor = dockerVersion.split('.')[0]; + const dockerVersionMinor = dockerVersion.split('.')[1]; + if (dockerVersionMajor < 1 || (dockerVersionMajor === 1 && dockerVersionMinor < 10)) { + this.log.error( + chalk.red( + `Docker version 1.10.0 or later is not installed on your computer. + Docker version found: ${dockerVersion} + Read http://docs.docker.com/engine/installation/#installation`, + ), + ); + throw new Error(); + } else { + this.log.verboseInfo('Docker is installed'); + } +}; + +/** + * This is the Generator base class. + * This provides all the public API methods exposed via the module system. + * The public API methods can be directly utilized as well using commonJS require. + * + * The method signatures in public API should not be changed without a major version change + */ +export default { + checkDocker, +}; diff --git a/generators/docker/support/check-docker.mjs b/generators/docker/support/check-docker.mjs deleted file mode 100644 index b5ca8740d006..000000000000 --- a/generators/docker/support/check-docker.mjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; - -/** - * Check that Docker exists. - * @this {import('../../base-core/index.mjs').default} - */ -export const checkDocker = async function () { - if (this.abort || this.skipChecks) return; - const ret = await this.spawnCommand('docker -v', { reject: false, stdio: 'pipe' }); - if (ret.exitCode !== 0) { - this.log.error( - chalk.red( - `Docker version 1.10.0 or later is not installed on your computer. - Read http://docs.docker.com/engine/installation/#installation -`, - ), - ); - throw new Error(); - } - - const dockerVersion = ret.stdout.split(' ')[2].replace(/,/g, ''); - const dockerVersionMajor = dockerVersion.split('.')[0]; - const dockerVersionMinor = dockerVersion.split('.')[1]; - if (dockerVersionMajor < 1 || (dockerVersionMajor === 1 && dockerVersionMinor < 10)) { - this.log.error( - chalk.red( - `Docker version 1.10.0 or later is not installed on your computer. - Docker version found: ${dockerVersion} - Read http://docs.docker.com/engine/installation/#installation`, - ), - ); - throw new Error(); - } else { - this.log.verboseInfo('Docker is installed'); - } -}; - -/** - * This is the Generator base class. - * This provides all the public API methods exposed via the module system. - * The public API methods can be directly utilized as well using commonJS require. - * - * The method signatures in public API should not be changed without a major version change - */ -export default { - checkDocker, -}; diff --git a/generators/docker/support/docker-compose-file.mjs b/generators/docker/support/docker-compose-file.js similarity index 100% rename from generators/docker/support/docker-compose-file.mjs rename to generators/docker/support/docker-compose-file.js diff --git a/generators/docker/support/docker-compose-file.spec.mts b/generators/docker/support/docker-compose-file.spec.mts deleted file mode 100644 index 888a02605f86..000000000000 --- a/generators/docker/support/docker-compose-file.spec.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect } from 'esmocha'; -import { createDockerComposeFile } from './docker-compose-file.mjs'; - -describe('generator - docker - docker-compose-file', () => { - describe('createDockerComposeFile', () => { - it('should return a docker compose file header with default name', () => { - expect(createDockerComposeFile()).toMatchInlineSnapshot(` -"# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -name: jhipster -" -`); - }); - - it('should return a docker compose file header with custom name', () => { - expect(createDockerComposeFile('customService')).toMatchInlineSnapshot(` -"# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -name: customService -" -`); - }); - }); -}); diff --git a/generators/docker/support/docker-compose-file.spec.ts b/generators/docker/support/docker-compose-file.spec.ts new file mode 100644 index 000000000000..12515b74dc76 --- /dev/null +++ b/generators/docker/support/docker-compose-file.spec.ts @@ -0,0 +1,22 @@ +import { expect } from 'esmocha'; +import { createDockerComposeFile } from './docker-compose-file.js'; + +describe('generator - docker - docker-compose-file', () => { + describe('createDockerComposeFile', () => { + it('should return a docker compose file header with default name', () => { + expect(createDockerComposeFile()).toMatchInlineSnapshot(` +"# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: jhipster +" +`); + }); + + it('should return a docker compose file header with custom name', () => { + expect(createDockerComposeFile('customService')).toMatchInlineSnapshot(` +"# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: customService +" +`); + }); + }); +}); diff --git a/generators/docker/support/index.mts b/generators/docker/support/index.mts deleted file mode 100644 index 01834fe61c79..000000000000 --- a/generators/docker/support/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './check-docker.mjs'; -export * from './docker-compose-file.mjs'; diff --git a/generators/docker/support/index.ts b/generators/docker/support/index.ts new file mode 100644 index 000000000000..79825f1c4aaf --- /dev/null +++ b/generators/docker/support/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './check-docker.js'; +export * from './docker-compose-file.js'; diff --git a/generators/docker/utils.mjs b/generators/docker/utils.js similarity index 100% rename from generators/docker/utils.mjs rename to generators/docker/utils.js diff --git a/generators/entities/__snapshots__/generator.spec.mts.snap b/generators/entities/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/entities/__snapshots__/generator.spec.mts.snap rename to generators/entities/__snapshots__/generator.spec.ts.snap diff --git a/generators/entities/command.mts b/generators/entities/command.mts deleted file mode 100644 index acd240227160..000000000000 --- a/generators/entities/command.mts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; -import serverCommand from '../server/command.mjs'; - -const command: JHipsterCommandDefinition = { - arguments: { - entities: { - type: Array, - required: false, - description: 'Entities to regenerate.', - }, - }, - options: { - skipDbChangelog: serverCommand.options!.skipDbChangelog, - }, -}; - -export default command; diff --git a/generators/entities/command.ts b/generators/entities/command.ts new file mode 100644 index 000000000000..eff816a5c5d6 --- /dev/null +++ b/generators/entities/command.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; +import serverCommand from '../server/command.js'; + +const command: JHipsterCommandDefinition = { + arguments: { + entities: { + type: Array, + required: false, + description: 'Entities to regenerate.', + }, + }, + options: { + skipDbChangelog: serverCommand.options!.skipDbChangelog, + }, +}; + +export default command; diff --git a/generators/entities/generator.js b/generators/entities/generator.js new file mode 100644 index 000000000000..c5935c0c9417 --- /dev/null +++ b/generators/entities/generator.js @@ -0,0 +1,74 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_ENTITIES, GENERATOR_APP } from '../generator-list.js'; +import command from './command.js'; + +export default class EntitiesGenerator extends BaseApplicationGenerator { + entities; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_ENTITIES); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadArguments() { + this.jhipsterConfig.entities = this.jhipsterConfig.entities || []; + + this.parseJHipsterArguments(command.arguments); + if (!this.entities || this.entities.length === 0) { + this.entities = this.getExistingEntityNames(); + } else { + for (const entity of this.entities) { + if (!this.jhipsterConfig.entities.includes(entity)) { + this.jhipsterConfig.entities.push(entity); + } + } + } + if (this.entities) { + this.log.verboseInfo('Generating entities', ...this.entities); + } + }, + loadOptions() { + this.parseJHipsterOptions(command.options); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get composing() { + return { + async composeApp() { + await this.composeWithJHipster(GENERATOR_APP, { + generatorOptions: { skipPriorities: ['writing', 'postWriting'], entities: this.entities }, + }); + }, + }; + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } +} diff --git a/generators/entities/generator.mjs b/generators/entities/generator.mjs deleted file mode 100644 index e172b5d1e600..000000000000 --- a/generators/entities/generator.mjs +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_ENTITIES, GENERATOR_APP } from '../generator-list.mjs'; -import command from './command.mjs'; - -export default class EntitiesGenerator extends BaseApplicationGenerator { - entities; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_ENTITIES); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadArguments() { - this.jhipsterConfig.entities = this.jhipsterConfig.entities || []; - - this.parseJHipsterArguments(command.arguments); - if (!this.entities || this.entities.length === 0) { - this.entities = this.getExistingEntityNames(); - } else { - for (const entity of this.entities) { - if (!this.jhipsterConfig.entities.includes(entity)) { - this.jhipsterConfig.entities.push(entity); - } - } - } - if (this.entities) { - this.log.verboseInfo('Generating entities', ...this.entities); - } - }, - loadOptions() { - this.parseJHipsterOptions(command.options); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get composing() { - return { - async composeApp() { - await this.composeWithJHipster(GENERATOR_APP, { - generatorOptions: { skipPriorities: ['writing', 'postWriting'], entities: this.entities }, - }); - }, - }; - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } -} diff --git a/generators/entities/generator.spec.mts b/generators/entities/generator.spec.mts deleted file mode 100644 index 8ed06c5d0123..000000000000 --- a/generators/entities/generator.spec.mts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR, CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; -import { skipPrettierHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; - -const { snakeCase } = lodash; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const generator = basename(__dirname); -const generatorPath = `${__dirname}/index.mts`; - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - context('regenerating', () => { - const entities = [ - { - name: 'Foo', - changelogDate: '20160926101210', - }, - { - name: 'Bar', - changelogDate: '20160926101211', - }, - { - name: 'Skip', - changelogDate: '20160926101212', - }, - ]; - - const fooFiles = [ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, - `${CLIENT_MAIN_SRC_DIR}app/entities/foo/foo.model.ts`, - ]; - - const barFiles = [ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101211_added_entity_Bar.xml`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, - `${CLIENT_MAIN_SRC_DIR}app/entities/bar/bar.model.ts`, - ]; - - const skipFiles = [ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101212_added_entity_Skip.xml`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Skip.java`, - `${CLIENT_MAIN_SRC_DIR}app/entities/skip/skip.model.ts`, - ]; - - describe('some entities', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({}, entities) - .withArguments(['Foo', 'Bar']) - .withOptions({ - regenerate: true, - force: true, - ignoreNeedlesError: true, - }) - .withMockedSource(); - }); - - it('should match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - it('should match source calls', () => { - expect(runResult.sourceCallsArg).toMatchSnapshot(); - }); - - it('should create files for entity Foo', () => { - runResult.assertFile(fooFiles); - }); - - it('should create files for the entity Bar', () => { - runResult.assertFile(barFiles); - }); - - it('should not create files for the entity Skip', () => { - runResult.assertNoFile(skipFiles); - }); - }); - - describe('all entities', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({}, entities) - .withOptions({ - regenerate: true, - force: true, - ignoreNeedlesError: true, - }) - .withMockedSource(); - }); - - it('should match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - it('should match source calls', () => { - expect(runResult.sourceCallsArg).toMatchSnapshot(); - }); - - it('should create files for entity Foo', () => { - runResult.assertFile(fooFiles); - }); - - it('should create files for the entity Bar', () => { - runResult.assertFile(barFiles); - }); - - it('should create files for the entity Skip', () => { - runResult.assertFile(skipFiles); - }); - }); - }); -}); diff --git a/generators/entities/generator.spec.ts b/generators/entities/generator.spec.ts new file mode 100644 index 000000000000..62bd51f9516a --- /dev/null +++ b/generators/entities/generator.spec.ts @@ -0,0 +1,145 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR, CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; +import { skipPrettierHelpers as helpers, result as runResult } from '../../test/support/index.js'; + +const { snakeCase } = lodash; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const generator = basename(__dirname); +const generatorPath = `${__dirname}/index.ts`; + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + context('regenerating', () => { + const entities = [ + { + name: 'Foo', + changelogDate: '20160926101210', + }, + { + name: 'Bar', + changelogDate: '20160926101211', + }, + { + name: 'Skip', + changelogDate: '20160926101212', + }, + ]; + + const fooFiles = [ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, + `${CLIENT_MAIN_SRC_DIR}app/entities/foo/foo.model.ts`, + ]; + + const barFiles = [ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101211_added_entity_Bar.xml`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, + `${CLIENT_MAIN_SRC_DIR}app/entities/bar/bar.model.ts`, + ]; + + const skipFiles = [ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101212_added_entity_Skip.xml`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Skip.java`, + `${CLIENT_MAIN_SRC_DIR}app/entities/skip/skip.model.ts`, + ]; + + describe('some entities', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({}, entities) + .withArguments(['Foo', 'Bar']) + .withOptions({ + regenerate: true, + force: true, + ignoreNeedlesError: true, + }) + .withMockedSource(); + }); + + it('should match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + it('should match source calls', () => { + expect(runResult.sourceCallsArg).toMatchSnapshot(); + }); + + it('should create files for entity Foo', () => { + runResult.assertFile(fooFiles); + }); + + it('should create files for the entity Bar', () => { + runResult.assertFile(barFiles); + }); + + it('should not create files for the entity Skip', () => { + runResult.assertNoFile(skipFiles); + }); + }); + + describe('all entities', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({}, entities) + .withOptions({ + regenerate: true, + force: true, + ignoreNeedlesError: true, + }) + .withMockedSource(); + }); + + it('should match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + it('should match source calls', () => { + expect(runResult.sourceCallsArg).toMatchSnapshot(); + }); + + it('should create files for entity Foo', () => { + runResult.assertFile(fooFiles); + }); + + it('should create files for the entity Bar', () => { + runResult.assertFile(barFiles); + }); + + it('should create files for the entity Skip', () => { + runResult.assertFile(skipFiles); + }); + }); + }); +}); diff --git a/generators/entities/index.mts b/generators/entities/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/entities/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/entities/index.ts b/generators/entities/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/entities/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/entity/__snapshots__/single-entity.spec.mts.snap b/generators/entity/__snapshots__/single-entity.spec.ts.snap similarity index 100% rename from generators/entity/__snapshots__/single-entity.spec.mts.snap rename to generators/entity/__snapshots__/single-entity.spec.ts.snap diff --git a/generators/entity/command.mts b/generators/entity/command.mts deleted file mode 100644 index a514cf62dce6..000000000000 --- a/generators/entity/command.mts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - arguments: { - name: { - type: String, - required: true, - description: 'Entity name', - }, - }, - options: { - regenerate: { - description: 'Regenerate the entity without presenting an option to update it', - type: Boolean, - default: false, - }, - tableName: { - description: 'Specify table name that will be used by the entity', - type: String, - }, - fluentMethods: { - description: 'Generate fluent methods in entity beans to allow chained object construction', - type: Boolean, - }, - angularSuffix: { - description: 'Use a suffix to generate Angular routes and files, to avoid name clashes', - type: String, - }, - clientRootFolder: { - description: - 'Use a root folder name for entities on client side. By default its empty for monoliths and name of the microservice for gateways', - type: String, - }, - skipUiGrouping: { - description: 'Disables the UI grouping behaviour for entity client side code', - type: Boolean, - }, - skipDbChangelog: { - description: 'Skip the generation of database changelog (liquibase for sql databases)', - type: Boolean, - }, - singleEntity: { - description: 'Regenerate only a single entity, relationships can be not correctly generated', - type: Boolean, - }, - }, -}; - -export default command; diff --git a/generators/entity/command.ts b/generators/entity/command.ts new file mode 100644 index 000000000000..5af4e3f93e2c --- /dev/null +++ b/generators/entity/command.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + arguments: { + name: { + type: String, + required: true, + description: 'Entity name', + }, + }, + options: { + regenerate: { + description: 'Regenerate the entity without presenting an option to update it', + type: Boolean, + default: false, + }, + tableName: { + description: 'Specify table name that will be used by the entity', + type: String, + }, + fluentMethods: { + description: 'Generate fluent methods in entity beans to allow chained object construction', + type: Boolean, + }, + angularSuffix: { + description: 'Use a suffix to generate Angular routes and files, to avoid name clashes', + type: String, + }, + clientRootFolder: { + description: + 'Use a root folder name for entities on client side. By default its empty for monoliths and name of the microservice for gateways', + type: String, + }, + skipUiGrouping: { + description: 'Disables the UI grouping behaviour for entity client side code', + type: Boolean, + }, + skipDbChangelog: { + description: 'Skip the generation of database changelog (liquibase for sql databases)', + type: Boolean, + }, + singleEntity: { + description: 'Regenerate only a single entity, relationships can be not correctly generated', + type: Boolean, + }, + }, +}; + +export default command; diff --git a/generators/entity/database-changelog.spec.mts b/generators/entity/database-changelog.spec.mts deleted file mode 100644 index 551005616556..000000000000 --- a/generators/entity/database-changelog.spec.mts +++ /dev/null @@ -1,53 +0,0 @@ -import { defaultHelpers as helpers, runResult, getGenerator } from '../../test/support/index.mjs'; -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import BaseApplicationGenerator from '../base-application/generator.mjs'; - -class MockedLanguagesGenerator extends BaseApplicationGenerator { - get [BaseApplicationGenerator.PREPARING]() { - return { - mockTranslations({ control }) { - control.getWebappTranslation = () => 'translations'; - }, - }; - } -} - -const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; - -describe('generator - entity database changelogs', () => { - context('when regenerating the entity', () => { - describe('with cassandra database', () => { - before(async () => { - await helpers - .run(getGenerator('entity')) - .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) - .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo]) - .withArguments(['Foo']) - .withOptions({ regenerate: true, force: true, ignoreNeedlesError: true }); - }); - - it('should create database changelog for the entity', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`]); - }); - }); - describe('with gateway application type', () => { - before(async () => { - await helpers - .run(getGenerator('entity')) - .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) - .withJHipsterConfig({ applicationType: 'gateway' }, [ - { ...entityFoo, microservicePath: 'microservice1', microserviceName: 'microservice1' }, - ]) - .withArguments(['Foo']) - .withOptions({ regenerate: true, force: true, ignoreNeedlesError: true }); - }); - - it('should not create database changelogs', () => { - runResult.assertNoFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_constraints_Foo.xml`, - ]); - }); - }); - }); -}); diff --git a/generators/entity/database-changelog.spec.ts b/generators/entity/database-changelog.spec.ts new file mode 100644 index 000000000000..231bd0421691 --- /dev/null +++ b/generators/entity/database-changelog.spec.ts @@ -0,0 +1,53 @@ +import { defaultHelpers as helpers, runResult, getGenerator } from '../../test/support/index.js'; +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import BaseApplicationGenerator from '../base-application/generator.js'; + +class MockedLanguagesGenerator extends BaseApplicationGenerator { + get [BaseApplicationGenerator.PREPARING]() { + return { + mockTranslations({ control }) { + control.getWebappTranslation = () => 'translations'; + }, + }; + } +} + +const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; + +describe('generator - entity database changelogs', () => { + context('when regenerating the entity', () => { + describe('with cassandra database', () => { + before(async () => { + await helpers + .run(getGenerator('entity')) + .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) + .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo]) + .withArguments(['Foo']) + .withOptions({ regenerate: true, force: true, ignoreNeedlesError: true }); + }); + + it('should create database changelog for the entity', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`]); + }); + }); + describe('with gateway application type', () => { + before(async () => { + await helpers + .run(getGenerator('entity')) + .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) + .withJHipsterConfig({ applicationType: 'gateway' }, [ + { ...entityFoo, microservicePath: 'microservice1', microserviceName: 'microservice1' }, + ]) + .withArguments(['Foo']) + .withOptions({ regenerate: true, force: true, ignoreNeedlesError: true }); + }); + + it('should not create database changelogs', () => { + runResult.assertNoFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_constraints_Foo.xml`, + ]); + }); + }); + }); +}); diff --git a/generators/entity/generator.js b/generators/entity/generator.js new file mode 100644 index 000000000000..950bbeacf823 --- /dev/null +++ b/generators/entity/generator.js @@ -0,0 +1,356 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import fs from 'fs'; +import path from 'path'; +import chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import prompts from './prompts.js'; +import { JHIPSTER_CONFIG_DIR } from '../generator-constants.js'; +import { applicationTypes, reservedKeywords } from '../../jdl/jhipster/index.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_ENTITIES, GENERATOR_ENTITY } from '../generator-list.js'; +import { getDBTypeFromDBValue, hibernateSnakeCase } from '../server/support/index.js'; +import command from './command.js'; + +const { GATEWAY, MICROSERVICE } = applicationTypes; +const { isReservedClassName } = reservedKeywords; + +export default class EntityGenerator extends BaseApplicationGenerator { + name; + application = {}; + + constructor(args, options, features) { + super(args, options, { unique: 'argument', ...features }); + } + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_ENTITY); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + // Public API method used by the getter and also by Blueprints + get initializing() { + return this.asInitializingTaskGroup({ + parseOptions() { + this.parseJHipsterArguments(command.arguments); + const name = _.upperFirst(this.name).replace('.json', ''); + this.entityStorage = this.getEntityConfig(name, true); + this.entityConfig = this.entityStorage.createProxy(); + + const configExisted = this.entityStorage.existed; + const filename = path.join(JHIPSTER_CONFIG_DIR, `${name}.json`); + const entityExisted = fs.existsSync(this.destinationPath(filename)); + + this.jhipsterConfig.entities = [...(this.jhipsterConfig.entities ?? []), name]; + this.entityData = { + name, + filename, + configExisted, + entityExisted, + configurationFileExists: this.fs.exists(this.destinationPath(filename)), + }; + + this._setupEntityOptions(this, this, this.entityData); + }, + + loadOptions() { + if (this.options.db) { + this.entityConfig.databaseType = getDBTypeFromDBValue(this.options.db); + if (this.entityConfig.databaseType === 'sql') { + this.entityConfig.prodDatabaseType = this.options.db; + this.entityConfig.devDatabaseType = this.options.db; + } + } + + if (this.options.skipServer !== undefined) { + this.entityConfig.skipServer = this.options.skipServer; + } + if (this.options.skipDbChangelog !== undefined) { + this.entityConfig.skipDbChangelog = this.options.skipDbChangelog; + } + if (this.options.skipClient !== undefined) { + this.entityConfig.skipClient = this.options.skipClient; + } + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return this.asPromptingTaskGroup({ + /* Use need microservice path to load the entity file */ + askForMicroserviceJson: prompts.askForMicroserviceJson, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get loading() { + return this.asLoadingTaskGroup({ + isBuiltInEntity() { + if (this.isBuiltInUser(this.entityData.name) || this.isBuiltInAuthority(this.entityData.name)) { + throw new Error(`Is not possible to override built in ${this.entityData.name}`); + } + }, + + setupMicroServiceEntity({ application }) { + const context = this.entityData; + + if (application.applicationType === MICROSERVICE) { + context.microserviceName = this.entityConfig.microserviceName = this.jhipsterConfig.baseName; + if (!this.entityConfig.clientRootFolder) { + context.clientRootFolder = this.entityConfig.clientRootFolder = this.entityConfig.microserviceName; + } + } else if (application.applicationType === GATEWAY) { + // If microservicePath is set we are loading the entity from the microservice side. + context.useMicroserviceJson = !!this.entityConfig.microservicePath; + if (context.useMicroserviceJson) { + context.microserviceFileName = this.destinationPath(this.entityConfig.microservicePath, context.filename); + context.useConfigurationFile = true; + + this.log.verboseInfo(`\nThe entity ${context.name} is being updated.\n`); + try { + // We are generating a entity from a microservice. + // Load it directly into our entity configuration. + this.microserviceConfig = this.fs.readJSON(context.microserviceFileName); + if (this.microserviceConfig) { + this.entityStorage.set(this.microserviceConfig); + } + } catch (err) { + this.log.debug('Error:', err); + throw new Error(`The entity configuration file could not be read! ${err}`, { cause: err }); + } + } + if (this.entityConfig.clientRootFolder === undefined) { + context.clientRootFolder = this.entityConfig.clientRootFolder = context.skipUiGrouping + ? '' + : this.entityConfig.microserviceName; + } + } + }, + + loadEntitySpecificOptions({ application }) { + this.entityData.skipClient = this.entityData.skipClient || this.entityConfig.skipClient; + this.entityData.databaseType = this.entityData.databaseType || this.entityConfig.databaseType || application.databaseType; + }, + + validateEntityName() { + const validation = this._validateEntityName(this.entityData.name); + if (validation !== true) { + throw new Error(validation); + } + }, + + bootstrapConfig({ application }) { + const context = this.entityData; + const entityName = context.name; + if ([MICROSERVICE, GATEWAY].includes(application.applicationType)) { + if (this.entityConfig.databaseType === undefined) { + this.entityConfig.databaseType = context.databaseType; + } + } + context.useConfigurationFile = context.configurationFileExists || context.useConfigurationFile; + if (context.configurationFileExists) { + this.log.log(chalk.green(`\nFound the ${context.filename} configuration file, entity can be automatically generated!\n`)); + } + + // Structure for prompts. + this.entityStorage.defaults({ fields: [], relationships: [] }); + + if (!context.useConfigurationFile) { + this.log.verboseInfo(`\nThe entity ${entityName} is being created.\n`); + } + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get postPreparing() { + return this.asPostPreparingTaskGroup({ + /* ask question to user if s/he wants to update entity */ + askForUpdate: prompts.askForUpdate, + askForFields: prompts.askForFields, + askForFieldsToRemove: prompts.askForFieldsToRemove, + askForRelationships: prompts.askForRelationships, + askForRelationsToRemove: prompts.askForRelationsToRemove, + askForService: prompts.askForService, + askForDTO: prompts.askForDTO, + askForFiltering: prompts.askForFiltering, + askForReadOnly: prompts.askForReadOnly, + askForPagination: prompts.askForPagination, + }); + } + + get [BaseApplicationGenerator.POST_PREPARING]() { + return this.delegateTasksToBlueprint(() => this.postPreparing); + } + + // Public API method used by the getter and also by Blueprints + get default() { + return this.asDefaultTaskGroup({ + async composeEntities() { + // We need to compose with others entities to update relationships. + await this.composeWithJHipster(GENERATOR_ENTITIES, { + generatorArgs: this.options.singleEntity ? [this.entityData.name] : [], + generatorOptions: { + skipDbChangelog: this.options.skipDbChangelog, + skipInstall: this.options.skipInstall, + }, + }); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + // Public API method used by the getter and also by Blueprints + get end() { + return { + end() { + this.log.log(chalk.bold.green(`Entity ${this.entityData.entityNameCapitalized} generated successfully.`)); + }, + }; + } + + get [BaseApplicationGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * @private + * Setup Entity instance level options from context. + * all variables should be set to dest, + * all variables should be referred from context, + * all methods should be called on generator, + * @param {any} generator - generator instance + * @param {any} context - context to use default is generator instance + * @param {any} dest - destination context to use default is context + */ + _setupEntityOptions(generator, context = generator, dest = context) { + dest.regenerate = context.options.regenerate; + + if (context.options.skipCheckLengthOfIdentifier !== undefined) { + this.entityConfig.skipCheckLengthOfIdentifier = context.options.skipCheckLengthOfIdentifier; + } + if (context.options.angularSuffix !== undefined) { + this.entityConfig.angularJSSuffix = context.options.angularSuffix; + } + if (context.options.skipUiGrouping !== undefined) { + this.entityConfig.skipUiGrouping = context.options.skipUiGrouping; + } + if (context.options.clientRootFolder !== undefined) { + if (this.entityConfig.skipUiGrouping) { + this.warn('Ignoring client-root-folder due to skip-ui-grouping configuration'); + } else { + this.entityConfig.clientRootFolder = context.options.clientRootFolder; + } + } + if (context.options.skipClient !== undefined) { + this.entityConfig.skipClient = context.options.skipClient; + } + if (context.options.skipServer !== undefined) { + this.entityConfig.skipServer = context.options.skipServer; + } + + if (context.options.tableName) { + this.entityConfig.entityTableName = hibernateSnakeCase(context.options.tableName); + } + } + + /** + * @private + * Validate the entityName + * @return {true|string} true for a valid value or error message. + */ + _validateEntityName(entityName) { + if (!/^([a-zA-Z0-9]*)$/.test(entityName)) { + return 'The entity name must be alphanumeric only'; + } + if (/^[0-9].*$/.test(entityName)) { + return 'The entity name cannot start with a number'; + } + if (entityName === '') { + return 'The entity name cannot be empty'; + } + if (entityName.indexOf('Detail', entityName.length - 'Detail'.length) !== -1) { + return "The entity name cannot end with 'Detail'"; + } + if (!this.entityData.skipServer && isReservedClassName(entityName)) { + return 'The entity name cannot contain a Java or JHipster reserved keyword'; + } + return true; + } + + /** + * @private + * Verify if the entity is a built-in User. + * @param {String} entityName - Entity name to verify. + * @return {boolean} true if the entity is User and is built-in. + */ + isBuiltInUser(entityName) { + return this.generateBuiltInUserEntity && this.isUserEntity(entityName); + } + + /** + * @private + * Verify if the entity is a User entity. + * @param {String} entityName - Entity name to verify. + * @return {boolean} true if the entity is User. + */ + isUserEntity(entityName) { + return _.upperFirst(entityName) === 'User'; + } + + /** + * @private + * Verify if the entity is a Authority entity. + * @param {String} entityName - Entity name to verify. + * @return {boolean} true if the entity is Authority. + */ + isAuthorityEntity(entityName) { + return _.upperFirst(entityName) === 'Authority'; + } + + /** + * @private + * Verify if the entity is a built-in Authority. + * @param {String} entityName - Entity name to verify. + * @return {boolean} true if the entity is Authority and is built-in. + */ + isBuiltInAuthority(entityName) { + return this.generateBuiltInAuthorityEntity && this.isAuthorityEntity(entityName); + } +} diff --git a/generators/entity/generator.mjs b/generators/entity/generator.mjs deleted file mode 100644 index 3925fc0b64e2..000000000000 --- a/generators/entity/generator.mjs +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import fs from 'fs'; -import path from 'path'; -import chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import prompts from './prompts.mjs'; -import { JHIPSTER_CONFIG_DIR } from '../generator-constants.mjs'; -import { applicationTypes, reservedKeywords } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_ENTITIES, GENERATOR_ENTITY } from '../generator-list.mjs'; -import { getDBTypeFromDBValue, hibernateSnakeCase } from '../server/support/index.mjs'; -import command from './command.mjs'; - -const { GATEWAY, MICROSERVICE } = applicationTypes; -const { isReservedClassName } = reservedKeywords; - -export default class EntityGenerator extends BaseApplicationGenerator { - name; - application = {}; - - constructor(args, options, features) { - super(args, options, { unique: 'argument', ...features }); - } - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_ENTITY); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - // Public API method used by the getter and also by Blueprints - get initializing() { - return this.asInitializingTaskGroup({ - parseOptions() { - this.parseJHipsterArguments(command.arguments); - const name = _.upperFirst(this.name).replace('.json', ''); - this.entityStorage = this.getEntityConfig(name, true); - this.entityConfig = this.entityStorage.createProxy(); - - const configExisted = this.entityStorage.existed; - const filename = path.join(JHIPSTER_CONFIG_DIR, `${name}.json`); - const entityExisted = fs.existsSync(this.destinationPath(filename)); - - this.jhipsterConfig.entities = [...(this.jhipsterConfig.entities ?? []), name]; - this.entityData = { - name, - filename, - configExisted, - entityExisted, - configurationFileExists: this.fs.exists(this.destinationPath(filename)), - }; - - this._setupEntityOptions(this, this, this.entityData); - }, - - loadOptions() { - if (this.options.db) { - this.entityConfig.databaseType = getDBTypeFromDBValue(this.options.db); - if (this.entityConfig.databaseType === 'sql') { - this.entityConfig.prodDatabaseType = this.options.db; - this.entityConfig.devDatabaseType = this.options.db; - } - } - - if (this.options.skipServer !== undefined) { - this.entityConfig.skipServer = this.options.skipServer; - } - if (this.options.skipDbChangelog !== undefined) { - this.entityConfig.skipDbChangelog = this.options.skipDbChangelog; - } - if (this.options.skipClient !== undefined) { - this.entityConfig.skipClient = this.options.skipClient; - } - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return this.asPromptingTaskGroup({ - /* Use need microservice path to load the entity file */ - askForMicroserviceJson: prompts.askForMicroserviceJson, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get loading() { - return this.asLoadingTaskGroup({ - isBuiltInEntity() { - if (this.isBuiltInUser(this.entityData.name) || this.isBuiltInAuthority(this.entityData.name)) { - throw new Error(`Is not possible to override built in ${this.entityData.name}`); - } - }, - - setupMicroServiceEntity({ application }) { - const context = this.entityData; - - if (application.applicationType === MICROSERVICE) { - context.microserviceName = this.entityConfig.microserviceName = this.jhipsterConfig.baseName; - if (!this.entityConfig.clientRootFolder) { - context.clientRootFolder = this.entityConfig.clientRootFolder = this.entityConfig.microserviceName; - } - } else if (application.applicationType === GATEWAY) { - // If microservicePath is set we are loading the entity from the microservice side. - context.useMicroserviceJson = !!this.entityConfig.microservicePath; - if (context.useMicroserviceJson) { - context.microserviceFileName = this.destinationPath(this.entityConfig.microservicePath, context.filename); - context.useConfigurationFile = true; - - this.log.verboseInfo(`\nThe entity ${context.name} is being updated.\n`); - try { - // We are generating a entity from a microservice. - // Load it directly into our entity configuration. - this.microserviceConfig = this.fs.readJSON(context.microserviceFileName); - if (this.microserviceConfig) { - this.entityStorage.set(this.microserviceConfig); - } - } catch (err) { - this.log.debug('Error:', err); - throw new Error(`The entity configuration file could not be read! ${err}`, { cause: err }); - } - } - if (this.entityConfig.clientRootFolder === undefined) { - context.clientRootFolder = this.entityConfig.clientRootFolder = context.skipUiGrouping - ? '' - : this.entityConfig.microserviceName; - } - } - }, - - loadEntitySpecificOptions({ application }) { - this.entityData.skipClient = this.entityData.skipClient || this.entityConfig.skipClient; - this.entityData.databaseType = this.entityData.databaseType || this.entityConfig.databaseType || application.databaseType; - }, - - validateEntityName() { - const validation = this._validateEntityName(this.entityData.name); - if (validation !== true) { - throw new Error(validation); - } - }, - - bootstrapConfig({ application }) { - const context = this.entityData; - const entityName = context.name; - if ([MICROSERVICE, GATEWAY].includes(application.applicationType)) { - if (this.entityConfig.databaseType === undefined) { - this.entityConfig.databaseType = context.databaseType; - } - } - context.useConfigurationFile = context.configurationFileExists || context.useConfigurationFile; - if (context.configurationFileExists) { - this.log.log(chalk.green(`\nFound the ${context.filename} configuration file, entity can be automatically generated!\n`)); - } - - // Structure for prompts. - this.entityStorage.defaults({ fields: [], relationships: [] }); - - if (!context.useConfigurationFile) { - this.log.verboseInfo(`\nThe entity ${entityName} is being created.\n`); - } - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get postPreparing() { - return this.asPostPreparingTaskGroup({ - /* ask question to user if s/he wants to update entity */ - askForUpdate: prompts.askForUpdate, - askForFields: prompts.askForFields, - askForFieldsToRemove: prompts.askForFieldsToRemove, - askForRelationships: prompts.askForRelationships, - askForRelationsToRemove: prompts.askForRelationsToRemove, - askForService: prompts.askForService, - askForDTO: prompts.askForDTO, - askForFiltering: prompts.askForFiltering, - askForReadOnly: prompts.askForReadOnly, - askForPagination: prompts.askForPagination, - }); - } - - get [BaseApplicationGenerator.POST_PREPARING]() { - return this.delegateTasksToBlueprint(() => this.postPreparing); - } - - // Public API method used by the getter and also by Blueprints - get default() { - return this.asDefaultTaskGroup({ - async composeEntities() { - // We need to compose with others entities to update relationships. - await this.composeWithJHipster(GENERATOR_ENTITIES, { - generatorArgs: this.options.singleEntity ? [this.entityData.name] : [], - generatorOptions: { - skipDbChangelog: this.options.skipDbChangelog, - skipInstall: this.options.skipInstall, - }, - }); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - // Public API method used by the getter and also by Blueprints - get end() { - return { - end() { - this.log.log(chalk.bold.green(`Entity ${this.entityData.entityNameCapitalized} generated successfully.`)); - }, - }; - } - - get [BaseApplicationGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * @private - * Setup Entity instance level options from context. - * all variables should be set to dest, - * all variables should be referred from context, - * all methods should be called on generator, - * @param {any} generator - generator instance - * @param {any} context - context to use default is generator instance - * @param {any} dest - destination context to use default is context - */ - _setupEntityOptions(generator, context = generator, dest = context) { - dest.regenerate = context.options.regenerate; - - if (context.options.skipCheckLengthOfIdentifier !== undefined) { - this.entityConfig.skipCheckLengthOfIdentifier = context.options.skipCheckLengthOfIdentifier; - } - if (context.options.angularSuffix !== undefined) { - this.entityConfig.angularJSSuffix = context.options.angularSuffix; - } - if (context.options.skipUiGrouping !== undefined) { - this.entityConfig.skipUiGrouping = context.options.skipUiGrouping; - } - if (context.options.clientRootFolder !== undefined) { - if (this.entityConfig.skipUiGrouping) { - this.warn('Ignoring client-root-folder due to skip-ui-grouping configuration'); - } else { - this.entityConfig.clientRootFolder = context.options.clientRootFolder; - } - } - if (context.options.skipClient !== undefined) { - this.entityConfig.skipClient = context.options.skipClient; - } - if (context.options.skipServer !== undefined) { - this.entityConfig.skipServer = context.options.skipServer; - } - - if (context.options.tableName) { - this.entityConfig.entityTableName = hibernateSnakeCase(context.options.tableName); - } - } - - /** - * @private - * Validate the entityName - * @return {true|string} true for a valid value or error message. - */ - _validateEntityName(entityName) { - if (!/^([a-zA-Z0-9]*)$/.test(entityName)) { - return 'The entity name must be alphanumeric only'; - } - if (/^[0-9].*$/.test(entityName)) { - return 'The entity name cannot start with a number'; - } - if (entityName === '') { - return 'The entity name cannot be empty'; - } - if (entityName.indexOf('Detail', entityName.length - 'Detail'.length) !== -1) { - return "The entity name cannot end with 'Detail'"; - } - if (!this.entityData.skipServer && isReservedClassName(entityName)) { - return 'The entity name cannot contain a Java or JHipster reserved keyword'; - } - return true; - } - - /** - * @private - * Verify if the entity is a built-in User. - * @param {String} entityName - Entity name to verify. - * @return {boolean} true if the entity is User and is built-in. - */ - isBuiltInUser(entityName) { - return this.generateBuiltInUserEntity && this.isUserEntity(entityName); - } - - /** - * @private - * Verify if the entity is a User entity. - * @param {String} entityName - Entity name to verify. - * @return {boolean} true if the entity is User. - */ - isUserEntity(entityName) { - return _.upperFirst(entityName) === 'User'; - } - - /** - * @private - * Verify if the entity is a Authority entity. - * @param {String} entityName - Entity name to verify. - * @return {boolean} true if the entity is Authority. - */ - isAuthorityEntity(entityName) { - return _.upperFirst(entityName) === 'Authority'; - } - - /** - * @private - * Verify if the entity is a built-in Authority. - * @param {String} entityName - Entity name to verify. - * @return {boolean} true if the entity is Authority and is built-in. - */ - isBuiltInAuthority(entityName) { - return this.generateBuiltInAuthorityEntity && this.isAuthorityEntity(entityName); - } -} diff --git a/generators/entity/generator.spec.js b/generators/entity/generator.spec.js new file mode 100644 index 000000000000..37f1f7ec1551 --- /dev/null +++ b/generators/entity/generator.spec.js @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe.skip('blueprint support', () => testBlueprintSupport(generator)); +}); diff --git a/generators/entity/generator.spec.mjs b/generators/entity/generator.spec.mjs deleted file mode 100644 index 503e6ed783ef..000000000000 --- a/generators/entity/generator.spec.mjs +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe.skip('blueprint support', () => testBlueprintSupport(generator)); -}); diff --git a/generators/entity/index.mts b/generators/entity/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/entity/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/entity/index.ts b/generators/entity/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/entity/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/entity/prompts.js b/generators/entity/prompts.js new file mode 100644 index 000000000000..de8f95bc1460 --- /dev/null +++ b/generators/entity/prompts.js @@ -0,0 +1,958 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import * as _ from 'lodash-es'; +import { + reservedKeywords, + databaseTypes, + applicationTypes, + entityOptions, + fieldTypes, + validations, + clientFrameworkTypes, +} from '../../jdl/jhipster/index.js'; +import { inputIsNumber, inputIsSignedDecimalNumber, inputIsSignedNumber } from './support/index.js'; + +const { isReservedPaginationWords, isReservedFieldName, isReservedTableName } = reservedKeywords; +const { CASSANDRA, SQL } = databaseTypes; +const { GATEWAY } = applicationTypes; +const { FilteringTypes, MapperTypes, ServiceTypes, PaginationTypes } = entityOptions; +const { ANGULAR, REACT } = clientFrameworkTypes; +const { JPA_METAMODEL } = FilteringTypes; +const NO_FILTERING = FilteringTypes.NO; +const { INFINITE_SCROLL, PAGINATION } = PaginationTypes; +const NO_PAGINATION = PaginationTypes.NO; +const { SERVICE_IMPL, SERVICE_CLASS } = ServiceTypes; +const NO_SERVICE = ServiceTypes.NO; +const { MAPSTRUCT } = MapperTypes; +const NO_MAPPER = MapperTypes.NO; + +const NO_DATABASE = databaseTypes.NO; +const { CommonDBTypes, RelationalOnlyDBTypes, BlobTypes } = fieldTypes; + +const { BIG_DECIMAL, BOOLEAN, DOUBLE, DURATION, ENUM, FLOAT, INTEGER, INSTANT, LOCAL_DATE, LONG, STRING, UUID, ZONED_DATE_TIME } = + CommonDBTypes; +const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; +const { ANY, IMAGE, TEXT } = BlobTypes; + +const { + Validations: { PATTERN, MINBYTES, MAXBYTES, MINLENGTH, MAXLENGTH, MIN, MAX, REQUIRED, UNIQUE }, +} = validations; + +const prompts = { + askForMicroserviceJson, + askForUpdate, + askForFields, + askForFieldsToRemove, + askForRelationships, + askForRelationsToRemove, + askForDTO, + askForService, + askForFiltering, + askForReadOnly, + askForPagination, +}; + +export default prompts; + +const getFieldNameUndercored = fields => + ['id'].concat( + fields.map(field => { + return _.snakeCase(field.fieldName); + }), + ); + +function askForMicroserviceJson() { + const context = this.entityData; + if (this.jhipsterConfig.applicationType !== GATEWAY || context.configExisted) { + return undefined; + } + + const databaseType = this.jhipsterConfig.databaseType; + + const prompts = [ + { + when: () => databaseType !== NO_DATABASE, + type: 'confirm', + name: 'useMicroserviceJson', + message: 'Do you want to generate this entity from an existing microservice?', + default: true, + }, + { + when: response => response.useMicroserviceJson === true || databaseType === NO_DATABASE, + type: 'input', + name: 'microservicePath', + message: 'Enter the path to the microservice root directory:', + store: true, + default: this.entityConfig.microservicePath, + validate: input => { + if (fs.existsSync(this.destinationPath(input, context.filename))) { + return true; + } + return `${context.filename} not found in ${input}/`; + }, + }, + ]; + + return this.prompt(prompts).then(answers => { + if (answers.microservicePath) { + this.log.log(chalk.green(`\nFound the ${context.filename} configuration file, entity can be automatically generated!\n`)); + context.microservicePath = this.entityConfig.microservicePath = answers.microservicePath; + } + }); +} + +function askForUpdate() { + const context = this.entityData; + // ask only if running an existing entity without arg option --force or --regenerate + const isForce = this.options.force || context.regenerate; + context.updateEntity = 'regenerate'; // default if skipping questions by --force + if (isForce || !context.useConfigurationFile) { + return undefined; + } + const prompts = [ + { + type: 'list', + name: 'updateEntity', + message: + 'Do you want to update the entity? This will replace the existing files for this entity, all your custom code will be overwritten', + choices: [ + { + value: 'regenerate', + name: 'Yes, re generate the entity', + }, + { + value: 'add', + name: 'Yes, add more fields and relationships', + }, + { + value: 'remove', + name: 'Yes, remove fields and relationships', + }, + { + value: 'none', + name: 'No, exit', + }, + ], + default: 0, + }, + ]; + return this.prompt(prompts).then(props => { + context.updateEntity = props.updateEntity; + if (context.updateEntity === 'none') { + throw new Error(chalk.green('Aborting entity update, no changes were made.')); + } + }); +} + +function askForFields() { + const context = this.entityData; + // don't prompt if data is imported from a file + if (this.options.defaults || (context.useConfigurationFile && context.updateEntity !== 'add')) { + return undefined; + } + + if (context.updateEntity === 'add') { + logFieldsAndRelationships.call(this); + } + + return askForField.call(this); +} + +function askForFieldsToRemove() { + const context = this.entityData; + // prompt only if data is imported from a file + if (!context.useConfigurationFile || context.updateEntity !== 'remove' || this.entityConfig.fields.length === 0) { + return undefined; + } + + const prompts = [ + { + type: 'checkbox', + name: 'fieldsToRemove', + message: 'Please choose the fields you want to remove', + choices: () => + this.entityConfig.fields.map(field => { + return { name: field.fieldName, value: field.fieldName }; + }), + }, + { + when: response => response.fieldsToRemove.length !== 0, + type: 'confirm', + name: 'confirmRemove', + message: 'Are you sure to remove these fields?', + default: true, + }, + ]; + return this.prompt(prompts).then(props => { + if (props.confirmRemove) { + this.log.log(chalk.red(`\nRemoving fields: ${props.fieldsToRemove}\n`)); + const fields = this.entityConfig.fields; + for (let i = fields.length - 1; i >= 0; i -= 1) { + const field = this.entityConfig.fields[i]; + if (props.fieldsToRemove.filter(val => val === field.fieldName).length > 0) { + fields.splice(i, 1); + } + } + this.entityConfig.fields = fields; + } + }); +} + +function askForRelationships(...args) { + const context = this.entityData; + // don't prompt if data is imported from a file + if (context.useConfigurationFile && context.updateEntity !== 'add') { + return undefined; + } + if (context.databaseType === CASSANDRA) { + return undefined; + } + + return askForRelationship.call(this, ...args); +} + +function askForRelationsToRemove() { + const context = this.entityData; + // prompt only if data is imported from a file + if (!context.useConfigurationFile || context.updateEntity !== 'remove' || this.entityConfig.relationships.length === 0) { + return undefined; + } + if (context.databaseType === CASSANDRA) { + return undefined; + } + + const prompts = [ + { + type: 'checkbox', + name: 'relsToRemove', + message: 'Please choose the relationships you want to remove', + choices: () => + this.entityConfig.relationships.map(rel => { + return { + name: `${rel.relationshipName}:${rel.relationshipType}`, + value: `${rel.relationshipName}:${rel.relationshipType}`, + }; + }), + }, + { + when: response => response.relsToRemove.length !== 0, + type: 'confirm', + name: 'confirmRemove', + message: 'Are you sure to remove these relationships?', + default: true, + }, + ]; + return this.prompt(prompts).then(props => { + if (props.confirmRemove) { + this.log.log(chalk.red(`\nRemoving relationships: ${props.relsToRemove}\n`)); + const relationships = this.entityConfig.relationships; + for (let i = relationships.length - 1; i >= 0; i -= 1) { + const rel = relationships[i]; + if (props.relsToRemove.filter(val => val === `${rel.relationshipName}:${rel.relationshipType}`).length > 0) { + relationships.splice(i, 1); + } + } + this.entityConfig.relationships = relationships; + } + }); +} + +function askForFiltering() { + const context = this.entityData; + // don't prompt if server is skipped, or the backend is not sql, or no service requested + if (context.useConfigurationFile || context.skipServer || context.databaseType !== 'sql' || this.entityConfig.service === 'no') { + return undefined; + } + const prompts = [ + { + type: 'list', + name: 'filtering', + message: 'Do you want to add filtering?', + choices: [ + { + value: NO_FILTERING, + name: 'Not needed', + }, + { + name: 'Dynamic filtering for the entities with JPA Static metamodel', + value: JPA_METAMODEL, + }, + ], + default: 0, + }, + ]; + return this.prompt(prompts).then(props => { + this.entityConfig.jpaMetamodelFiltering = props.filtering === JPA_METAMODEL; + }); +} + +function askForReadOnly() { + const context = this.entityData; + // don't prompt if data is imported from a file + if (context.useConfigurationFile) { + return undefined; + } + const prompts = [ + { + type: 'confirm', + name: 'readOnly', + message: 'Is this entity read-only?', + default: false, + }, + ]; + return this.prompt(prompts).then(props => { + this.entityConfig.readOnly = props.readOnly; + }); +} + +function askForDTO() { + const context = this.entityData; + // don't prompt if data is imported from a file or server is skipped or if no service layer + if (context.useConfigurationFile || context.skipServer || this.entityConfig.service === 'no') { + return undefined; + } + const prompts = [ + { + type: 'list', + name: 'dto', + message: 'Do you want to use a Data Transfer Object (DTO)?', + choices: [ + { + value: NO_MAPPER, + name: 'No, use the entity directly', + }, + { + value: MAPSTRUCT, + name: 'Yes, generate a DTO with MapStruct', + }, + ], + default: 0, + }, + ]; + return this.prompt(prompts).then(props => { + this.entityConfig.dto = props.dto; + }); +} + +function askForService() { + const context = this.entityData; + // don't prompt if data is imported from a file or server is skipped + if (context.useConfigurationFile || context.skipServer) { + return undefined; + } + const prompts = [ + { + type: 'list', + name: 'service', + message: 'Do you want to use separate service class for your business logic?', + choices: [ + { + value: NO_SERVICE, + name: 'No, the REST controller should use the repository directly', + }, + { + value: SERVICE_CLASS, + name: 'Yes, generate a separate service class', + }, + { + value: SERVICE_IMPL, + name: 'Yes, generate a separate service interface and implementation', + }, + ], + default: 0, + }, + ]; + return this.prompt(prompts).then(props => { + this.entityConfig.service = props.service; + }); +} + +function askForPagination() { + const context = this.entityData; + // don't prompt if data are imported from a file + if (context.useConfigurationFile) { + return undefined; + } + if (context.databaseType === CASSANDRA) { + return undefined; + } + const prompts = [ + { + type: 'list', + name: 'pagination', + message: 'Do you want pagination and sorting on your entity?', + choices: [ + { + value: NO_PAGINATION, + name: 'No', + }, + { + value: PAGINATION, + name: 'Yes, with pagination links and sorting headers', + }, + { + value: INFINITE_SCROLL, + name: 'Yes, with infinite scroll and sorting headers', + }, + ], + default: 0, + }, + ]; + return this.prompt(prompts).then(props => { + this.entityConfig.pagination = props.pagination; + this.log.log(chalk.green('\nEverything is configured, generating the entity...\n')); + }); +} + +/** + * ask question for a field creation + */ +async function askForField() { + const context = this.entityData; + this.log.log(chalk.green(`\nGenerating field #${this.entityConfig.fields.length + 1}\n`)); + const databaseType = context.databaseType; + const clientFramework = context.clientFramework; + const possibleFiltering = databaseType === SQL && !context.reactive; + const fieldAddAnswer = await this.prompt([ + { + type: 'confirm', + name: 'fieldAdd', + message: 'Do you want to add a field to your entity?', + default: true, + }, + ]); + + if (!fieldAddAnswer.fieldAdd) { + logFieldsAndRelationships.call(this); + return; + } + const answers = await this.prompt([ + { + type: 'input', + name: 'fieldName', + validate: input => { + if (!/^([a-zA-Z0-9_]*)$/.test(input)) { + return 'Your field name cannot contain special characters'; + } + if (input === '') { + return 'Your field name cannot be empty'; + } + if (input.charAt(0) === input.charAt(0).toUpperCase()) { + return 'Your field name cannot start with an upper case letter'; + } + if (input === 'id' || getFieldNameUndercored(this.entityConfig.fields).includes(_.snakeCase(input))) { + return 'Your field name cannot use an already existing field name'; + } + if ((clientFramework === undefined || clientFramework === ANGULAR) && isReservedFieldName(input, ANGULAR)) { + return 'Your field name cannot contain a Java or Angular reserved keyword'; + } + if ((clientFramework !== undefined || clientFramework === REACT) && isReservedFieldName(input, REACT)) { + return 'Your field name cannot contain a Java or React reserved keyword'; + } + // we don't know, if filtering will be used + if (possibleFiltering && isReservedPaginationWords(input)) { + return 'Your field name cannot be a value, which is used as a parameter by Spring for pagination'; + } + return true; + }, + message: 'What is the name of your field?', + }, + { + type: 'list', + name: 'fieldType', + message: 'What is the type of your field?', + choices: () => [ + { value: STRING, name: 'String' }, + { value: INTEGER, name: 'Integer' }, + { value: LONG, name: 'Long' }, + { value: FLOAT, name: 'Float' }, + { value: DOUBLE, name: 'Double' }, + { value: BIG_DECIMAL, name: 'BigDecimal' }, + { value: LOCAL_DATE, name: 'LocalDate' }, + { value: INSTANT, name: 'Instant' }, + { value: ZONED_DATE_TIME, name: 'ZonedDateTime' }, + { value: DURATION, name: 'Duration' }, + { value: BOOLEAN, name: 'Boolean' }, + { value: ENUM, name: 'Enumeration (Java enum type)' }, + { value: UUID, name: 'UUID' }, + { value: UUID, name: 'UUID' }, + ...(databaseType === CASSANDRA ? [{ value: BYTE_BUFFER, name: '[BETA] Blob' }] : [{ value: BYTES, name: '[BETA] Blob' }]), + ], + default: 0, + }, + { + when: response => { + if (response.fieldType === ENUM) { + response.fieldIsEnum = true; + return true; + } + response.fieldIsEnum = false; + return false; + }, + type: 'input', + name: 'enumType', + validate: input => { + if (input === '') { + return 'Your class name cannot be empty.'; + } + if (isReservedTableName(input, 'JAVA')) { + return 'Your enum name cannot contain a Java reserved keyword'; + } + if (!/^[A-Za-z0-9_]*$/.test(input)) { + return 'Your enum name cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; + } + if (context.enums && context.enums.includes(input)) { + context.existingEnum = true; + } else if (context.enums) { + context.enums.push(input); + } else { + context.enums = [input]; + } + return true; + }, + message: 'What is the class name of your enumeration?', + }, + { + when: response => response.fieldIsEnum, + type: 'input', + name: 'fieldValues', + validate: input => { + if (input === '' && context.existingEnum) { + context.existingEnum = false; + return true; + } + if (input === '') { + return 'You must specify values for your enumeration'; + } + // Commas allowed so that user can input a list of values split by commas. + if (!/^[A-Za-z0-9_,]+$/.test(input)) { + return 'Enum values cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; + } + const enums = input.replace(/\s/g, '').split(','); + if (_.uniq(enums).length !== enums.length) { + return `Enum values cannot contain duplicates (typed values: ${input})`; + } + for (let i = 0; i < enums.length; i++) { + if (/^[0-9].*/.test(enums[i])) { + return `Enum value "${enums[i]}" cannot start with a number`; + } + if (enums[i] === '') { + return 'Enum value cannot be empty (did you accidentally type "," twice in a row?)'; + } + } + + return true; + }, + message: () => { + if (!context.existingEnum) { + return 'What are the values of your enumeration (separated by comma, no spaces)?'; + } + return 'What are the new values of your enumeration (separated by comma, no spaces)?\nThe new values will replace the old ones.\nNothing will be done if there are no new values.'; + }, + }, + { + when: response => response.fieldType === BYTES || response.fieldType === BYTE_BUFFER, + type: 'list', + name: 'fieldTypeBlobContent', + message: 'What is the content of the Blob field?', + choices: answers => [ + { value: IMAGE, name: 'An image' }, + { value: ANY, name: 'A binary file' }, + ...(answers.fieldType === BYTES ? [{ value: TEXT, name: 'A CLOB (Text field)' }] : []), + ], + default: 0, + }, + { + when: response => response.fieldType !== BYTE_BUFFER, + type: 'confirm', + name: 'fieldValidate', + message: 'Do you want to add validation rules to your field?', + default: false, + }, + { + when: response => response.fieldValidate === true, + type: 'checkbox', + name: 'fieldValidateRules', + message: 'Which validation rules do you want to add?', + choices: response => { + // Default rules applicable for fieldType 'LocalDate', 'Instant', + // 'ZonedDateTime', 'Duration', 'UUID', 'Boolean', 'ByteBuffer' and 'Enum' + const opts = [ + { + name: 'Required', + value: REQUIRED, + }, + { + name: 'Unique', + value: UNIQUE, + }, + ]; + if (response.fieldType === STRING || response.fieldTypeBlobContent === TEXT) { + opts.push( + { + name: 'Minimum length', + value: MINLENGTH, + }, + { + name: 'Maximum length', + value: MAXLENGTH, + }, + { + name: 'Regular expression pattern', + value: PATTERN, + }, + ); + } else if ([INTEGER, LONG, FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { + opts.push( + { + name: 'Minimum', + value: MIN, + }, + { + name: 'Maximum', + value: MAX, + }, + ); + } + return opts; + }, + default: 0, + }, + { + when: response => response.fieldValidate === true && response.fieldValidateRules.includes('minlength'), + type: 'input', + name: 'fieldValidateRulesMinlength', + validate: input => (inputIsNumber(input) ? true : 'Minimum length must be a positive number'), + message: 'What is the minimum length of your field?', + default: 0, + }, + { + when: response => response.fieldValidate === true && response.fieldValidateRules.includes('maxlength'), + type: 'input', + name: 'fieldValidateRulesMaxlength', + validate: input => (inputIsNumber(input) ? true : 'Maximum length must be a positive number'), + message: 'What is the maximum length of your field?', + default: 20, + }, + { + when: response => response.fieldValidate === true && response.fieldValidateRules.includes('min'), + type: 'input', + name: 'fieldValidateRulesMin', + message: 'What is the minimum of your field?', + validate: (input, response) => { + if ([FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { + return inputIsSignedDecimalNumber(input) ? true : 'Minimum must be a decimal number'; + } + return inputIsSignedNumber(input) ? true : 'Minimum must be a number'; + }, + default: 0, + }, + { + when: response => response.fieldValidate === true && response.fieldValidateRules.includes('max'), + type: 'input', + name: 'fieldValidateRulesMax', + message: 'What is the maximum of your field?', + validate: (input, response) => { + if ([FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { + return inputIsSignedDecimalNumber(input) ? true : 'Maximum must be a decimal number'; + } + return inputIsSignedNumber(input) ? true : 'Maximum must be a number'; + }, + default: 100, + }, + { + when: response => + response.fieldValidate === true && + response.fieldValidateRules.includes(MINBYTES) && + response.fieldType === BYTES && + response.fieldTypeBlobContent !== TEXT, + type: 'input', + name: 'fieldValidateRulesMinbytes', + message: 'What is the minimum byte size of your field?', + validate: input => (inputIsNumber(input) ? true : 'Minimum byte size must be a positive number'), + default: 0, + }, + { + when: response => + response.fieldValidate === true && + response.fieldValidateRules.includes(MAXBYTES) && + response.fieldType === BYTES && + response.fieldTypeBlobContent !== TEXT, + type: 'input', + name: 'fieldValidateRulesMaxbytes', + message: 'What is the maximum byte size of your field?', + validate: input => (inputIsNumber(input) ? true : 'Maximum byte size must be a positive number'), + default: 5000000, + }, + { + when: response => response.fieldValidate === true && response.fieldValidateRules.includes('pattern'), + type: 'input', + name: 'fieldValidateRulesPattern', + message: 'What is the regular expression pattern you want to apply on your field?', + default: '^[a-zA-Z0-9]*$', + }, + ]); + + if (answers.fieldIsEnum) { + answers.fieldType = _.upperFirst(answers.fieldType); + answers.fieldValues = answers.fieldValues.toUpperCase(); + } + + const field = { + fieldName: answers.fieldName, + fieldType: answers.enumType || answers.fieldType, + fieldTypeBlobContent: answers.fieldTypeBlobContent, + fieldValues: answers.fieldValues, + fieldValidateRules: answers.fieldValidateRules, + fieldValidateRulesMinlength: answers.fieldValidateRulesMinlength, + fieldValidateRulesMaxlength: answers.fieldValidateRulesMaxlength, + fieldValidateRulesPattern: answers.fieldValidateRulesPattern, + fieldValidateRulesMin: answers.fieldValidateRulesMin, + fieldValidateRulesMax: answers.fieldValidateRulesMax, + fieldValidateRulesMinbytes: answers.fieldValidateRulesMinbytes, + fieldValidateRulesMaxbytes: answers.fieldValidateRulesMaxbytes, + }; + + this.entityConfig.fields = this.entityConfig.fields.concat(field); + + logFieldsAndRelationships.call(this); + await askForField.call(this); +} + +/** + * ask question for a relationship creation + */ +async function askForRelationship(...args) { + const [{ application }] = args; + const context = this.entityData; + const name = context.name; + this.log.log(chalk.green('\nGenerating relationships to other entities\n')); + const addRelationshipAnswers = await this.prompt([ + { + type: 'confirm', + name: 'relationshipAdd', + message: 'Do you want to add a relationship to another entity?', + default: true, + }, + ]); + + if (!addRelationshipAnswers.relationshipAdd) { + logFieldsAndRelationships.call(this); + this.log.log('\n'); + return; + } + + const answers = await this.prompt([ + { + type: 'list', + name: 'otherEntityName', + message: 'What is the other entity?', + choices: () => [...this.getExistingEntityNames(), ...(application.generateBuiltInUserEntity ? ['User'] : [])], + }, + { + type: 'input', + name: 'relationshipName', + validate: input => { + if (!/^([a-zA-Z0-9_]*)$/.test(input)) { + return 'Your relationship cannot contain special characters'; + } + if (input === '') { + return 'Your relationship cannot be empty'; + } + if (input.charAt(0) === input.charAt(0).toUpperCase()) { + return 'Your relationship cannot start with an upper case letter'; + } + if (input === 'id' || getFieldNameUndercored(this.entityConfig.fields).includes(_.snakeCase(input))) { + return 'Your relationship cannot use an already existing field name'; + } + if (isReservedTableName(input, 'JAVA')) { + return 'Your relationship cannot contain a Java reserved keyword'; + } + return true; + }, + message: 'What is the name of the relationship?', + default: response => _.lowerFirst(response.otherEntityName), + }, + { + type: 'list', + name: 'relationshipType', + message: 'What is the type of the relationship?', + choices: response => [ + 'many-to-one', + 'many-to-many', + 'one-to-one', + ...(this.isBuiltInUser(response.otherEntityName) ? [] : ['one-to-many']), + ], + default: 0, + }, + { + when: response => application.databaseType === SQL && response.relationshipType === 'one-to-one', + type: 'confirm', + name: 'id', + message: 'Do you want to use JPA Derived Identifier - @MapsId?', + default: false, + }, + { + when: answers => { + if (this.isBuiltInUser(answers.otherEntityName)) { + answers.bidirectional = false; + return false; + } + + if (!application.databaseTypeNeo4j && answers.relationshipType !== 'many-to-one') { + // Relationships requires bidirectional. + answers.bidirectional = true; + return false; + } + + return true; + }, + type: 'input', + name: 'bidirectional', + message: 'Do you want to generate a bidirectional relationship', + default: true, + }, + { + when: response => response.bidirectional, + type: 'input', + name: 'otherEntityRelationshipName', + message: 'What is the name of this relationship in the other entity?', + default: () => _.lowerFirst(name), + }, + { + type: 'input', + name: 'otherEntityField', + message: response => + `When you display this relationship on client-side, which field from '${response.otherEntityName}' do you want to use? This field will be displayed as a String, so it cannot be a Blob`, + default: answers => (answers.otherEntityName === 'User' ? 'login' : 'id'), + }, + { + when: response => + (response.otherEntityName.toLowerCase() !== context.name.toLowerCase() && response.relationshipType === 'many-to-one') || + response.relationshipType === 'many-to-many' || + response.relationshipType === 'one-to-one', + type: 'confirm', + name: 'relationshipValidate', + message: 'Do you want to add any validation rules to this relationship?', + default: false, + }, + { + when: response => response.relationshipValidate === true, + type: 'checkbox', + name: 'relationshipValidateRules', + message: 'Which validation rules do you want to add?', + choices: [ + { + name: 'Required', + value: REQUIRED, + }, + ], + default: 0, + }, + ]); + + const relationship = { + relationshipSide: 'left', + relationshipName: answers.relationshipName, + otherEntityName: _.lowerFirst(answers.otherEntityName), + relationshipType: answers.relationshipType, + relationshipValidateRules: answers.relationshipValidateRules, + otherEntityField: answers.otherEntityField, + ownerSide: answers.ownerSide, + id: answers.id, + otherEntityRelationshipName: answers.otherEntityRelationshipName, + }; + + if (this.isBuiltInUser(answers.otherEntityName)) { + relationship.otherEntityRelationshipName = _.lowerFirst(name); + } + + this.entityConfig.relationships = this.entityConfig.relationships.concat(relationship); + + await askForRelationship.call(this, ...args); +} + +/** + * Show the entity and it's fields and relationships in console + */ +function logFieldsAndRelationships() { + const context = this.entityData; + if (this.entityConfig.fields.length > 0 || this.entityConfig.relationships.length > 0) { + this.log.log(chalk.red(chalk.white('\n================= ') + context.name + chalk.white(' ================='))); + } + if (this.entityConfig.fields.length > 0) { + this.log.log(chalk.white('Fields')); + this.entityConfig.fields.forEach(field => { + const validationDetails = []; + const fieldValidate = _.isArray(field.fieldValidateRules) && field.fieldValidateRules.length >= 1; + if (fieldValidate === true) { + if (field.fieldValidateRules.includes(REQUIRED)) { + validationDetails.push(REQUIRED); + } + if (field.fieldValidateRules.includes(UNIQUE)) { + validationDetails.push(UNIQUE); + } + if (field.fieldValidateRules.includes(MINLENGTH)) { + validationDetails.push(`${MINLENGTH}='${field.fieldValidateRulesMinlength}'`); + } + if (field.fieldValidateRules.includes(MAXLENGTH)) { + validationDetails.push(`${MAXLENGTH}='${field.fieldValidateRulesMaxlength}'`); + } + if (field.fieldValidateRules.includes(PATTERN)) { + validationDetails.push(`${PATTERN}='${field.fieldValidateRulesPattern}'`); + } + if (field.fieldValidateRules.includes(MIN)) { + validationDetails.push(`${MIN}='${field.fieldValidateRulesMin}'`); + } + if (field.fieldValidateRules.includes(MAX)) { + validationDetails.push(`${MAX}='${field.fieldValidateRulesMax}'`); + } + if (field.fieldValidateRules.includes(MINBYTES)) { + validationDetails.push(`${MINBYTES}='${field.fieldValidateRulesMinbytes}'`); + } + if (field.fieldValidateRules.includes(MAXBYTES)) { + validationDetails.push(`${MAXBYTES}='${field.fieldValidateRulesMaxbytes}'`); + } + } + this.log.log( + chalk.red(field.fieldName) + + chalk.white(` (${field.fieldType}${field.fieldTypeBlobContent ? ` ${field.fieldTypeBlobContent}` : ''}) `) + + chalk.cyan(validationDetails.join(' ')), + ); + }); + this.log.log(); + } + if (this.entityConfig.relationships.length > 0) { + this.log.log(chalk.white('Relationships')); + this.entityConfig.relationships.forEach(relationship => { + const validationDetails = []; + if (relationship.relationshipValidateRules && relationship.relationshipValidateRules.includes(REQUIRED)) { + validationDetails.push(REQUIRED); + } + this.log.log( + `${chalk.red(relationship.relationshipName)} ${chalk.white(`(${_.upperFirst(relationship.otherEntityName)})`)} ${chalk.cyan( + relationship.relationshipType, + )} ${chalk.cyan(validationDetails.join(' '))}`, + ); + }); + this.log.log(); + } +} diff --git a/generators/entity/prompts.mjs b/generators/entity/prompts.mjs deleted file mode 100644 index 1815fcec055e..000000000000 --- a/generators/entity/prompts.mjs +++ /dev/null @@ -1,958 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import * as _ from 'lodash-es'; -import { - reservedKeywords, - databaseTypes, - applicationTypes, - entityOptions, - fieldTypes, - validations, - clientFrameworkTypes, -} from '../../jdl/jhipster/index.mjs'; -import { inputIsNumber, inputIsSignedDecimalNumber, inputIsSignedNumber } from './support/index.mjs'; - -const { isReservedPaginationWords, isReservedFieldName, isReservedTableName } = reservedKeywords; -const { CASSANDRA, SQL } = databaseTypes; -const { GATEWAY } = applicationTypes; -const { FilteringTypes, MapperTypes, ServiceTypes, PaginationTypes } = entityOptions; -const { ANGULAR, REACT } = clientFrameworkTypes; -const { JPA_METAMODEL } = FilteringTypes; -const NO_FILTERING = FilteringTypes.NO; -const { INFINITE_SCROLL, PAGINATION } = PaginationTypes; -const NO_PAGINATION = PaginationTypes.NO; -const { SERVICE_IMPL, SERVICE_CLASS } = ServiceTypes; -const NO_SERVICE = ServiceTypes.NO; -const { MAPSTRUCT } = MapperTypes; -const NO_MAPPER = MapperTypes.NO; - -const NO_DATABASE = databaseTypes.NO; -const { CommonDBTypes, RelationalOnlyDBTypes, BlobTypes } = fieldTypes; - -const { BIG_DECIMAL, BOOLEAN, DOUBLE, DURATION, ENUM, FLOAT, INTEGER, INSTANT, LOCAL_DATE, LONG, STRING, UUID, ZONED_DATE_TIME } = - CommonDBTypes; -const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; -const { ANY, IMAGE, TEXT } = BlobTypes; - -const { - Validations: { PATTERN, MINBYTES, MAXBYTES, MINLENGTH, MAXLENGTH, MIN, MAX, REQUIRED, UNIQUE }, -} = validations; - -const prompts = { - askForMicroserviceJson, - askForUpdate, - askForFields, - askForFieldsToRemove, - askForRelationships, - askForRelationsToRemove, - askForDTO, - askForService, - askForFiltering, - askForReadOnly, - askForPagination, -}; - -export default prompts; - -const getFieldNameUndercored = fields => - ['id'].concat( - fields.map(field => { - return _.snakeCase(field.fieldName); - }), - ); - -function askForMicroserviceJson() { - const context = this.entityData; - if (this.jhipsterConfig.applicationType !== GATEWAY || context.configExisted) { - return undefined; - } - - const databaseType = this.jhipsterConfig.databaseType; - - const prompts = [ - { - when: () => databaseType !== NO_DATABASE, - type: 'confirm', - name: 'useMicroserviceJson', - message: 'Do you want to generate this entity from an existing microservice?', - default: true, - }, - { - when: response => response.useMicroserviceJson === true || databaseType === NO_DATABASE, - type: 'input', - name: 'microservicePath', - message: 'Enter the path to the microservice root directory:', - store: true, - default: this.entityConfig.microservicePath, - validate: input => { - if (fs.existsSync(this.destinationPath(input, context.filename))) { - return true; - } - return `${context.filename} not found in ${input}/`; - }, - }, - ]; - - return this.prompt(prompts).then(answers => { - if (answers.microservicePath) { - this.log.log(chalk.green(`\nFound the ${context.filename} configuration file, entity can be automatically generated!\n`)); - context.microservicePath = this.entityConfig.microservicePath = answers.microservicePath; - } - }); -} - -function askForUpdate() { - const context = this.entityData; - // ask only if running an existing entity without arg option --force or --regenerate - const isForce = this.options.force || context.regenerate; - context.updateEntity = 'regenerate'; // default if skipping questions by --force - if (isForce || !context.useConfigurationFile) { - return undefined; - } - const prompts = [ - { - type: 'list', - name: 'updateEntity', - message: - 'Do you want to update the entity? This will replace the existing files for this entity, all your custom code will be overwritten', - choices: [ - { - value: 'regenerate', - name: 'Yes, re generate the entity', - }, - { - value: 'add', - name: 'Yes, add more fields and relationships', - }, - { - value: 'remove', - name: 'Yes, remove fields and relationships', - }, - { - value: 'none', - name: 'No, exit', - }, - ], - default: 0, - }, - ]; - return this.prompt(prompts).then(props => { - context.updateEntity = props.updateEntity; - if (context.updateEntity === 'none') { - throw new Error(chalk.green('Aborting entity update, no changes were made.')); - } - }); -} - -function askForFields() { - const context = this.entityData; - // don't prompt if data is imported from a file - if (this.options.defaults || (context.useConfigurationFile && context.updateEntity !== 'add')) { - return undefined; - } - - if (context.updateEntity === 'add') { - logFieldsAndRelationships.call(this); - } - - return askForField.call(this); -} - -function askForFieldsToRemove() { - const context = this.entityData; - // prompt only if data is imported from a file - if (!context.useConfigurationFile || context.updateEntity !== 'remove' || this.entityConfig.fields.length === 0) { - return undefined; - } - - const prompts = [ - { - type: 'checkbox', - name: 'fieldsToRemove', - message: 'Please choose the fields you want to remove', - choices: () => - this.entityConfig.fields.map(field => { - return { name: field.fieldName, value: field.fieldName }; - }), - }, - { - when: response => response.fieldsToRemove.length !== 0, - type: 'confirm', - name: 'confirmRemove', - message: 'Are you sure to remove these fields?', - default: true, - }, - ]; - return this.prompt(prompts).then(props => { - if (props.confirmRemove) { - this.log.log(chalk.red(`\nRemoving fields: ${props.fieldsToRemove}\n`)); - const fields = this.entityConfig.fields; - for (let i = fields.length - 1; i >= 0; i -= 1) { - const field = this.entityConfig.fields[i]; - if (props.fieldsToRemove.filter(val => val === field.fieldName).length > 0) { - fields.splice(i, 1); - } - } - this.entityConfig.fields = fields; - } - }); -} - -function askForRelationships(...args) { - const context = this.entityData; - // don't prompt if data is imported from a file - if (context.useConfigurationFile && context.updateEntity !== 'add') { - return undefined; - } - if (context.databaseType === CASSANDRA) { - return undefined; - } - - return askForRelationship.call(this, ...args); -} - -function askForRelationsToRemove() { - const context = this.entityData; - // prompt only if data is imported from a file - if (!context.useConfigurationFile || context.updateEntity !== 'remove' || this.entityConfig.relationships.length === 0) { - return undefined; - } - if (context.databaseType === CASSANDRA) { - return undefined; - } - - const prompts = [ - { - type: 'checkbox', - name: 'relsToRemove', - message: 'Please choose the relationships you want to remove', - choices: () => - this.entityConfig.relationships.map(rel => { - return { - name: `${rel.relationshipName}:${rel.relationshipType}`, - value: `${rel.relationshipName}:${rel.relationshipType}`, - }; - }), - }, - { - when: response => response.relsToRemove.length !== 0, - type: 'confirm', - name: 'confirmRemove', - message: 'Are you sure to remove these relationships?', - default: true, - }, - ]; - return this.prompt(prompts).then(props => { - if (props.confirmRemove) { - this.log.log(chalk.red(`\nRemoving relationships: ${props.relsToRemove}\n`)); - const relationships = this.entityConfig.relationships; - for (let i = relationships.length - 1; i >= 0; i -= 1) { - const rel = relationships[i]; - if (props.relsToRemove.filter(val => val === `${rel.relationshipName}:${rel.relationshipType}`).length > 0) { - relationships.splice(i, 1); - } - } - this.entityConfig.relationships = relationships; - } - }); -} - -function askForFiltering() { - const context = this.entityData; - // don't prompt if server is skipped, or the backend is not sql, or no service requested - if (context.useConfigurationFile || context.skipServer || context.databaseType !== 'sql' || this.entityConfig.service === 'no') { - return undefined; - } - const prompts = [ - { - type: 'list', - name: 'filtering', - message: 'Do you want to add filtering?', - choices: [ - { - value: NO_FILTERING, - name: 'Not needed', - }, - { - name: 'Dynamic filtering for the entities with JPA Static metamodel', - value: JPA_METAMODEL, - }, - ], - default: 0, - }, - ]; - return this.prompt(prompts).then(props => { - this.entityConfig.jpaMetamodelFiltering = props.filtering === JPA_METAMODEL; - }); -} - -function askForReadOnly() { - const context = this.entityData; - // don't prompt if data is imported from a file - if (context.useConfigurationFile) { - return undefined; - } - const prompts = [ - { - type: 'confirm', - name: 'readOnly', - message: 'Is this entity read-only?', - default: false, - }, - ]; - return this.prompt(prompts).then(props => { - this.entityConfig.readOnly = props.readOnly; - }); -} - -function askForDTO() { - const context = this.entityData; - // don't prompt if data is imported from a file or server is skipped or if no service layer - if (context.useConfigurationFile || context.skipServer || this.entityConfig.service === 'no') { - return undefined; - } - const prompts = [ - { - type: 'list', - name: 'dto', - message: 'Do you want to use a Data Transfer Object (DTO)?', - choices: [ - { - value: NO_MAPPER, - name: 'No, use the entity directly', - }, - { - value: MAPSTRUCT, - name: 'Yes, generate a DTO with MapStruct', - }, - ], - default: 0, - }, - ]; - return this.prompt(prompts).then(props => { - this.entityConfig.dto = props.dto; - }); -} - -function askForService() { - const context = this.entityData; - // don't prompt if data is imported from a file or server is skipped - if (context.useConfigurationFile || context.skipServer) { - return undefined; - } - const prompts = [ - { - type: 'list', - name: 'service', - message: 'Do you want to use separate service class for your business logic?', - choices: [ - { - value: NO_SERVICE, - name: 'No, the REST controller should use the repository directly', - }, - { - value: SERVICE_CLASS, - name: 'Yes, generate a separate service class', - }, - { - value: SERVICE_IMPL, - name: 'Yes, generate a separate service interface and implementation', - }, - ], - default: 0, - }, - ]; - return this.prompt(prompts).then(props => { - this.entityConfig.service = props.service; - }); -} - -function askForPagination() { - const context = this.entityData; - // don't prompt if data are imported from a file - if (context.useConfigurationFile) { - return undefined; - } - if (context.databaseType === CASSANDRA) { - return undefined; - } - const prompts = [ - { - type: 'list', - name: 'pagination', - message: 'Do you want pagination and sorting on your entity?', - choices: [ - { - value: NO_PAGINATION, - name: 'No', - }, - { - value: PAGINATION, - name: 'Yes, with pagination links and sorting headers', - }, - { - value: INFINITE_SCROLL, - name: 'Yes, with infinite scroll and sorting headers', - }, - ], - default: 0, - }, - ]; - return this.prompt(prompts).then(props => { - this.entityConfig.pagination = props.pagination; - this.log.log(chalk.green('\nEverything is configured, generating the entity...\n')); - }); -} - -/** - * ask question for a field creation - */ -async function askForField() { - const context = this.entityData; - this.log.log(chalk.green(`\nGenerating field #${this.entityConfig.fields.length + 1}\n`)); - const databaseType = context.databaseType; - const clientFramework = context.clientFramework; - const possibleFiltering = databaseType === SQL && !context.reactive; - const fieldAddAnswer = await this.prompt([ - { - type: 'confirm', - name: 'fieldAdd', - message: 'Do you want to add a field to your entity?', - default: true, - }, - ]); - - if (!fieldAddAnswer.fieldAdd) { - logFieldsAndRelationships.call(this); - return; - } - const answers = await this.prompt([ - { - type: 'input', - name: 'fieldName', - validate: input => { - if (!/^([a-zA-Z0-9_]*)$/.test(input)) { - return 'Your field name cannot contain special characters'; - } - if (input === '') { - return 'Your field name cannot be empty'; - } - if (input.charAt(0) === input.charAt(0).toUpperCase()) { - return 'Your field name cannot start with an upper case letter'; - } - if (input === 'id' || getFieldNameUndercored(this.entityConfig.fields).includes(_.snakeCase(input))) { - return 'Your field name cannot use an already existing field name'; - } - if ((clientFramework === undefined || clientFramework === ANGULAR) && isReservedFieldName(input, ANGULAR)) { - return 'Your field name cannot contain a Java or Angular reserved keyword'; - } - if ((clientFramework !== undefined || clientFramework === REACT) && isReservedFieldName(input, REACT)) { - return 'Your field name cannot contain a Java or React reserved keyword'; - } - // we don't know, if filtering will be used - if (possibleFiltering && isReservedPaginationWords(input)) { - return 'Your field name cannot be a value, which is used as a parameter by Spring for pagination'; - } - return true; - }, - message: 'What is the name of your field?', - }, - { - type: 'list', - name: 'fieldType', - message: 'What is the type of your field?', - choices: () => [ - { value: STRING, name: 'String' }, - { value: INTEGER, name: 'Integer' }, - { value: LONG, name: 'Long' }, - { value: FLOAT, name: 'Float' }, - { value: DOUBLE, name: 'Double' }, - { value: BIG_DECIMAL, name: 'BigDecimal' }, - { value: LOCAL_DATE, name: 'LocalDate' }, - { value: INSTANT, name: 'Instant' }, - { value: ZONED_DATE_TIME, name: 'ZonedDateTime' }, - { value: DURATION, name: 'Duration' }, - { value: BOOLEAN, name: 'Boolean' }, - { value: ENUM, name: 'Enumeration (Java enum type)' }, - { value: UUID, name: 'UUID' }, - { value: UUID, name: 'UUID' }, - ...(databaseType === CASSANDRA ? [{ value: BYTE_BUFFER, name: '[BETA] Blob' }] : [{ value: BYTES, name: '[BETA] Blob' }]), - ], - default: 0, - }, - { - when: response => { - if (response.fieldType === ENUM) { - response.fieldIsEnum = true; - return true; - } - response.fieldIsEnum = false; - return false; - }, - type: 'input', - name: 'enumType', - validate: input => { - if (input === '') { - return 'Your class name cannot be empty.'; - } - if (isReservedTableName(input, 'JAVA')) { - return 'Your enum name cannot contain a Java reserved keyword'; - } - if (!/^[A-Za-z0-9_]*$/.test(input)) { - return 'Your enum name cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; - } - if (context.enums && context.enums.includes(input)) { - context.existingEnum = true; - } else if (context.enums) { - context.enums.push(input); - } else { - context.enums = [input]; - } - return true; - }, - message: 'What is the class name of your enumeration?', - }, - { - when: response => response.fieldIsEnum, - type: 'input', - name: 'fieldValues', - validate: input => { - if (input === '' && context.existingEnum) { - context.existingEnum = false; - return true; - } - if (input === '') { - return 'You must specify values for your enumeration'; - } - // Commas allowed so that user can input a list of values split by commas. - if (!/^[A-Za-z0-9_,]+$/.test(input)) { - return 'Enum values cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; - } - const enums = input.replace(/\s/g, '').split(','); - if (_.uniq(enums).length !== enums.length) { - return `Enum values cannot contain duplicates (typed values: ${input})`; - } - for (let i = 0; i < enums.length; i++) { - if (/^[0-9].*/.test(enums[i])) { - return `Enum value "${enums[i]}" cannot start with a number`; - } - if (enums[i] === '') { - return 'Enum value cannot be empty (did you accidentally type "," twice in a row?)'; - } - } - - return true; - }, - message: () => { - if (!context.existingEnum) { - return 'What are the values of your enumeration (separated by comma, no spaces)?'; - } - return 'What are the new values of your enumeration (separated by comma, no spaces)?\nThe new values will replace the old ones.\nNothing will be done if there are no new values.'; - }, - }, - { - when: response => response.fieldType === BYTES || response.fieldType === BYTE_BUFFER, - type: 'list', - name: 'fieldTypeBlobContent', - message: 'What is the content of the Blob field?', - choices: answers => [ - { value: IMAGE, name: 'An image' }, - { value: ANY, name: 'A binary file' }, - ...(answers.fieldType === BYTES ? [{ value: TEXT, name: 'A CLOB (Text field)' }] : []), - ], - default: 0, - }, - { - when: response => response.fieldType !== BYTE_BUFFER, - type: 'confirm', - name: 'fieldValidate', - message: 'Do you want to add validation rules to your field?', - default: false, - }, - { - when: response => response.fieldValidate === true, - type: 'checkbox', - name: 'fieldValidateRules', - message: 'Which validation rules do you want to add?', - choices: response => { - // Default rules applicable for fieldType 'LocalDate', 'Instant', - // 'ZonedDateTime', 'Duration', 'UUID', 'Boolean', 'ByteBuffer' and 'Enum' - const opts = [ - { - name: 'Required', - value: REQUIRED, - }, - { - name: 'Unique', - value: UNIQUE, - }, - ]; - if (response.fieldType === STRING || response.fieldTypeBlobContent === TEXT) { - opts.push( - { - name: 'Minimum length', - value: MINLENGTH, - }, - { - name: 'Maximum length', - value: MAXLENGTH, - }, - { - name: 'Regular expression pattern', - value: PATTERN, - }, - ); - } else if ([INTEGER, LONG, FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { - opts.push( - { - name: 'Minimum', - value: MIN, - }, - { - name: 'Maximum', - value: MAX, - }, - ); - } - return opts; - }, - default: 0, - }, - { - when: response => response.fieldValidate === true && response.fieldValidateRules.includes('minlength'), - type: 'input', - name: 'fieldValidateRulesMinlength', - validate: input => (inputIsNumber(input) ? true : 'Minimum length must be a positive number'), - message: 'What is the minimum length of your field?', - default: 0, - }, - { - when: response => response.fieldValidate === true && response.fieldValidateRules.includes('maxlength'), - type: 'input', - name: 'fieldValidateRulesMaxlength', - validate: input => (inputIsNumber(input) ? true : 'Maximum length must be a positive number'), - message: 'What is the maximum length of your field?', - default: 20, - }, - { - when: response => response.fieldValidate === true && response.fieldValidateRules.includes('min'), - type: 'input', - name: 'fieldValidateRulesMin', - message: 'What is the minimum of your field?', - validate: (input, response) => { - if ([FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { - return inputIsSignedDecimalNumber(input) ? true : 'Minimum must be a decimal number'; - } - return inputIsSignedNumber(input) ? true : 'Minimum must be a number'; - }, - default: 0, - }, - { - when: response => response.fieldValidate === true && response.fieldValidateRules.includes('max'), - type: 'input', - name: 'fieldValidateRulesMax', - message: 'What is the maximum of your field?', - validate: (input, response) => { - if ([FLOAT, DOUBLE, BIG_DECIMAL].includes(response.fieldType)) { - return inputIsSignedDecimalNumber(input) ? true : 'Maximum must be a decimal number'; - } - return inputIsSignedNumber(input) ? true : 'Maximum must be a number'; - }, - default: 100, - }, - { - when: response => - response.fieldValidate === true && - response.fieldValidateRules.includes(MINBYTES) && - response.fieldType === BYTES && - response.fieldTypeBlobContent !== TEXT, - type: 'input', - name: 'fieldValidateRulesMinbytes', - message: 'What is the minimum byte size of your field?', - validate: input => (inputIsNumber(input) ? true : 'Minimum byte size must be a positive number'), - default: 0, - }, - { - when: response => - response.fieldValidate === true && - response.fieldValidateRules.includes(MAXBYTES) && - response.fieldType === BYTES && - response.fieldTypeBlobContent !== TEXT, - type: 'input', - name: 'fieldValidateRulesMaxbytes', - message: 'What is the maximum byte size of your field?', - validate: input => (inputIsNumber(input) ? true : 'Maximum byte size must be a positive number'), - default: 5000000, - }, - { - when: response => response.fieldValidate === true && response.fieldValidateRules.includes('pattern'), - type: 'input', - name: 'fieldValidateRulesPattern', - message: 'What is the regular expression pattern you want to apply on your field?', - default: '^[a-zA-Z0-9]*$', - }, - ]); - - if (answers.fieldIsEnum) { - answers.fieldType = _.upperFirst(answers.fieldType); - answers.fieldValues = answers.fieldValues.toUpperCase(); - } - - const field = { - fieldName: answers.fieldName, - fieldType: answers.enumType || answers.fieldType, - fieldTypeBlobContent: answers.fieldTypeBlobContent, - fieldValues: answers.fieldValues, - fieldValidateRules: answers.fieldValidateRules, - fieldValidateRulesMinlength: answers.fieldValidateRulesMinlength, - fieldValidateRulesMaxlength: answers.fieldValidateRulesMaxlength, - fieldValidateRulesPattern: answers.fieldValidateRulesPattern, - fieldValidateRulesMin: answers.fieldValidateRulesMin, - fieldValidateRulesMax: answers.fieldValidateRulesMax, - fieldValidateRulesMinbytes: answers.fieldValidateRulesMinbytes, - fieldValidateRulesMaxbytes: answers.fieldValidateRulesMaxbytes, - }; - - this.entityConfig.fields = this.entityConfig.fields.concat(field); - - logFieldsAndRelationships.call(this); - await askForField.call(this); -} - -/** - * ask question for a relationship creation - */ -async function askForRelationship(...args) { - const [{ application }] = args; - const context = this.entityData; - const name = context.name; - this.log.log(chalk.green('\nGenerating relationships to other entities\n')); - const addRelationshipAnswers = await this.prompt([ - { - type: 'confirm', - name: 'relationshipAdd', - message: 'Do you want to add a relationship to another entity?', - default: true, - }, - ]); - - if (!addRelationshipAnswers.relationshipAdd) { - logFieldsAndRelationships.call(this); - this.log.log('\n'); - return; - } - - const answers = await this.prompt([ - { - type: 'list', - name: 'otherEntityName', - message: 'What is the other entity?', - choices: () => [...this.getExistingEntityNames(), ...(application.generateBuiltInUserEntity ? ['User'] : [])], - }, - { - type: 'input', - name: 'relationshipName', - validate: input => { - if (!/^([a-zA-Z0-9_]*)$/.test(input)) { - return 'Your relationship cannot contain special characters'; - } - if (input === '') { - return 'Your relationship cannot be empty'; - } - if (input.charAt(0) === input.charAt(0).toUpperCase()) { - return 'Your relationship cannot start with an upper case letter'; - } - if (input === 'id' || getFieldNameUndercored(this.entityConfig.fields).includes(_.snakeCase(input))) { - return 'Your relationship cannot use an already existing field name'; - } - if (isReservedTableName(input, 'JAVA')) { - return 'Your relationship cannot contain a Java reserved keyword'; - } - return true; - }, - message: 'What is the name of the relationship?', - default: response => _.lowerFirst(response.otherEntityName), - }, - { - type: 'list', - name: 'relationshipType', - message: 'What is the type of the relationship?', - choices: response => [ - 'many-to-one', - 'many-to-many', - 'one-to-one', - ...(this.isBuiltInUser(response.otherEntityName) ? [] : ['one-to-many']), - ], - default: 0, - }, - { - when: response => application.databaseType === SQL && response.relationshipType === 'one-to-one', - type: 'confirm', - name: 'id', - message: 'Do you want to use JPA Derived Identifier - @MapsId?', - default: false, - }, - { - when: answers => { - if (this.isBuiltInUser(answers.otherEntityName)) { - answers.bidirectional = false; - return false; - } - - if (!application.databaseTypeNeo4j && answers.relationshipType !== 'many-to-one') { - // Relationships requires bidirectional. - answers.bidirectional = true; - return false; - } - - return true; - }, - type: 'input', - name: 'bidirectional', - message: 'Do you want to generate a bidirectional relationship', - default: true, - }, - { - when: response => response.bidirectional, - type: 'input', - name: 'otherEntityRelationshipName', - message: 'What is the name of this relationship in the other entity?', - default: () => _.lowerFirst(name), - }, - { - type: 'input', - name: 'otherEntityField', - message: response => - `When you display this relationship on client-side, which field from '${response.otherEntityName}' do you want to use? This field will be displayed as a String, so it cannot be a Blob`, - default: answers => (answers.otherEntityName === 'User' ? 'login' : 'id'), - }, - { - when: response => - (response.otherEntityName.toLowerCase() !== context.name.toLowerCase() && response.relationshipType === 'many-to-one') || - response.relationshipType === 'many-to-many' || - response.relationshipType === 'one-to-one', - type: 'confirm', - name: 'relationshipValidate', - message: 'Do you want to add any validation rules to this relationship?', - default: false, - }, - { - when: response => response.relationshipValidate === true, - type: 'checkbox', - name: 'relationshipValidateRules', - message: 'Which validation rules do you want to add?', - choices: [ - { - name: 'Required', - value: REQUIRED, - }, - ], - default: 0, - }, - ]); - - const relationship = { - relationshipSide: 'left', - relationshipName: answers.relationshipName, - otherEntityName: _.lowerFirst(answers.otherEntityName), - relationshipType: answers.relationshipType, - relationshipValidateRules: answers.relationshipValidateRules, - otherEntityField: answers.otherEntityField, - ownerSide: answers.ownerSide, - id: answers.id, - otherEntityRelationshipName: answers.otherEntityRelationshipName, - }; - - if (this.isBuiltInUser(answers.otherEntityName)) { - relationship.otherEntityRelationshipName = _.lowerFirst(name); - } - - this.entityConfig.relationships = this.entityConfig.relationships.concat(relationship); - - await askForRelationship.call(this, ...args); -} - -/** - * Show the entity and it's fields and relationships in console - */ -function logFieldsAndRelationships() { - const context = this.entityData; - if (this.entityConfig.fields.length > 0 || this.entityConfig.relationships.length > 0) { - this.log.log(chalk.red(chalk.white('\n================= ') + context.name + chalk.white(' ================='))); - } - if (this.entityConfig.fields.length > 0) { - this.log.log(chalk.white('Fields')); - this.entityConfig.fields.forEach(field => { - const validationDetails = []; - const fieldValidate = _.isArray(field.fieldValidateRules) && field.fieldValidateRules.length >= 1; - if (fieldValidate === true) { - if (field.fieldValidateRules.includes(REQUIRED)) { - validationDetails.push(REQUIRED); - } - if (field.fieldValidateRules.includes(UNIQUE)) { - validationDetails.push(UNIQUE); - } - if (field.fieldValidateRules.includes(MINLENGTH)) { - validationDetails.push(`${MINLENGTH}='${field.fieldValidateRulesMinlength}'`); - } - if (field.fieldValidateRules.includes(MAXLENGTH)) { - validationDetails.push(`${MAXLENGTH}='${field.fieldValidateRulesMaxlength}'`); - } - if (field.fieldValidateRules.includes(PATTERN)) { - validationDetails.push(`${PATTERN}='${field.fieldValidateRulesPattern}'`); - } - if (field.fieldValidateRules.includes(MIN)) { - validationDetails.push(`${MIN}='${field.fieldValidateRulesMin}'`); - } - if (field.fieldValidateRules.includes(MAX)) { - validationDetails.push(`${MAX}='${field.fieldValidateRulesMax}'`); - } - if (field.fieldValidateRules.includes(MINBYTES)) { - validationDetails.push(`${MINBYTES}='${field.fieldValidateRulesMinbytes}'`); - } - if (field.fieldValidateRules.includes(MAXBYTES)) { - validationDetails.push(`${MAXBYTES}='${field.fieldValidateRulesMaxbytes}'`); - } - } - this.log.log( - chalk.red(field.fieldName) + - chalk.white(` (${field.fieldType}${field.fieldTypeBlobContent ? ` ${field.fieldTypeBlobContent}` : ''}) `) + - chalk.cyan(validationDetails.join(' ')), - ); - }); - this.log.log(); - } - if (this.entityConfig.relationships.length > 0) { - this.log.log(chalk.white('Relationships')); - this.entityConfig.relationships.forEach(relationship => { - const validationDetails = []; - if (relationship.relationshipValidateRules && relationship.relationshipValidateRules.includes(REQUIRED)) { - validationDetails.push(REQUIRED); - } - this.log.log( - `${chalk.red(relationship.relationshipName)} ${chalk.white(`(${_.upperFirst(relationship.otherEntityName)})`)} ${chalk.cyan( - relationship.relationshipType, - )} ${chalk.cyan(validationDetails.join(' '))}`, - ); - }); - this.log.log(); - } -} diff --git a/generators/entity/single-entity.spec.mts b/generators/entity/single-entity.spec.mts deleted file mode 100644 index f8ef2b246a7f..000000000000 --- a/generators/entity/single-entity.spec.mts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect } from 'esmocha'; - -import { skipPrettierHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR, CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import BaseApplicationGenerator from '../base-application/generator.mjs'; -import { GENERATOR_ENTITY } from '../generator-list.mjs'; - -class MockedLanguagesGenerator extends BaseApplicationGenerator { - get [BaseApplicationGenerator.PREPARING]() { - return { - mockTranslations({ control }) { - control.getWebappTranslation = () => 'translations'; - }, - }; - } -} - -const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; -const entityBar = { name: 'Bar', changelogDate: '20160926101211' }; - -describe('generator - entity --single-entity', () => { - context('when regenerating', () => { - describe('with default configuration', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_ENTITY) - .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) - .withJHipsterConfig({}, [entityFoo, entityBar]) - .withArguments(['Foo']) - .withOptions({ ignoreNeedlesError: true, regenerate: true, force: true, singleEntity: true }) - .withMockedSource(); - }); - - it('should match source calls', () => { - expect(runResult.sourceCallsArg).toMatchSnapshot(); - }); - - it('should create files for entity Foo', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, - `${CLIENT_MAIN_SRC_DIR}app/entities/foo/foo.model.ts`, - ]); - }); - - it('should not create files for the entity Bar', () => { - runResult.assertNoFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101211_added_entity_Bar.xml`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, - `${CLIENT_MAIN_SRC_DIR}app/entities/bar/bar.model.ts`, - ]); - }); - }); - - describe('with cassandra database', () => { - let runResult; - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_ENTITY) - .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) - .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo, entityBar]) - .withArguments(['Foo']) - .withOptions({ ignoreNeedlesError: true, regenerate: true, force: true, singleEntity: true }); - }); - - after(() => runResult.cleanup()); - - it('should create files for entity Foo', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, - ]); - }); - - it('should not create files for the entity Bar', () => { - runResult.assertNoFile([ - `${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101211_added_entity_Bar.cql`, - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, - ]); - }); - }); - }); -}); diff --git a/generators/entity/single-entity.spec.ts b/generators/entity/single-entity.spec.ts new file mode 100644 index 000000000000..00f98234c80b --- /dev/null +++ b/generators/entity/single-entity.spec.ts @@ -0,0 +1,83 @@ +import { expect } from 'esmocha'; + +import { skipPrettierHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR, CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import BaseApplicationGenerator from '../base-application/generator.js'; +import { GENERATOR_ENTITY } from '../generator-list.js'; + +class MockedLanguagesGenerator extends BaseApplicationGenerator { + get [BaseApplicationGenerator.PREPARING]() { + return { + mockTranslations({ control }) { + control.getWebappTranslation = () => 'translations'; + }, + }; + } +} + +const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; +const entityBar = { name: 'Bar', changelogDate: '20160926101211' }; + +describe('generator - entity --single-entity', () => { + context('when regenerating', () => { + describe('with default configuration', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_ENTITY) + .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) + .withJHipsterConfig({}, [entityFoo, entityBar]) + .withArguments(['Foo']) + .withOptions({ ignoreNeedlesError: true, regenerate: true, force: true, singleEntity: true }) + .withMockedSource(); + }); + + it('should match source calls', () => { + expect(runResult.sourceCallsArg).toMatchSnapshot(); + }); + + it('should create files for entity Foo', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101210_added_entity_Foo.xml`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, + `${CLIENT_MAIN_SRC_DIR}app/entities/foo/foo.model.ts`, + ]); + }); + + it('should not create files for the entity Bar', () => { + runResult.assertNoFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20160926101211_added_entity_Bar.xml`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, + `${CLIENT_MAIN_SRC_DIR}app/entities/bar/bar.model.ts`, + ]); + }); + }); + + describe('with cassandra database', () => { + let runResult; + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_ENTITY) + .withGenerators([[MockedLanguagesGenerator, 'jhipster:languages']]) + .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo, entityBar]) + .withArguments(['Foo']) + .withOptions({ ignoreNeedlesError: true, regenerate: true, force: true, singleEntity: true }); + }); + + after(() => runResult.cleanup()); + + it('should create files for entity Foo', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Foo.java`, + ]); + }); + + it('should not create files for the entity Bar', () => { + runResult.assertNoFile([ + `${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101211_added_entity_Bar.cql`, + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/domain/Bar.java`, + ]); + }); + }); + }); +}); diff --git a/generators/entity/support/asserts.mjs b/generators/entity/support/asserts.js similarity index 100% rename from generators/entity/support/asserts.mjs rename to generators/entity/support/asserts.js diff --git a/generators/entity/support/index.mts b/generators/entity/support/index.mts deleted file mode 100644 index 6e6d9f3bcd40..000000000000 --- a/generators/entity/support/index.mts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -// eslint-disable-next-line import/prefer-default-export -export { - isNumber as inputIsNumber, - isSignedNumber as inputIsSignedNumber, - isSignedDecimalNumber as inputIsSignedDecimalNumber, -} from './asserts.mjs'; diff --git a/generators/entity/support/index.ts b/generators/entity/support/index.ts new file mode 100644 index 000000000000..aafaad7edd79 --- /dev/null +++ b/generators/entity/support/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2013-2023 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. + */ +// eslint-disable-next-line import/prefer-default-export +export { + isNumber as inputIsNumber, + isSignedNumber as inputIsSignedNumber, + isSignedDecimalNumber as inputIsSignedDecimalNumber, +} from './asserts.js'; diff --git a/generators/export-jdl/__snapshots__/export-jdl.spec.mts.snap b/generators/export-jdl/__snapshots__/export-jdl.spec.ts.snap similarity index 100% rename from generators/export-jdl/__snapshots__/export-jdl.spec.mts.snap rename to generators/export-jdl/__snapshots__/export-jdl.spec.ts.snap diff --git a/generators/export-jdl/export-jdl.spec.mts b/generators/export-jdl/export-jdl.spec.mts deleted file mode 100644 index 6a8c1fe0cf1b..000000000000 --- a/generators/export-jdl/export-jdl.spec.mts +++ /dev/null @@ -1,406 +0,0 @@ -import { expect } from 'esmocha'; - -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { GENERATOR_EXPORT_JDL } from '../generator-list.mjs'; - -const files = { - '.jhipster/Country.json': { - fluentMethods: true, - relationships: [ - { - relationshipType: 'one-to-one', - relationshipName: 'region', - otherEntityName: 'region', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'country', - }, - ], - fields: [ - { - fieldName: 'countryId', - fieldType: 'Long', - documentation: 'The country Id', - }, - { - fieldName: 'countryName', - fieldType: 'String', - }, - ], - changelogDate: '20160926101210', - entityTableName: 'country', - dto: 'no', - pagination: 'no', - service: 'no', - }, - '.jhipster/Department.json': { - fluentMethods: false, - relationships: [ - { - relationshipType: 'one-to-one', - relationshipName: 'location', - otherEntityName: 'location', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'department', - }, - { - relationshipType: 'one-to-many', - relationshipValidateRules: 'required', - relationshipName: 'employee', - relationshipSide: 'left', - otherEntityName: 'employee', - otherEntityRelationshipName: 'department', - }, - ], - fields: [ - { - fieldName: 'departmentId', - fieldType: 'Long', - }, - { - fieldName: 'departmentName', - fieldType: 'String', - fieldValidateRules: ['required'], - }, - ], - changelogDate: '20160926092246', - entityTableName: 'department', - dto: 'no', - pagination: 'no', - service: 'no', - }, - '.jhipster/Employee.json': { - relationships: [ - { - relationshipSide: 'right', - relationshipName: 'department', - otherEntityName: 'department', - relationshipType: 'many-to-one', - otherEntityField: 'foo', - otherEntityRelationshipName: 'employee', - }, - { - relationshipType: 'one-to-many', - relationshipName: 'job', - otherEntityName: 'job', - relationshipSide: 'left', - otherEntityRelationshipName: 'employee', - }, - { - relationshipType: 'many-to-one', - relationshipName: 'manager', - otherEntityName: 'employee', - relationshipSide: 'left', - otherEntityField: 'id', - }, - ], - fields: [ - { - fieldName: 'employeeId', - fieldType: 'Long', - }, - { - fieldName: 'employeeUuid', - fieldType: 'UUID', - }, - { - fieldName: 'firstName', - documentation: 'The firstname attribute.', - fieldType: 'String', - }, - { - fieldName: 'lastName', - fieldType: 'String', - }, - { - fieldName: 'email', - fieldType: 'String', - }, - { - fieldName: 'phoneNumber', - fieldType: 'String', - }, - { - fieldName: 'hireDate', - fieldType: 'ZonedDateTime', - }, - { - fieldName: 'salary', - fieldType: 'Long', - fieldValidateRules: ['min', 'max'], - fieldValidateRulesMin: 10000, - fieldValidateRulesMax: 1000000, - }, - { - fieldName: 'commissionPct', - fieldType: 'Long', - }, - ], - changelogDate: '20160926083805', - documentation: 'The Employee entity.', - entityTableName: 'emp', - dto: 'mapstruct', - pagination: 'infinite-scroll', - service: 'serviceClass', - fluentMethods: false, - searchEngine: 'elasticsearch', - angularJSSuffix: 'myentities', - microserviceName: 'mymicroservice', - }, - '.jhipster/Job.json': { - fluentMethods: true, - relationships: [ - { - relationshipName: 'employee', - otherEntityName: 'employee', - relationshipType: 'many-to-one', - relationshipSide: 'right', - otherEntityField: 'id', - otherEntityRelationshipName: 'job', - }, - { - relationshipType: 'many-to-many', - otherEntityRelationshipName: 'job', - relationshipName: 'task', - otherEntityName: 'task', - otherEntityField: 'title', - relationshipSide: 'left', - }, - ], - fields: [ - { - fieldName: 'jobId', - fieldType: 'Long', - }, - { - fieldName: 'jobTitle', - fieldType: 'String', - }, - { - fieldName: 'minSalary', - fieldType: 'Long', - }, - { - fieldName: 'maxSalary', - fieldType: 'Long', - }, - ], - changelogDate: '20160924093047', - entityTableName: 'job', - dto: 'no', - pagination: 'pagination', - service: 'no', - }, - '.jhipster/JobHistory.json': { - fluentMethods: true, - relationships: [ - { - relationshipType: 'one-to-one', - relationshipName: 'job', - otherEntityName: 'job', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'jobHistory', - }, - { - relationshipType: 'one-to-one', - relationshipName: 'department', - otherEntityName: 'department', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'jobHistory', - }, - { - relationshipType: 'one-to-one', - relationshipName: 'employee', - otherEntityName: 'employee', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'jobHistory', - }, - ], - fields: [ - { - fieldName: 'startDate', - fieldType: 'ZonedDateTime', - }, - { - fieldName: 'endDate', - fieldType: 'ZonedDateTime', - }, - { - fieldName: 'language', - fieldType: 'Language', - fieldValues: 'FRENCH,ENGLISH,SPANISH', - }, - ], - changelogDate: '20160924092444', - entityTableName: 'job_history', - dto: 'no', - pagination: 'infinite-scroll', - service: 'no', - }, - '.jhipster/NoEntity.txt': { - fluentMethods: true, - relationships: [ - { - relationshipType: 'one-to-one', - relationshipName: 'country', - otherEntityName: 'country', - otherEntityField: 'id', - relationshipSide: 'left', - otherEntityRelationshipName: 'location', - }, - ], - fields: [ - { - fieldName: 'locationId', - fieldType: 'Long', - }, - { - fieldName: 'streetAddress', - fieldType: 'String', - }, - { - fieldName: 'postalCode', - fieldType: 'String', - }, - { - fieldName: 'city', - fieldType: 'String', - }, - { - fieldName: 'stateProvince', - fieldType: 'String', - }, - ], - changelogDate: '20160924092439', - entityTableName: 'location', - dto: 'no', - pagination: 'no', - service: 'no', - }, - '.jhipster/Region.json': { - fluentMethods: true, - relationships: [ - { - relationshipType: 'one-to-one', - relationshipName: 'country', - otherEntityName: 'country', - relationshipSide: 'right', - otherEntityRelationshipName: 'region', - }, - ], - fields: [ - { - fieldName: 'regionId', - fieldType: 'Long', - }, - { - fieldName: 'regionName', - fieldType: 'String', - }, - ], - changelogDate: '20160926083800', - entityTableName: 'region', - dto: 'no', - pagination: 'no', - service: 'no', - }, - '.jhipster/Task.json': { - fluentMethods: true, - relationships: [ - { - relationshipType: 'many-to-many', - relationshipValidateRules: 'required', - relationshipName: 'job', - otherEntityName: 'job', - relationshipSide: 'right', - otherEntityRelationshipName: 'task', - }, - ], - fields: [ - { - fieldName: 'taskId', - fieldType: 'Long', - }, - { - fieldName: 'title', - fieldType: 'String', - }, - { - fieldName: 'description', - fieldType: 'String', - }, - ], - changelogDate: '20160926100704', - entityTableName: 'task', - dto: 'no', - pagination: 'no', - service: 'no', - }, -}; - -const applicationConfig = { - jhipsterVersion: '3.7.1', - baseName: 'standard', - packageName: 'com.mycompany.myapp', - packageFolder: 'com/mycompany/myapp', - serverPort: '8080', - authenticationType: 'session', - cacheProvider: 'ehcache', - enableHibernateCache: true, - websocket: 'no', - databaseType: 'sql', - devDatabaseType: 'h2Memory', - prodDatabaseType: 'postgresql', - searchEngine: 'no', - buildTool: 'gradle', - rememberMeKey: '5f1100e7eae25e2abe32d7b2031ac1f2acc778d8', - applicationType: 'monolith', - testFrameworks: [], - jhiPrefix: 'jhi', - enableTranslation: true, - nativeLanguage: 'en', - languages: ['en'], - skipClient: true, - skipServer: true, -}; - -describe('generator - export-jdl', () => { - describe('exports entities to a JDL file without argument', () => { - let runResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_EXPORT_JDL).withJHipsterConfig(applicationConfig).withFiles(files).commitFiles(); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot(file => file.path.endsWith('.jdl'))).toMatchSnapshot(); - }); - it('creates the jdl file based on app name', () => { - runResult.assertFile('standard.jdl'); - }); - }); - - describe('exports entities to a JDL file with file argument', () => { - let runResult; - - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_EXPORT_JDL) - .withJHipsterConfig(applicationConfig) - .withFiles(files) - .commitFiles() - .withArguments('custom-app.jdl'); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot(file => file.path.endsWith('.jdl'))).toMatchSnapshot(); - }); - it('creates the jdl file', () => { - runResult.assertFile('custom-app.jdl'); - }); - }); -}); diff --git a/generators/export-jdl/export-jdl.spec.ts b/generators/export-jdl/export-jdl.spec.ts new file mode 100644 index 000000000000..29a7222763f3 --- /dev/null +++ b/generators/export-jdl/export-jdl.spec.ts @@ -0,0 +1,406 @@ +import { expect } from 'esmocha'; + +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { GENERATOR_EXPORT_JDL } from '../generator-list.js'; + +const files = { + '.jhipster/Country.json': { + fluentMethods: true, + relationships: [ + { + relationshipType: 'one-to-one', + relationshipName: 'region', + otherEntityName: 'region', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'country', + }, + ], + fields: [ + { + fieldName: 'countryId', + fieldType: 'Long', + documentation: 'The country Id', + }, + { + fieldName: 'countryName', + fieldType: 'String', + }, + ], + changelogDate: '20160926101210', + entityTableName: 'country', + dto: 'no', + pagination: 'no', + service: 'no', + }, + '.jhipster/Department.json': { + fluentMethods: false, + relationships: [ + { + relationshipType: 'one-to-one', + relationshipName: 'location', + otherEntityName: 'location', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'department', + }, + { + relationshipType: 'one-to-many', + relationshipValidateRules: 'required', + relationshipName: 'employee', + relationshipSide: 'left', + otherEntityName: 'employee', + otherEntityRelationshipName: 'department', + }, + ], + fields: [ + { + fieldName: 'departmentId', + fieldType: 'Long', + }, + { + fieldName: 'departmentName', + fieldType: 'String', + fieldValidateRules: ['required'], + }, + ], + changelogDate: '20160926092246', + entityTableName: 'department', + dto: 'no', + pagination: 'no', + service: 'no', + }, + '.jhipster/Employee.json': { + relationships: [ + { + relationshipSide: 'right', + relationshipName: 'department', + otherEntityName: 'department', + relationshipType: 'many-to-one', + otherEntityField: 'foo', + otherEntityRelationshipName: 'employee', + }, + { + relationshipType: 'one-to-many', + relationshipName: 'job', + otherEntityName: 'job', + relationshipSide: 'left', + otherEntityRelationshipName: 'employee', + }, + { + relationshipType: 'many-to-one', + relationshipName: 'manager', + otherEntityName: 'employee', + relationshipSide: 'left', + otherEntityField: 'id', + }, + ], + fields: [ + { + fieldName: 'employeeId', + fieldType: 'Long', + }, + { + fieldName: 'employeeUuid', + fieldType: 'UUID', + }, + { + fieldName: 'firstName', + documentation: 'The firstname attribute.', + fieldType: 'String', + }, + { + fieldName: 'lastName', + fieldType: 'String', + }, + { + fieldName: 'email', + fieldType: 'String', + }, + { + fieldName: 'phoneNumber', + fieldType: 'String', + }, + { + fieldName: 'hireDate', + fieldType: 'ZonedDateTime', + }, + { + fieldName: 'salary', + fieldType: 'Long', + fieldValidateRules: ['min', 'max'], + fieldValidateRulesMin: 10000, + fieldValidateRulesMax: 1000000, + }, + { + fieldName: 'commissionPct', + fieldType: 'Long', + }, + ], + changelogDate: '20160926083805', + documentation: 'The Employee entity.', + entityTableName: 'emp', + dto: 'mapstruct', + pagination: 'infinite-scroll', + service: 'serviceClass', + fluentMethods: false, + searchEngine: 'elasticsearch', + angularJSSuffix: 'myentities', + microserviceName: 'mymicroservice', + }, + '.jhipster/Job.json': { + fluentMethods: true, + relationships: [ + { + relationshipName: 'employee', + otherEntityName: 'employee', + relationshipType: 'many-to-one', + relationshipSide: 'right', + otherEntityField: 'id', + otherEntityRelationshipName: 'job', + }, + { + relationshipType: 'many-to-many', + otherEntityRelationshipName: 'job', + relationshipName: 'task', + otherEntityName: 'task', + otherEntityField: 'title', + relationshipSide: 'left', + }, + ], + fields: [ + { + fieldName: 'jobId', + fieldType: 'Long', + }, + { + fieldName: 'jobTitle', + fieldType: 'String', + }, + { + fieldName: 'minSalary', + fieldType: 'Long', + }, + { + fieldName: 'maxSalary', + fieldType: 'Long', + }, + ], + changelogDate: '20160924093047', + entityTableName: 'job', + dto: 'no', + pagination: 'pagination', + service: 'no', + }, + '.jhipster/JobHistory.json': { + fluentMethods: true, + relationships: [ + { + relationshipType: 'one-to-one', + relationshipName: 'job', + otherEntityName: 'job', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'jobHistory', + }, + { + relationshipType: 'one-to-one', + relationshipName: 'department', + otherEntityName: 'department', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'jobHistory', + }, + { + relationshipType: 'one-to-one', + relationshipName: 'employee', + otherEntityName: 'employee', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'jobHistory', + }, + ], + fields: [ + { + fieldName: 'startDate', + fieldType: 'ZonedDateTime', + }, + { + fieldName: 'endDate', + fieldType: 'ZonedDateTime', + }, + { + fieldName: 'language', + fieldType: 'Language', + fieldValues: 'FRENCH,ENGLISH,SPANISH', + }, + ], + changelogDate: '20160924092444', + entityTableName: 'job_history', + dto: 'no', + pagination: 'infinite-scroll', + service: 'no', + }, + '.jhipster/NoEntity.txt': { + fluentMethods: true, + relationships: [ + { + relationshipType: 'one-to-one', + relationshipName: 'country', + otherEntityName: 'country', + otherEntityField: 'id', + relationshipSide: 'left', + otherEntityRelationshipName: 'location', + }, + ], + fields: [ + { + fieldName: 'locationId', + fieldType: 'Long', + }, + { + fieldName: 'streetAddress', + fieldType: 'String', + }, + { + fieldName: 'postalCode', + fieldType: 'String', + }, + { + fieldName: 'city', + fieldType: 'String', + }, + { + fieldName: 'stateProvince', + fieldType: 'String', + }, + ], + changelogDate: '20160924092439', + entityTableName: 'location', + dto: 'no', + pagination: 'no', + service: 'no', + }, + '.jhipster/Region.json': { + fluentMethods: true, + relationships: [ + { + relationshipType: 'one-to-one', + relationshipName: 'country', + otherEntityName: 'country', + relationshipSide: 'right', + otherEntityRelationshipName: 'region', + }, + ], + fields: [ + { + fieldName: 'regionId', + fieldType: 'Long', + }, + { + fieldName: 'regionName', + fieldType: 'String', + }, + ], + changelogDate: '20160926083800', + entityTableName: 'region', + dto: 'no', + pagination: 'no', + service: 'no', + }, + '.jhipster/Task.json': { + fluentMethods: true, + relationships: [ + { + relationshipType: 'many-to-many', + relationshipValidateRules: 'required', + relationshipName: 'job', + otherEntityName: 'job', + relationshipSide: 'right', + otherEntityRelationshipName: 'task', + }, + ], + fields: [ + { + fieldName: 'taskId', + fieldType: 'Long', + }, + { + fieldName: 'title', + fieldType: 'String', + }, + { + fieldName: 'description', + fieldType: 'String', + }, + ], + changelogDate: '20160926100704', + entityTableName: 'task', + dto: 'no', + pagination: 'no', + service: 'no', + }, +}; + +const applicationConfig = { + jhipsterVersion: '3.7.1', + baseName: 'standard', + packageName: 'com.mycompany.myapp', + packageFolder: 'com/mycompany/myapp', + serverPort: '8080', + authenticationType: 'session', + cacheProvider: 'ehcache', + enableHibernateCache: true, + websocket: 'no', + databaseType: 'sql', + devDatabaseType: 'h2Memory', + prodDatabaseType: 'postgresql', + searchEngine: 'no', + buildTool: 'gradle', + rememberMeKey: '5f1100e7eae25e2abe32d7b2031ac1f2acc778d8', + applicationType: 'monolith', + testFrameworks: [], + jhiPrefix: 'jhi', + enableTranslation: true, + nativeLanguage: 'en', + languages: ['en'], + skipClient: true, + skipServer: true, +}; + +describe('generator - export-jdl', () => { + describe('exports entities to a JDL file without argument', () => { + let runResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_EXPORT_JDL).withJHipsterConfig(applicationConfig).withFiles(files).commitFiles(); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot(file => file.path.endsWith('.jdl'))).toMatchSnapshot(); + }); + it('creates the jdl file based on app name', () => { + runResult.assertFile('standard.jdl'); + }); + }); + + describe('exports entities to a JDL file with file argument', () => { + let runResult; + + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_EXPORT_JDL) + .withJHipsterConfig(applicationConfig) + .withFiles(files) + .commitFiles() + .withArguments('custom-app.jdl'); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot(file => file.path.endsWith('.jdl'))).toMatchSnapshot(); + }); + it('creates the jdl file', () => { + runResult.assertFile('custom-app.jdl'); + }); + }); +}); diff --git a/generators/export-jdl/generator.mts b/generators/export-jdl/generator.mts deleted file mode 100644 index 648ac2ad5b4e..000000000000 --- a/generators/export-jdl/generator.mts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; - -import BaseGenerator from '../base/index.mjs'; - -import statistics from '../statistics.mjs'; -import { GENERATOR_EXPORT_JDL } from '../generator-list.mjs'; -import { applicationOptions } from '../../jdl/jhipster/index.mjs'; -import JSONToJDLConverter from '../../jdl/converters/json-to-jdl-converter.js'; -import type { JHipsterGeneratorOptions, JHipsterGeneratorFeatures } from '../base/api.mjs'; - -const { OptionNames } = applicationOptions; - -const { BASE_NAME } = OptionNames; - -export default class extends BaseGenerator { - baseName!: string; - - jdlFile!: string; - jdlContent?: string; - - constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { - super(args, options, { skipParseOptions: false, ...features }); - - this.argument('jdlFile', { type: String, required: false }); - - if (this.options.help) { - return; - } - this.baseName = this.config.get(BASE_NAME); - this.jdlFile = this.options.jdlFile || `${this.baseName}.jdl`; - } - - get [BaseGenerator.DEFAULT]() { - return this.asDefaultTaskGroup({ - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_EXPORT_JDL); - }, - - convertToJDL() { - try { - const jdlObject = JSONToJDLConverter.convertToJDL(this.destinationPath(), false); - if (jdlObject) { - this.jdlContent = jdlObject.toString(); - } - } catch (error: unknown) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - throw new Error(`An error occurred while exporting to JDL: ${(error as any).message}\n${error}`, { cause: error }); - } - }, - }); - } - - get [BaseGenerator.WRITING]() { - return this.asDefaultTaskGroup({ - writeJdl() { - if (this.jdlContent) { - this.writeDestination(this.jdlFile, this.jdlContent); - } - }, - }); - } - - get [BaseGenerator.END]() { - return this.asEndTaskGroup({ - end() { - this.log.log(chalk.green.bold('\nThe JDL export is complete!\n')); - }, - }); - } -} diff --git a/generators/export-jdl/generator.spec.mts b/generators/export-jdl/generator.spec.mts deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/export-jdl/generator.spec.mts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/export-jdl/generator.spec.ts b/generators/export-jdl/generator.spec.ts new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/export-jdl/generator.spec.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/export-jdl/generator.ts b/generators/export-jdl/generator.ts new file mode 100644 index 000000000000..d5c40ae82ab2 --- /dev/null +++ b/generators/export-jdl/generator.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; + +import BaseGenerator from '../base/index.js'; + +import statistics from '../statistics.js'; +import { GENERATOR_EXPORT_JDL } from '../generator-list.js'; +import { applicationOptions } from '../../jdl/jhipster/index.js'; +import JSONToJDLConverter from '../../jdl/converters/json-to-jdl-converter.js'; +import type { JHipsterGeneratorOptions, JHipsterGeneratorFeatures } from '../base/api.js'; + +const { OptionNames } = applicationOptions; + +const { BASE_NAME } = OptionNames; + +export default class extends BaseGenerator { + baseName!: string; + + jdlFile!: string; + jdlContent?: string; + + constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { + super(args, options, { skipParseOptions: false, ...features }); + + this.argument('jdlFile', { type: String, required: false }); + + if (this.options.help) { + return; + } + this.baseName = this.config.get(BASE_NAME); + this.jdlFile = this.options.jdlFile || `${this.baseName}.jdl`; + } + + get [BaseGenerator.DEFAULT]() { + return this.asDefaultTaskGroup({ + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_EXPORT_JDL); + }, + + convertToJDL() { + try { + const jdlObject = JSONToJDLConverter.convertToJDL(this.destinationPath(), false); + if (jdlObject) { + this.jdlContent = jdlObject.toString(); + } + } catch (error: unknown) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + throw new Error(`An error occurred while exporting to JDL: ${(error as any).message}\n${error}`, { cause: error }); + } + }, + }); + } + + get [BaseGenerator.WRITING]() { + return this.asDefaultTaskGroup({ + writeJdl() { + if (this.jdlContent) { + this.writeDestination(this.jdlFile, this.jdlContent); + } + }, + }); + } + + get [BaseGenerator.END]() { + return this.asEndTaskGroup({ + end() { + this.log.log(chalk.green.bold('\nThe JDL export is complete!\n')); + }, + }); + } +} diff --git a/generators/export-jdl/index.mts b/generators/export-jdl/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/export-jdl/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/export-jdl/index.ts b/generators/export-jdl/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/export-jdl/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/feign-client/__snapshots__/generator.spec.mts.snap b/generators/feign-client/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/feign-client/__snapshots__/generator.spec.mts.snap rename to generators/feign-client/__snapshots__/generator.spec.ts.snap diff --git a/generators/feign-client/cleanup.mts b/generators/feign-client/cleanup.mts deleted file mode 100644 index c66499831ecc..000000000000 --- a/generators/feign-client/cleanup.mts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupTask(this: BaseGenerator, { application }: any) { - if (this.isJhipsterVersionLessThan('8.0.1')) { - if (application.authenticationTypeOauth2) { - this.removeFile(`${application.javaPackageSrcDir}security/oauth2/AuthorizationHeaderUtil.java`); - this.removeFile(`${application.javaPackageTestDir}security/oauth2/AuthorizationHeaderUtilTest.java`); - } - } -} diff --git a/generators/feign-client/cleanup.ts b/generators/feign-client/cleanup.ts new file mode 100644 index 000000000000..c3540581bd0d --- /dev/null +++ b/generators/feign-client/cleanup.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupTask(this: BaseGenerator, { application }: any) { + if (this.isJhipsterVersionLessThan('8.0.1')) { + if (application.authenticationTypeOauth2) { + this.removeFile(`${application.javaPackageSrcDir}security/oauth2/AuthorizationHeaderUtil.java`); + this.removeFile(`${application.javaPackageTestDir}security/oauth2/AuthorizationHeaderUtilTest.java`); + } + } +} diff --git a/generators/feign-client/files.mts b/generators/feign-client/files.mts deleted file mode 100644 index c0591672d14c..000000000000 --- a/generators/feign-client/files.mts +++ /dev/null @@ -1,36 +0,0 @@ -import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../java/support/index.mjs'; - -export const feignFiles = { - microserviceFeignFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/FeignConfiguration.java'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'client/AuthorizationHeaderUtil.java', - 'client/AuthorizedFeignClient.java', - 'client/OAuth2InterceptedFeignConfiguration.java', - 'client/TokenRelayRequestInterceptor.java', - 'client/OAuthIdpTokenResponseDTO.java', - ], - }, - { - condition: generator => generator.authenticationTypeJwt, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['client/UserFeignClientInterceptor_jwt.java'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['client/AuthorizationHeaderUtilTest.java'], - }, - ], -}; diff --git a/generators/feign-client/files.ts b/generators/feign-client/files.ts new file mode 100644 index 000000000000..ac311f8b4b94 --- /dev/null +++ b/generators/feign-client/files.ts @@ -0,0 +1,36 @@ +import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../java/support/index.js'; + +export const feignFiles = { + microserviceFeignFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/FeignConfiguration.java'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'client/AuthorizationHeaderUtil.java', + 'client/AuthorizedFeignClient.java', + 'client/OAuth2InterceptedFeignConfiguration.java', + 'client/TokenRelayRequestInterceptor.java', + 'client/OAuthIdpTokenResponseDTO.java', + ], + }, + { + condition: generator => generator.authenticationTypeJwt, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['client/UserFeignClientInterceptor_jwt.java'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['client/AuthorizationHeaderUtilTest.java'], + }, + ], +}; diff --git a/generators/feign-client/generator.mts b/generators/feign-client/generator.mts deleted file mode 100644 index c15bdee3b5c6..000000000000 --- a/generators/feign-client/generator.mts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_FEIGN_CLIENT } from '../generator-list.mjs'; -import { feignFiles } from './files.mjs'; -import cleanupTask from './cleanup.mjs'; - -export default class FeignClientGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_FEIGN_CLIENT); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - async writeTask({ application }) { - await this.writeFiles({ - sections: feignFiles, - context: application, - }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - const openFeignArtifact = { groupId: 'org.springframework.cloud', artifactId: 'spring-cloud-starter-openfeign' }; - if (application.buildToolMaven) { - source.addMavenDependency?.(openFeignArtifact); - } - if (application.buildToolGradle) { - source.addGradleDependency?.({ - ...openFeignArtifact, - scope: 'implementation', - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/feign-client/generator.spec.mts b/generators/feign-client/generator.spec.mts deleted file mode 100644 index d0f2f74236af..000000000000 --- a/generators/feign-client/generator.spec.mts +++ /dev/null @@ -1,43 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { checkEnforcements, defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from '../server/index.mjs'; - -import { GENERATOR_FEIGN_CLIENT } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe('generator - feign-client', () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs')).GENERATOR_FEIGN_CLIENT).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({}, GENERATOR_FEIGN_CLIENT); - - describe('with jwt', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_FEIGN_CLIENT).withJHipsterConfig({ authenticationType: 'jwt' }); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - - describe('with oauth2', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_FEIGN_CLIENT).withJHipsterConfig({ authenticationType: 'oauth2' }); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/feign-client/generator.spec.ts b/generators/feign-client/generator.spec.ts new file mode 100644 index 000000000000..639d8250e26f --- /dev/null +++ b/generators/feign-client/generator.spec.ts @@ -0,0 +1,43 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { checkEnforcements, defaultHelpers as helpers, runResult } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from '../server/index.js'; + +import { GENERATOR_FEIGN_CLIENT } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe('generator - feign-client', () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js')).GENERATOR_FEIGN_CLIENT).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({}, GENERATOR_FEIGN_CLIENT); + + describe('with jwt', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_FEIGN_CLIENT).withJHipsterConfig({ authenticationType: 'jwt' }); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + + describe('with oauth2', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_FEIGN_CLIENT).withJHipsterConfig({ authenticationType: 'oauth2' }); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/feign-client/generator.ts b/generators/feign-client/generator.ts new file mode 100644 index 000000000000..b0209c72760f --- /dev/null +++ b/generators/feign-client/generator.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_FEIGN_CLIENT } from '../generator-list.js'; +import { feignFiles } from './files.js'; +import cleanupTask from './cleanup.js'; + +export default class FeignClientGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_FEIGN_CLIENT); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + async writeTask({ application }) { + await this.writeFiles({ + sections: feignFiles, + context: application, + }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + const openFeignArtifact = { groupId: 'org.springframework.cloud', artifactId: 'spring-cloud-starter-openfeign' }; + if (application.buildToolMaven) { + source.addMavenDependency?.(openFeignArtifact); + } + if (application.buildToolGradle) { + source.addGradleDependency?.({ + ...openFeignArtifact, + scope: 'implementation', + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/feign-client/index.mts b/generators/feign-client/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/feign-client/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/feign-client/index.ts b/generators/feign-client/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/feign-client/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/gatling/__snapshots__/generator.spec.mts.snap b/generators/gatling/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/gatling/__snapshots__/generator.spec.mts.snap rename to generators/gatling/__snapshots__/generator.spec.ts.snap diff --git a/generators/gatling/cleanup.mts b/generators/gatling/cleanup.ts similarity index 100% rename from generators/gatling/cleanup.mts rename to generators/gatling/cleanup.ts diff --git a/generators/gatling/entity-files.mts b/generators/gatling/entity-files.mts deleted file mode 100644 index d836b0101313..000000000000 --- a/generators/gatling/entity-files.mts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { TEST_DIR } from '../generator-constants.mjs'; - -export const gatlingFiles = { - gatlingFiles: [ - { - path: TEST_DIR, - templates: [ - { - file: 'java/gatling/simulations/_entityClass_GatlingTest.java', - renameTo: generator => `java/gatling/simulations/${generator.entityClass}GatlingTest.java`, - }, - ], - }, - ], -}; - -export function cleanupEntitiesTask() {} - -export default async function writeEntitiesTask(this: Generator, { application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - await this.writeFiles({ - sections: gatlingFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/gatling/entity-files.ts b/generators/gatling/entity-files.ts new file mode 100644 index 000000000000..d8c930a7e826 --- /dev/null +++ b/generators/gatling/entity-files.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { TEST_DIR } from '../generator-constants.js'; + +export const gatlingFiles = { + gatlingFiles: [ + { + path: TEST_DIR, + templates: [ + { + file: 'java/gatling/simulations/_entityClass_GatlingTest.java', + renameTo: generator => `java/gatling/simulations/${generator.entityClass}GatlingTest.java`, + }, + ], + }, + ], +}; + +export function cleanupEntitiesTask() {} + +export default async function writeEntitiesTask(this: Generator, { application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + await this.writeFiles({ + sections: gatlingFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/gatling/files.mts b/generators/gatling/files.mts deleted file mode 100644 index 76682abe1ef6..000000000000 --- a/generators/gatling/files.mts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { TEST_DIR } from '../generator-constants.mjs'; -import { WriteFileSection } from '../base/api.mjs'; -import { SpringBootApplication } from '../server/types.mjs'; - -const gatlingFiles: WriteFileSection = { - gatlingFiles: [ - { - templates: ['README.md.jhi.gatling'], - }, - { - path: TEST_DIR, - templates: [ - // Create Gatling test files - 'gatling/conf/gatling.conf', - 'gatling/conf/logback.xml', - ], - }, - ], -}; - -export default async function writeTask(this: Generator, { application }) { - await this.writeFiles({ - sections: gatlingFiles, - context: application, - }); -} diff --git a/generators/gatling/files.ts b/generators/gatling/files.ts new file mode 100644 index 000000000000..5c6bd59221b9 --- /dev/null +++ b/generators/gatling/files.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { TEST_DIR } from '../generator-constants.js'; +import { WriteFileSection } from '../base/api.js'; +import { SpringBootApplication } from '../server/types.js'; + +const gatlingFiles: WriteFileSection = { + gatlingFiles: [ + { + templates: ['README.md.jhi.gatling'], + }, + { + path: TEST_DIR, + templates: [ + // Create Gatling test files + 'gatling/conf/gatling.conf', + 'gatling/conf/logback.xml', + ], + }, + ], +}; + +export default async function writeTask(this: Generator, { application }) { + await this.writeFiles({ + sections: gatlingFiles, + context: application, + }); +} diff --git a/generators/gatling/generator.mts b/generators/gatling/generator.mts deleted file mode 100644 index cb33fc55d551..000000000000 --- a/generators/gatling/generator.mts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_GATLING, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; -import writeEntityTask, { cleanupEntitiesTask } from './entity-files.mjs'; - -export default class GatlingGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_GATLING); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - cleanupEntitiesTask, - writeEntityTask, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - const { javaDependencies } = application; - if (application.buildToolMaven) { - source.addMavenDefinition?.({ - properties: [ - { property: 'gatling.version', value: javaDependencies?.gatling }, - { property: 'gatling-maven-plugin.version', value: javaDependencies?.['gatling-maven-plugin'] }, - ], - dependencies: [ - // eslint-disable-next-line no-template-curly-in-string - { groupId: 'io.gatling.highcharts', artifactId: 'gatling-charts-highcharts', version: '${gatling.version}', scope: 'test' }, - ], - plugins: [{ groupId: 'io.gatling', artifactId: 'gatling-maven-plugin' }], - pluginManagement: [ - { - groupId: 'io.gatling', - artifactId: 'gatling-maven-plugin', - // eslint-disable-next-line no-template-curly-in-string - version: '${gatling-maven-plugin.version}', - additionalContent: ` - - true - \${project.basedir}/src/test/gatling/conf - -`, - }, - ], - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/gatling/generator.spec.mts b/generators/gatling/generator.spec.mts deleted file mode 100644 index 054cf7feaab1..000000000000 --- a/generators/gatling/generator.spec.mts +++ /dev/null @@ -1,35 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { defaultHelpers as helpers, result } from '../../test/support/index.mjs'; - -import { GENERATOR_GATLING } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('with default config', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_GATLING).withJHipsterConfig(); - }); - - it('should match files snapshot', () => { - expect(result.getSnapshot()).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/gatling/generator.spec.ts b/generators/gatling/generator.spec.ts new file mode 100644 index 000000000000..1c09430750c4 --- /dev/null +++ b/generators/gatling/generator.spec.ts @@ -0,0 +1,35 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { defaultHelpers as helpers, result } from '../../test/support/index.js'; + +import { GENERATOR_GATLING } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('with default config', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_GATLING).withJHipsterConfig(); + }); + + it('should match files snapshot', () => { + expect(result.getSnapshot()).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/gatling/generator.ts b/generators/gatling/generator.ts new file mode 100644 index 000000000000..df4188b7de24 --- /dev/null +++ b/generators/gatling/generator.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_GATLING, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; +import writeEntityTask, { cleanupEntitiesTask } from './entity-files.js'; + +export default class GatlingGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_GATLING); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + cleanupEntitiesTask, + writeEntityTask, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + const { javaDependencies } = application; + if (application.buildToolMaven) { + source.addMavenDefinition?.({ + properties: [ + { property: 'gatling.version', value: javaDependencies?.gatling }, + { property: 'gatling-maven-plugin.version', value: javaDependencies?.['gatling-maven-plugin'] }, + ], + dependencies: [ + // eslint-disable-next-line no-template-curly-in-string + { groupId: 'io.gatling.highcharts', artifactId: 'gatling-charts-highcharts', version: '${gatling.version}', scope: 'test' }, + ], + plugins: [{ groupId: 'io.gatling', artifactId: 'gatling-maven-plugin' }], + pluginManagement: [ + { + groupId: 'io.gatling', + artifactId: 'gatling-maven-plugin', + // eslint-disable-next-line no-template-curly-in-string + version: '${gatling-maven-plugin.version}', + additionalContent: ` + + true + \${project.basedir}/src/test/gatling/conf + +`, + }, + ], + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/gatling/index.mts b/generators/gatling/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/gatling/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/gatling/index.ts b/generators/gatling/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/gatling/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/generate-blueprint/__snapshots__/generator.spec.mjs.snap b/generators/generate-blueprint/__snapshots__/generator.spec.js.snap similarity index 100% rename from generators/generate-blueprint/__snapshots__/generator.spec.mjs.snap rename to generators/generate-blueprint/__snapshots__/generator.spec.js.snap diff --git a/generators/generate-blueprint/command.mts b/generators/generate-blueprint/command.mts deleted file mode 100644 index 14d1525d82bc..000000000000 --- a/generators/generate-blueprint/command.mts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_INIT } from '../generator-list.mjs'; -import { - ADDITIONAL_SUB_GENERATORS, - ALL_GENERATORS, - ALL_PRIORITIES, - CLI_OPTION, - DYNAMIC, - GENERATE_SNAPSHOTS, - JS, - LINK_JHIPSTER_DEPENDENCY, - LOCAL_BLUEPRINT_OPTION, - SUB_GENERATORS, -} from './constants.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - [GENERATE_SNAPSHOTS]: { - description: 'Generate test snapshots', - type: Boolean, - }, - [LINK_JHIPSTER_DEPENDENCY]: { - description: 'Link JHipster dependency for testing', - type: Boolean, - hide: true, - }, - [SUB_GENERATORS]: { - description: 'Sub generators to generate', - type: Array, - scope: 'storage', - }, - [ADDITIONAL_SUB_GENERATORS]: { - description: 'Comma separated additional sub generators to generate', - type: String, - scope: 'storage', - }, - [DYNAMIC]: { - description: 'Generate dynamic generators (advanced)', - type: Boolean, - scope: 'storage', - }, - [JS]: { - description: 'Use js extension', - type: Boolean, - scope: 'storage', - }, - [LOCAL_BLUEPRINT_OPTION]: { - description: 'Generate a local blueprint', - type: Boolean, - scope: 'storage', - }, - [CLI_OPTION]: { - description: 'Generate a cli for the blueprint', - type: Boolean, - scope: 'storage', - }, - [ALL_GENERATORS]: { - description: 'Generate every sub generator', - type: Boolean, - scope: 'generator', - }, - [ALL_PRIORITIES]: { - description: 'Generate every priority', - type: Boolean, - }, - }, - import: [GENERATOR_INIT], -}; - -export default command; diff --git a/generators/generate-blueprint/command.ts b/generators/generate-blueprint/command.ts new file mode 100644 index 000000000000..aa0637b0eef8 --- /dev/null +++ b/generators/generate-blueprint/command.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_INIT } from '../generator-list.js'; +import { + ADDITIONAL_SUB_GENERATORS, + ALL_GENERATORS, + ALL_PRIORITIES, + CLI_OPTION, + DYNAMIC, + GENERATE_SNAPSHOTS, + JS, + LINK_JHIPSTER_DEPENDENCY, + LOCAL_BLUEPRINT_OPTION, + SUB_GENERATORS, +} from './constants.js'; + +const command: JHipsterCommandDefinition = { + options: { + [GENERATE_SNAPSHOTS]: { + description: 'Generate test snapshots', + type: Boolean, + }, + [LINK_JHIPSTER_DEPENDENCY]: { + description: 'Link JHipster dependency for testing', + type: Boolean, + hide: true, + }, + [SUB_GENERATORS]: { + description: 'Sub generators to generate', + type: Array, + scope: 'storage', + }, + [ADDITIONAL_SUB_GENERATORS]: { + description: 'Comma separated additional sub generators to generate', + type: String, + scope: 'storage', + }, + [DYNAMIC]: { + description: 'Generate dynamic generators (advanced)', + type: Boolean, + scope: 'storage', + }, + [JS]: { + description: 'Use js extension', + type: Boolean, + scope: 'storage', + }, + [LOCAL_BLUEPRINT_OPTION]: { + description: 'Generate a local blueprint', + type: Boolean, + scope: 'storage', + }, + [CLI_OPTION]: { + description: 'Generate a cli for the blueprint', + type: Boolean, + scope: 'storage', + }, + [ALL_GENERATORS]: { + description: 'Generate every sub generator', + type: Boolean, + scope: 'generator', + }, + [ALL_PRIORITIES]: { + description: 'Generate every priority', + type: Boolean, + }, + }, + import: [GENERATOR_INIT], +}; + +export default command; diff --git a/generators/generate-blueprint/constants.js b/generators/generate-blueprint/constants.js new file mode 100644 index 000000000000..d43b035c57f0 --- /dev/null +++ b/generators/generate-blueprint/constants.js @@ -0,0 +1,152 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; + +import * as GENERATOR_LIST from '../generator-list.js'; +import { PRIORITY_NAMES_LIST } from '../base-application/priorities.js'; + +const prioritiesForSub = () => PRIORITY_NAMES_LIST; + +export const GENERATE_SNAPSHOTS = 'generateSnapshots'; +export const LINK_JHIPSTER_DEPENDENCY = 'linkJhipsterDependency'; +export const GENERATORS = 'generators'; +export const SUB_GENERATORS = 'subGenerators'; +export const ADDITIONAL_SUB_GENERATORS = 'additionalSubGenerators'; +export const DYNAMIC = 'dynamic'; +export const JS = 'js'; +export const LOCAL_BLUEPRINT_OPTION = 'localBlueprint'; +export const CLI_OPTION = 'cli'; + +export const SBS = 'sbs'; +export const COMMAND = 'command'; +export const PRIORITIES = 'priorities'; +export const ALL_GENERATORS = 'allGenerators'; +export const ALL_PRIORITIES = 'allPriorities'; +export const WRITTEN = 'written'; + +/** + * Config that needs to be written to config + */ +export const requiredConfig = () => ({}); + +/** + * Default config that will be used for templates + */ +export const defaultConfig = ({ config = {} } = {}) => ({ + ...requiredConfig, + [DYNAMIC]: false, + [JS]: false, + [LOCAL_BLUEPRINT_OPTION]: false, + [CLI_OPTION]: !config[LOCAL_BLUEPRINT_OPTION], + [SUB_GENERATORS]: [], + [ADDITIONAL_SUB_GENERATORS]: '', +}); + +export const defaultSubGeneratorConfig = () => ({ + [SBS]: true, + [COMMAND]: false, + [WRITTEN]: false, + [PRIORITIES]: [], +}); + +const allSubGeneratorConfig = subGenerator => ({ + [SBS]: true, + [COMMAND]: false, + [PRIORITIES]: prioritiesForSub(subGenerator), +}); + +export const allGeneratorsConfig = () => ({ + ...requiredConfig, + [SUB_GENERATORS]: Object.values(GENERATOR_LIST), + [ADDITIONAL_SUB_GENERATORS]: '', + [DYNAMIC]: false, + [JS]: false, + [GENERATORS]: Object.fromEntries( + Object.values(GENERATOR_LIST).map(subGenerator => { + return [subGenerator, allSubGeneratorConfig(subGenerator)]; + }), + ), +}); + +export const prompts = () => { + const { [LOCAL_BLUEPRINT_OPTION]: LOCAL_BLUEPRINT_OPTION_DEFAULT_VALUE, [CLI_OPTION]: CLI_OPTION_DEFAULT_VALUE } = defaultConfig(); + return [ + { + type: 'confirm', + name: LOCAL_BLUEPRINT_OPTION, + message: 'Do you want to generate a local blueprint inside your application?', + default: LOCAL_BLUEPRINT_OPTION_DEFAULT_VALUE, + }, + { + type: 'checkbox', + name: SUB_GENERATORS, + message: 'Which sub-generators do you want to override?', + choices: Object.values(GENERATOR_LIST), + pageSize: 30, + loop: false, + }, + { + type: 'input', + name: ADDITIONAL_SUB_GENERATORS, + message: 'Comma separated additional sub-generators.', + validate: input => { + if (input) { + return /^([\w,-]*)$/.test(input) ? true : 'Please provide valid generator names'; + } + return true; + }, + }, + { + when: answers => !answers[LOCAL_BLUEPRINT_OPTION], + type: 'confirm', + name: CLI_OPTION, + message: 'Add a cli?', + default: CLI_OPTION_DEFAULT_VALUE, + }, + ]; +}; + +export const subGeneratorPrompts = ({ subGenerator, additionalSubGenerator, localBlueprint }) => { + const { [SBS]: SBS_DEFAULT_VALUE } = defaultSubGeneratorConfig(); + return [ + { + type: 'confirm', + name: SBS, + when: !additionalSubGenerator, + message: `Is ${chalk.yellow(subGenerator)} generator a side-by-side blueprint?`, + default: SBS_DEFAULT_VALUE, + }, + { + when: !localBlueprint, + type: 'confirm', + name: COMMAND, + message: `Is ${chalk.yellow(subGenerator)} generator a cli command?`, + default: false, + }, + { + type: 'checkbox', + name: PRIORITIES, + message: `What task do you want do implement at ${chalk.yellow(subGenerator)} generator?`, + choices: prioritiesForSub(subGenerator), + pageSize: 30, + default: answers => (answers.sbs || additionalSubGenerator ? [] : prioritiesForSub(subGenerator)), + loop: false, + }, + ]; +}; diff --git a/generators/generate-blueprint/constants.mjs b/generators/generate-blueprint/constants.mjs deleted file mode 100644 index 9e82cee9cfd2..000000000000 --- a/generators/generate-blueprint/constants.mjs +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; - -import * as GENERATOR_LIST from '../generator-list.mjs'; -import { PRIORITY_NAMES_LIST } from '../base-application/priorities.mjs'; - -const prioritiesForSub = () => PRIORITY_NAMES_LIST; - -export const GENERATE_SNAPSHOTS = 'generateSnapshots'; -export const LINK_JHIPSTER_DEPENDENCY = 'linkJhipsterDependency'; -export const GENERATORS = 'generators'; -export const SUB_GENERATORS = 'subGenerators'; -export const ADDITIONAL_SUB_GENERATORS = 'additionalSubGenerators'; -export const DYNAMIC = 'dynamic'; -export const JS = 'js'; -export const LOCAL_BLUEPRINT_OPTION = 'localBlueprint'; -export const CLI_OPTION = 'cli'; - -export const SBS = 'sbs'; -export const COMMAND = 'command'; -export const PRIORITIES = 'priorities'; -export const ALL_GENERATORS = 'allGenerators'; -export const ALL_PRIORITIES = 'allPriorities'; -export const WRITTEN = 'written'; - -/** - * Config that needs to be written to config - */ -export const requiredConfig = () => ({}); - -/** - * Default config that will be used for templates - */ -export const defaultConfig = ({ config = {} } = {}) => ({ - ...requiredConfig, - [DYNAMIC]: false, - [JS]: false, - [LOCAL_BLUEPRINT_OPTION]: false, - [CLI_OPTION]: !config[LOCAL_BLUEPRINT_OPTION], - [SUB_GENERATORS]: [], - [ADDITIONAL_SUB_GENERATORS]: '', -}); - -export const defaultSubGeneratorConfig = () => ({ - [SBS]: true, - [COMMAND]: false, - [WRITTEN]: false, - [PRIORITIES]: [], -}); - -const allSubGeneratorConfig = subGenerator => ({ - [SBS]: true, - [COMMAND]: false, - [PRIORITIES]: prioritiesForSub(subGenerator), -}); - -export const allGeneratorsConfig = () => ({ - ...requiredConfig, - [SUB_GENERATORS]: Object.values(GENERATOR_LIST), - [ADDITIONAL_SUB_GENERATORS]: '', - [DYNAMIC]: false, - [JS]: false, - [GENERATORS]: Object.fromEntries( - Object.values(GENERATOR_LIST).map(subGenerator => { - return [subGenerator, allSubGeneratorConfig(subGenerator)]; - }), - ), -}); - -export const prompts = () => { - const { [LOCAL_BLUEPRINT_OPTION]: LOCAL_BLUEPRINT_OPTION_DEFAULT_VALUE, [CLI_OPTION]: CLI_OPTION_DEFAULT_VALUE } = defaultConfig(); - return [ - { - type: 'confirm', - name: LOCAL_BLUEPRINT_OPTION, - message: 'Do you want to generate a local blueprint inside your application?', - default: LOCAL_BLUEPRINT_OPTION_DEFAULT_VALUE, - }, - { - type: 'checkbox', - name: SUB_GENERATORS, - message: 'Which sub-generators do you want to override?', - choices: Object.values(GENERATOR_LIST), - pageSize: 30, - loop: false, - }, - { - type: 'input', - name: ADDITIONAL_SUB_GENERATORS, - message: 'Comma separated additional sub-generators.', - validate: input => { - if (input) { - return /^([\w,-]*)$/.test(input) ? true : 'Please provide valid generator names'; - } - return true; - }, - }, - { - when: answers => !answers[LOCAL_BLUEPRINT_OPTION], - type: 'confirm', - name: CLI_OPTION, - message: 'Add a cli?', - default: CLI_OPTION_DEFAULT_VALUE, - }, - ]; -}; - -export const subGeneratorPrompts = ({ subGenerator, additionalSubGenerator, localBlueprint }) => { - const { [SBS]: SBS_DEFAULT_VALUE } = defaultSubGeneratorConfig(); - return [ - { - type: 'confirm', - name: SBS, - when: !additionalSubGenerator, - message: `Is ${chalk.yellow(subGenerator)} generator a side-by-side blueprint?`, - default: SBS_DEFAULT_VALUE, - }, - { - when: !localBlueprint, - type: 'confirm', - name: COMMAND, - message: `Is ${chalk.yellow(subGenerator)} generator a cli command?`, - default: false, - }, - { - type: 'checkbox', - name: PRIORITIES, - message: `What task do you want do implement at ${chalk.yellow(subGenerator)} generator?`, - choices: prioritiesForSub(subGenerator), - pageSize: 30, - default: answers => (answers.sbs || additionalSubGenerator ? [] : prioritiesForSub(subGenerator)), - loop: false, - }, - ]; -}; diff --git a/generators/generate-blueprint/files.js b/generators/generate-blueprint/files.js new file mode 100644 index 000000000000..db312efcd229 --- /dev/null +++ b/generators/generate-blueprint/files.js @@ -0,0 +1,95 @@ +/** + * Copyright 2013-2023 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 { LOCAL_BLUEPRINT_OPTION } from './constants.js'; + +export const files = { + baseFiles: [ + { + condition: ctx => !ctx[LOCAL_BLUEPRINT_OPTION], + templates: [ + '.eslintrc.json', + '.github/workflows/generator.yml', + '.prettierignore.jhi.blueprint', + 'README.md', + 'tsconfig.json', + 'vitest.config.ts', + '.blueprint/cli/commands.mjs', + '.blueprint/generate-sample/command.mjs', + '.blueprint/generate-sample/generator.mjs', + '.blueprint/generate-sample/index.mjs', + ], + }, + { + condition: ctx => !ctx[LOCAL_BLUEPRINT_OPTION] && !ctx.sampleWritten, + templates: ['.blueprint/generate-sample/templates/samples/sample.jdl'], + }, + { + condition: ctx => ctx.cli, + templates: ['cli/cli.cjs'], + }, + { + condition: ctx => ctx.commands.length > 0, + templates: ['cli/commands.cjs'], + }, + ], +}; + +export const generatorFiles = { + generator: [ + { + path: 'generators/generator', + to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, + templates: [ + { file: 'generator.mjs.jhi', renameTo: ctx => (ctx.js ? 'generator.js.jhi' : 'generator.mjs.jhi') }, + { file: 'index.mjs', renameTo: ctx => (ctx.js ? 'index.js' : 'index.mjs') }, + ], + }, + { + path: 'generators/generator', + condition: ctx => ctx.priorities.find(priority => priority.name === 'initializing'), + to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, + templates: [{ file: 'command.mjs', renameTo: ctx => (ctx.js ? 'command.js' : 'command.mjs') }], + }, + { + path: 'generators/generator', + to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, + condition: ctx => !ctx.generator.startsWith('entity') && !ctx.application[LOCAL_BLUEPRINT_OPTION], + templates: [ + { + file: 'generator.spec.mjs', + renameTo: ctx => (ctx.js ? 'generator.spec.js' : 'generator.spec.mjs'), + }, + ], + }, + { + path: 'generators/generator', + to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, + condition(ctx) { + return (this.options.force || !ctx.written) && ctx.priorities.find(priority => priority.name === 'writing'); + }, + transform: false, + templates: [ + { + file: 'templates/template-file.ejs', + renameTo: ctx => `templates/template-file-${ctx.generator}.ejs`, + }, + ], + }, + ], +}; diff --git a/generators/generate-blueprint/files.mjs b/generators/generate-blueprint/files.mjs deleted file mode 100644 index 5301df24ffd1..000000000000 --- a/generators/generate-blueprint/files.mjs +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2013-2023 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 { LOCAL_BLUEPRINT_OPTION } from './constants.mjs'; - -export const files = { - baseFiles: [ - { - condition: ctx => !ctx[LOCAL_BLUEPRINT_OPTION], - templates: [ - '.eslintrc.json', - '.github/workflows/generator.yml', - '.prettierignore.jhi.blueprint', - 'README.md', - 'tsconfig.json', - 'vitest.config.ts', - '.blueprint/cli/commands.mjs', - '.blueprint/generate-sample/command.mjs', - '.blueprint/generate-sample/generator.mjs', - '.blueprint/generate-sample/index.mjs', - ], - }, - { - condition: ctx => !ctx[LOCAL_BLUEPRINT_OPTION] && !ctx.sampleWritten, - templates: ['.blueprint/generate-sample/templates/samples/sample.jdl'], - }, - { - condition: ctx => ctx.cli, - templates: ['cli/cli.cjs'], - }, - { - condition: ctx => ctx.commands.length > 0, - templates: ['cli/commands.cjs'], - }, - ], -}; - -export const generatorFiles = { - generator: [ - { - path: 'generators/generator', - to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, - templates: [ - { file: 'generator.mjs.jhi', renameTo: ctx => (ctx.js ? 'generator.js.jhi' : 'generator.mjs.jhi') }, - { file: 'index.mjs', renameTo: ctx => (ctx.js ? 'index.js' : 'index.mjs') }, - ], - }, - { - path: 'generators/generator', - condition: ctx => ctx.priorities.find(priority => priority.name === 'initializing'), - to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, - templates: [{ file: 'command.mjs', renameTo: ctx => (ctx.js ? 'command.js' : 'command.mjs') }], - }, - { - path: 'generators/generator', - to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, - condition: ctx => !ctx.generator.startsWith('entity') && !ctx.application[LOCAL_BLUEPRINT_OPTION], - templates: [ - { - file: 'generator.spec.mjs', - renameTo: ctx => (ctx.js ? 'generator.spec.js' : 'generator.spec.mjs'), - }, - ], - }, - { - path: 'generators/generator', - to: ctx => `${ctx.application.blueprintsPath}${ctx.generator}`, - condition(ctx) { - return (this.options.force || !ctx.written) && ctx.priorities.find(priority => priority.name === 'writing'); - }, - transform: false, - templates: [ - { - file: 'templates/template-file.ejs', - renameTo: ctx => `templates/template-file-${ctx.generator}.ejs`, - }, - ], - }, - ], -}; diff --git a/generators/generate-blueprint/generator.js b/generators/generate-blueprint/generator.js new file mode 100644 index 000000000000..560d5c450362 --- /dev/null +++ b/generators/generate-blueprint/generator.js @@ -0,0 +1,399 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import lodash from 'lodash'; + +import BaseGenerator from '../base/index.js'; +import { PRIORITY_NAMES_LIST as BASE_PRIORITY_NAMES_LIST } from '../base/priorities.js'; + +import { + requiredConfig, + defaultConfig, + defaultSubGeneratorConfig, + allGeneratorsConfig, + prompts, + subGeneratorPrompts, + GENERATE_SNAPSHOTS, + LINK_JHIPSTER_DEPENDENCY, + ALL_GENERATORS, + GENERATORS, + PRIORITIES, + SUB_GENERATORS, + ADDITIONAL_SUB_GENERATORS, + WRITTEN, + LOCAL_BLUEPRINT_OPTION, + ALL_PRIORITIES, +} from './constants.js'; + +import * as GENERATOR_LIST from '../generator-list.js'; +import { files, generatorFiles } from './files.js'; +import { packageJson } from '../../lib/index.js'; +import { SKIP_COMMIT_HOOK } from '../init/constants.js'; +import command from './command.js'; +import { BLUEPRINT_API_VERSION, NODE_VERSION } from '../generator-constants.js'; + +const { camelCase, upperFirst, snakeCase } = lodash; +const { GENERATOR_PROJECT_NAME, GENERATOR_INIT, GENERATOR_GENERATE_BLUEPRINT } = GENERATOR_LIST; + +export default class extends BaseGenerator { + async _beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_GENERATE_BLUEPRINT); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_PROJECT_NAME); + } + } + + get initializing() { + return { + loadOptions() { + this.parseJHipsterOptions(command.options); + if (this[ALL_GENERATORS]) { + this.config.set(allGeneratorsConfig()); + } + if (this.options.defaults) { + this.config.defaults(defaultConfig({ config: this.jhipsterConfig })); + } + }, + }; + } + + get [BaseGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return { + async prompting() { + await this.prompt(prompts(this), this.config); + }, + async eachSubGenerator() { + const { localBlueprint } = this.jhipsterConfig; + const { [ALL_PRIORITIES]: allPriorities } = this.options; + const subGenerators = this.config.get(SUB_GENERATORS) || []; + for (const subGenerator of subGenerators) { + const subGeneratorStorage = this.getSubGeneratorStorage(subGenerator); + if (allPriorities) { + subGeneratorStorage.defaults({ [PRIORITIES]: BASE_PRIORITY_NAMES_LIST }); + } + await this.prompt(subGeneratorPrompts({ subGenerator, localBlueprint, options: this.options }), subGeneratorStorage); + } + }, + async eachAdditionalSubGenerator() { + const { localBlueprint } = this.jhipsterConfig; + const { [ALL_PRIORITIES]: allPriorities } = this.options; + const additionalSubGenerators = this.config.get(ADDITIONAL_SUB_GENERATORS) || ''; + for (const subGenerator of additionalSubGenerators + .split(',') + .map(sub => sub.trim()) + .filter(Boolean)) { + const subGeneratorStorage = this.getSubGeneratorStorage(subGenerator); + if (allPriorities) { + subGeneratorStorage.defaults({ [PRIORITIES]: BASE_PRIORITY_NAMES_LIST }); + } + await this.prompt(subGeneratorPrompts({ subGenerator, localBlueprint, additionalSubGenerator: true }), subGeneratorStorage); + } + }, + }; + } + + get [BaseGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get configuring() { + return { + requiredConfig() { + this.config.defaults(requiredConfig()); + }, + conditionalConfig() { + if (!this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) { + this.config.defaults({ + [SKIP_COMMIT_HOOK]: true, + }); + } + }, + }; + } + + get [BaseGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get composing() { + return { + async compose() { + if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; + await this.composeWithJHipster(GENERATOR_INIT); + }, + }; + } + + get [BaseGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get loading() { + return { + createContext() { + this.application = { ...defaultConfig(), ...this.config.getAll() }; + }, + async load() { + this.application.packagejs = packageJson; + }, + }; + } + + get [BaseGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return { + prepareCommands() { + this.application.commands = []; + this.application.nodeVersion = NODE_VERSION; + if (!this.application[GENERATORS]) return; + for (const generator of Object.keys(this.application[GENERATORS])) { + const subGeneratorConfig = this.getSubGeneratorStorage(generator).getAll(); + if (subGeneratorConfig.command) { + this.application.commands.push(generator); + } + } + }, + preparePath() { + this.application.blueprintsPath = this.application[LOCAL_BLUEPRINT_OPTION] ? '.blueprint/' : 'generators/'; + }, + }; + } + + get [BaseGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return { + async writing() { + this.application.sampleWritten = this.jhipsterConfig.sampleWritten; + await this.writeFiles({ + sections: files, + context: this.application, + }); + this.jhipsterConfig.sampleWritten = true; + }, + async writingGenerators() { + if (!this.application[GENERATORS]) return; + for (const generator of Object.keys(this.application[GENERATORS])) { + const subGeneratorStorage = this.getSubGeneratorStorage(generator); + const subGeneratorConfig = subGeneratorStorage.getAll(); + const priorities = (subGeneratorConfig[PRIORITIES] || []).map(priority => ({ + name: priority, + asTaskGroup: `as${upperFirst(priority)}TaskGroup`, + constant: `${snakeCase(priority).toUpperCase()}`, + })); + const customGenerator = !Object.values(GENERATOR_LIST).includes(generator); + const jhipsterGenerator = customGenerator || subGeneratorConfig.sbs ? 'base-application' : generator; + const subTemplateData = { + js: this.jhipsterConfig.js, + application: this.application, + ...defaultSubGeneratorConfig(), + ...subGeneratorConfig, + generator, + customGenerator, + jhipsterGenerator, + subGenerator: generator, + generatorClass: upperFirst(camelCase(jhipsterGenerator)), + priorities, + }; + await this.writeFiles({ + sections: generatorFiles, + context: subTemplateData, + }); + subGeneratorStorage.set(WRITTEN, true); + } + }, + }; + } + + get [BaseGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + packageJson() { + if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; + const { packagejs } = this.application; + const mainDependencies = { + ...packagejs.dependencies, + ...packagejs.devDependencies, + }; + this.loadNodeDependenciesFromPackageJson( + mainDependencies, + this.fetchFromInstalledJHipster('generate-blueprint/resources/package.json'), + ); + this.packageJson.merge({ + name: `generator-jhipster-${this.jhipsterConfig.baseName}`, + keywords: ['yeoman-generator', 'jhipster-blueprint', BLUEPRINT_API_VERSION], + type: 'module', + files: ['generators'], + scripts: { + ejslint: 'ejslint generators/**/*.ejs', + lint: 'eslint .', + 'lint-fix': 'npm run ejslint && npm run lint -- --fix', + pretest: 'npm run prettier-check && npm run lint', + test: 'vitest run', + 'update-snapshot': 'vitest run --update', + vitest: 'vitest', + }, + devDependencies: { + 'ejs-lint': `${mainDependencies['ejs-lint']}`, + eslint: `${mainDependencies.eslint}`, + 'eslint-config-airbnb-base': `${mainDependencies['eslint-config-airbnb-base']}`, + 'eslint-config-prettier': `${mainDependencies['eslint-config-prettier']}`, + 'eslint-plugin-import': `${mainDependencies['eslint-plugin-import']}`, + 'eslint-plugin-prettier': `${mainDependencies['eslint-plugin-prettier']}`, + vitest: mainDependencies.vitest, + prettier: `${mainDependencies.prettier}`, + /* + * yeoman-test version is loaded through generator-jhipster peer dependency. + * generator-jhipster uses a fixed version, blueprints must set a compatible range. + */ + 'yeoman-test': '>=8.0.0-rc.1', + }, + engines: { + node: packagejs.engines.node, + }, + }); + }, + addCliToPackageJson() { + if (!this.jhipsterConfig.cli || this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; + const { baseName, cliName = `jhipster-${baseName}` } = this.application; + this.packageJson.merge({ + bin: { + [cliName]: 'cli/cli.cjs', + }, + files: ['cli', 'generators'], + }); + }, + addGeneratorJHipsterDependency() { + if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; + const { packagejs } = this.application; + if (this.jhipsterConfig.dynamic) { + this.packageJson.merge({ + devDependencies: { + 'generator-jhipster': `${packagejs.version}`, + }, + peerDependencies: { + 'generator-jhipster': `^${packagejs.version}`, + }, + }); + } else { + this.packageJson.merge({ + dependencies: { + 'generator-jhipster': `${packagejs.version}`, + }, + }); + } + }, + }); + } + + get [BaseGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + get postInstall() { + return { + async addSnapshot() { + const { [LOCAL_BLUEPRINT_OPTION]: localBlueprint } = this.jhipsterConfig; + const { + skipInstall, + skipGit, + existed, + [GENERATE_SNAPSHOTS]: generateSnapshots = !localBlueprint && !skipInstall && !skipGit && !existed, + } = this.options; + if (!generateSnapshots) return; + + try { + if (this.options[LINK_JHIPSTER_DEPENDENCY]) { + this.log.verboseInfo('Linking generator-jhipster'); + await this.spawnCommand('npm', ['link', 'generator-jhipster'], { stdio: 'inherit' }); + } + + // Generate snapshots to add to git. + this.log.verboseInfo(` +This is a new blueprint, executing '${chalk.yellow('npm run update-snapshot')}' to generate snapshots and commit to git.`); + await this.spawnCommand('npm', ['run', 'update-snapshot']); + } catch (error) { + if (generateSnapshots !== undefined) { + // We are forcing to generate snapshots fail the generation. + throw error; + } + this.log.warn('Fail to generate snapshots'); + } + }, + }; + } + + get [BaseGenerator.POST_INSTALL]() { + return this.delegateTasksToBlueprint(() => this.postInstall); + } + + get end() { + return { + end() { + if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; + + this.log.log(`${chalk.bold.green('##### USAGE #####')} +To begin to work: +- launch: ${chalk.yellow.bold('npm install')} +- link: ${chalk.yellow.bold('npm link')} +- link JHipster: ${chalk.yellow.bold('npm link generator-jhipster')} +- test your module in a JHipster project: + - create a new directory and go into it + - link the blueprint: ${chalk.yellow.bold(`npm link generator-jhipster-${this.moduleName}`)} + - launch JHipster with flags: ${chalk.yellow.bold(`jhipster --blueprints ${this.moduleName}`)} +- then, come back here, and begin to code! +`); + }, + }; + } + + get [BaseGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + getSubGeneratorStorage(subGenerator) { + return this.config.createStorage(`${GENERATORS}.${subGenerator}`); + } + + validateGitHubName(input) { + if (/^([a-zA-Z0-9]+)(-([a-zA-Z0-9])+)*$/.test(input) && input !== '') return true; + return 'Your username is mandatory, cannot contain special characters or a blank space'; + } + + validateModuleName(input) { + return /^[a-zA-Z0-9-]+$/.test(input) + ? true + : 'Your blueprint name is mandatory, cannot contain special characters or a blank space, using the default name instead'; + } +} diff --git a/generators/generate-blueprint/generator.mjs b/generators/generate-blueprint/generator.mjs deleted file mode 100644 index 23f549ded765..000000000000 --- a/generators/generate-blueprint/generator.mjs +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import lodash from 'lodash'; - -import BaseGenerator from '../base/index.mjs'; -import { PRIORITY_NAMES_LIST as BASE_PRIORITY_NAMES_LIST } from '../base/priorities.mjs'; - -import { - requiredConfig, - defaultConfig, - defaultSubGeneratorConfig, - allGeneratorsConfig, - prompts, - subGeneratorPrompts, - GENERATE_SNAPSHOTS, - LINK_JHIPSTER_DEPENDENCY, - ALL_GENERATORS, - GENERATORS, - PRIORITIES, - SUB_GENERATORS, - ADDITIONAL_SUB_GENERATORS, - WRITTEN, - LOCAL_BLUEPRINT_OPTION, - ALL_PRIORITIES, -} from './constants.mjs'; - -import * as GENERATOR_LIST from '../generator-list.mjs'; -import { files, generatorFiles } from './files.mjs'; -import { packageJson } from '../../lib/index.mjs'; -import { SKIP_COMMIT_HOOK } from '../init/constants.mjs'; -import command from './command.mjs'; -import { BLUEPRINT_API_VERSION, NODE_VERSION } from '../generator-constants.mjs'; - -const { camelCase, upperFirst, snakeCase } = lodash; -const { GENERATOR_PROJECT_NAME, GENERATOR_INIT, GENERATOR_GENERATE_BLUEPRINT } = GENERATOR_LIST; - -export default class extends BaseGenerator { - async _beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_GENERATE_BLUEPRINT); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_PROJECT_NAME); - } - } - - get initializing() { - return { - loadOptions() { - this.parseJHipsterOptions(command.options); - if (this[ALL_GENERATORS]) { - this.config.set(allGeneratorsConfig()); - } - if (this.options.defaults) { - this.config.defaults(defaultConfig({ config: this.jhipsterConfig })); - } - }, - }; - } - - get [BaseGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return { - async prompting() { - await this.prompt(prompts(this), this.config); - }, - async eachSubGenerator() { - const { localBlueprint } = this.jhipsterConfig; - const { [ALL_PRIORITIES]: allPriorities } = this.options; - const subGenerators = this.config.get(SUB_GENERATORS) || []; - for (const subGenerator of subGenerators) { - const subGeneratorStorage = this.getSubGeneratorStorage(subGenerator); - if (allPriorities) { - subGeneratorStorage.defaults({ [PRIORITIES]: BASE_PRIORITY_NAMES_LIST }); - } - await this.prompt(subGeneratorPrompts({ subGenerator, localBlueprint, options: this.options }), subGeneratorStorage); - } - }, - async eachAdditionalSubGenerator() { - const { localBlueprint } = this.jhipsterConfig; - const { [ALL_PRIORITIES]: allPriorities } = this.options; - const additionalSubGenerators = this.config.get(ADDITIONAL_SUB_GENERATORS) || ''; - for (const subGenerator of additionalSubGenerators - .split(',') - .map(sub => sub.trim()) - .filter(Boolean)) { - const subGeneratorStorage = this.getSubGeneratorStorage(subGenerator); - if (allPriorities) { - subGeneratorStorage.defaults({ [PRIORITIES]: BASE_PRIORITY_NAMES_LIST }); - } - await this.prompt(subGeneratorPrompts({ subGenerator, localBlueprint, additionalSubGenerator: true }), subGeneratorStorage); - } - }, - }; - } - - get [BaseGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get configuring() { - return { - requiredConfig() { - this.config.defaults(requiredConfig()); - }, - conditionalConfig() { - if (!this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) { - this.config.defaults({ - [SKIP_COMMIT_HOOK]: true, - }); - } - }, - }; - } - - get [BaseGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get composing() { - return { - async compose() { - if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; - await this.composeWithJHipster(GENERATOR_INIT); - }, - }; - } - - get [BaseGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get loading() { - return { - createContext() { - this.application = { ...defaultConfig(), ...this.config.getAll() }; - }, - async load() { - this.application.packagejs = packageJson; - }, - }; - } - - get [BaseGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return { - prepareCommands() { - this.application.commands = []; - this.application.nodeVersion = NODE_VERSION; - if (!this.application[GENERATORS]) return; - for (const generator of Object.keys(this.application[GENERATORS])) { - const subGeneratorConfig = this.getSubGeneratorStorage(generator).getAll(); - if (subGeneratorConfig.command) { - this.application.commands.push(generator); - } - } - }, - preparePath() { - this.application.blueprintsPath = this.application[LOCAL_BLUEPRINT_OPTION] ? '.blueprint/' : 'generators/'; - }, - }; - } - - get [BaseGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return { - async writing() { - this.application.sampleWritten = this.jhipsterConfig.sampleWritten; - await this.writeFiles({ - sections: files, - context: this.application, - }); - this.jhipsterConfig.sampleWritten = true; - }, - async writingGenerators() { - if (!this.application[GENERATORS]) return; - for (const generator of Object.keys(this.application[GENERATORS])) { - const subGeneratorStorage = this.getSubGeneratorStorage(generator); - const subGeneratorConfig = subGeneratorStorage.getAll(); - const priorities = (subGeneratorConfig[PRIORITIES] || []).map(priority => ({ - name: priority, - asTaskGroup: `as${upperFirst(priority)}TaskGroup`, - constant: `${snakeCase(priority).toUpperCase()}`, - })); - const customGenerator = !Object.values(GENERATOR_LIST).includes(generator); - const jhipsterGenerator = customGenerator || subGeneratorConfig.sbs ? 'base-application' : generator; - const subTemplateData = { - js: this.jhipsterConfig.js, - application: this.application, - ...defaultSubGeneratorConfig(), - ...subGeneratorConfig, - generator, - customGenerator, - jhipsterGenerator, - subGenerator: generator, - generatorClass: upperFirst(camelCase(jhipsterGenerator)), - priorities, - }; - await this.writeFiles({ - sections: generatorFiles, - context: subTemplateData, - }); - subGeneratorStorage.set(WRITTEN, true); - } - }, - }; - } - - get [BaseGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - packageJson() { - if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; - const { packagejs } = this.application; - const mainDependencies = { - ...packagejs.dependencies, - ...packagejs.devDependencies, - }; - this.loadNodeDependenciesFromPackageJson( - mainDependencies, - this.fetchFromInstalledJHipster('generate-blueprint/resources/package.json'), - ); - this.packageJson.merge({ - name: `generator-jhipster-${this.jhipsterConfig.baseName}`, - keywords: ['yeoman-generator', 'jhipster-blueprint', BLUEPRINT_API_VERSION], - type: 'module', - files: ['generators'], - scripts: { - ejslint: 'ejslint generators/**/*.ejs', - lint: 'eslint .', - 'lint-fix': 'npm run ejslint && npm run lint -- --fix', - pretest: 'npm run prettier-check && npm run lint', - test: 'vitest run', - 'update-snapshot': 'vitest run --update', - vitest: 'vitest', - }, - devDependencies: { - 'ejs-lint': `${mainDependencies['ejs-lint']}`, - eslint: `${mainDependencies.eslint}`, - 'eslint-config-airbnb-base': `${mainDependencies['eslint-config-airbnb-base']}`, - 'eslint-config-prettier': `${mainDependencies['eslint-config-prettier']}`, - 'eslint-plugin-import': `${mainDependencies['eslint-plugin-import']}`, - 'eslint-plugin-prettier': `${mainDependencies['eslint-plugin-prettier']}`, - vitest: mainDependencies.vitest, - prettier: `${mainDependencies.prettier}`, - /* - * yeoman-test version is loaded through generator-jhipster peer dependency. - * generator-jhipster uses a fixed version, blueprints must set a compatible range. - */ - 'yeoman-test': '>=8.0.0-rc.1', - }, - engines: { - node: packagejs.engines.node, - }, - }); - }, - addCliToPackageJson() { - if (!this.jhipsterConfig.cli || this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; - const { baseName, cliName = `jhipster-${baseName}` } = this.application; - this.packageJson.merge({ - bin: { - [cliName]: 'cli/cli.cjs', - }, - files: ['cli', 'generators'], - }); - }, - addGeneratorJHipsterDependency() { - if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; - const { packagejs } = this.application; - if (this.jhipsterConfig.dynamic) { - this.packageJson.merge({ - devDependencies: { - 'generator-jhipster': `${packagejs.version}`, - }, - peerDependencies: { - 'generator-jhipster': `^${packagejs.version}`, - }, - }); - } else { - this.packageJson.merge({ - dependencies: { - 'generator-jhipster': `${packagejs.version}`, - }, - }); - } - }, - }); - } - - get [BaseGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - get postInstall() { - return { - async addSnapshot() { - const { [LOCAL_BLUEPRINT_OPTION]: localBlueprint } = this.jhipsterConfig; - const { - skipInstall, - skipGit, - existed, - [GENERATE_SNAPSHOTS]: generateSnapshots = !localBlueprint && !skipInstall && !skipGit && !existed, - } = this.options; - if (!generateSnapshots) return; - - try { - if (this.options[LINK_JHIPSTER_DEPENDENCY]) { - this.log.verboseInfo('Linking generator-jhipster'); - await this.spawnCommand('npm', ['link', 'generator-jhipster'], { stdio: 'inherit' }); - } - - // Generate snapshots to add to git. - this.log.verboseInfo(` -This is a new blueprint, executing '${chalk.yellow('npm run update-snapshot')}' to generate snapshots and commit to git.`); - await this.spawnCommand('npm', ['run', 'update-snapshot']); - } catch (error) { - if (generateSnapshots !== undefined) { - // We are forcing to generate snapshots fail the generation. - throw error; - } - this.log.warn('Fail to generate snapshots'); - } - }, - }; - } - - get [BaseGenerator.POST_INSTALL]() { - return this.delegateTasksToBlueprint(() => this.postInstall); - } - - get end() { - return { - end() { - if (this.jhipsterConfig[LOCAL_BLUEPRINT_OPTION]) return; - - this.log.log(`${chalk.bold.green('##### USAGE #####')} -To begin to work: -- launch: ${chalk.yellow.bold('npm install')} -- link: ${chalk.yellow.bold('npm link')} -- link JHipster: ${chalk.yellow.bold('npm link generator-jhipster')} -- test your module in a JHipster project: - - create a new directory and go into it - - link the blueprint: ${chalk.yellow.bold(`npm link generator-jhipster-${this.moduleName}`)} - - launch JHipster with flags: ${chalk.yellow.bold(`jhipster --blueprints ${this.moduleName}`)} -- then, come back here, and begin to code! -`); - }, - }; - } - - get [BaseGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - getSubGeneratorStorage(subGenerator) { - return this.config.createStorage(`${GENERATORS}.${subGenerator}`); - } - - validateGitHubName(input) { - if (/^([a-zA-Z0-9]+)(-([a-zA-Z0-9])+)*$/.test(input) && input !== '') return true; - return 'Your username is mandatory, cannot contain special characters or a blank space'; - } - - validateModuleName(input) { - return /^[a-zA-Z0-9-]+$/.test(input) - ? true - : 'Your blueprint name is mandatory, cannot contain special characters or a blank space, using the default name instead'; - } -} diff --git a/generators/generate-blueprint/generator.spec.js b/generators/generate-blueprint/generator.spec.js new file mode 100644 index 000000000000..a4e89704ff98 --- /dev/null +++ b/generators/generate-blueprint/generator.spec.js @@ -0,0 +1,123 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generatorPath = join(__dirname, 'index.js'); +const generator = basename(__dirname); + +const mockedGenerators = ['jhipster:init']; + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + const generatorList = await import('../generator-list.js'); + await expect(generatorList[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('with', () => { + describe('default config', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withJHipsterConfig().withMockedGenerators(mockedGenerators); + }); + it('should compose with init generator', () => { + expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(true); + }); + it('should write files and match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + describe('all option', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withOptions({ allGenerators: true }).withMockedGenerators(mockedGenerators); + }); + it('should compose with init generator', () => { + expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(true); + }); + it('should match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + describe('local-blueprint option', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withOptions({ localBlueprint: true }).withMockedGenerators(mockedGenerators); + }); + it('should not compose with init generator', () => { + expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(false); + }); + it('should match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` +{ + ".yo-rc.json": { + "stateCleared": "modified", + }, +} +`); + }); + }); + describe('local-blueprint option and app generator', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withOptions({ localBlueprint: true, subGenerators: ['app'], allPriorities: true }) + .withMockedGenerators(mockedGenerators); + }); + it('should not compose with init generator', () => { + expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(false); + }); + it('should write java files with gradle build tool and match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` +{ + ".blueprint/app/command.mjs": { + "stateCleared": "modified", + }, + ".blueprint/app/generator.mjs": { + "stateCleared": "modified", + }, + ".blueprint/app/index.mjs": { + "stateCleared": "modified", + }, + ".blueprint/app/templates/template-file-app.ejs": { + "stateCleared": "modified", + }, + ".yo-rc.json": { + "stateCleared": "modified", + }, +} +`); + }); + }); + }); +}); diff --git a/generators/generate-blueprint/generator.spec.mjs b/generators/generate-blueprint/generator.spec.mjs deleted file mode 100644 index 04ba12bfa164..000000000000 --- a/generators/generate-blueprint/generator.spec.mjs +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generatorPath = join(__dirname, 'index.mjs'); -const generator = basename(__dirname); - -const mockedGenerators = ['jhipster:init']; - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - const generatorList = await import('../generator-list.mjs'); - await expect(generatorList[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('with', () => { - describe('default config', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withJHipsterConfig().withMockedGenerators(mockedGenerators); - }); - it('should compose with init generator', () => { - expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(true); - }); - it('should write files and match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - describe('all option', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withOptions({ allGenerators: true }).withMockedGenerators(mockedGenerators); - }); - it('should compose with init generator', () => { - expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(true); - }); - it('should match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - describe('local-blueprint option', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withOptions({ localBlueprint: true }).withMockedGenerators(mockedGenerators); - }); - it('should not compose with init generator', () => { - expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(false); - }); - it('should match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` -{ - ".yo-rc.json": { - "stateCleared": "modified", - }, -} -`); - }); - }); - describe('local-blueprint option and app generator', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withOptions({ localBlueprint: true, subGenerators: ['app'], allPriorities: true }) - .withMockedGenerators(mockedGenerators); - }); - it('should not compose with init generator', () => { - expect(runResult.mockedGenerators['jhipster:init'].calledOnce).toBe(false); - }); - it('should write java files with gradle build tool and match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchInlineSnapshot(` -{ - ".blueprint/app/command.mjs": { - "stateCleared": "modified", - }, - ".blueprint/app/generator.mjs": { - "stateCleared": "modified", - }, - ".blueprint/app/index.mjs": { - "stateCleared": "modified", - }, - ".blueprint/app/templates/template-file-app.ejs": { - "stateCleared": "modified", - }, - ".yo-rc.json": { - "stateCleared": "modified", - }, -} -`); - }); - }); - }); -}); diff --git a/generators/generate-blueprint/index.mts b/generators/generate-blueprint/index.mts deleted file mode 100644 index 5806742834ec..000000000000 --- a/generators/generate-blueprint/index.mts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; -export { files, generatorFiles } from './files.mjs'; diff --git a/generators/generate-blueprint/index.ts b/generators/generate-blueprint/index.ts new file mode 100644 index 000000000000..74f75a5d2a4d --- /dev/null +++ b/generators/generate-blueprint/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; +export { files, generatorFiles } from './files.js'; diff --git a/generators/generator-constants.mjs b/generators/generator-constants.js similarity index 100% rename from generators/generator-constants.mjs rename to generators/generator-constants.js diff --git a/generators/generator-list.mjs b/generators/generator-list.js similarity index 100% rename from generators/generator-list.mjs rename to generators/generator-list.js diff --git a/generators/git/__snapshots__/generator.spec.mts.snap b/generators/git/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/git/__snapshots__/generator.spec.mts.snap rename to generators/git/__snapshots__/generator.spec.ts.snap diff --git a/generators/git/command.mts b/generators/git/command.mts deleted file mode 100644 index 73a409e168ef..000000000000 --- a/generators/git/command.mts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - skipGit: { - description: 'Skip git repository initialization', - type: Boolean, - scope: 'generator', - }, - monorepository: { - type: Boolean, - description: 'Use monorepository', - scope: 'storage', - }, - }, -}; - -export default command; diff --git a/generators/git/command.ts b/generators/git/command.ts new file mode 100644 index 000000000000..b53c8656ad10 --- /dev/null +++ b/generators/git/command.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + skipGit: { + description: 'Skip git repository initialization', + type: Boolean, + scope: 'generator', + }, + monorepository: { + type: Boolean, + description: 'Use monorepository', + scope: 'storage', + }, + }, +}; + +export default command; diff --git a/generators/git/files.mjs b/generators/git/files.js similarity index 100% rename from generators/git/files.mjs rename to generators/git/files.js diff --git a/generators/git/generator.mts b/generators/git/generator.mts deleted file mode 100644 index 410700a5a61e..000000000000 --- a/generators/git/generator.mts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import chalk from 'chalk'; -import type { QueuedAdapter } from '@yeoman/types'; - -import BaseGenerator from '../base/index.mjs'; -import { GENERATOR_GIT } from '../generator-list.mjs'; -import { files } from './files.mjs'; -import command from './command.mjs'; - -/** - * @class - * @extends {BaseGenerator} - */ -export default class InitGenerator extends BaseGenerator { - gitInstalled; - gitInitialized; - skipGit; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_GIT); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - parseOptions() { - this.parseJHipsterOptions(command.options); - }, - async checkGit() { - if (!this.skipGit) { - this.gitInstalled = (await this.createGit().version()).installed; - if (!this.gitInstalled) { - this.log.warn('Git repository will not be created, as Git is not installed on your system'); - this.skipGit = true; - } - } - }, - async initializeMonorepository() { - if (!this.skipGit && this.jhipsterConfig.monorepository) { - await this.initializeGitRepository(); - } - }, - }); - } - - get [BaseGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get writing() { - return this.asWritingTaskGroup({ - async writeFiles() { - await this.writeFiles({ sections: files, context: {} }); - }, - }); - } - - get [BaseGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - /** Husky commit hook install at install priority requires git to be initialized */ - async initGitRepo() { - if (!this.skipGit && !this.jhipsterConfig.monorepository) { - await this.initializeGitRepository(); - } - }, - }); - } - - get [BaseGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - get end() { - return this.asEndTaskGroup({ - /** Initial commit to git repository after package manager install for package-lock.json */ - async gitCommit() { - if (this.skipGit) return; - if (!this.gitInitialized) { - this.log.warn('The generated application could not be committed to Git, as a Git repository could not be initialized.'); - return; - } - - const commitFiles = async () => { - this.debug('Committing files to git'); - const git = this.createGit(); - const repositoryRoot = await git.revparse(['--show-toplevel']); - const result = await git.log(['-n', '1', '--', '.yo-rc.json']).catch(() => {}); - if (result && result.total > 0) { - this.log.info( - `Found .yo-rc.json in Git from ${repositoryRoot}. So we assume this is application regeneration. Therefore automatic Git commit is not done. You can do Git commit manually.`, - ); - return; - } - try { - const statusResult = await git.status(); - if (statusResult.staged.length > 0) { - this.log.verboseInfo(`The repository ${repositoryRoot} has staged files, skipping commit.`); - return; - } - let commitMsg = `Initial version of ${this.jhipsterConfig.baseName} generated by generator-jhipster@${this.jhipsterConfig.jhipsterVersion}`; - if (this.jhipsterConfig.blueprints && this.jhipsterConfig.blueprints.length > 0) { - const bpInfo = this.jhipsterConfig.blueprints.map(bp => `${bp.name}@${bp.version}`).join(', '); - commitMsg += ` with blueprints ${bpInfo}`; - } - await git.add(['.']).commit(commitMsg); - this.log.ok(`Application successfully committed to Git from ${repositoryRoot}.`); - } catch (e) { - this.log.warn( - chalk.red.bold(`Application commit to Git failed from ${repositoryRoot}. Try to commit manually. (${(e as any).message})`), - ); - } - }; - - // @ts-expect-error keep compatibility with other adapters - if (this.env.adapter.queue) { - await (this.env.adapter as QueuedAdapter).queue(commitFiles); - } else { - await commitFiles(); - } - }, - }); - } - - get [BaseGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - async initializeGitRepository() { - try { - const git = this.createGit(); - if (await git.checkIsRepo('root' as any)) { - this.log.info('Using existing git repository.'); - } else if (await git.checkIsRepo()) { - this.log.info('Using existing git repository at parent folder.'); - } else if (await git.init()) { - this.log.ok('Git repository initialized.'); - } - this.gitInitialized = true; - } catch (error) { - this.log.warn(`Failed to initialize Git repository.\n ${error}`); - } - } -} diff --git a/generators/git/generator.spec.mts b/generators/git/generator.spec.mts deleted file mode 100644 index 9cdc589fdec6..000000000000 --- a/generators/git/generator.spec.mts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join, resolve } from 'path'; -import { fileURLToPath } from 'url'; -import { access } from 'fs/promises'; -import { expect } from 'esmocha'; - -import { testBlueprintSupport } from '../../test/support/tests.mjs'; -import { skipPrettierHelpers as helpers } from '../../test/support/index.mjs'; -import { GENERATOR_GIT } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorPath = join(__dirname, 'index.mts'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', () => { - expect(GENERATOR_GIT).toBe(generator); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('with', () => { - describe('default config', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath); - }); - it('should write files and match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - }); - describe('git feature', () => { - describe('with default option', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath); - }); - it('should create .git', async () => { - await expect(access(resolve(runResult.cwd, '.git'))).resolves.toBeUndefined(); - }); - it('should create 1 commit', async () => { - const git = runResult.generator.createGit(); - await expect(git.log()).resolves.toMatchObject({ - total: 1, - latest: { message: expect.stringMatching(/^Initial version of/) }, - }); - }); - }); - describe('with skipGit option', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withOptions({ skipGit: true }); - }); - it('should not create .git', async () => { - await expect(access(resolve(runResult.cwd, '.git'))).rejects.toMatchObject({ code: 'ENOENT' }); - }); - }); - describe('regenerating', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath); - runResult = await runResult.create(generatorPath).withOptions({ baseName: 'changed' }).run(); - }); - it('should have 1 commit', async () => { - const git = runResult.generator.createGit(); - await expect(git.log()).resolves.toMatchObject({ total: 1 }); - }); - }); - }); -}); diff --git a/generators/git/generator.spec.ts b/generators/git/generator.spec.ts new file mode 100644 index 000000000000..9bd5b11a4ebe --- /dev/null +++ b/generators/git/generator.spec.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join, resolve } from 'path'; +import { fileURLToPath } from 'url'; +import { access } from 'fs/promises'; +import { expect } from 'esmocha'; + +import { testBlueprintSupport } from '../../test/support/tests.js'; +import { skipPrettierHelpers as helpers } from '../../test/support/index.js'; +import { GENERATOR_GIT } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorPath = join(__dirname, 'index.ts'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', () => { + expect(GENERATOR_GIT).toBe(generator); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('with', () => { + describe('default config', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath); + }); + it('should write files and match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + }); + describe('git feature', () => { + describe('with default option', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath); + }); + it('should create .git', async () => { + await expect(access(resolve(runResult.cwd, '.git'))).resolves.toBeUndefined(); + }); + it('should create 1 commit', async () => { + const git = runResult.generator.createGit(); + await expect(git.log()).resolves.toMatchObject({ + total: 1, + latest: { message: expect.stringMatching(/^Initial version of/) }, + }); + }); + }); + describe('with skipGit option', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withOptions({ skipGit: true }); + }); + it('should not create .git', async () => { + await expect(access(resolve(runResult.cwd, '.git'))).rejects.toMatchObject({ code: 'ENOENT' }); + }); + }); + describe('regenerating', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath); + runResult = await runResult.create(generatorPath).withOptions({ baseName: 'changed' }).run(); + }); + it('should have 1 commit', async () => { + const git = runResult.generator.createGit(); + await expect(git.log()).resolves.toMatchObject({ total: 1 }); + }); + }); + }); +}); diff --git a/generators/git/generator.ts b/generators/git/generator.ts new file mode 100644 index 000000000000..1c231a9d5e82 --- /dev/null +++ b/generators/git/generator.ts @@ -0,0 +1,166 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import chalk from 'chalk'; +import type { QueuedAdapter } from '@yeoman/types'; + +import BaseGenerator from '../base/index.js'; +import { GENERATOR_GIT } from '../generator-list.js'; +import { files } from './files.js'; +import command from './command.js'; + +/** + * @class + * @extends {BaseGenerator} + */ +export default class InitGenerator extends BaseGenerator { + gitInstalled; + gitInitialized; + skipGit; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_GIT); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + parseOptions() { + this.parseJHipsterOptions(command.options); + }, + async checkGit() { + if (!this.skipGit) { + this.gitInstalled = (await this.createGit().version()).installed; + if (!this.gitInstalled) { + this.log.warn('Git repository will not be created, as Git is not installed on your system'); + this.skipGit = true; + } + } + }, + async initializeMonorepository() { + if (!this.skipGit && this.jhipsterConfig.monorepository) { + await this.initializeGitRepository(); + } + }, + }); + } + + get [BaseGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get writing() { + return this.asWritingTaskGroup({ + async writeFiles() { + await this.writeFiles({ sections: files, context: {} }); + }, + }); + } + + get [BaseGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + /** Husky commit hook install at install priority requires git to be initialized */ + async initGitRepo() { + if (!this.skipGit && !this.jhipsterConfig.monorepository) { + await this.initializeGitRepository(); + } + }, + }); + } + + get [BaseGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + get end() { + return this.asEndTaskGroup({ + /** Initial commit to git repository after package manager install for package-lock.json */ + async gitCommit() { + if (this.skipGit) return; + if (!this.gitInitialized) { + this.log.warn('The generated application could not be committed to Git, as a Git repository could not be initialized.'); + return; + } + + const commitFiles = async () => { + this.debug('Committing files to git'); + const git = this.createGit(); + const repositoryRoot = await git.revparse(['--show-toplevel']); + const result = await git.log(['-n', '1', '--', '.yo-rc.json']).catch(() => {}); + if (result && result.total > 0) { + this.log.info( + `Found .yo-rc.json in Git from ${repositoryRoot}. So we assume this is application regeneration. Therefore automatic Git commit is not done. You can do Git commit manually.`, + ); + return; + } + try { + const statusResult = await git.status(); + if (statusResult.staged.length > 0) { + this.log.verboseInfo(`The repository ${repositoryRoot} has staged files, skipping commit.`); + return; + } + let commitMsg = `Initial version of ${this.jhipsterConfig.baseName} generated by generator-jhipster@${this.jhipsterConfig.jhipsterVersion}`; + if (this.jhipsterConfig.blueprints && this.jhipsterConfig.blueprints.length > 0) { + const bpInfo = this.jhipsterConfig.blueprints.map(bp => `${bp.name}@${bp.version}`).join(', '); + commitMsg += ` with blueprints ${bpInfo}`; + } + await git.add(['.']).commit(commitMsg); + this.log.ok(`Application successfully committed to Git from ${repositoryRoot}.`); + } catch (e) { + this.log.warn( + chalk.red.bold(`Application commit to Git failed from ${repositoryRoot}. Try to commit manually. (${(e as any).message})`), + ); + } + }; + + // @ts-expect-error keep compatibility with other adapters + if (this.env.adapter.queue) { + await (this.env.adapter as QueuedAdapter).queue(commitFiles); + } else { + await commitFiles(); + } + }, + }); + } + + get [BaseGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + async initializeGitRepository() { + try { + const git = this.createGit(); + if (await git.checkIsRepo('root' as any)) { + this.log.info('Using existing git repository.'); + } else if (await git.checkIsRepo()) { + this.log.info('Using existing git repository at parent folder.'); + } else if (await git.init()) { + this.log.ok('Git repository initialized.'); + } + this.gitInitialized = true; + } catch (error) { + this.log.warn(`Failed to initialize Git repository.\n ${error}`); + } + } +} diff --git a/generators/git/index.mts b/generators/git/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/git/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/git/index.ts b/generators/git/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/git/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/gradle/__snapshots__/generator.spec.mjs.snap b/generators/gradle/__snapshots__/generator.spec.js.snap similarity index 100% rename from generators/gradle/__snapshots__/generator.spec.mjs.snap rename to generators/gradle/__snapshots__/generator.spec.js.snap diff --git a/generators/gradle/cleanup.mts b/generators/gradle/cleanup.mts deleted file mode 100644 index 3067b12177a1..000000000000 --- a/generators/gradle/cleanup.mts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupOldServerFilesTask(this: BaseGenerator) { - if (this.isJhipsterVersionLessThan('5.0.0')) { - this.removeFile('gradle/mapstruct.gradle'); - } - if (this.isJhipsterVersionLessThan('5.2.2')) { - this.removeFile('gradle/liquibase.gradle'); - } -} diff --git a/generators/gradle/cleanup.ts b/generators/gradle/cleanup.ts new file mode 100644 index 000000000000..438010d7dcb6 --- /dev/null +++ b/generators/gradle/cleanup.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupOldServerFilesTask(this: BaseGenerator) { + if (this.isJhipsterVersionLessThan('5.0.0')) { + this.removeFile('gradle/mapstruct.gradle'); + } + if (this.isJhipsterVersionLessThan('5.2.2')) { + this.removeFile('gradle/liquibase.gradle'); + } +} diff --git a/generators/gradle/constants.mjs b/generators/gradle/constants.js similarity index 100% rename from generators/gradle/constants.mjs rename to generators/gradle/constants.js diff --git a/generators/gradle/files.mts b/generators/gradle/files.ts similarity index 100% rename from generators/gradle/files.mts rename to generators/gradle/files.ts diff --git a/generators/gradle/generator.mts b/generators/gradle/generator.mts deleted file mode 100644 index cede53a46bb2..000000000000 --- a/generators/gradle/generator.mts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import assert from 'assert/strict'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; - -import { GENERATOR_GRADLE, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.mjs'; -import files from './files.mjs'; -import { GRADLE } from './constants.mjs'; -import cleanupOldServerFilesTask from './cleanup.mjs'; -import { - applyFromGradleCallback, - addGradleDependencyCallback, - addGradleMavenRepositoryCallback, - addGradlePluginCallback, - addGradlePluginManagementCallback, - addGradlePropertyCallback, -} from './internal/needles.mjs'; - -export default class GradleGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_GRADLE); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); - } - } - - get configuring() { - return this.asConfiguringTaskGroup({ - configure() { - if (this.jhipsterConfigWithDefaults.buildTool !== GRADLE) { - this.config.defaults({ - buildTool: GRADLE, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get preparing() { - return this.asPreparingTaskGroup({ - async verify({ application }) { - assert.equal(application.buildTool, GRADLE); - }, - addSourceNeddles({ source }) { - source.applyFromGradle = script => this.editFile('build.gradle', applyFromGradleCallback(script)); - source.addGradleDependency = dependency => this.editFile('build.gradle', addGradleDependencyCallback(dependency)); - source.addGradlePlugin = plugin => this.editFile('build.gradle', addGradlePluginCallback(plugin)); - source.addGradleMavenRepository = repository => this.editFile('build.gradle', addGradleMavenRepositoryCallback(repository)); - source.addGradlePluginManagement = plugin => this.editFile('settings.gradle', addGradlePluginManagementCallback(plugin)); - source.addGradleProperty = property => this.editFile('gradle.properties', addGradlePropertyCallback(property)); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupOldServerFilesTask, - async writeFiles({ application }) { - await this.writeFiles({ sections: files, context: application }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } -} diff --git a/generators/gradle/generator.spec.js b/generators/gradle/generator.spec.js new file mode 100644 index 000000000000..0c4b22e3080c --- /dev/null +++ b/generators/gradle/generator.spec.js @@ -0,0 +1,65 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { GENERATOR_JHIPSTER } from '../generator-constants.js'; +import { GENERATOR_GRADLE } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', () => { + expect(GENERATOR_GRADLE).toBe(generator); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('with valid configuration', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorFile).withJHipsterConfig({ + baseName: 'existing', + packageName: 'tech.jhipster', + }); + }); + it('should generate only gradle files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('should set buildTool config', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { buildTool: 'gradle' } }); + }); + }); + describe('with empty configuration', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorFile).withJHipsterConfig(); + }); + it('should generate only gradle files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('should set buildTool config', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { buildTool: 'gradle' } }); + }); + }); +}); diff --git a/generators/gradle/generator.spec.mjs b/generators/gradle/generator.spec.mjs deleted file mode 100644 index cacffd5c7421..000000000000 --- a/generators/gradle/generator.spec.mjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { GENERATOR_JHIPSTER } from '../generator-constants.mjs'; -import { GENERATOR_GRADLE } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', () => { - expect(GENERATOR_GRADLE).toBe(generator); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('with valid configuration', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorFile).withJHipsterConfig({ - baseName: 'existing', - packageName: 'tech.jhipster', - }); - }); - it('should generate only gradle files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('should set buildTool config', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { buildTool: 'gradle' } }); - }); - }); - describe('with empty configuration', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorFile).withJHipsterConfig(); - }); - it('should generate only gradle files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('should set buildTool config', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { buildTool: 'gradle' } }); - }); - }); -}); diff --git a/generators/gradle/generator.ts b/generators/gradle/generator.ts new file mode 100644 index 000000000000..df9af5c6d939 --- /dev/null +++ b/generators/gradle/generator.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import assert from 'assert/strict'; + +import BaseApplicationGenerator from '../base-application/index.js'; + +import { GENERATOR_GRADLE, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.js'; +import files from './files.js'; +import { GRADLE } from './constants.js'; +import cleanupOldServerFilesTask from './cleanup.js'; +import { + applyFromGradleCallback, + addGradleDependencyCallback, + addGradleMavenRepositoryCallback, + addGradlePluginCallback, + addGradlePluginManagementCallback, + addGradlePropertyCallback, +} from './internal/needles.js'; + +export default class GradleGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_GRADLE); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); + } + } + + get configuring() { + return this.asConfiguringTaskGroup({ + configure() { + if (this.jhipsterConfigWithDefaults.buildTool !== GRADLE) { + this.config.defaults({ + buildTool: GRADLE, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get preparing() { + return this.asPreparingTaskGroup({ + async verify({ application }) { + assert.equal(application.buildTool, GRADLE); + }, + addSourceNeddles({ source }) { + source.applyFromGradle = script => this.editFile('build.gradle', applyFromGradleCallback(script)); + source.addGradleDependency = dependency => this.editFile('build.gradle', addGradleDependencyCallback(dependency)); + source.addGradlePlugin = plugin => this.editFile('build.gradle', addGradlePluginCallback(plugin)); + source.addGradleMavenRepository = repository => this.editFile('build.gradle', addGradleMavenRepositoryCallback(repository)); + source.addGradlePluginManagement = plugin => this.editFile('settings.gradle', addGradlePluginManagementCallback(plugin)); + source.addGradleProperty = property => this.editFile('gradle.properties', addGradlePropertyCallback(property)); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupOldServerFilesTask, + async writeFiles({ application }) { + await this.writeFiles({ sections: files, context: application }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } +} diff --git a/generators/gradle/index.mts b/generators/gradle/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/gradle/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/gradle/index.ts b/generators/gradle/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/gradle/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/gradle/internal/needles.mts b/generators/gradle/internal/needles.mts deleted file mode 100644 index 3b50e1a53b69..000000000000 --- a/generators/gradle/internal/needles.mts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2013-2023 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 { createNeedleCallback } from '../../base/support/index.mjs'; -import { GradleScript, GradleDependency, GradlePlugin, GradleProperty, GradleRepository } from '../types.mjs'; - -export const applyFromGradleCallback = ({ script }: GradleScript) => - createNeedleCallback({ - needle: 'gradle-apply-from', - contentToAdd: `apply from: "${script}"`, - }); - -export const addGradleDependencyCallback = ({ groupId, artifactId, version, scope }: GradleDependency) => - createNeedleCallback({ - needle: 'gradle-dependency', - contentToAdd: `${scope} "${groupId}:${artifactId}${version ? `:${version}` : ''}"`, - }); - -export const addGradlePluginCallback = ({ id, version }: GradlePlugin) => - createNeedleCallback({ - needle: 'gradle-plugins', - contentToAdd: `id "${id}"${version ? ` version "${version}"` : ''}`, - }); - -export const addGradlePluginManagementCallback = ({ id, version }: GradlePlugin) => - createNeedleCallback({ - needle: 'gradle-plugin-management-plugins', - contentToAdd: `id "${id}" version "${version}"`, - }); - -export const addGradlePropertyCallback = ({ property, value }: GradleProperty) => - createNeedleCallback({ - needle: 'gradle-property', - contentToAdd: `${property}=${value}`, - contentToCheck: new RegExp(`\n${property}=`), - autoIndent: false, - }); - -export const addGradleMavenRepositoryCallback = ({ url, username, password }: GradleRepository) => - createNeedleCallback({ - needle: 'gradle-repositories', - // prettier-ignore - contentToAdd: ` -maven { - url "${url}"${ username || password ? ` - credentials {${ username ? ` - username = "${username}"` : ''}${ password ? ` - password = "${password}"` : ''} - }` : ''} -}`, - }); diff --git a/generators/gradle/internal/needles.ts b/generators/gradle/internal/needles.ts new file mode 100644 index 000000000000..db748c6c898c --- /dev/null +++ b/generators/gradle/internal/needles.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2013-2023 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 { createNeedleCallback } from '../../base/support/index.js'; +import { GradleScript, GradleDependency, GradlePlugin, GradleProperty, GradleRepository } from '../types.js'; + +export const applyFromGradleCallback = ({ script }: GradleScript) => + createNeedleCallback({ + needle: 'gradle-apply-from', + contentToAdd: `apply from: "${script}"`, + }); + +export const addGradleDependencyCallback = ({ groupId, artifactId, version, scope }: GradleDependency) => + createNeedleCallback({ + needle: 'gradle-dependency', + contentToAdd: `${scope} "${groupId}:${artifactId}${version ? `:${version}` : ''}"`, + }); + +export const addGradlePluginCallback = ({ id, version }: GradlePlugin) => + createNeedleCallback({ + needle: 'gradle-plugins', + contentToAdd: `id "${id}"${version ? ` version "${version}"` : ''}`, + }); + +export const addGradlePluginManagementCallback = ({ id, version }: GradlePlugin) => + createNeedleCallback({ + needle: 'gradle-plugin-management-plugins', + contentToAdd: `id "${id}" version "${version}"`, + }); + +export const addGradlePropertyCallback = ({ property, value }: GradleProperty) => + createNeedleCallback({ + needle: 'gradle-property', + contentToAdd: `${property}=${value}`, + contentToCheck: new RegExp(`\n${property}=`), + autoIndent: false, + }); + +export const addGradleMavenRepositoryCallback = ({ url, username, password }: GradleRepository) => + createNeedleCallback({ + needle: 'gradle-repositories', + // prettier-ignore + contentToAdd: ` +maven { + url "${url}"${ username || password ? ` + credentials {${ username ? ` + username = "${username}"` : ''}${ password ? ` + password = "${password}"` : ''} + }` : ''} +}`, + }); diff --git a/generators/gradle/needles.spec.mts b/generators/gradle/needles.spec.mts deleted file mode 100644 index bd46ec767f47..000000000000 --- a/generators/gradle/needles.spec.mts +++ /dev/null @@ -1,74 +0,0 @@ -import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SERVER } from '../generator-list.mjs'; - -class mockBlueprintSubGen extends BaseApplicationGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - gradleStep({ source }) { - source.addGradleProperty?.({ property: 'name', value: 'value' }); - source.addGradlePlugin?.({ id: 'id', version: 'version' }); - source.addGradleDependency?.({ scope: 'scope3', groupId: 'group3', artifactId: 'name3', version: 'version3' }); - source.addGradleDependency?.({ scope: 'scope4', groupId: 'group4', artifactId: 'name4' }); - source.applyFromGradle?.({ script: 'name.gradle' }); - source.addGradleMavenRepository?.({ url: 'url', username: 'username', password: 'password' }); - }, - }); - } -} - -describe('needle API server gradle: JHipster server generator with blueprint', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_SERVER) - .withJHipsterConfig({ - blueprint: 'myblueprint', - clientFramework: 'no', - buildTool: 'gradle', - }) - .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:server']]); - }); - - it('Assert gradle.properties has the property added', () => { - runResult.assertFileContent('gradle.properties', 'name=value'); - }); - - it('Assert gradle.properties has not snake case properties', () => { - runResult.assertNoFileContent('gradle.properties', /^(?!.*#).*_.*[$|=]/m); // Not comment and contains underscore - }); - - it('Assert build.gradle has the PluginToPluginsBlock added', () => { - runResult.assertFileContent('build.gradle', 'id "id" version "version"'); - }); - - it('Assert build.gradle has the Dependency with version added', () => { - runResult.assertFileContent('build.gradle', 'scope3 "group3:name3:version3"'); - }); - - it('Assert build.gradle has the Dependency without version added', () => { - runResult.assertFileContent('build.gradle', 'scope4 "group4:name4"'); - }); - - it('Assert build.gradle has the apply gradle script added', () => { - runResult.assertFileContent('build.gradle', 'apply from: "name.gradle"'); - }); - - it('Assert build.gradle has the maven repository added', () => { - runResult.assertFileContent( - 'build.gradle', - ' maven {\n' + - ' url "url"\n' + - ' credentials {\n' + - ' username = "username"\n' + - ' password = "password"\n' + - ' }\n' + - ' }', - ); - }); -}); diff --git a/generators/gradle/needles.spec.ts b/generators/gradle/needles.spec.ts new file mode 100644 index 000000000000..b1fff842b1c2 --- /dev/null +++ b/generators/gradle/needles.spec.ts @@ -0,0 +1,74 @@ +import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SERVER } from '../generator-list.js'; + +class mockBlueprintSubGen extends BaseApplicationGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + gradleStep({ source }) { + source.addGradleProperty?.({ property: 'name', value: 'value' }); + source.addGradlePlugin?.({ id: 'id', version: 'version' }); + source.addGradleDependency?.({ scope: 'scope3', groupId: 'group3', artifactId: 'name3', version: 'version3' }); + source.addGradleDependency?.({ scope: 'scope4', groupId: 'group4', artifactId: 'name4' }); + source.applyFromGradle?.({ script: 'name.gradle' }); + source.addGradleMavenRepository?.({ url: 'url', username: 'username', password: 'password' }); + }, + }); + } +} + +describe('needle API server gradle: JHipster server generator with blueprint', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_SERVER) + .withJHipsterConfig({ + blueprint: 'myblueprint', + clientFramework: 'no', + buildTool: 'gradle', + }) + .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:server']]); + }); + + it('Assert gradle.properties has the property added', () => { + runResult.assertFileContent('gradle.properties', 'name=value'); + }); + + it('Assert gradle.properties has not snake case properties', () => { + runResult.assertNoFileContent('gradle.properties', /^(?!.*#).*_.*[$|=]/m); // Not comment and contains underscore + }); + + it('Assert build.gradle has the PluginToPluginsBlock added', () => { + runResult.assertFileContent('build.gradle', 'id "id" version "version"'); + }); + + it('Assert build.gradle has the Dependency with version added', () => { + runResult.assertFileContent('build.gradle', 'scope3 "group3:name3:version3"'); + }); + + it('Assert build.gradle has the Dependency without version added', () => { + runResult.assertFileContent('build.gradle', 'scope4 "group4:name4"'); + }); + + it('Assert build.gradle has the apply gradle script added', () => { + runResult.assertFileContent('build.gradle', 'apply from: "name.gradle"'); + }); + + it('Assert build.gradle has the maven repository added', () => { + runResult.assertFileContent( + 'build.gradle', + ' maven {\n' + + ' url "url"\n' + + ' credentials {\n' + + ' username = "username"\n' + + ' password = "password"\n' + + ' }\n' + + ' }', + ); + }); +}); diff --git a/generators/gradle/types.d.mts b/generators/gradle/types.d.ts similarity index 100% rename from generators/gradle/types.d.mts rename to generators/gradle/types.d.ts diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.ts.snap similarity index 100% rename from generators/heroku/__snapshots__/heroku.spec.mts.snap rename to generators/heroku/__snapshots__/heroku.spec.ts.snap diff --git a/generators/heroku/generator.js b/generators/heroku/generator.js new file mode 100644 index 000000000000..c81ffc4aa49d --- /dev/null +++ b/generators/heroku/generator.js @@ -0,0 +1,603 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import { kebabCase } from 'lodash-es'; +import chalk from 'chalk'; +import { glob } from 'glob'; + +import BaseGenerator from '../base-application/index.js'; + +import statistics from '../statistics.js'; +import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_HEROKU } from '../generator-list.js'; +import { mavenProfileContent } from './templates.js'; +import { createPomStorage } from '../maven/support/pom-store.js'; +import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.js'; + +export default class HerokuGenerator extends BaseGenerator { + hasHerokuCli; + + herokuAppName; + herokuDeployType; + herokuJavaVersion; + herokuRegion; + herokuAppExists; + herokuSkipDeploy; + herokuSkipBuild; + dynoSize; + + constructor(args, options, features) { + super(args, options, features); + + this.option('skip-build', { + description: 'Skips building the application', + type: Boolean, + default: false, + }); + + this.option('skip-deploy', { + description: 'Skips deployment to Heroku', + type: Boolean, + default: false, + }); + + if (this.options.help) { + return; + } + + this.herokuSkipBuild = this.options.skipBuild; + this.herokuSkipDeploy = this.options.skipDeploy || this.options.skipBuild; + } + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_HEROKU); + } + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + async checkInstallation() { + const { exitCode } = await this.spawnHerokuCommand('--version', { verboseInfo: false }); + this.hasHerokuCli = exitCode === 0; + if (!this.hasHerokuCli) { + const error = + "You don't have the Heroku CLI installed. See https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli to learn how to install it."; + if (this.skipChecks) { + this.log.warn(error); + this.log.warn('Generation will continue with limited support'); + } else { + throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`); + } + } + }, + async herokuLogin() { + if (!this.hasHerokuCli) return; + + const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false }); + if (exitCode !== 0) { + this.log.log(chalk.bold('Log in to Heroku to continue.')); + await this.spawnHerokuCommand('login', { reject: true, stdio: 'inherit' }); + } + }, + + initializing() { + this.log.log(chalk.bold('Heroku configuration is starting')); + this.dynoSize = 'Basic'; + this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName); + }, + }); + } + + get [BaseGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return this.asPromptingTaskGroup({ + async askForApp() { + if (this.hasHerokuCli && this.herokuAppExists) { + const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { + verboseInfo: false, + }); + if (exitCode !== 0) { + this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); + this.herokuAppName = null; + throw new Error('Run the generator again to create a new application.'); + } else { + const json = JSON.parse(stdout); + this.herokuAppName = json.app.name; + if (json.dynos.length > 0) { + this.dynoSize = json.dynos[0].size; + } + this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`); + this.config.set({ + herokuAppName: this.herokuAppName, + }); + } + } else { + await this.prompt( + [ + { + type: 'input', + name: 'herokuAppName', + message: 'Name to deploy as:', + default: this.baseName, + }, + ], + this.config, + ); + + const answers = await this.prompt([ + { + type: 'list', + name: 'herokuRegion', + message: 'On which region do you want to deploy?', + choices: ['us', 'eu'], + default: 0, + }, + ]); + this.herokuRegion = answers.herokuRegion; + } + }, + async askForHerokuDeployType() { + await this.prompt( + [ + { + type: 'list', + name: 'herokuDeployType', + message: 'Which type of deployment do you want?', + choices: [ + { value: 'git', name: 'Git (compile on Heroku)' }, + { value: 'jar', name: 'JAR (compile locally)' }, + ], + default: 0, + }, + ], + this.config, + ); + }, + + async askForHerokuJavaVersion() { + await this.prompt( + [ + { + type: 'list', + name: 'herokuJavaVersion', + message: 'Which Java version would you like to use to build and run your app?', + choices: JAVA_COMPATIBLE_VERSIONS.map(version => ({ value: version })), + default: JAVA_VERSION, + }, + ], + this.config, + ); + }, + }); + } + + get [BaseGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get loading() { + return this.asConfiguringTaskGroup({ + saveConfig() { + this.herokuAppName = kebabCase(this.jhipsterConfig.herokuAppName); + this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion; + this.herokuDeployType = this.jhipsterConfig.herokuDeployType; + }, + }); + } + + get [BaseGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get default() { + return this.asDefaultTaskGroup({ + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_HEROKU); + }, + + async gitInit() { + if (!this.herokuDeployType === 'git') return; + + const git = this.createGit(); + if (await git.checkIsRepo()) { + this.log.log(chalk.bold('\nUsing existing Git repository')); + } else { + this.log.log(chalk.bold('\nInitializing Git repository')); + await git.init(); + } + }, + + async installHerokuDeployPlugin() { + if (!this.hasHerokuCli) return; + + const cliPlugin = 'heroku-cli-deploy'; + + const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { stdio: 'pipe' }); + if (exitCode !== 0) { + if (stdout.includes(cliPlugin)) { + this.log.log('\nHeroku CLI deployment plugin already installed'); + } else { + this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); + const { exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`); + if (exitCode !== 0) { + throw new Error(stderr); + } + } + } + }, + + async herokuCreate() { + if (!this.hasHerokuCli || this.herokuAppExists) return; + + const regionParams = this.herokuRegion !== 'us' ? ['--region', this.herokuRegion] : []; + + this.log.log(chalk.bold('\nCreating Heroku application and setting up Node environment')); + const { stdout, stderr, exitCode } = await this.spawnHeroku(['create', this.herokuAppName, ...regionParams]); + + if (stdout.includes('Heroku credentials')) { + throw new Error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); + } + + if (exitCode !== 0) { + if (stderr.includes('is already taken')) { + const prompts = [ + { + type: 'list', + name: 'herokuForceName', + message: `The Heroku application "${chalk.cyan(this.herokuAppName)}" already exists! Use it anyways?`, + choices: [ + { + value: 'Yes', + name: 'Yes, I have access to it', + }, + { + value: 'No', + name: 'No, generate a random name', + }, + ], + default: 0, + }, + ]; + + this.log.log(''); + const props = await this.prompt(prompts); + if (props.herokuForceName === 'Yes') { + await this.spawnHeroku(['git:remote', '--app', this.herokuAppName], { reject: true }); + } else { + const { stdout } = await this.spawnHeroku(['create', ...regionParams]); + // Extract from "Created random-app-name-1234... done" + this.herokuAppName = stdout.substring(stdout.lastIndexOf('/') + 1, stdout.indexOf('.git')); + // ensure that the git remote is the same as the appName + await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); + this.jhipsterConfig.herokuAppName = this.herokuAppName; + } + } else if (stderr.includes('Invalid credentials')) { + this.log.error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); + } else { + throw new Error(stderr); + } + } + }, + + async herokuAddonsCreate({ application }) { + if (!this.hasHerokuCli || this.herokuAppExists) return; + + this.log.log(chalk.bold('\nProvisioning addons')); + if (application.searchEngineElasticsearch) { + this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); + const { stdout, stderr } = await this.spawn('heroku', [ + 'addons:create', + 'bonsai:sandbox-6', + '--as', + 'BONSAI', + '--app', + this.herokuAppName, + ]); + this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr }); + } + + let dbAddOn; + if (application.prodDatabaseTypePostgresql) { + dbAddOn = 'heroku-postgresql'; + } else if (application.prodDatabaseTypeMysql) { + dbAddOn = 'jawsdb:kitefin'; + } else if (application.prodDatabaseTypeMariadb) { + dbAddOn = 'jawsdb-maria:kitefin'; + } + + if (dbAddOn) { + this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); + const { stdout, stderr } = await this.spawn('heroku', [ + 'addons:create', + dbAddOn, + '--as', + 'DATABASE', + '--app', + this.herokuAppName, + ]); + this.checkAddOnReturn({ addOn: 'Database', stdout, stderr }); + } else { + this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); + } + + let cacheAddOn; + if (application.cacheProviderMemcached) { + cacheAddOn = ['memcachier:dev', '--as', 'MEMCACHIER']; + } else if (application.cacheProviderRedis) { + cacheAddOn = ['heroku-redis:hobby-dev', '--as', 'REDIS']; + } + + if (cacheAddOn) { + this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); + + const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName]); + this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); + } + }, + + async configureJHipsterRegistry({ application }) { + if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka) return undefined; + + this.log.log(''); + const answers = await this.prompt([ + { + type: 'input', + name: 'herokuJHipsterRegistryApp', + message: 'What is the name of your JHipster Registry Heroku application?', + default: 'jhipster-registry', + }, + { + type: 'input', + name: 'herokuJHipsterRegistryUsername', + message: 'What is your JHipster Registry username?', + default: 'admin', + }, + { + type: 'input', + name: 'herokuJHipsterRegistryPassword', + message: 'What is your JHipster Registry password?', + default: 'password', + }, + ]); + + // Encode username/password to avoid errors caused by spaces + const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername); + const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword); + const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`; + const configSetCmd = ['config:set', 'JHIPSTER_REGISTRY_URL', herokuJHipsterRegistry, '--app', this.herokuAppName]; + await this.spawnHeroku(configSetCmd, { stdio: 'pipe' }); + }, + }); + } + + get [BaseGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return this.asWritingTaskGroup({ + copyHerokuFiles({ application }) { + this.log.log(chalk.bold('\nCreating Heroku deployment files')); + const context = { + ...application, + herokuAppName: this.herokuAppName, + dynoSize: this.dynoSize, + herokuJavaVersion: this.herokuJavaVersion, + herokuDeployType: this.herokuDeployType, + }; + + this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, context); + this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, context); + this.writeFile('Procfile.ejs', 'Procfile', context); + this.writeFile('system.properties.ejs', 'system.properties', context); + if (application.buildToolGradle) { + this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle', context); + } + }, + + addHerokuBuildPlugin({ application }) { + if (!application.buildToolGradle) return; + // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator + this.editFile('build.gradle', addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' })); + // TODO applyFromGradleCallback is an internal api, switch to source api when converted to BaseApplicationGenerator + this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); + }, + + addHerokuMavenProfile({ application }) { + if (application.buildToolMaven) { + this.addMavenProfile('heroku', mavenProfileContent(application)); + } + }, + }); + } + + get [BaseGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get end() { + return this.asEndTaskGroup({ + async productionBuild() { + if (this.herokuSkipBuild || this.herokuDeployType === 'git') { + this.log.log(chalk.bold('\nSkipping build')); + return; + } + + this.log.log(chalk.bold('\nBuilding application')); + + // Use npm script so blueprints just need to override it. + await this.printChildOutput(this.spawnCommand('npm run java:jar:prod', { stdio: 'pipe' })); + }, + + async productionDeploy({ application }) { + if (this.herokuSkipDeploy || !this.hasHerokuCli) { + this.log.log(chalk.bold('\nSkipping deployment')); + return; + } + + if (this.herokuDeployType === 'git') { + try { + this.log.log(chalk.bold('\nUpdating Git repository')); + const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr })); + await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null }); + + let buildpack = 'heroku/java'; + let configName = 'MAVEN_CUSTOM_OPTS'; + let configValues = '-Pprod,heroku -DskipTests'; + if (application.buildToolGradle) { + buildpack = 'heroku/gradle'; + configName = 'GRADLE_TASK'; + configValues = 'stage -Pprod -PnodeInstall'; + } + + this.log.log(chalk.bold('\nConfiguring Heroku')); + const { stdout: configData } = await this.spawnHeroku(['config:get', configName, '--app', this.herokuAppName]); + if (!configData) { + await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); + } + + const { stdout: buildpackData } = await this.spawnHeroku(['buildpacks', '--app', this.herokuAppName]); + if (!buildpackData.includes(buildpack)) { + await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); + } + + this.log.log(chalk.bold('\nDeploying application...')); + + await git.push('heroku', 'HEAD:main'); + + this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); + this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); + this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); + } catch (err) { + this.log.error(err); + } + } else { + this.log.log(chalk.bold('\nDeploying application')); + let jarFileWildcard = 'target/*.jar'; + if (application.buildToolGradle) { + jarFileWildcard = 'build/libs/*.jar'; + } + + const files = glob.sync(jarFileWildcard, {}); + const jarFile = files[0]; + + this.log.log( + chalk.bold( + `\nUploading your application code.\nThis may take ${chalk.cyan('several minutes')} depending on your connection speed...`, + ), + ); + try { + await this.spawnHeroku(['deploy:jar', jarFile, '--app', this.herokuAppName], { stdio: 'pipe' }); + await this.spawnHerokuCommand('buildpacks:set heroku/jvm', { stdio: 'pipe' }); + this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); + this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); + this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); + } catch (err) { + this.log.error(err); + } + } + }, + }); + } + + get [BaseGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * TODO drop when dropped from gae, azure-spring-cloud and heroku generators + * @private + * Add a new Maven profile. + * + * @param {string} profileId - profile ID + * @param {string} other - explicit other thing: build, dependencies... + */ + addMavenProfile(profileId, other) { + createPomStorage(this).addProfile({ id: profileId, content: other }); + } + + /** + * @param {string} command + * @param {import('execa').Options} opt + * @returns {ReturnType} + */ + spawnHerokuCommand(command, opt) { + opt = { stdio: 'pipe', reject: false, ...opt }; + const { verboseInfo, ...spawnOptions } = opt; + const child = this.spawnCommand(`heroku ${command}`, spawnOptions); + if (opt.stdio !== 'pipe' || verboseInfo === false) { + return child; + } + return this.printChildOutput(child); + } + + /** + * @param {string[]} args + * @param {import('execa').Options} opt + * @returns {ReturnType} + */ + spawnHeroku(args, opt) { + opt = { stdio: 'pipe', reject: false, ...opt }; + const { verboseInfo, ...spawnOptions } = opt; + const child = this.spawn('heroku', args, spawnOptions); + if (spawnOptions.stdio !== 'pipe' || verboseInfo === false) { + return child; + } + return this.printChildOutput(child); + } + + /** + * @template {{stdout: any; stderr: any}} T + * @param {T} child + * @param {(chunk: any) => void} child + * @returns {T} + */ + printChildOutput(child, log = data => this.log.verboseInfo(data)) { + const { stdout, stderr } = child; + stdout.on('data', data => { + data.toString().split(/\r?\n/).filter(Boolean).forEach(log); + }); + stderr.on('data', data => { + data.toString().split(/\r?\n/).filter(Boolean).forEach(log); + }); + return child; + } + + checkAddOnReturn({ addOn, stdout, stderr }) { + if (stdout) { + this.log.ok(`Created ${addOn.valueOf()} add-on`); + this.log.ok(stdout); + } else if (stderr) { + const verifyAccountUrl = 'https://heroku.com/verify'; + if (stderr.includes(verifyAccountUrl)) { + this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`); + throw new Error(stderr); + } else { + this.log.verboseInfo(`No new ${addOn.valueOf()} add-on created`); + } + } + } +} diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs deleted file mode 100644 index 168bf3c8e7e2..000000000000 --- a/generators/heroku/generator.mjs +++ /dev/null @@ -1,603 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import { kebabCase } from 'lodash-es'; -import chalk from 'chalk'; -import { glob } from 'glob'; - -import BaseGenerator from '../base-application/index.mjs'; - -import statistics from '../statistics.mjs'; -import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_HEROKU } from '../generator-list.mjs'; -import { mavenProfileContent } from './templates.mjs'; -import { createPomStorage } from '../maven/support/pom-store.mjs'; -import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.mjs'; - -export default class HerokuGenerator extends BaseGenerator { - hasHerokuCli; - - herokuAppName; - herokuDeployType; - herokuJavaVersion; - herokuRegion; - herokuAppExists; - herokuSkipDeploy; - herokuSkipBuild; - dynoSize; - - constructor(args, options, features) { - super(args, options, features); - - this.option('skip-build', { - description: 'Skips building the application', - type: Boolean, - default: false, - }); - - this.option('skip-deploy', { - description: 'Skips deployment to Heroku', - type: Boolean, - default: false, - }); - - if (this.options.help) { - return; - } - - this.herokuSkipBuild = this.options.skipBuild; - this.herokuSkipDeploy = this.options.skipDeploy || this.options.skipBuild; - } - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_HEROKU); - } - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - async checkInstallation() { - const { exitCode } = await this.spawnHerokuCommand('--version', { verboseInfo: false }); - this.hasHerokuCli = exitCode === 0; - if (!this.hasHerokuCli) { - const error = - "You don't have the Heroku CLI installed. See https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli to learn how to install it."; - if (this.skipChecks) { - this.log.warn(error); - this.log.warn('Generation will continue with limited support'); - } else { - throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`); - } - } - }, - async herokuLogin() { - if (!this.hasHerokuCli) return; - - const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false }); - if (exitCode !== 0) { - this.log.log(chalk.bold('Log in to Heroku to continue.')); - await this.spawnHerokuCommand('login', { reject: true, stdio: 'inherit' }); - } - }, - - initializing() { - this.log.log(chalk.bold('Heroku configuration is starting')); - this.dynoSize = 'Basic'; - this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName); - }, - }); - } - - get [BaseGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return this.asPromptingTaskGroup({ - async askForApp() { - if (this.hasHerokuCli && this.herokuAppExists) { - const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { - verboseInfo: false, - }); - if (exitCode !== 0) { - this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); - this.herokuAppName = null; - throw new Error('Run the generator again to create a new application.'); - } else { - const json = JSON.parse(stdout); - this.herokuAppName = json.app.name; - if (json.dynos.length > 0) { - this.dynoSize = json.dynos[0].size; - } - this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`); - this.config.set({ - herokuAppName: this.herokuAppName, - }); - } - } else { - await this.prompt( - [ - { - type: 'input', - name: 'herokuAppName', - message: 'Name to deploy as:', - default: this.baseName, - }, - ], - this.config, - ); - - const answers = await this.prompt([ - { - type: 'list', - name: 'herokuRegion', - message: 'On which region do you want to deploy?', - choices: ['us', 'eu'], - default: 0, - }, - ]); - this.herokuRegion = answers.herokuRegion; - } - }, - async askForHerokuDeployType() { - await this.prompt( - [ - { - type: 'list', - name: 'herokuDeployType', - message: 'Which type of deployment do you want?', - choices: [ - { value: 'git', name: 'Git (compile on Heroku)' }, - { value: 'jar', name: 'JAR (compile locally)' }, - ], - default: 0, - }, - ], - this.config, - ); - }, - - async askForHerokuJavaVersion() { - await this.prompt( - [ - { - type: 'list', - name: 'herokuJavaVersion', - message: 'Which Java version would you like to use to build and run your app?', - choices: JAVA_COMPATIBLE_VERSIONS.map(version => ({ value: version })), - default: JAVA_VERSION, - }, - ], - this.config, - ); - }, - }); - } - - get [BaseGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get loading() { - return this.asConfiguringTaskGroup({ - saveConfig() { - this.herokuAppName = kebabCase(this.jhipsterConfig.herokuAppName); - this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion; - this.herokuDeployType = this.jhipsterConfig.herokuDeployType; - }, - }); - } - - get [BaseGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get default() { - return this.asDefaultTaskGroup({ - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_HEROKU); - }, - - async gitInit() { - if (!this.herokuDeployType === 'git') return; - - const git = this.createGit(); - if (await git.checkIsRepo()) { - this.log.log(chalk.bold('\nUsing existing Git repository')); - } else { - this.log.log(chalk.bold('\nInitializing Git repository')); - await git.init(); - } - }, - - async installHerokuDeployPlugin() { - if (!this.hasHerokuCli) return; - - const cliPlugin = 'heroku-cli-deploy'; - - const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { stdio: 'pipe' }); - if (exitCode !== 0) { - if (stdout.includes(cliPlugin)) { - this.log.log('\nHeroku CLI deployment plugin already installed'); - } else { - this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); - const { exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`); - if (exitCode !== 0) { - throw new Error(stderr); - } - } - } - }, - - async herokuCreate() { - if (!this.hasHerokuCli || this.herokuAppExists) return; - - const regionParams = this.herokuRegion !== 'us' ? ['--region', this.herokuRegion] : []; - - this.log.log(chalk.bold('\nCreating Heroku application and setting up Node environment')); - const { stdout, stderr, exitCode } = await this.spawnHeroku(['create', this.herokuAppName, ...regionParams]); - - if (stdout.includes('Heroku credentials')) { - throw new Error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); - } - - if (exitCode !== 0) { - if (stderr.includes('is already taken')) { - const prompts = [ - { - type: 'list', - name: 'herokuForceName', - message: `The Heroku application "${chalk.cyan(this.herokuAppName)}" already exists! Use it anyways?`, - choices: [ - { - value: 'Yes', - name: 'Yes, I have access to it', - }, - { - value: 'No', - name: 'No, generate a random name', - }, - ], - default: 0, - }, - ]; - - this.log.log(''); - const props = await this.prompt(prompts); - if (props.herokuForceName === 'Yes') { - await this.spawnHeroku(['git:remote', '--app', this.herokuAppName], { reject: true }); - } else { - const { stdout } = await this.spawnHeroku(['create', ...regionParams]); - // Extract from "Created random-app-name-1234... done" - this.herokuAppName = stdout.substring(stdout.lastIndexOf('/') + 1, stdout.indexOf('.git')); - // ensure that the git remote is the same as the appName - await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); - this.jhipsterConfig.herokuAppName = this.herokuAppName; - } - } else if (stderr.includes('Invalid credentials')) { - this.log.error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); - } else { - throw new Error(stderr); - } - } - }, - - async herokuAddonsCreate({ application }) { - if (!this.hasHerokuCli || this.herokuAppExists) return; - - this.log.log(chalk.bold('\nProvisioning addons')); - if (application.searchEngineElasticsearch) { - this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - const { stdout, stderr } = await this.spawn('heroku', [ - 'addons:create', - 'bonsai:sandbox-6', - '--as', - 'BONSAI', - '--app', - this.herokuAppName, - ]); - this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr }); - } - - let dbAddOn; - if (application.prodDatabaseTypePostgresql) { - dbAddOn = 'heroku-postgresql'; - } else if (application.prodDatabaseTypeMysql) { - dbAddOn = 'jawsdb:kitefin'; - } else if (application.prodDatabaseTypeMariadb) { - dbAddOn = 'jawsdb-maria:kitefin'; - } - - if (dbAddOn) { - this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - const { stdout, stderr } = await this.spawn('heroku', [ - 'addons:create', - dbAddOn, - '--as', - 'DATABASE', - '--app', - this.herokuAppName, - ]); - this.checkAddOnReturn({ addOn: 'Database', stdout, stderr }); - } else { - this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); - } - - let cacheAddOn; - if (application.cacheProviderMemcached) { - cacheAddOn = ['memcachier:dev', '--as', 'MEMCACHIER']; - } else if (application.cacheProviderRedis) { - cacheAddOn = ['heroku-redis:hobby-dev', '--as', 'REDIS']; - } - - if (cacheAddOn) { - this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - - const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName]); - this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); - } - }, - - async configureJHipsterRegistry({ application }) { - if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka) return undefined; - - this.log.log(''); - const answers = await this.prompt([ - { - type: 'input', - name: 'herokuJHipsterRegistryApp', - message: 'What is the name of your JHipster Registry Heroku application?', - default: 'jhipster-registry', - }, - { - type: 'input', - name: 'herokuJHipsterRegistryUsername', - message: 'What is your JHipster Registry username?', - default: 'admin', - }, - { - type: 'input', - name: 'herokuJHipsterRegistryPassword', - message: 'What is your JHipster Registry password?', - default: 'password', - }, - ]); - - // Encode username/password to avoid errors caused by spaces - const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername); - const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword); - const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`; - const configSetCmd = ['config:set', 'JHIPSTER_REGISTRY_URL', herokuJHipsterRegistry, '--app', this.herokuAppName]; - await this.spawnHeroku(configSetCmd, { stdio: 'pipe' }); - }, - }); - } - - get [BaseGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return this.asWritingTaskGroup({ - copyHerokuFiles({ application }) { - this.log.log(chalk.bold('\nCreating Heroku deployment files')); - const context = { - ...application, - herokuAppName: this.herokuAppName, - dynoSize: this.dynoSize, - herokuJavaVersion: this.herokuJavaVersion, - herokuDeployType: this.herokuDeployType, - }; - - this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, context); - this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, context); - this.writeFile('Procfile.ejs', 'Procfile', context); - this.writeFile('system.properties.ejs', 'system.properties', context); - if (application.buildToolGradle) { - this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle', context); - } - }, - - addHerokuBuildPlugin({ application }) { - if (!application.buildToolGradle) return; - // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator - this.editFile('build.gradle', addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' })); - // TODO applyFromGradleCallback is an internal api, switch to source api when converted to BaseApplicationGenerator - this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); - }, - - addHerokuMavenProfile({ application }) { - if (application.buildToolMaven) { - this.addMavenProfile('heroku', mavenProfileContent(application)); - } - }, - }); - } - - get [BaseGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get end() { - return this.asEndTaskGroup({ - async productionBuild() { - if (this.herokuSkipBuild || this.herokuDeployType === 'git') { - this.log.log(chalk.bold('\nSkipping build')); - return; - } - - this.log.log(chalk.bold('\nBuilding application')); - - // Use npm script so blueprints just need to override it. - await this.printChildOutput(this.spawnCommand('npm run java:jar:prod', { stdio: 'pipe' })); - }, - - async productionDeploy({ application }) { - if (this.herokuSkipDeploy || !this.hasHerokuCli) { - this.log.log(chalk.bold('\nSkipping deployment')); - return; - } - - if (this.herokuDeployType === 'git') { - try { - this.log.log(chalk.bold('\nUpdating Git repository')); - const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr })); - await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null }); - - let buildpack = 'heroku/java'; - let configName = 'MAVEN_CUSTOM_OPTS'; - let configValues = '-Pprod,heroku -DskipTests'; - if (application.buildToolGradle) { - buildpack = 'heroku/gradle'; - configName = 'GRADLE_TASK'; - configValues = 'stage -Pprod -PnodeInstall'; - } - - this.log.log(chalk.bold('\nConfiguring Heroku')); - const { stdout: configData } = await this.spawnHeroku(['config:get', configName, '--app', this.herokuAppName]); - if (!configData) { - await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); - } - - const { stdout: buildpackData } = await this.spawnHeroku(['buildpacks', '--app', this.herokuAppName]); - if (!buildpackData.includes(buildpack)) { - await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); - } - - this.log.log(chalk.bold('\nDeploying application...')); - - await git.push('heroku', 'HEAD:main'); - - this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); - this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); - this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); - } catch (err) { - this.log.error(err); - } - } else { - this.log.log(chalk.bold('\nDeploying application')); - let jarFileWildcard = 'target/*.jar'; - if (application.buildToolGradle) { - jarFileWildcard = 'build/libs/*.jar'; - } - - const files = glob.sync(jarFileWildcard, {}); - const jarFile = files[0]; - - this.log.log( - chalk.bold( - `\nUploading your application code.\nThis may take ${chalk.cyan('several minutes')} depending on your connection speed...`, - ), - ); - try { - await this.spawnHeroku(['deploy:jar', jarFile, '--app', this.herokuAppName], { stdio: 'pipe' }); - await this.spawnHerokuCommand('buildpacks:set heroku/jvm', { stdio: 'pipe' }); - this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); - this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); - this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); - } catch (err) { - this.log.error(err); - } - } - }, - }); - } - - get [BaseGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * TODO drop when dropped from gae, azure-spring-cloud and heroku generators - * @private - * Add a new Maven profile. - * - * @param {string} profileId - profile ID - * @param {string} other - explicit other thing: build, dependencies... - */ - addMavenProfile(profileId, other) { - createPomStorage(this).addProfile({ id: profileId, content: other }); - } - - /** - * @param {string} command - * @param {import('execa').Options} opt - * @returns {ReturnType} - */ - spawnHerokuCommand(command, opt) { - opt = { stdio: 'pipe', reject: false, ...opt }; - const { verboseInfo, ...spawnOptions } = opt; - const child = this.spawnCommand(`heroku ${command}`, spawnOptions); - if (opt.stdio !== 'pipe' || verboseInfo === false) { - return child; - } - return this.printChildOutput(child); - } - - /** - * @param {string[]} args - * @param {import('execa').Options} opt - * @returns {ReturnType} - */ - spawnHeroku(args, opt) { - opt = { stdio: 'pipe', reject: false, ...opt }; - const { verboseInfo, ...spawnOptions } = opt; - const child = this.spawn('heroku', args, spawnOptions); - if (spawnOptions.stdio !== 'pipe' || verboseInfo === false) { - return child; - } - return this.printChildOutput(child); - } - - /** - * @template {{stdout: any; stderr: any}} T - * @param {T} child - * @param {(chunk: any) => void} child - * @returns {T} - */ - printChildOutput(child, log = data => this.log.verboseInfo(data)) { - const { stdout, stderr } = child; - stdout.on('data', data => { - data.toString().split(/\r?\n/).filter(Boolean).forEach(log); - }); - stderr.on('data', data => { - data.toString().split(/\r?\n/).filter(Boolean).forEach(log); - }); - return child; - } - - checkAddOnReturn({ addOn, stdout, stderr }) { - if (stdout) { - this.log.ok(`Created ${addOn.valueOf()} add-on`); - this.log.ok(stdout); - } else if (stderr) { - const verifyAccountUrl = 'https://heroku.com/verify'; - if (stderr.includes(verifyAccountUrl)) { - this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`); - throw new Error(stderr); - } else { - this.log.verboseInfo(`No new ${addOn.valueOf()} add-on created`); - } - } - } -} diff --git a/generators/heroku/generator.spec.js b/generators/heroku/generator.spec.js new file mode 100644 index 000000000000..e0a8e739c014 --- /dev/null +++ b/generators/heroku/generator.spec.js @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); +}); diff --git a/generators/heroku/generator.spec.mjs b/generators/heroku/generator.spec.mjs deleted file mode 100644 index 8569ef76601a..000000000000 --- a/generators/heroku/generator.spec.mjs +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); -}); diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts deleted file mode 100644 index a9fe1e688928..000000000000 --- a/generators/heroku/heroku.spec.mts +++ /dev/null @@ -1,271 +0,0 @@ -import sinon, { SinonStub } from 'sinon'; -import { expect } from 'esmocha'; - -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import { GENERATOR_HEROKU } from '../generator-list.mjs'; - -const expectedFiles = { - monolith: ['Procfile', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`], -}; - -const createSpawnCommandReturn = (resolvedValue?, data?) => - Object.assign( - Promise.resolve({ - exitCode: 0, - stdout: '', - stderr: '', - ...resolvedValue, - }), - { - ...data, - stdout: { on: () => {} }, - stderr: { on: () => {} }, - }, - ); - -describe('generator - Heroku', () => { - const herokuAppName = 'jhipster-test'; - let stub: SinonStub; - - beforeEach(() => { - stub = sinon.stub(); - // Add catch all - stub.withArgs('spawnCommand').returns(createSpawnCommandReturn()); - stub.withArgs('spawn').returns(createSpawnCommandReturn()); - - stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); - }); - afterEach(() => { - stub.resetHistory(); - }); - - describe('microservice application', () => { - describe('with JAR deployment', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig({ applicationType: 'microservice' }) - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'us', - herokuDeployType: 'jar', - herokuJHipsterRegistryApp: 'sushi', - herokuJHipsterRegistryUsername: 'admin', - herokuJHipsterRegistryPassword: 'changeme', - herokuJavaVersion: '17', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected files', () => { - runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); - }); - }); - }); - - describe('monolith application', () => { - describe('with an unavailable app name', () => { - const autogeneratedAppName = 'random-app-name'; - beforeEach(async () => { - stub - .withArgs('spawn', 'heroku', sinon.match(['create', herokuAppName])) - .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); - stub - .withArgs('spawn', 'heroku', sinon.match(['create'])) - .returns(createSpawnCommandReturn({ stdout: `https://git.heroku.com/${autogeneratedAppName}.git` })); - - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig() - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'us', - herokuDeployType: 'jar', - herokuForceName: 'No', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { herokuAppName: autogeneratedAppName } }); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('with Git deployment', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig() - .withAnswers({ - herokuAppName, - herokuRegion: 'us', - herokuDeployType: 'git', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "git"'); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('in the US', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig() - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'us', - herokuDeployType: 'jar', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); - runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('in the EU', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig() - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'eu', - herokuDeployType: 'jar', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('with PostgreSQL', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig() - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'eu', - herokuDeployType: 'jar', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('with existing app', () => { - const existingHerokuAppName = 'jhipster-existing'; - beforeEach(async () => { - stub - .withArgs('spawn', 'heroku', sinon.match(['apps:info', '--json', existingHerokuAppName])) - .returns(createSpawnCommandReturn({ stdout: `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}` })); - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' }) - .withOptions({ skipBuild: true }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${existingHerokuAppName}"`); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - - describe('with elasticsearch', () => { - beforeEach(async () => { - await helpers - .createJHipster(GENERATOR_HEROKU) - .withJHipsterConfig({ searchEngine: 'elasticsearch' }) - .withOptions({ skipBuild: true }) - .withAnswers({ - herokuAppName, - herokuRegion: 'us', - herokuDeployType: 'jar', - herokuJavaVersion: '11', - }) - .withSpawnMock(stub) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected monolith files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); - runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); - }); - it('calls should match snapshot', () => { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/heroku/heroku.spec.ts b/generators/heroku/heroku.spec.ts new file mode 100644 index 000000000000..00038649c7a3 --- /dev/null +++ b/generators/heroku/heroku.spec.ts @@ -0,0 +1,271 @@ +import sinon, { SinonStub } from 'sinon'; +import { expect } from 'esmocha'; + +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { defaultHelpers as helpers, runResult } from '../../test/support/index.js'; +import { GENERATOR_HEROKU } from '../generator-list.js'; + +const expectedFiles = { + monolith: ['Procfile', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`], +}; + +const createSpawnCommandReturn = (resolvedValue?, data?) => + Object.assign( + Promise.resolve({ + exitCode: 0, + stdout: '', + stderr: '', + ...resolvedValue, + }), + { + ...data, + stdout: { on: () => {} }, + stderr: { on: () => {} }, + }, + ); + +describe('generator - Heroku', () => { + const herokuAppName = 'jhipster-test'; + let stub: SinonStub; + + beforeEach(() => { + stub = sinon.stub(); + // Add catch all + stub.withArgs('spawnCommand').returns(createSpawnCommandReturn()); + stub.withArgs('spawn').returns(createSpawnCommandReturn()); + + stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); + }); + afterEach(() => { + stub.resetHistory(); + }); + + describe('microservice application', () => { + describe('with JAR deployment', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig({ applicationType: 'microservice' }) + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'us', + herokuDeployType: 'jar', + herokuJHipsterRegistryApp: 'sushi', + herokuJHipsterRegistryUsername: 'admin', + herokuJHipsterRegistryPassword: 'changeme', + herokuJavaVersion: '17', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected files', () => { + runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); + }); + }); + }); + + describe('monolith application', () => { + describe('with an unavailable app name', () => { + const autogeneratedAppName = 'random-app-name'; + beforeEach(async () => { + stub + .withArgs('spawn', 'heroku', sinon.match(['create', herokuAppName])) + .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); + stub + .withArgs('spawn', 'heroku', sinon.match(['create'])) + .returns(createSpawnCommandReturn({ stdout: `https://git.heroku.com/${autogeneratedAppName}.git` })); + + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig() + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'us', + herokuDeployType: 'jar', + herokuForceName: 'No', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { herokuAppName: autogeneratedAppName } }); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('with Git deployment', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig() + .withAnswers({ + herokuAppName, + herokuRegion: 'us', + herokuDeployType: 'git', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "git"'); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('in the US', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig() + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'us', + herokuDeployType: 'jar', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); + runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('in the EU', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig() + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'eu', + herokuDeployType: 'jar', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('with PostgreSQL', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig() + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'eu', + herokuDeployType: 'jar', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('with existing app', () => { + const existingHerokuAppName = 'jhipster-existing'; + beforeEach(async () => { + stub + .withArgs('spawn', 'heroku', sinon.match(['apps:info', '--json', existingHerokuAppName])) + .returns(createSpawnCommandReturn({ stdout: `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}` })); + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' }) + .withOptions({ skipBuild: true }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${existingHerokuAppName}"`); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + + describe('with elasticsearch', () => { + beforeEach(async () => { + await helpers + .createJHipster(GENERATOR_HEROKU) + .withJHipsterConfig({ searchEngine: 'elasticsearch' }) + .withOptions({ skipBuild: true }) + .withAnswers({ + herokuAppName, + herokuRegion: 'us', + herokuDeployType: 'jar', + herokuJavaVersion: '11', + }) + .withSpawnMock(stub) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected monolith files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"'); + runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); + }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/heroku/index.mts b/generators/heroku/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/heroku/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/heroku/index.ts b/generators/heroku/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/heroku/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/heroku/templates.mjs b/generators/heroku/templates.js similarity index 100% rename from generators/heroku/templates.mjs rename to generators/heroku/templates.js diff --git a/generators/index.ts b/generators/index.ts index deee5cad8466..967b7d6cc808 100644 --- a/generators/index.ts +++ b/generators/index.ts @@ -21,10 +21,10 @@ export { SERVER_TEST_RES_DIR as TEMPLATES_TEST_RESOURCES_DIR, CLIENT_MAIN_SRC_DIR as TEMPLATES_WEBAPP_SOURCES_DIR, CLIENT_TEST_SRC_DIR as TEMPLATES_JAVASCRIPT_TEST_DIR, -} from './generator-constants.mjs'; +} from './generator-constants.js'; -export type { JHipsterCommandDefinition } from './base/api.mjs'; +export type { JHipsterCommandDefinition } from './base/api.js'; -export { default as GeneratorBase } from './base/index.mjs'; -export { default as GeneratorBaseCore } from './base-core/index.mjs'; -export { default as GeneratorBaseApplication } from './base-application/index.mjs'; +export { default as GeneratorBase } from './base/index.js'; +export { default as GeneratorBaseCore } from './base-core/index.js'; +export { default as GeneratorBaseApplication } from './base-application/index.js'; diff --git a/generators/info/generator.mts b/generators/info/generator.mts deleted file mode 100644 index 131cce89441b..000000000000 --- a/generators/info/generator.mts +++ /dev/null @@ -1,149 +0,0 @@ -// We use console.log() in this generator because we want to print on stdout not on -// stderr unlike yeoman's log() so that user can easily redirect output to a file. -/* eslint-disable no-console */ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import type { ExecaReturnValue } from 'execa'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import JSONToJDLEntityConverter from '../../jdl/converters/json-to-jdl-entity-converter.js'; -import JSONToJDLOptionConverter from '../../jdl/converters/json-to-jdl-option-converter.js'; -import type { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from '../base/api.mjs'; -import { YO_RC_FILE } from '../generator-constants.mjs'; -import { replaceSensitiveConfig } from './support/utils.mjs'; - -const isInfoCommand = commandName => commandName === 'info' || undefined; - -export default class InfoGenerator extends BaseApplicationGenerator { - constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { - super(args, options, { - jhipsterBootstrap: false, - storeJHipsterVersion: false, - customInstallTask: isInfoCommand(options.commandName), - customCommitTask: isInfoCommand(options.commandName), - ...features, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.asInitializingTaskGroup({ - sayHello() { - this.log.log(chalk.white('Welcome to the JHipster Information Sub-Generator\n')); - }, - - async checkJHipster() { - const { stdout } = await this.spawnCommand('npm list generator-jhipster', { stdio: 'pipe', reject: false }); - console.log(`\n\`\`\`\n${stdout}\`\`\`\n`); - }, - - displayConfiguration() { - // Omit sensitive information. - const yoRc = this.readDestinationJSON(YO_RC_FILE); - if (yoRc) { - const result = JSON.stringify(replaceSensitiveConfig(yoRc), null, 2); - console.log(`\n##### **JHipster configuration, a \`${YO_RC_FILE}\` file generated in the root folder**\n`); - console.log(`\n
    \n${YO_RC_FILE} file\n
    \n${result}\n
    \n
    \n`); - } else { - console.log('\n##### **JHipster configuration not found**\n'); - } - - const packages = this.jhipsterConfig.appsFolders ?? this.jhipsterConfig.packages ?? []; - if (packages.length > 0) { - for (const pkg of packages) { - const yoRc = this.readDestinationJSON(this.destinationPath(pkg, YO_RC_FILE)); - if (yoRc) { - const result = JSON.stringify(replaceSensitiveConfig(yoRc), null, 2); - console.log(`\n
    \n${YO_RC_FILE} file for ${pkg}\n
    \n${result}\n
    \n
    \n`); - } else { - console.log(`\n##### **JHipster configuration for ${pkg} not found**\n`); - } - } - } - }, - - async checkJava() { - console.log('\n##### **Environment and Tools**\n'); - await this.checkCommand('java', ['-version'], ({ stderr }) => console.log(stderr)); - console.log(); - }, - - async checkGit() { - await this.checkCommand('git', ['version']); - console.log(); - }, - - async checkNode() { - await this.checkCommand('node', ['-v'], ({ stdout }) => console.log(`node: ${stdout}`)); - }, - - async checkNpm() { - await this.checkCommand('npm', ['-v'], ({ stdout }) => console.log(`npm: ${stdout}`)); - console.log(); - }, - - async checkDocker() { - await this.checkCommand('docker', ['-v']); - }, - - checkApplication() { - if (this.jhipsterConfig.baseName === undefined) { - this.log.warn("Current location doesn't contain a valid JHipster application"); - this.cancelCancellableTasks(); - } - }, - - displayEntities() { - console.log('\n##### **JDL for the Entity configuration(s) `entityName.json` files generated in the `.jhipster` directory**\n'); - const jdl = this.generateJDLFromEntities(); - console.log('
    \nJDL entity definitions\n'); - console.log(`
    \n${jdl?.toString()}\n
    \n
    \n`); - }, - }); - } - - async checkCommand(command: string, args: string[], printInfo = ({ stdout }: ExecaReturnValue) => console.log(stdout)) { - try { - printInfo(await this.spawn(command, args, { stdio: 'pipe' })); - } catch (_error) { - console.log(chalk.red(`'${command}' command could not be found`)); - } - } - - /** - * @returns generated JDL from entities - */ - generateJDLFromEntities() { - let jdlObject; - const entities = new Map(); - try { - this.getExistingEntities().forEach(entity => { - entities.set(entity.name, entity.definition); - }); - jdlObject = JSONToJDLEntityConverter.convertEntitiesToJDL({ - entities, - }); - JSONToJDLOptionConverter.convertServerOptionsToJDL({ 'generator-jhipster': this.config.getAll() }, jdlObject); - } catch (error) { - this.log.error('Error while parsing entities to JDL', error); - throw new Error('\nError while parsing entities to JDL\n', { cause: error }); - } - return jdlObject; - } -} diff --git a/generators/info/generator.spec.mts b/generators/info/generator.spec.mts deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/info/generator.spec.mts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/info/generator.spec.ts b/generators/info/generator.spec.ts new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/info/generator.spec.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/info/generator.ts b/generators/info/generator.ts new file mode 100644 index 000000000000..1e87f3fcb113 --- /dev/null +++ b/generators/info/generator.ts @@ -0,0 +1,149 @@ +// We use console.log() in this generator because we want to print on stdout not on +// stderr unlike yeoman's log() so that user can easily redirect output to a file. +/* eslint-disable no-console */ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import type { ExecaReturnValue } from 'execa'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import JSONToJDLEntityConverter from '../../jdl/converters/json-to-jdl-entity-converter.js'; +import JSONToJDLOptionConverter from '../../jdl/converters/json-to-jdl-option-converter.js'; +import type { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from '../base/api.js'; +import { YO_RC_FILE } from '../generator-constants.js'; +import { replaceSensitiveConfig } from './support/utils.js'; + +const isInfoCommand = commandName => commandName === 'info' || undefined; + +export default class InfoGenerator extends BaseApplicationGenerator { + constructor(args: string | string[], options: JHipsterGeneratorOptions, features: JHipsterGeneratorFeatures) { + super(args, options, { + jhipsterBootstrap: false, + storeJHipsterVersion: false, + customInstallTask: isInfoCommand(options.commandName), + customCommitTask: isInfoCommand(options.commandName), + ...features, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.asInitializingTaskGroup({ + sayHello() { + this.log.log(chalk.white('Welcome to the JHipster Information Sub-Generator\n')); + }, + + async checkJHipster() { + const { stdout } = await this.spawnCommand('npm list generator-jhipster', { stdio: 'pipe', reject: false }); + console.log(`\n\`\`\`\n${stdout}\`\`\`\n`); + }, + + displayConfiguration() { + // Omit sensitive information. + const yoRc = this.readDestinationJSON(YO_RC_FILE); + if (yoRc) { + const result = JSON.stringify(replaceSensitiveConfig(yoRc), null, 2); + console.log(`\n##### **JHipster configuration, a \`${YO_RC_FILE}\` file generated in the root folder**\n`); + console.log(`\n
    \n${YO_RC_FILE} file\n
    \n${result}\n
    \n
    \n`); + } else { + console.log('\n##### **JHipster configuration not found**\n'); + } + + const packages = this.jhipsterConfig.appsFolders ?? this.jhipsterConfig.packages ?? []; + if (packages.length > 0) { + for (const pkg of packages) { + const yoRc = this.readDestinationJSON(this.destinationPath(pkg, YO_RC_FILE)); + if (yoRc) { + const result = JSON.stringify(replaceSensitiveConfig(yoRc), null, 2); + console.log(`\n
    \n${YO_RC_FILE} file for ${pkg}\n
    \n${result}\n
    \n
    \n`); + } else { + console.log(`\n##### **JHipster configuration for ${pkg} not found**\n`); + } + } + } + }, + + async checkJava() { + console.log('\n##### **Environment and Tools**\n'); + await this.checkCommand('java', ['-version'], ({ stderr }) => console.log(stderr)); + console.log(); + }, + + async checkGit() { + await this.checkCommand('git', ['version']); + console.log(); + }, + + async checkNode() { + await this.checkCommand('node', ['-v'], ({ stdout }) => console.log(`node: ${stdout}`)); + }, + + async checkNpm() { + await this.checkCommand('npm', ['-v'], ({ stdout }) => console.log(`npm: ${stdout}`)); + console.log(); + }, + + async checkDocker() { + await this.checkCommand('docker', ['-v']); + }, + + checkApplication() { + if (this.jhipsterConfig.baseName === undefined) { + this.log.warn("Current location doesn't contain a valid JHipster application"); + this.cancelCancellableTasks(); + } + }, + + displayEntities() { + console.log('\n##### **JDL for the Entity configuration(s) `entityName.json` files generated in the `.jhipster` directory**\n'); + const jdl = this.generateJDLFromEntities(); + console.log('
    \nJDL entity definitions\n'); + console.log(`
    \n${jdl?.toString()}\n
    \n
    \n`); + }, + }); + } + + async checkCommand(command: string, args: string[], printInfo = ({ stdout }: ExecaReturnValue) => console.log(stdout)) { + try { + printInfo(await this.spawn(command, args, { stdio: 'pipe' })); + } catch (_error) { + console.log(chalk.red(`'${command}' command could not be found`)); + } + } + + /** + * @returns generated JDL from entities + */ + generateJDLFromEntities() { + let jdlObject; + const entities = new Map(); + try { + this.getExistingEntities().forEach(entity => { + entities.set(entity.name, entity.definition); + }); + jdlObject = JSONToJDLEntityConverter.convertEntitiesToJDL({ + entities, + }); + JSONToJDLOptionConverter.convertServerOptionsToJDL({ 'generator-jhipster': this.config.getAll() }, jdlObject); + } catch (error) { + this.log.error('Error while parsing entities to JDL', error); + throw new Error('\nError while parsing entities to JDL\n', { cause: error }); + } + return jdlObject; + } +} diff --git a/generators/info/index.mts b/generators/info/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/info/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/info/index.ts b/generators/info/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/info/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/info/support/index.mts b/generators/info/support/index.mts deleted file mode 100644 index 4ff36232b6d6..000000000000 --- a/generators/info/support/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './utils.mjs'; diff --git a/generators/info/support/index.ts b/generators/info/support/index.ts new file mode 100644 index 000000000000..33e9c4e9afc5 --- /dev/null +++ b/generators/info/support/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './utils.js'; diff --git a/generators/info/support/utils.mts b/generators/info/support/utils.mts deleted file mode 100644 index 59cdecbfbbfd..000000000000 --- a/generators/info/support/utils.mts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2013-2023 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 { GENERATOR_JHIPSTER } from '../../generator-constants.mjs'; - -export const replaceSensitiveConfig = (yoRc: any) => ({ - ...yoRc, - [GENERATOR_JHIPSTER]: { ...yoRc[GENERATOR_JHIPSTER], jwtSecretKey: undefined, rememberMeKey: undefined }, -}); diff --git a/generators/info/support/utils.ts b/generators/info/support/utils.ts new file mode 100644 index 000000000000..ed4b34f5ed8b --- /dev/null +++ b/generators/info/support/utils.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2013-2023 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 { GENERATOR_JHIPSTER } from '../../generator-constants.js'; + +export const replaceSensitiveConfig = (yoRc: any) => ({ + ...yoRc, + [GENERATOR_JHIPSTER]: { ...yoRc[GENERATOR_JHIPSTER], jwtSecretKey: undefined, rememberMeKey: undefined }, +}); diff --git a/generators/init/__snapshots__/generator.spec.mts.snap b/generators/init/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/init/__snapshots__/generator.spec.mts.snap rename to generators/init/__snapshots__/generator.spec.ts.snap diff --git a/generators/init/command.mts b/generators/init/command.mts deleted file mode 100644 index eea004cf382f..000000000000 --- a/generators/init/command.mts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013-2023 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 { SKIP_COMMIT_HOOK, SKIP_COMMIT_HOOK_DESCRIPTION } from './constants.mjs'; -import { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_PROJECT_NAME } from '../generator-list.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - [SKIP_COMMIT_HOOK]: { - description: SKIP_COMMIT_HOOK_DESCRIPTION, - type: Boolean, - scope: 'storage', - }, - }, - import: [GENERATOR_PROJECT_NAME], -}; - -export default command; diff --git a/generators/init/command.ts b/generators/init/command.ts new file mode 100644 index 000000000000..492e171aca29 --- /dev/null +++ b/generators/init/command.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2013-2023 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 { SKIP_COMMIT_HOOK, SKIP_COMMIT_HOOK_DESCRIPTION } from './constants.js'; +import { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_PROJECT_NAME } from '../generator-list.js'; + +const command: JHipsterCommandDefinition = { + options: { + [SKIP_COMMIT_HOOK]: { + description: SKIP_COMMIT_HOOK_DESCRIPTION, + type: Boolean, + scope: 'storage', + }, + }, + import: [GENERATOR_PROJECT_NAME], +}; + +export default command; diff --git a/generators/init/config.js b/generators/init/config.js new file mode 100644 index 000000000000..86dd9bc08fc2 --- /dev/null +++ b/generators/init/config.js @@ -0,0 +1,28 @@ +/** + * Copyright 2013-2023 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 { SKIP_COMMIT_HOOK, SKIP_COMMIT_HOOK_DEFAULT_VALUE } from './constants.js'; + +/** Config required at .yo-rc.json */ +export const requiredConfig = {}; + +/** Default config for templates */ +export const defaultConfig = { + ...requiredConfig, + [SKIP_COMMIT_HOOK]: SKIP_COMMIT_HOOK_DEFAULT_VALUE, +}; diff --git a/generators/init/config.mjs b/generators/init/config.mjs deleted file mode 100644 index cdd57538573f..000000000000 --- a/generators/init/config.mjs +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2013-2023 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 { SKIP_COMMIT_HOOK, SKIP_COMMIT_HOOK_DEFAULT_VALUE } from './constants.mjs'; - -/** Config required at .yo-rc.json */ -export const requiredConfig = {}; - -/** Default config for templates */ -export const defaultConfig = { - ...requiredConfig, - [SKIP_COMMIT_HOOK]: SKIP_COMMIT_HOOK_DEFAULT_VALUE, -}; diff --git a/generators/init/constants.mjs b/generators/init/constants.js similarity index 100% rename from generators/init/constants.mjs rename to generators/init/constants.js diff --git a/generators/init/files.mjs b/generators/init/files.js similarity index 100% rename from generators/init/files.mjs rename to generators/init/files.js diff --git a/generators/init/generator.js b/generators/init/generator.js new file mode 100644 index 000000000000..f68f3b48db42 --- /dev/null +++ b/generators/init/generator.js @@ -0,0 +1,142 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_INIT, GENERATOR_GIT, GENERATOR_PROJECT_NAME } from '../generator-list.js'; +import { defaultConfig } from './config.js'; +import { SKIP_COMMIT_HOOK } from './constants.js'; +import { files, commitHooksFiles } from './files.js'; +import command from './command.js'; +import { packageJson } from '../../lib/index.js'; + +/** + * @class + * @extends {BaseApplicationGenerator} + */ +export default class InitGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_INIT); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_PROJECT_NAME); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadOptions() { + this.parseJHipsterOptions(command.options); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get loading() { + return this.asLoadingTaskGroup({ + loadConfig({ application }) { + const config = { ...defaultConfig, ...this.config.getAll() }; + application.applicationNodeEngine = packageJson.engines.node; + application[SKIP_COMMIT_HOOK] = config[SKIP_COMMIT_HOOK]; + }, + loadNodeDependencies({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster(GENERATOR_INIT, 'resources', 'package.json'), + ); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get composing() { + return this.asComposingTaskGroup({ + async compose() { + await this.composeWithJHipster(GENERATOR_GIT); + }, + }); + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanup() { + if (this.isJhipsterVersionLessThan('7.5.1')) { + this.removeFile('.lintstagedrc.js'); + } + }, + async writeFiles({ application }) { + await this.writeFiles({ sections: files, context: application }); + }, + async writeCommitHookFiles({ application }) { + if (application.skipCommitHook) return; + await this.writeFiles({ sections: commitHooksFiles, context: application }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addPrettierDependencies({ application }) { + this.packageJson.merge({ + scripts: { + 'prettier-check': 'prettier --check "{,**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java}"', + 'prettier-format': 'prettier --write "{,**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java}"', + }, + devDependencies: { + prettier: application.nodeDependencies.prettier, + 'prettier-plugin-packagejson': application.nodeDependencies['prettier-plugin-packagejson'], + }, + engines: { + node: application.applicationNodeEngine, + }, + }); + }, + addCommitHookDependencies({ application }) { + if (application.skipCommitHook) return; + this.packageJson.merge({ + scripts: { + prepare: 'husky install', + }, + devDependencies: { + husky: application.nodeDependencies.husky, + 'lint-staged': application.nodeDependencies['lint-staged'], + }, + }); + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/init/generator.mjs b/generators/init/generator.mjs deleted file mode 100644 index 5829d80fca1c..000000000000 --- a/generators/init/generator.mjs +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_INIT, GENERATOR_GIT, GENERATOR_PROJECT_NAME } from '../generator-list.mjs'; -import { defaultConfig } from './config.mjs'; -import { SKIP_COMMIT_HOOK } from './constants.mjs'; -import { files, commitHooksFiles } from './files.mjs'; -import command from './command.mjs'; -import { packageJson } from '../../lib/index.mjs'; - -/** - * @class - * @extends {BaseApplicationGenerator} - */ -export default class InitGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_INIT); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_PROJECT_NAME); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadOptions() { - this.parseJHipsterOptions(command.options); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get loading() { - return this.asLoadingTaskGroup({ - loadConfig({ application }) { - const config = { ...defaultConfig, ...this.config.getAll() }; - application.applicationNodeEngine = packageJson.engines.node; - application[SKIP_COMMIT_HOOK] = config[SKIP_COMMIT_HOOK]; - }, - loadNodeDependencies({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_INIT, 'resources', 'package.json'), - ); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get composing() { - return this.asComposingTaskGroup({ - async compose() { - await this.composeWithJHipster(GENERATOR_GIT); - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanup() { - if (this.isJhipsterVersionLessThan('7.5.1')) { - this.removeFile('.lintstagedrc.js'); - } - }, - async writeFiles({ application }) { - await this.writeFiles({ sections: files, context: application }); - }, - async writeCommitHookFiles({ application }) { - if (application.skipCommitHook) return; - await this.writeFiles({ sections: commitHooksFiles, context: application }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addPrettierDependencies({ application }) { - this.packageJson.merge({ - scripts: { - 'prettier-check': 'prettier --check "{,**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java}"', - 'prettier-format': 'prettier --write "{,**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java}"', - }, - devDependencies: { - prettier: application.nodeDependencies.prettier, - 'prettier-plugin-packagejson': application.nodeDependencies['prettier-plugin-packagejson'], - }, - engines: { - node: application.applicationNodeEngine, - }, - }); - }, - addCommitHookDependencies({ application }) { - if (application.skipCommitHook) return; - this.packageJson.merge({ - scripts: { - prepare: 'husky install', - }, - devDependencies: { - husky: application.nodeDependencies.husky, - 'lint-staged': application.nodeDependencies['lint-staged'], - }, - }); - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/init/generator.spec.mts b/generators/init/generator.spec.mts deleted file mode 100644 index 545364391ec7..000000000000 --- a/generators/init/generator.spec.mts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { basicTests, getCommandHelpOutput, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; -import { defaultConfig, requiredConfig } from './config.mjs'; -import { GENERATOR_JHIPSTER } from '../generator-constants.mjs'; -import { GENERATOR_INIT } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorPath = join(__dirname, 'index.mjs'); - -const contextBuilder = () => helpers.create(generatorPath); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', () => { - expect(GENERATOR_INIT).toBe(generator); - }); - basicTests({ - requiredConfig, - defaultConfig, - customPrompts: {}, - contextBuilder, - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('help', () => { - it('should print expected information', async () => { - expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); - }); - }); - describe('with', () => { - describe('default config', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath); - }); - it('should write files and match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - describe('skipCommitHook option', () => { - let runResult; - const options = { skipCommitHook: true, baseName: 'jhipster' }; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withMockedGenerators(['jhipster:git']) - .withOptions({ ...options }); - }); - it('should write options to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: options }); - }); - it('should not create husky files and match snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/init/generator.spec.ts b/generators/init/generator.spec.ts new file mode 100644 index 000000000000..ef4d3ea139ae --- /dev/null +++ b/generators/init/generator.spec.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { basicTests, getCommandHelpOutput, testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers } from '../../test/support/index.js'; +import { defaultConfig, requiredConfig } from './config.js'; +import { GENERATOR_JHIPSTER } from '../generator-constants.js'; +import { GENERATOR_INIT } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorPath = join(__dirname, 'index.js'); + +const contextBuilder = () => helpers.create(generatorPath); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', () => { + expect(GENERATOR_INIT).toBe(generator); + }); + basicTests({ + requiredConfig, + defaultConfig, + customPrompts: {}, + contextBuilder, + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('help', () => { + it('should print expected information', async () => { + expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); + }); + }); + describe('with', () => { + describe('default config', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath); + }); + it('should write files and match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + describe('skipCommitHook option', () => { + let runResult; + const options = { skipCommitHook: true, baseName: 'jhipster' }; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withMockedGenerators(['jhipster:git']) + .withOptions({ ...options }); + }); + it('should write options to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: options }); + }); + it('should not create husky files and match snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/init/index.mts b/generators/init/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/init/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/init/index.ts b/generators/init/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/init/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/java/__snapshots__/generator.spec.mts.snap b/generators/java/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/java/__snapshots__/generator.spec.mts.snap rename to generators/java/__snapshots__/generator.spec.ts.snap diff --git a/generators/java/cleanup.mts b/generators/java/cleanup.ts similarity index 100% rename from generators/java/cleanup.mts rename to generators/java/cleanup.ts diff --git a/generators/java/command.mts b/generators/java/command.mts deleted file mode 100644 index 24ea40770c1c..000000000000 --- a/generators/java/command.mts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - withGeneratedFlag: { - description: 'Add a GeneratedByJHipster annotation to all generated java classes and interfaces', - type: Boolean, - scope: 'storage', - }, - packageInfoFile: { - description: 'write package-info.java file for every package', - type: Boolean, - default: true, - scope: 'generator', - hide: true, - }, - generateEntities: { - type: Boolean, - default: true, - scope: 'generator', - hide: true, - }, - useJakartaValidation: { - type: Boolean, - default: true, - scope: 'generator', - hide: true, - }, - useJacksonIdentityInfo: { - type: Boolean, - default: false, - scope: 'generator', - hide: true, - }, - generateEnums: { - type: Boolean, - default: true, - scope: 'generator', - hide: true, - }, - }, -}; - -export default command; diff --git a/generators/java/command.ts b/generators/java/command.ts new file mode 100644 index 000000000000..518d39bd3c99 --- /dev/null +++ b/generators/java/command.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + withGeneratedFlag: { + description: 'Add a GeneratedByJHipster annotation to all generated java classes and interfaces', + type: Boolean, + scope: 'storage', + }, + packageInfoFile: { + description: 'write package-info.java file for every package', + type: Boolean, + default: true, + scope: 'generator', + hide: true, + }, + generateEntities: { + type: Boolean, + default: true, + scope: 'generator', + hide: true, + }, + useJakartaValidation: { + type: Boolean, + default: true, + scope: 'generator', + hide: true, + }, + useJacksonIdentityInfo: { + type: Boolean, + default: false, + scope: 'generator', + hide: true, + }, + generateEnums: { + type: Boolean, + default: true, + scope: 'generator', + hide: true, + }, + }, +}; + +export default command; diff --git a/generators/java/entity-files.mts b/generators/java/entity-files.mts deleted file mode 100644 index c92cb5db5817..000000000000 --- a/generators/java/entity-files.mts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.mjs'; -import { javaMainPackageTemplatesBlock, javaTestPackageTemplatesBlock } from './support/index.mjs'; - -export const entityServerFiles: WriteFileSection = { - model: [ - javaMainPackageTemplatesBlock({ - templates: ['_entityPackage_/domain/_persistClass_.java.jhi'], - }), - ], - modelTestFiles: [ - javaTestPackageTemplatesBlock({ - templates: ['_entityPackage_/domain/_persistClass_Test.java', '_entityPackage_/domain/_persistClass_TestSamples.java'], - }), - ], - server: [ - javaMainPackageTemplatesBlock({ - condition: ctx => ctx.useJakartaValidation, - templates: ['_entityPackage_/domain/_persistClass_.java.jhi.jakarta_validation'], - }), - javaMainPackageTemplatesBlock({ - condition: ctx => ctx.useJacksonIdentityInfo, - templates: ['_entityPackage_/domain/_persistClass_.java.jhi.jackson_identity_info'], - }), - ], -}; - -export const enumFiles: WriteFileSection = { - enumFiles: [ - javaMainPackageTemplatesBlock({ - renameTo: (data, filepath) => filepath.replace('_enumName_', data.enumName), - templates: ['_entityPackage_/domain/enumeration/_enumName_.java'], - }), - ], -}; diff --git a/generators/java/entity-files.ts b/generators/java/entity-files.ts new file mode 100644 index 000000000000..6353cece0763 --- /dev/null +++ b/generators/java/entity-files.ts @@ -0,0 +1,52 @@ +/** + * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.js'; +import { javaMainPackageTemplatesBlock, javaTestPackageTemplatesBlock } from './support/index.js'; + +export const entityServerFiles: WriteFileSection = { + model: [ + javaMainPackageTemplatesBlock({ + templates: ['_entityPackage_/domain/_persistClass_.java.jhi'], + }), + ], + modelTestFiles: [ + javaTestPackageTemplatesBlock({ + templates: ['_entityPackage_/domain/_persistClass_Test.java', '_entityPackage_/domain/_persistClass_TestSamples.java'], + }), + ], + server: [ + javaMainPackageTemplatesBlock({ + condition: ctx => ctx.useJakartaValidation, + templates: ['_entityPackage_/domain/_persistClass_.java.jhi.jakarta_validation'], + }), + javaMainPackageTemplatesBlock({ + condition: ctx => ctx.useJacksonIdentityInfo, + templates: ['_entityPackage_/domain/_persistClass_.java.jhi.jackson_identity_info'], + }), + ], +}; + +export const enumFiles: WriteFileSection = { + enumFiles: [ + javaMainPackageTemplatesBlock({ + renameTo: (data, filepath) => filepath.replace('_enumName_', data.enumName), + templates: ['_entityPackage_/domain/enumeration/_enumName_.java'], + }), + ], +}; diff --git a/generators/java/files.mts b/generators/java/files.mts deleted file mode 100644 index 44a83e42a222..000000000000 --- a/generators/java/files.mts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { WriteFileSection } from '../base/api.mjs'; -import { JavaApplication } from './types.mjs'; -import { SERVER_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir } from './support/index.mjs'; - -const files: WriteFileSection = { - baseFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_`, - renameTo: moveToJavaPackageSrcDir, - templates: ['GeneratedByJHipster.java'], - }, - ], -}; - -export default async function writeTask(this: Generator, { application }) { - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/java/files.ts b/generators/java/files.ts new file mode 100644 index 000000000000..aead8f9e93f4 --- /dev/null +++ b/generators/java/files.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { WriteFileSection } from '../base/api.js'; +import { JavaApplication } from './types.js'; +import { SERVER_MAIN_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir } from './support/index.js'; + +const files: WriteFileSection = { + baseFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_`, + renameTo: moveToJavaPackageSrcDir, + templates: ['GeneratedByJHipster.java'], + }, + ], +}; + +export default async function writeTask(this: Generator, { application }) { + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/java/generator.mts b/generators/java/generator.mts deleted file mode 100644 index 56c113e95cce..000000000000 --- a/generators/java/generator.mts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright 2013-2023 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 { isFileStateModified } from 'mem-fs-editor/state'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_JAVA, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; -import { packageInfoTransform, generatedAnnotationTransform, checkJava } from './support/index.mjs'; -import { JavaApplication } from './types.mjs'; -import { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from '../base-application/tasks.mjs'; -import { GenericSourceTypeDefinition } from '../base/tasks.mjs'; -import command from './command.mjs'; -import { JAVA_COMPATIBLE_VERSIONS } from '../generator-constants.mjs'; -import { matchMainJavaFiles } from './support/package-info-transform.mjs'; -import { entityServerFiles, enumFiles } from './entity-files.mjs'; -import { getEnumInfo } from '../base-application/support/index.mjs'; - -export type ApplicationDefinition = GenericApplicationDefinition; -export type GeneratorDefinition = BaseApplicationGeneratorDefinition; - -export default class JavaGenerator extends BaseApplicationGenerator { - packageInfoFile!: boolean; - generateEntities!: boolean; - useJakartaValidation!: boolean; - useJacksonIdentityInfo!: boolean; - generateEnums!: boolean; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_JAVA); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadConfig() { - this.parseJHipsterCommand(command); - }, - - validateJava() { - if (!this.skipChecks) { - this.checkJava(); - } - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); - } - - get default() { - return this.asDefaultTaskGroup({ - generatedAnnotation({ application }) { - if (this.jhipsterConfig.withGeneratedFlag) { - this.queueTransformStream( - { - name: 'adding @GeneratedByJHipster annotations', - filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && file.path.endsWith('.java'), - refresh: false, - }, - generatedAnnotationTransform(application.packageName), - ); - } - }, - generatedPackageInfo({ application }) { - const mainPackageMatch = matchMainJavaFiles(application.srcMainJava); - if (this.packageInfoFile) { - this.queueTransformStream( - { - name: 'adding package-info.java files', - filter: file => - isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && mainPackageMatch.match(file.path), - refresh: true, - }, - packageInfoTransform({ - javaRoots: [this.destinationPath(application.srcMainJava)], - javadocs: { - ...Object.fromEntries(application.packageInfoJavadocs.map(doc => [doc.packageName, doc.documentation])), - [`${application.packageName}`]: 'Application root.', - [`${application.packageName}.config`]: 'Application configuration.', - [`${application.packageName}.domain`]: 'Domain objects.', - [`${application.packageName}.repository`]: 'Repository layer.', - [`${application.packageName}.service`]: 'Service layer.', - [`${application.packageName}.web.rest`]: 'Rest layer.', - }, - }), - ); - } - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - async writeServerFiles({ application, entities }) { - if (!this.generateEntities) return; - - const { useJakartaValidation, useJacksonIdentityInfo } = this; - for (const entity of entities.filter(entity => !entity.skipServer && !entity.builtIn)) { - await this.writeFiles({ - sections: entityServerFiles, - context: { ...application, ...entity, useJakartaValidation, useJacksonIdentityInfo }, - }); - } - }, - - async writeEnumFiles({ application, entities }) { - if (!this.generateEnums) return; - - for (const entity of entities.filter(entity => !entity.skipServer)) { - for (const field of entity.fields.filter(field => field.fieldIsEnum)) { - const enumInfo = { - ...getEnumInfo(field, (entity as any).clientRootFolder), - frontendAppName: (entity as any).frontendAppName, - packageName: application.packageName, - javaPackageSrcDir: application.javaPackageSrcDir, - entityJavaPackageFolder: (entity as any).entityJavaPackageFolder, - entityAbsolutePackage: (entity as any).entityAbsolutePackage || application.packageName, - }; - await this.writeFiles({ - sections: enumFiles, - context: enumInfo, - }); - } - } - }, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - /** - * Check if a supported Java is installed - * - * Blueprints can customize or disable java checks versions by overriding this method. - * @example - * // disable checks - * checkJava() {} - * @examples - * // enforce java lts versions - * checkJava() { - * super.checkJava(['8', '11', '17'], { throwOnError: true }); - * } - */ - checkJava(javaCompatibleVersions = JAVA_COMPATIBLE_VERSIONS, checkResultValidation?) { - this.validateResult(checkJava(javaCompatibleVersions), { throwOnError: false, ...checkResultValidation }); - } -} diff --git a/generators/java/generator.spec.mts b/generators/java/generator.spec.mts deleted file mode 100644 index de7546f46561..000000000000 --- a/generators/java/generator.spec.mts +++ /dev/null @@ -1,142 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { defaultHelpers as helpers, result } from '../../test/support/index.mjs'; - -import { GENERATOR_JAVA } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('with default config', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_JAVA).withJHipsterConfig({}, [ - { - name: 'Foo', - fields: [ - { fieldName: 'name', documentation: 'My Name', fieldType: 'String', fieldValidateRules: ['required'] }, - { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH', fieldTypeDocumentation: 'Enum Doc' }, - ], - }, - { - name: 'Bar', - documentation: 'Custom Bar', - fields: [{ fieldName: 'name2', fieldType: 'String', fieldValidateRules: ['required'] }], - }, - ]); - }); - - it('should match files snapshot', () => { - expect(result.getStateSnapshot()).toMatchSnapshot(); - }); - - it('should generate entities containing jakarta', () => { - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', 'jakarta'); - }); - - it('should generate javadocs', () => { - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', '* A Foo'); - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', '* My Name'); - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Bar.java', '* Custom Bar'); - }); - - it('should generate openapi @Schema', () => { - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Bar.java', '@Schema(description = "Custom Bar")'); - }); - - it('should write enum files', () => { - result.assertFile('src/main/java/com/mycompany/myapp/domain/enumeration/MyEnum.java'); - expect(Object.keys(result.getStateSnapshot('**/enumeration/**')).length).toBe(2); - }); - - it('should generate enum javadoc', () => { - result.assertFileContent('src/main/java/com/mycompany/myapp/domain/enumeration/MyEnum.java', '* Enum Doc'); - }); - - it('should have options defaults set', () => { - expect(result.generator.generateEntities).toBe(true); - expect(result.generator.generateEnums).toBe(true); - expect(result.generator.useJakartaValidation).toBe(true); - }); - }); - - describe('with jakarta and enums disabled', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_JAVA) - .withJHipsterConfig({}, [ - { - name: 'Foo', - fields: [ - { fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }, - { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH' }, - ], - }, - ]) - .withOptions({ useJakartaValidation: false, generateEnums: false }); - }); - - it('should generate entities not containing jakarta', () => { - result.assertNoFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', 'jakarta'); - }); - - it('should not write enum files', () => { - expect(Object.keys(result.getStateSnapshot('**/enumeration/**')).length).toBe(0); - }); - }); - - describe('with entities disabled', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_JAVA) - .withJHipsterConfig({}, [ - { - name: 'Foo', - fields: [ - { fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }, - { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH' }, - ], - }, - ]) - .withOptions({ generateEntities: false }); - }); - - it('should not contain jakarta', () => { - result.assertNoFile('src/main/java/com/mycompany/myapp/domain/Foo.java'); - }); - }); - - describe('with custom properties values', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_JAVA) - .withJHipsterConfig({}) - .onGenerator(generator => { - generator.generateEntities = false; - generator.generateEnums = false; - generator.useJakartaValidation = false; - }); - }); - - it('should not override custom values', () => { - expect(result.generator.generateEntities).toBe(false); - expect(result.generator.generateEnums).toBe(false); - expect(result.generator.useJakartaValidation).toBe(false); - }); - }); -}); diff --git a/generators/java/generator.spec.ts b/generators/java/generator.spec.ts new file mode 100644 index 000000000000..301aeb792326 --- /dev/null +++ b/generators/java/generator.spec.ts @@ -0,0 +1,142 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { defaultHelpers as helpers, result } from '../../test/support/index.js'; + +import { GENERATOR_JAVA } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('with default config', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_JAVA).withJHipsterConfig({}, [ + { + name: 'Foo', + fields: [ + { fieldName: 'name', documentation: 'My Name', fieldType: 'String', fieldValidateRules: ['required'] }, + { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH', fieldTypeDocumentation: 'Enum Doc' }, + ], + }, + { + name: 'Bar', + documentation: 'Custom Bar', + fields: [{ fieldName: 'name2', fieldType: 'String', fieldValidateRules: ['required'] }], + }, + ]); + }); + + it('should match files snapshot', () => { + expect(result.getStateSnapshot()).toMatchSnapshot(); + }); + + it('should generate entities containing jakarta', () => { + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', 'jakarta'); + }); + + it('should generate javadocs', () => { + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', '* A Foo'); + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', '* My Name'); + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Bar.java', '* Custom Bar'); + }); + + it('should generate openapi @Schema', () => { + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/Bar.java', '@Schema(description = "Custom Bar")'); + }); + + it('should write enum files', () => { + result.assertFile('src/main/java/com/mycompany/myapp/domain/enumeration/MyEnum.java'); + expect(Object.keys(result.getStateSnapshot('**/enumeration/**')).length).toBe(2); + }); + + it('should generate enum javadoc', () => { + result.assertFileContent('src/main/java/com/mycompany/myapp/domain/enumeration/MyEnum.java', '* Enum Doc'); + }); + + it('should have options defaults set', () => { + expect(result.generator.generateEntities).toBe(true); + expect(result.generator.generateEnums).toBe(true); + expect(result.generator.useJakartaValidation).toBe(true); + }); + }); + + describe('with jakarta and enums disabled', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_JAVA) + .withJHipsterConfig({}, [ + { + name: 'Foo', + fields: [ + { fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }, + { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH' }, + ], + }, + ]) + .withOptions({ useJakartaValidation: false, generateEnums: false }); + }); + + it('should generate entities not containing jakarta', () => { + result.assertNoFileContent('src/main/java/com/mycompany/myapp/domain/Foo.java', 'jakarta'); + }); + + it('should not write enum files', () => { + expect(Object.keys(result.getStateSnapshot('**/enumeration/**')).length).toBe(0); + }); + }); + + describe('with entities disabled', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_JAVA) + .withJHipsterConfig({}, [ + { + name: 'Foo', + fields: [ + { fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }, + { fieldName: 'myEnum', fieldType: 'MyEnum', fieldValues: 'FRENCH,ENGLISH' }, + ], + }, + ]) + .withOptions({ generateEntities: false }); + }); + + it('should not contain jakarta', () => { + result.assertNoFile('src/main/java/com/mycompany/myapp/domain/Foo.java'); + }); + }); + + describe('with custom properties values', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_JAVA) + .withJHipsterConfig({}) + .onGenerator(generator => { + generator.generateEntities = false; + generator.generateEnums = false; + generator.useJakartaValidation = false; + }); + }); + + it('should not override custom values', () => { + expect(result.generator.generateEntities).toBe(false); + expect(result.generator.generateEnums).toBe(false); + expect(result.generator.useJakartaValidation).toBe(false); + }); + }); +}); diff --git a/generators/java/generator.ts b/generators/java/generator.ts new file mode 100644 index 000000000000..4635f1f86ae3 --- /dev/null +++ b/generators/java/generator.ts @@ -0,0 +1,187 @@ +/** + * Copyright 2013-2023 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 { isFileStateModified } from 'mem-fs-editor/state'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_JAVA, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; +import { packageInfoTransform, generatedAnnotationTransform, checkJava } from './support/index.js'; +import { JavaApplication } from './types.js'; +import { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from '../base-application/tasks.js'; +import { GenericSourceTypeDefinition } from '../base/tasks.js'; +import command from './command.js'; +import { JAVA_COMPATIBLE_VERSIONS } from '../generator-constants.js'; +import { matchMainJavaFiles } from './support/package-info-transform.js'; +import { entityServerFiles, enumFiles } from './entity-files.js'; +import { getEnumInfo } from '../base-application/support/index.js'; + +export type ApplicationDefinition = GenericApplicationDefinition; +export type GeneratorDefinition = BaseApplicationGeneratorDefinition; + +export default class JavaGenerator extends BaseApplicationGenerator { + packageInfoFile!: boolean; + generateEntities!: boolean; + useJakartaValidation!: boolean; + useJacksonIdentityInfo!: boolean; + generateEnums!: boolean; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_JAVA); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadConfig() { + this.parseJHipsterCommand(command); + }, + + validateJava() { + if (!this.skipChecks) { + this.checkJava(); + } + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); + } + + get default() { + return this.asDefaultTaskGroup({ + generatedAnnotation({ application }) { + if (this.jhipsterConfig.withGeneratedFlag) { + this.queueTransformStream( + { + name: 'adding @GeneratedByJHipster annotations', + filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && file.path.endsWith('.java'), + refresh: false, + }, + generatedAnnotationTransform(application.packageName), + ); + } + }, + generatedPackageInfo({ application }) { + const mainPackageMatch = matchMainJavaFiles(application.srcMainJava); + if (this.packageInfoFile) { + this.queueTransformStream( + { + name: 'adding package-info.java files', + filter: file => + isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && mainPackageMatch.match(file.path), + refresh: true, + }, + packageInfoTransform({ + javaRoots: [this.destinationPath(application.srcMainJava)], + javadocs: { + ...Object.fromEntries(application.packageInfoJavadocs.map(doc => [doc.packageName, doc.documentation])), + [`${application.packageName}`]: 'Application root.', + [`${application.packageName}.config`]: 'Application configuration.', + [`${application.packageName}.domain`]: 'Domain objects.', + [`${application.packageName}.repository`]: 'Repository layer.', + [`${application.packageName}.service`]: 'Service layer.', + [`${application.packageName}.web.rest`]: 'Rest layer.', + }, + }), + ); + } + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + async writeServerFiles({ application, entities }) { + if (!this.generateEntities) return; + + const { useJakartaValidation, useJacksonIdentityInfo } = this; + for (const entity of entities.filter(entity => !entity.skipServer && !entity.builtIn)) { + await this.writeFiles({ + sections: entityServerFiles, + context: { ...application, ...entity, useJakartaValidation, useJacksonIdentityInfo }, + }); + } + }, + + async writeEnumFiles({ application, entities }) { + if (!this.generateEnums) return; + + for (const entity of entities.filter(entity => !entity.skipServer)) { + for (const field of entity.fields.filter(field => field.fieldIsEnum)) { + const enumInfo = { + ...getEnumInfo(field, (entity as any).clientRootFolder), + frontendAppName: (entity as any).frontendAppName, + packageName: application.packageName, + javaPackageSrcDir: application.javaPackageSrcDir, + entityJavaPackageFolder: (entity as any).entityJavaPackageFolder, + entityAbsolutePackage: (entity as any).entityAbsolutePackage || application.packageName, + }; + await this.writeFiles({ + sections: enumFiles, + context: enumInfo, + }); + } + } + }, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + /** + * Check if a supported Java is installed + * + * Blueprints can customize or disable java checks versions by overriding this method. + * @example + * // disable checks + * checkJava() {} + * @examples + * // enforce java lts versions + * checkJava() { + * super.checkJava(['8', '11', '17'], { throwOnError: true }); + * } + */ + checkJava(javaCompatibleVersions = JAVA_COMPATIBLE_VERSIONS, checkResultValidation?) { + this.validateResult(checkJava(javaCompatibleVersions), { throwOnError: false, ...checkResultValidation }); + } +} diff --git a/generators/java/index.mts b/generators/java/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/java/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/java/index.ts b/generators/java/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/java/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/java/support/add-java-annotation.mts b/generators/java/support/add-java-annotation.ts similarity index 100% rename from generators/java/support/add-java-annotation.mts rename to generators/java/support/add-java-annotation.ts diff --git a/generators/java/support/checks/check-java.mts b/generators/java/support/checks/check-java.mts deleted file mode 100644 index 67c8650b1860..000000000000 --- a/generators/java/support/checks/check-java.mts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import { execaCommandSync } from 'execa'; -import { type ValidationResult } from '../../../base/api.mjs'; - -/** - * Check if installed java version is compatible - * @param javaCompatibleVersions - */ -export default (javaCompatibleVersions: string[]): ValidationResult & { javaVersion?: string } => { - try { - const { exitCode, stderr } = execaCommandSync('java -version', { stdio: 'pipe' }); - if (exitCode === 0 && stderr) { - const matchResult = stderr.match(/(?:java|openjdk)(?: version)? "?(.*)"? /s); - if (matchResult && matchResult.length > 0) { - const javaVersion = matchResult[1]; - const debug = `Detected java version ${javaVersion}`; - if (javaCompatibleVersions && !javaVersion.match(new RegExp(`(${javaCompatibleVersions.map(ver => `^${ver}`).join('|')})`))) { - const [latest, ...others] = javaCompatibleVersions.concat().reverse(); - const humanizedVersions = `${others.reverse().join(', ')} or ${latest}`; - const warning = `Java ${humanizedVersions} are not found on your computer. Your Java version is: ${chalk.yellow(javaVersion)}`; - return { debug, warning, javaVersion }; - } - return { debug, javaVersion }; - } - } - return { error: `Error parsing Java version. Output: ${stderr}` }; - } catch (error) { - return { error: `Java was not found on your computer (${(error as any).message}).` }; - } -}; diff --git a/generators/java/support/checks/check-java.spec.mts b/generators/java/support/checks/check-java.spec.mts deleted file mode 100644 index 0c02960d0b5b..000000000000 --- a/generators/java/support/checks/check-java.spec.mts +++ /dev/null @@ -1,71 +0,0 @@ -import { expect, mock, resetAllMocks } from 'esmocha'; -import { ExecaSyncReturnValue } from 'execa'; - -const execa = await mock('execa'); - -const baseResult: ExecaSyncReturnValue = { - command: 'java', - escapedCommand: 'java', - exitCode: 0, - stdout: '', - stderr: '', - failed: false, - timedOut: false, - killed: false, -}; - -describe('generator - server - checkJava', () => { - afterEach(() => { - resetAllMocks(); - }); - - describe('with valid java --version output', () => { - const stderr = 'openjdk 17.0.1 2021-10-19'; - let result; - - before(async () => { - execa.execaCommandSync.mockReturnValue({ ...baseResult, stderr } as any); - const { default: checkJava } = await import('./check-java.mjs'); - result = checkJava(); - }); - - it('should return info and javaVersion', async () => { - expect(result).toMatchObject({ - debug: 'Detected java version 17.0.1', - javaVersion: '17.0.1', - }); - }); - }); - - describe('with invalid java --version output', () => { - const stderr = 'foo'; - const exitCode = 1; - let result; - - before(async () => { - execa.execaCommandSync.mockReturnValue({ ...baseResult, exitCode, stderr } as any); - const { default: checkJava } = await import('./check-java.mjs'); - result = checkJava(); - }); - - it('should return error', async () => { - expect(result.error).toBe('Error parsing Java version. Output: foo'); - }); - }); - - describe('on exception', () => { - let result; - - before(async () => { - execa.execaCommandSync.mockImplementation(() => { - throw new Error('foo'); - }); - const { default: checkJava } = await import('./check-java.mjs'); - result = checkJava(); - }); - - it('should return error', async () => { - expect(result.error).toBe('Java was not found on your computer (foo).'); - }); - }); -}); diff --git a/generators/java/support/checks/check-java.spec.ts b/generators/java/support/checks/check-java.spec.ts new file mode 100644 index 000000000000..aba8e505ce57 --- /dev/null +++ b/generators/java/support/checks/check-java.spec.ts @@ -0,0 +1,71 @@ +import { expect, mock, resetAllMocks } from 'esmocha'; +import { ExecaSyncReturnValue } from 'execa'; + +const execa = await mock('execa'); + +const baseResult: ExecaSyncReturnValue = { + command: 'java', + escapedCommand: 'java', + exitCode: 0, + stdout: '', + stderr: '', + failed: false, + timedOut: false, + killed: false, +}; + +describe('generator - server - checkJava', () => { + afterEach(() => { + resetAllMocks(); + }); + + describe('with valid java --version output', () => { + const stderr = 'openjdk 17.0.1 2021-10-19'; + let result; + + before(async () => { + execa.execaCommandSync.mockReturnValue({ ...baseResult, stderr } as any); + const { default: checkJava } = await import('./check-java.js'); + result = checkJava(); + }); + + it('should return info and javaVersion', async () => { + expect(result).toMatchObject({ + debug: 'Detected java version 17.0.1', + javaVersion: '17.0.1', + }); + }); + }); + + describe('with invalid java --version output', () => { + const stderr = 'foo'; + const exitCode = 1; + let result; + + before(async () => { + execa.execaCommandSync.mockReturnValue({ ...baseResult, exitCode, stderr } as any); + const { default: checkJava } = await import('./check-java.js'); + result = checkJava(); + }); + + it('should return error', async () => { + expect(result.error).toBe('Error parsing Java version. Output: foo'); + }); + }); + + describe('on exception', () => { + let result; + + before(async () => { + execa.execaCommandSync.mockImplementation(() => { + throw new Error('foo'); + }); + const { default: checkJava } = await import('./check-java.js'); + result = checkJava(); + }); + + it('should return error', async () => { + expect(result.error).toBe('Java was not found on your computer (foo).'); + }); + }); +}); diff --git a/generators/java/support/checks/check-java.ts b/generators/java/support/checks/check-java.ts new file mode 100644 index 000000000000..5743291ec3ec --- /dev/null +++ b/generators/java/support/checks/check-java.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import { execaCommandSync } from 'execa'; +import { type ValidationResult } from '../../../base/api.js'; + +/** + * Check if installed java version is compatible + * @param javaCompatibleVersions + */ +export default (javaCompatibleVersions: string[]): ValidationResult & { javaVersion?: string } => { + try { + const { exitCode, stderr } = execaCommandSync('java -version', { stdio: 'pipe' }); + if (exitCode === 0 && stderr) { + const matchResult = stderr.match(/(?:java|openjdk)(?: version)? "?(.*)"? /s); + if (matchResult && matchResult.length > 0) { + const javaVersion = matchResult[1]; + const debug = `Detected java version ${javaVersion}`; + if (javaCompatibleVersions && !javaVersion.match(new RegExp(`(${javaCompatibleVersions.map(ver => `^${ver}`).join('|')})`))) { + const [latest, ...others] = javaCompatibleVersions.concat().reverse(); + const humanizedVersions = `${others.reverse().join(', ')} or ${latest}`; + const warning = `Java ${humanizedVersions} are not found on your computer. Your Java version is: ${chalk.yellow(javaVersion)}`; + return { debug, warning, javaVersion }; + } + return { debug, javaVersion }; + } + } + return { error: `Error parsing Java version. Output: ${stderr}` }; + } catch (error) { + return { error: `Java was not found on your computer (${(error as any).message}).` }; + } +}; diff --git a/generators/java/support/checks/index.mts b/generators/java/support/checks/index.mts deleted file mode 100644 index d6833b851939..000000000000 --- a/generators/java/support/checks/index.mts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as checkJava } from './check-java.mjs'; diff --git a/generators/java/support/checks/index.ts b/generators/java/support/checks/index.ts new file mode 100644 index 000000000000..32d9c4cbc8d3 --- /dev/null +++ b/generators/java/support/checks/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as checkJava } from './check-java.js'; diff --git a/generators/java/support/files.mts b/generators/java/support/files.mts deleted file mode 100644 index 22633c17d05b..000000000000 --- a/generators/java/support/files.mts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright 2013-2023 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 { WriteFileBlock } from '../../base/api.mjs'; -import type CoreGenerator from '../../base-core/generator.mjs'; -import { SERVER_TEST_SRC_DIR, SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_RES_DIR } from '../../generator-constants.mjs'; - -export const replaceEntityFilePathVariables = (data: any, filePath: string) => { - filePath = filePath - ?.replace(/_package_/, data.packageFolder) - ?.replace(/_entityPackage_/, data.entityJavaPackageFolder) - ?.replace(/_mainClass_/, data.mainClass) - ?.replace(/_persistClass_/, data.persistClass) - ?.replace(/_entityClass_/, data.entityClass) - ?.replace(/_dtoClass_/, data.dtoClass); - return filePath?.includes('.jhi.') ? filePath : filePath?.replace(/_\w*/, ''); -}; - -/** - * Move the template to `javaPackageSrcDir` (defaults to`src/main/java/${packageFolder}/${filePath}`). - * Removes trailing specifiers. - */ -export const moveToJavaPackageSrcDir = (data: any, filePath: string) => - `${data.javaPackageSrcDir}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -/** - * Move the template to `javaPackageTestDir` (defaults to`src/main/java/${packageFolder}/${filePath}`). - * Removes trailing specifiers. - */ -export const moveToJavaPackageTestDir = (data: any, filePath: string) => - `${data.javaPackageTestDir}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -export const moveToJavaEntityPackageSrcDir = (data: any, filePath: string) => - `${data.srcMainJava}${data.entityAbsoluteFolder}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -export const moveToJavaEntityPackageTestDir = (data: any, filePath: string) => - `${data.srcTestJava}${data.entityAbsoluteFolder}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -export const moveToSrcMainResourcesDir = (data: any, filePath: string) => - `${data.srcMainResources}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -export const moveToSrcTestResourcesDir = (data: any, filePath: string) => - `${data.srcTestResources}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; - -type RelativeWriteFileBlock = WriteFileBlock & { relativePath?: string }; - -export function javaMainPackageTemplatesBlock(blockOrRelativePath?: string): Pick; -export function javaMainPackageTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function javaMainPackageTemplatesBlock( - blockOrRelativePath: string | RelativeWriteFileBlock = '', -): WriteFileBlock | Pick { - return javaBlock({ - srcPath: `${SERVER_MAIN_SRC_DIR}_package_/`, - destProperty: 'javaPackageSrcDir', - blockOrRelativePath, - }); -} - -export function javaMainResourceTemplatesBlock(blockOrRelativePath?: string): Pick; -export function javaMainResourceTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function javaMainResourceTemplatesBlock( - blockOrRelativePath: string | RelativeWriteFileBlock = '', -): WriteFileBlock | Pick { - return javaBlock({ - srcPath: SERVER_MAIN_RES_DIR, - destProperty: 'srcMainResources', - blockOrRelativePath, - }); -} - -export function javaTestResourceTemplatesBlock(blockOrRelativePath?: string): Pick; -export function javaTestResourceTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function javaTestResourceTemplatesBlock( - blockOrRelativePath: string | RelativeWriteFileBlock = '', -): WriteFileBlock | Pick { - return javaBlock({ - srcPath: SERVER_TEST_RES_DIR, - destProperty: 'srcTestResources', - blockOrRelativePath, - }); -} - -export function javaTestPackageTemplatesBlock(blockOrRelativePath?: string): Pick; -export function javaTestPackageTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; -export function javaTestPackageTemplatesBlock( - blockOrRelativePath: string | RelativeWriteFileBlock = '', -): WriteFileBlock | Pick { - return javaBlock({ - srcPath: `${SERVER_TEST_SRC_DIR}_package_/`, - destProperty: 'javaPackageTestDir', - blockOrRelativePath, - }); -} - -function javaBlock({ - srcPath, - destProperty, - blockOrRelativePath = '', -}: { - srcPath: string; - destProperty: string; - blockOrRelativePath: string | RelativeWriteFileBlock; -}): WriteFileBlock | Pick { - const block: RelativeWriteFileBlock | undefined = typeof blockOrRelativePath !== 'string' ? blockOrRelativePath : undefined; - const blockRenameTo = typeof block?.renameTo === 'function' ? block.renameTo : undefined; - const relativePath: string = typeof blockOrRelativePath === 'string' ? blockOrRelativePath : blockOrRelativePath.relativePath ?? ''; - return { - path: `${srcPath}${relativePath}`, - ...block, - renameTo(this: CoreGenerator, data: any, filePath: string) { - return `${data[destProperty]}${replaceEntityFilePathVariables(data, relativePath) ?? ''}${ - replaceEntityFilePathVariables(data, blockRenameTo?.call?.(this, data, filePath) ?? filePath) ?? '' - }`; - }, - }; -} diff --git a/generators/java/support/files.ts b/generators/java/support/files.ts new file mode 100644 index 000000000000..8c7a34cf5d2a --- /dev/null +++ b/generators/java/support/files.ts @@ -0,0 +1,132 @@ +/** + * Copyright 2013-2023 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 { WriteFileBlock } from '../../base/api.js'; +import type CoreGenerator from '../../base-core/generator.js'; +import { SERVER_TEST_SRC_DIR, SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_RES_DIR } from '../../generator-constants.js'; + +export const replaceEntityFilePathVariables = (data: any, filePath: string) => { + filePath = filePath + ?.replace(/_package_/, data.packageFolder) + ?.replace(/_entityPackage_/, data.entityJavaPackageFolder) + ?.replace(/_mainClass_/, data.mainClass) + ?.replace(/_persistClass_/, data.persistClass) + ?.replace(/_entityClass_/, data.entityClass) + ?.replace(/_dtoClass_/, data.dtoClass); + return filePath?.includes('.jhi.') ? filePath : filePath?.replace(/_\w*/, ''); +}; + +/** + * Move the template to `javaPackageSrcDir` (defaults to`src/main/java/${packageFolder}/${filePath}`). + * Removes trailing specifiers. + */ +export const moveToJavaPackageSrcDir = (data: any, filePath: string) => + `${data.javaPackageSrcDir}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +/** + * Move the template to `javaPackageTestDir` (defaults to`src/main/java/${packageFolder}/${filePath}`). + * Removes trailing specifiers. + */ +export const moveToJavaPackageTestDir = (data: any, filePath: string) => + `${data.javaPackageTestDir}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +export const moveToJavaEntityPackageSrcDir = (data: any, filePath: string) => + `${data.srcMainJava}${data.entityAbsoluteFolder}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +export const moveToJavaEntityPackageTestDir = (data: any, filePath: string) => + `${data.srcTestJava}${data.entityAbsoluteFolder}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +export const moveToSrcMainResourcesDir = (data: any, filePath: string) => + `${data.srcMainResources}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +export const moveToSrcTestResourcesDir = (data: any, filePath: string) => + `${data.srcTestResources}${replaceEntityFilePathVariables(data, filePath) ?? ''}`; + +type RelativeWriteFileBlock = WriteFileBlock & { relativePath?: string }; + +export function javaMainPackageTemplatesBlock(blockOrRelativePath?: string): Pick; +export function javaMainPackageTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function javaMainPackageTemplatesBlock( + blockOrRelativePath: string | RelativeWriteFileBlock = '', +): WriteFileBlock | Pick { + return javaBlock({ + srcPath: `${SERVER_MAIN_SRC_DIR}_package_/`, + destProperty: 'javaPackageSrcDir', + blockOrRelativePath, + }); +} + +export function javaMainResourceTemplatesBlock(blockOrRelativePath?: string): Pick; +export function javaMainResourceTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function javaMainResourceTemplatesBlock( + blockOrRelativePath: string | RelativeWriteFileBlock = '', +): WriteFileBlock | Pick { + return javaBlock({ + srcPath: SERVER_MAIN_RES_DIR, + destProperty: 'srcMainResources', + blockOrRelativePath, + }); +} + +export function javaTestResourceTemplatesBlock(blockOrRelativePath?: string): Pick; +export function javaTestResourceTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function javaTestResourceTemplatesBlock( + blockOrRelativePath: string | RelativeWriteFileBlock = '', +): WriteFileBlock | Pick { + return javaBlock({ + srcPath: SERVER_TEST_RES_DIR, + destProperty: 'srcTestResources', + blockOrRelativePath, + }); +} + +export function javaTestPackageTemplatesBlock(blockOrRelativePath?: string): Pick; +export function javaTestPackageTemplatesBlock(blockOrRelativePath: RelativeWriteFileBlock): WriteFileBlock; +export function javaTestPackageTemplatesBlock( + blockOrRelativePath: string | RelativeWriteFileBlock = '', +): WriteFileBlock | Pick { + return javaBlock({ + srcPath: `${SERVER_TEST_SRC_DIR}_package_/`, + destProperty: 'javaPackageTestDir', + blockOrRelativePath, + }); +} + +function javaBlock({ + srcPath, + destProperty, + blockOrRelativePath = '', +}: { + srcPath: string; + destProperty: string; + blockOrRelativePath: string | RelativeWriteFileBlock; +}): WriteFileBlock | Pick { + const block: RelativeWriteFileBlock | undefined = typeof blockOrRelativePath !== 'string' ? blockOrRelativePath : undefined; + const blockRenameTo = typeof block?.renameTo === 'function' ? block.renameTo : undefined; + const relativePath: string = typeof blockOrRelativePath === 'string' ? blockOrRelativePath : blockOrRelativePath.relativePath ?? ''; + return { + path: `${srcPath}${relativePath}`, + ...block, + renameTo(this: CoreGenerator, data: any, filePath: string) { + return `${data[destProperty]}${replaceEntityFilePathVariables(data, relativePath) ?? ''}${ + replaceEntityFilePathVariables(data, blockRenameTo?.call?.(this, data, filePath) ?? filePath) ?? '' + }`; + }, + }; +} diff --git a/generators/java/support/generated-annotation-transform.mts b/generators/java/support/generated-annotation-transform.mts deleted file mode 100644 index b10c3008b959..000000000000 --- a/generators/java/support/generated-annotation-transform.mts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { extname } from 'path'; -import { passthrough } from '@yeoman/transform'; -import { isFileStateDeleted } from 'mem-fs-editor/state'; -import addJavaAnnotation from './add-java-annotation.mjs'; - -const generatedAnnotationTransform = packageName => { - return passthrough(file => { - if ( - !file.path.endsWith('package-info.java') && - extname(file.path) === '.java' && - !isFileStateDeleted(file) && - !file.path.endsWith('GeneratedByJHipster.java') - ) { - file.contents = Buffer.from( - addJavaAnnotation(file.contents.toString('utf8'), { package: packageName, annotation: 'GeneratedByJHipster' }), - ); - } - }); -}; - -export default generatedAnnotationTransform; diff --git a/generators/java/support/generated-annotation-transform.spec.mts b/generators/java/support/generated-annotation-transform.spec.mts deleted file mode 100644 index 4d67249cbb25..000000000000 --- a/generators/java/support/generated-annotation-transform.spec.mts +++ /dev/null @@ -1,45 +0,0 @@ -import { Readable } from 'stream'; -import { expect } from 'esmocha'; - -import { pipeline } from 'p-transform'; -import generatedAnnotationTransform from './generated-annotation-transform.mjs'; - -describe('generators - java - generated-annotation-transform', () => { - it('should add GeneratedByJHipster to interface', async () => { - const file = { - contents: Buffer.from(`package package.name; - -interface Foo { -}`), - path: 'foo.java', - }; - await pipeline(Readable.from([file]), generatedAnnotationTransform('generated.by.package')); - expect(file.contents.toString()).toMatchInlineSnapshot(` -"package package.name; -import generated.by.package.GeneratedByJHipster; - -@GeneratedByJHipster -interface Foo { -}" -`); - }); - - it('should add GeneratedByJHipster to @interface', async () => { - const file = { - contents: Buffer.from(`package package.name; - -@interface Foo { -}`), - path: 'foo.java', - }; - await pipeline(Readable.from([file]), generatedAnnotationTransform('generated.by.package')); - expect(file.contents.toString()).toMatchInlineSnapshot(` -"package package.name; -import generated.by.package.GeneratedByJHipster; - -@GeneratedByJHipster -@interface Foo { -}" -`); - }); -}); diff --git a/generators/java/support/generated-annotation-transform.spec.ts b/generators/java/support/generated-annotation-transform.spec.ts new file mode 100644 index 000000000000..bd353162c282 --- /dev/null +++ b/generators/java/support/generated-annotation-transform.spec.ts @@ -0,0 +1,45 @@ +import { Readable } from 'stream'; +import { expect } from 'esmocha'; + +import { pipeline } from 'p-transform'; +import generatedAnnotationTransform from './generated-annotation-transform.js'; + +describe('generators - java - generated-annotation-transform', () => { + it('should add GeneratedByJHipster to interface', async () => { + const file = { + contents: Buffer.from(`package package.name; + +interface Foo { +}`), + path: 'foo.java', + }; + await pipeline(Readable.from([file]), generatedAnnotationTransform('generated.by.package')); + expect(file.contents.toString()).toMatchInlineSnapshot(` +"package package.name; +import generated.by.package.GeneratedByJHipster; + +@GeneratedByJHipster +interface Foo { +}" +`); + }); + + it('should add GeneratedByJHipster to @interface', async () => { + const file = { + contents: Buffer.from(`package package.name; + +@interface Foo { +}`), + path: 'foo.java', + }; + await pipeline(Readable.from([file]), generatedAnnotationTransform('generated.by.package')); + expect(file.contents.toString()).toMatchInlineSnapshot(` +"package package.name; +import generated.by.package.GeneratedByJHipster; + +@GeneratedByJHipster +@interface Foo { +}" +`); + }); +}); diff --git a/generators/java/support/generated-annotation-transform.ts b/generators/java/support/generated-annotation-transform.ts new file mode 100644 index 000000000000..a36152df5da9 --- /dev/null +++ b/generators/java/support/generated-annotation-transform.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { extname } from 'path'; +import { passthrough } from '@yeoman/transform'; +import { isFileStateDeleted } from 'mem-fs-editor/state'; +import addJavaAnnotation from './add-java-annotation.js'; + +const generatedAnnotationTransform = packageName => { + return passthrough(file => { + if ( + !file.path.endsWith('package-info.java') && + extname(file.path) === '.java' && + !isFileStateDeleted(file) && + !file.path.endsWith('GeneratedByJHipster.java') + ) { + file.contents = Buffer.from( + addJavaAnnotation(file.contents.toString('utf8'), { package: packageName, annotation: 'GeneratedByJHipster' }), + ); + } + }); +}; + +export default generatedAnnotationTransform; diff --git a/generators/java/support/index.mts b/generators/java/support/index.mts deleted file mode 100644 index 4df92399a7cf..000000000000 --- a/generators/java/support/index.mts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2013-2023 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 as addJavaAnnotation } from './add-java-annotation.mjs'; -export * from './checks/index.mjs'; -export * from './files.mjs'; -export { default as generatedAnnotationTransform } from './generated-annotation-transform.mjs'; -export { default as packageInfoTransform } from './package-info-transform.mjs'; -export * from './util.mjs'; diff --git a/generators/java/support/index.ts b/generators/java/support/index.ts new file mode 100644 index 000000000000..4f626846ab93 --- /dev/null +++ b/generators/java/support/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2013-2023 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 as addJavaAnnotation } from './add-java-annotation.js'; +export * from './checks/index.js'; +export * from './files.js'; +export { default as generatedAnnotationTransform } from './generated-annotation-transform.js'; +export { default as packageInfoTransform } from './package-info-transform.js'; +export * from './util.js'; diff --git a/generators/java/support/package-info-transform.mts b/generators/java/support/package-info-transform.ts similarity index 100% rename from generators/java/support/package-info-transform.mts rename to generators/java/support/package-info-transform.ts diff --git a/generators/java/support/util.mts b/generators/java/support/util.mts deleted file mode 100644 index d6b33f751017..000000000000 --- a/generators/java/support/util.mts +++ /dev/null @@ -1,14 +0,0 @@ -import * as _ from 'lodash-es'; -import { getMicroserviceAppName } from '../../base/support/index.mjs'; - -const { upperFirst } = _; - -/** - * get the java main class name. - */ -export const getMainClassName = ({ baseName }: { baseName: string }) => { - const main = upperFirst(getMicroserviceAppName({ microserviceName: baseName })); - const acceptableForJava = new RegExp('^[A-Z][a-zA-Z0-9_]*$'); - - return acceptableForJava.test(main) ? main : 'Application'; -}; diff --git a/generators/java/support/util.spec.mts b/generators/java/support/util.spec.mts deleted file mode 100644 index eac756e54f4b..000000000000 --- a/generators/java/support/util.spec.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from 'esmocha'; -import { getMainClassName } from './util.mjs'; - -describe('generator > java', () => { - describe('getMainClassName', () => { - describe('when called with name', () => { - it('return the app name', () => { - expect(getMainClassName({ baseName: 'myTest' })).toBe('MyTestApp'); - }); - }); - describe('when called with name having App', () => { - it('return the app name', () => { - expect(getMainClassName({ baseName: 'myApp' })).toBe('MyApp'); - }); - }); - describe('when called with name having invalid java chars', () => { - it('return the default app name', () => { - expect(getMainClassName({ baseName: '9myApp' })).toBe('Application'); - }); - }); - }); -}); diff --git a/generators/java/support/util.spec.ts b/generators/java/support/util.spec.ts new file mode 100644 index 000000000000..b6b6e825e905 --- /dev/null +++ b/generators/java/support/util.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'esmocha'; +import { getMainClassName } from './util.js'; + +describe('generator > java', () => { + describe('getMainClassName', () => { + describe('when called with name', () => { + it('return the app name', () => { + expect(getMainClassName({ baseName: 'myTest' })).toBe('MyTestApp'); + }); + }); + describe('when called with name having App', () => { + it('return the app name', () => { + expect(getMainClassName({ baseName: 'myApp' })).toBe('MyApp'); + }); + }); + describe('when called with name having invalid java chars', () => { + it('return the default app name', () => { + expect(getMainClassName({ baseName: '9myApp' })).toBe('Application'); + }); + }); + }); +}); diff --git a/generators/java/support/util.ts b/generators/java/support/util.ts new file mode 100644 index 000000000000..3cbcb601f2c5 --- /dev/null +++ b/generators/java/support/util.ts @@ -0,0 +1,14 @@ +import * as _ from 'lodash-es'; +import { getMicroserviceAppName } from '../../base/support/index.js'; + +const { upperFirst } = _; + +/** + * get the java main class name. + */ +export const getMainClassName = ({ baseName }: { baseName: string }) => { + const main = upperFirst(getMicroserviceAppName({ microserviceName: baseName })); + const acceptableForJava = new RegExp('^[A-Z][a-zA-Z0-9_]*$'); + + return acceptableForJava.test(main) ? main : 'Application'; +}; diff --git a/generators/java/types.d.mts b/generators/java/types.d.mts deleted file mode 100644 index 03f4f4802d89..000000000000 --- a/generators/java/types.d.mts +++ /dev/null @@ -1,25 +0,0 @@ -import { BaseApplication } from '../base-application/types.mjs'; - -export type JavaApplication = BaseApplication & { - packageName: string; - packageFolder: string; - - srcMainJava: string; - srcMainResources: string; - srcMainWebapp: string; - srcTestJava: string; - srcTestResources: string; - srcTestJavascript: string; - - javaPackageSrcDir: string; - javaPackageTestDir: string; - - temporaryDir: string; - - javaDependencies: Record; - packageInfoJavadocs: { packageName: string; documentation: string }[]; - - prettierJava: boolean; - - imperativeOrReactive: string; -}; diff --git a/generators/java/types.d.ts b/generators/java/types.d.ts new file mode 100644 index 000000000000..5c3c65516fb4 --- /dev/null +++ b/generators/java/types.d.ts @@ -0,0 +1,25 @@ +import { BaseApplication } from '../base-application/types.js'; + +export type JavaApplication = BaseApplication & { + packageName: string; + packageFolder: string; + + srcMainJava: string; + srcMainResources: string; + srcMainWebapp: string; + srcTestJava: string; + srcTestResources: string; + srcTestJavascript: string; + + javaPackageSrcDir: string; + javaPackageTestDir: string; + + temporaryDir: string; + + javaDependencies: Record; + packageInfoJavadocs: { packageName: string; documentation: string }[]; + + prettierJava: boolean; + + imperativeOrReactive: string; +}; diff --git a/generators/jdl/__snapshots__/generator.spec.mts.snap b/generators/jdl/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/jdl/__snapshots__/generator.spec.mts.snap rename to generators/jdl/__snapshots__/generator.spec.ts.snap diff --git a/generators/jdl/command.mts b/generators/jdl/command.mts deleted file mode 100644 index 2fd5493f4dee..000000000000 --- a/generators/jdl/command.mts +++ /dev/null @@ -1,51 +0,0 @@ -import { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../generator-list.mjs'; - -const command: JHipsterCommandDefinition = { - arguments: { - jdlFiles: { - type: Array, - }, - }, - options: { - interactive: { - description: - 'Generate multiple applications in series so that questions can be interacted with. This is the default when there is an existing application configuration in any of the folders', - type: Boolean, - scope: 'generator', - }, - jsonOnly: { - description: 'Generate only the JSON files and skip entity regeneration', - type: Boolean, - scope: 'generator', - }, - ignoreApplication: { - description: 'Ignores application generation', - type: Boolean, - scope: 'generator', - }, - ignoreDeployments: { - description: 'Ignores deployments generation', - type: Boolean, - scope: 'generator', - }, - skipSampleRepository: { - description: 'Disable fetching sample files when the file is not a URL', - type: Boolean, - scope: 'generator', - }, - inline: { - description: 'Pass JDL content inline. Argument can be skipped when passing this', - type: String, - scope: 'generator', - }, - skipUserManagement: { - description: 'Skip the user management module during app generation', - type: Boolean, - scope: 'generator', - }, - }, - import: [GENERATOR_APP, GENERATOR_WORKSPACES], -}; - -export default command; diff --git a/generators/jdl/command.ts b/generators/jdl/command.ts new file mode 100644 index 000000000000..00ea4acd8922 --- /dev/null +++ b/generators/jdl/command.ts @@ -0,0 +1,51 @@ +import { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_APP, GENERATOR_WORKSPACES } from '../generator-list.js'; + +const command: JHipsterCommandDefinition = { + arguments: { + jdlFiles: { + type: Array, + }, + }, + options: { + interactive: { + description: + 'Generate multiple applications in series so that questions can be interacted with. This is the default when there is an existing application configuration in any of the folders', + type: Boolean, + scope: 'generator', + }, + jsonOnly: { + description: 'Generate only the JSON files and skip entity regeneration', + type: Boolean, + scope: 'generator', + }, + ignoreApplication: { + description: 'Ignores application generation', + type: Boolean, + scope: 'generator', + }, + ignoreDeployments: { + description: 'Ignores deployments generation', + type: Boolean, + scope: 'generator', + }, + skipSampleRepository: { + description: 'Disable fetching sample files when the file is not a URL', + type: Boolean, + scope: 'generator', + }, + inline: { + description: 'Pass JDL content inline. Argument can be skipped when passing this', + type: String, + scope: 'generator', + }, + skipUserManagement: { + description: 'Skip the user management module during app generation', + type: Boolean, + scope: 'generator', + }, + }, + import: [GENERATOR_APP, GENERATOR_WORKSPACES], +}; + +export default command; diff --git a/generators/jdl/generator.mts b/generators/jdl/generator.mts deleted file mode 100644 index 134de820422c..000000000000 --- a/generators/jdl/generator.mts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright 2013-2023 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 { extname } from 'path'; -import { QueuedAdapter } from '@yeoman/adapter'; -import * as _ from 'lodash-es'; -import { create as createMemFs, type Store as MemFs } from 'mem-fs'; -import { create as createMemFsEditor, type MemFsEditor } from 'mem-fs-editor'; - -import { readFile } from 'fs/promises'; -import BaseGenerator from '../base/index.mjs'; -import command from './command.mjs'; -import { downloadJdlFile } from '../../cli/download.mjs'; -import EnvironmentBuilder from '../../cli/environment-builder.mjs'; -import { CLI_NAME } from '../../cli/utils.mjs'; -import { GENERATOR_APP, GENERATOR_ENTITIES, GENERATOR_WORKSPACES } from '../generator-list.mjs'; -import { ApplicationWithEntities, createImporterFromContent } from '../../jdl/jdl-importer.js'; -import { GENERATOR_JHIPSTER, JHIPSTER_CONFIG_DIR } from '../generator-constants.mjs'; -import statistics from '../statistics.mjs'; -import { addApplicationIndex, allNewApplications, customizeForMicroservices } from './internal/index.mjs'; -import { mergeYoRcContent } from '../../jdl/index.js'; -import { normalizeBlueprintName } from '../base/internal/blueprint.mjs'; -import { updateApplicationEntitiesTransform } from '../base-application/support/update-application-entities-transform.mjs'; - -const { upperFirst } = _; - -/** - * Add jdl extension to the file - */ -const toJdlFile = file => { - if (!extname(file)) { - return `${file}.jdl`; - } - return file; -}; - -type ApplicationWithEntitiesAndPath = ApplicationWithEntities & { folder?: string; sharedFs?: MemFs }; - -export default class JdlGenerator extends BaseGenerator { - jdlFiles?: string[]; - inline?: string; - jdlContents: string[] = []; - - interactive?: boolean; - jsonOnly?: boolean; - ignoreApplication?: boolean; - ignoreDeployments?: boolean; - skipSampleRepository?: boolean; - force?: boolean; - reproducible?: boolean; - createEnvBuilder = EnvironmentBuilder.createDefaultBuilder; - existingProject?: boolean; - - applications!: ApplicationWithEntitiesAndPath[]; - exportedApplicationsWithEntities!: Record; - exportedEntities!: any[]; - exportedDeployments!: any[]; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints('jdl'); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadArguments() { - this.parseJHipsterArguments(command.arguments); - if (this.jdlFiles) { - this.log.verboseInfo('Generating jdls', ...this.jdlFiles); - } - }, - loadOptions() { - this.parseJHipsterOptions(command.options); - }, - existingProject() { - this.existingProject = this.jhipsterConfig.baseName !== undefined && (this.config as any).existed; - }, - checkOptions() { - if (!this.inline && !this.jdlFiles?.length) { - throw new Error('At least one jdl file is required.'); - } - }, - }); - } - - get [BaseGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get configuring() { - return this.asConfiguringTaskGroup({ - insight() { - statistics.sendSubGenEvent('generator', 'import-jdl'); - }, - async downloadJdlFiles() { - if (this.jdlFiles) { - this.jdlFiles = await Promise.all( - this.jdlFiles.map(toJdlFile).map(async filename => { - try { - this.readDestination(filename); - } catch { - this.log.warn(`File not found: ${filename}. Attempting download from jdl-samples repository`); - const downloadedFile = await downloadJdlFile(filename, { skipSampleRepository: this.skipSampleRepository }); - // The file has null content at mem-fs, update with actual content. - this.writeDestination(downloadedFile, (await readFile(downloadedFile)).toString()); - return downloadedFile; - } - return filename; - }), - ); - } - }, - readJdlFiles() { - if (this.inline) { - this.jdlContents.push(this.inline); - } - for (const jdlFile of this.jdlFiles ?? []) { - this.jdlContents.push(this.readDestination(jdlFile)?.toString() ?? ''); - } - }, - async parseJDL() { - const configuration = { - applicationName: this.options.baseName ?? (this.existingProject ? this.jhipsterConfig.baseName : undefined), - databaseType: this.options.db ?? (this.existingProject ? this.jhipsterConfigWithDefaults.prodDatabaseType : undefined), - applicationType: this.options.applicationType, - skipUserManagement: this.options.skipUserManagement, - }; - - const importer = createImporterFromContent(this.jdlContents.join('\n'), configuration); - - const importState = importer.import(); - - this.exportedDeployments = importState.exportedDeployments; - this.exportedEntities = importState.exportedEntities; - this.exportedApplicationsWithEntities = importState.exportedApplicationsWithEntities; - - const applicationsWithEntities = Object.values(importState.exportedApplicationsWithEntities); - this.applications = - applicationsWithEntities.length === 1 - ? applicationsWithEntities - : [ - ...applicationsWithEntities.filter((app: ApplicationWithEntitiesAndPath) => app.config.applicationType === 'gateway'), - ...applicationsWithEntities.filter((app: ApplicationWithEntitiesAndPath) => app.config.applicationType !== 'gateway'), - ]; - }, - configure() { - const nrApplications = this.applications.length; - const allNew = allNewApplications(this.applications); - const interactiveFallback = !allNew; - - this.interactive = this.interactive ?? interactiveFallback; - this.force = this.options.force ?? (nrApplications > 0 && allNew) ? true : undefined; - this.reproducible = allNew; - }, - customizeApplication() { - for (const app of this.applications) { - app.config.entities = app.entities.map(entity => entity.name); - } - if (this.applications.length > 1) { - for (const app of this.applications) { - app.folder = app.config.baseName; - if (!this.interactive && !this.jsonOnly && !this.ignoreApplication) { - app.sharedFs = createMemFs(); - } - } - } - addApplicationIndex(this.applications); - customizeForMicroservices(this.exportedApplicationsWithEntities); - }, - async generateJson() { - if (this.applications.length === 0) { - this.writeConfig({ entities: this.exportedEntities }); - await this.env.sharedFs.pipeline( - { refresh: true }, - updateApplicationEntitiesTransform({ destinationPath: this.destinationPath(), throwOnMissingConfig: false }), - ); - } else { - this.writeConfig(...this.applications.map(app => (this.ignoreApplication ? { ...app, config: undefined } : app))); - } - }, - async generate() { - if (this.jsonOnly) { - return; - } - - const generatorOptions: any = { defaults: true, reproducible: this.reproducible, force: this.force }; - - if (this.ignoreApplication || this.applications.length === 0) { - if (this.applications.length === 0) { - const entities = this.exportedEntities; - await this.composeWithJHipster(GENERATOR_ENTITIES, { - generatorArgs: entities.map(entity => entity.name), - generatorOptions, - }); - } else { - for (const app of this.applications) { - await this.composeWithJHipster(GENERATOR_ENTITIES, { - generatorArgs: app.entities.map(entity => entity.name), - generatorOptions: { - ...generatorOptions, - destinationRoot: app.folder ? this.destinationPath(app.folder) : undefined, - }, - }); - } - } - } else if (this.applications.length === 1) { - this.log.info('Generating 1 application'); - await this.composeWithJHipster(GENERATOR_APP, { generatorOptions }); - } else { - this.log.info(`Generating ${this.applications.length} applications`); - await this.composeWithJHipster(GENERATOR_WORKSPACES, { - generatorOptions: { - workspacesFolders: this.applications.map(app => app.folder), - generateApplications: async () => this.runNonInteractive(this.applications, generatorOptions), - } as any, - }); - } - }, - }); - } - - get [BaseGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get end() { - return this.asEndTaskGroup({ - async generateDeployments() { - if (!this.exportedDeployments || this.exportedDeployments.length === 0) { - this.log.info('No deployment configured'); - return; - } - if (this.ignoreDeployments) { - this.log.info(`Ignoring ${this.exportedDeployments.length} deployments`); - return; - } - - this.log.info(`Generating ${this.exportedDeployments.length} deployments`); - for (const deployment of this.exportedDeployments) { - const deploymentConfig = deployment[GENERATOR_JHIPSTER]; - const deploymentType = deploymentConfig.deploymentType; - this.log.debug(`Generating deployment: ${JSON.stringify(deploymentConfig, null, 2)}`); - - await this.composeWithJHipster(deploymentType, { - generatorOptions: { - destinationRoot: this.destinationPath(deploymentType), - force: true, - skipPrompts: true, - } as any, - }); - } - }, - }); - } - - get [BaseGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - async runNonInteractive(applications: ApplicationWithEntitiesAndPath[], options) { - await Promise.all( - applications.map(async application => { - const rootCwd = this.destinationPath(); - const cwd = application.folder ? this.destinationPath(application.folder) : rootCwd; - const adapter = (this.env.adapter as QueuedAdapter).newAdapter(); - const envOptions: any = { cwd, logCwd: rootCwd, sharedFs: application.sharedFs, adapter }; - const generatorOptions = { ...this.options, ...options, skipPriorities: ['prompting'] }; - - // We should not reuse sharedData at non interactive runs - delete generatorOptions.sharedData; - - // Install should happen at the root of the monorepository. Force skip install at childs. - if (this.options.monorepository) { - generatorOptions.skipInstall = true; - } - const envBuilder = await this.createEnvBuilder(envOptions); - const env = envBuilder.getEnvironment(); - await env.run([`${CLI_NAME}:${GENERATOR_APP}`], generatorOptions); - }), - ); - } - - writeConfig(...applications: Partial[]) { - for (const application of applications) { - const { folder = '', entities = [], sharedFs } = application; - let { config, namespaceConfigs } = application; - - const appPath = folder ? `${folder}/` : folder; - const fs: MemFsEditor = sharedFs ? createMemFsEditor(sharedFs) : this.fs; - if (config) { - const configFile = this.destinationPath(`${appPath}.yo-rc.json`); - const oldConfig: any = fs.readJSON(configFile, {}); - if (Array.isArray(config.blueprints)) { - config = { - ...config, - blueprints: config.blueprints.map(({ name, ...remaining }) => ({ ...remaining, name: normalizeBlueprintName(name) })), - }; - } - if (namespaceConfigs) { - namespaceConfigs = Object.fromEntries( - Object.entries(namespaceConfigs).map(([ns, config]) => [normalizeBlueprintName(ns), config]), - ); - } - - fs.writeJSON(configFile, mergeYoRcContent(oldConfig, { ...namespaceConfigs, [GENERATOR_JHIPSTER]: config })); - } - for (const entity of entities) { - const configFile = this.destinationPath(`${appPath}${JHIPSTER_CONFIG_DIR}/${upperFirst(entity.name)}.json`); - const oldConfig: any = fs.readJSON(configFile, {}); - fs.writeJSON(configFile, { ...oldConfig, ...entity }); - } - } - } -} diff --git a/generators/jdl/generator.spec.mts b/generators/jdl/generator.spec.mts deleted file mode 100644 index 81c140fd7221..000000000000 --- a/generators/jdl/generator.spec.mts +++ /dev/null @@ -1,475 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; -import { SinonSpy } from 'sinon'; - -import { RunResult } from 'yeoman-test'; -import Generator from './index.mjs'; -import { getCommandHelpOutput, shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import * as GENERATORS from '../generator-list.mjs'; -import { GENERATOR_JDL } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -const mockedGeneratorsNames: typeof GENERATORS = {} as any; -for (const key of Object.keys(GENERATORS)) { - mockedGeneratorsNames[key] = `jhipster:${GENERATORS[key]}`; -} - -const { - GENERATOR_APP: MOCKED_APP, - GENERATOR_DOCKER_COMPOSE: MOCKED_DOCKER_COMPOSE, - GENERATOR_ENTITIES: MOCKED_ENTITIES, - GENERATOR_WORKSPACES: MOCKED_WORKSPACES, -} = mockedGeneratorsNames; -const mockedGenerators = [MOCKED_APP, MOCKED_ENTITIES, MOCKED_DOCKER_COMPOSE, MOCKED_WORKSPACES]; - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('help', () => { - it('should print expected information', async () => { - expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); - }); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('for entities only jdl', () => { - it('without databaseType should reject', async () => { - await expect( - helpers.runJHipster(GENERATOR_JDL).withOptions({ - inline: 'entity Foo {}', - baseName: 'jhipster', - }), - ).rejects.toThrow("The JDL object, the application's name, and its the database type are mandatory."); - }); - it('without baseName should reject', async () => { - await expect( - helpers.runJHipster(GENERATOR_JDL).withOptions({ - inline: 'entity Foo {}', - db: 'postgresql', - }), - ).rejects.toThrow("The JDL object, the application's name, and its the database type are mandatory."); - }); - - describe('with valid parameters', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - inline: 'entity Foo {}', - db: 'postgresql', - baseName: 'jhipster', - }); - }); - - it('should not compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - - it('should compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([['Foo'], expect.any(Object)]); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - - describe('with valid config', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withJHipsterConfig().withMockedGenerators(mockedGenerators).withOptions({ - inline: 'entity Foo {}', - }); - }); - - it('should not compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - - it('should compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([['Foo'], expect.any(Object)]); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - }); - - describe('for application jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - inline: 'application { }', - }); - }); - - it('should not compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([[], expect.not.objectContaining({ applicationWithEntities: expect.any(Object) })]); - }); - it('should write expected files', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - - describe('with blueprint jdl with blueprint config', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - inline: 'application { config { blueprints [foo, bar] } config(foo) { config fooValue } config(bar) { config barValue } }', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toMatchInlineSnapshot(` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "applicationIndex": 0, - "baseName": "jhipster", - "blueprints": [ - { - "name": "generator-jhipster-foo" - }, - { - "name": "generator-jhipster-bar" - } - ], - "entities": [] - }, - "generator-jhipster-bar": { - "config": "barValue" - } -} -", - "stateCleared": "modified", - }, -} -`); - }); - }); - }); - - describe('for one application and entity jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - inline: 'application { entities Foo } entity Foo {}', - }); - }); - - it('should not compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([[], expect.not.objectContaining({ applicationWithEntities: expect.any(Object) })]); - }); - it('should write expected files', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - - describe('with --ignore-application option', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - ignoreApplication: true, - inline: 'application { entities Foo } entity Foo {}', - }); - }); - - it('should write entity files', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - }); - - describe('for two applications and entity jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', - }); - }); - - it('should not compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should not compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should compose with workspaces', () => { - const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([ - [], - expect.objectContaining({ workspacesFolders: ['jhipster', 'jhipster2'], generateApplications: expect.any(Function) }), - ]); - }); - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({}); - }); - }); - - describe('with --ignore-application option', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ - ignoreApplication: true, - inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', - }); - }); - - it('should compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(2); - expect(mock.lastCall.args).toStrictEqual([['Bar'], expect.any(Object)]); - }); - it('should not compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should not compose with workspaces', () => { - const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should write expected files', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - }); - - describe('--json-only option', () => { - describe('for entities only jdl', () => { - describe('with valid parameters', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - inline: 'entity Foo {}', - db: 'postgresql', - baseName: 'jhipster', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - - describe('with valid config', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withJHipsterConfig().withOptions({ - jsonOnly: true, - inline: 'entity Foo {}', - }); - }); - - it('should match files snapshot', () => { - expect(runResult.getSnapshot()).toEqual({ - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - }); - - describe('for application jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - inline: 'application { }', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.yo-rc.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - }); - - describe('for one application and entity jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - inline: 'application { entities Foo } entity Foo {}', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.yo-rc.json': expect.objectContaining({ contents: expect.stringMatching(/"entities": \[\s*"Foo"\s*]/g) }), - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - - describe('with --ignore-application option', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - ignoreApplication: true, - inline: 'application { entities Foo } entity Foo {}', - }); - }); - - it('should write entity files', () => { - expect(runResult.getSnapshot()).toEqual({ - '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - }); - - describe('for two applications and entity jdl', () => { - describe('with valid jdl', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - - describe('with --ignore-application option', () => { - let runResult: RunResult; - - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ - jsonOnly: true, - ignoreApplication: true, - inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', - }); - }); - - it('should write expected files', () => { - expect(runResult.getSnapshot()).toEqual({ - 'jhipster/.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - 'jhipster2/.jhipster/Bar.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), - }); - }); - }); - }); - }); - describe('with a microservices stack', () => { - const jdl = ` -application { entities Foo } -entity Foo {} -application { config { baseName gatewayApp applicationType gateway } entities * } -entity Bar -`; - describe('generating the stack', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ inline: jdl }); - }); - - it('should not compose with entities', () => { - const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should not compose with app', () => { - const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; - expect(mock.callCount).toBe(0); - }); - it('should compose with workspaces', () => { - const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; - expect(mock.callCount).toBe(1); - expect(mock.lastCall.args).toStrictEqual([ - [], - expect.objectContaining({ workspacesFolders: ['gatewayApp', 'jhipster'], generateApplications: expect.any(Function) }), - ]); - }); - }); - describe('generating json', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ inline: jdl, jsonOnly: true }); - }); - - it('should generate expected config', () => { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/jdl/generator.spec.ts b/generators/jdl/generator.spec.ts new file mode 100644 index 000000000000..2996eb62c6d7 --- /dev/null +++ b/generators/jdl/generator.spec.ts @@ -0,0 +1,475 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; +import { SinonSpy } from 'sinon'; + +import { RunResult } from 'yeoman-test'; +import Generator from './index.js'; +import { getCommandHelpOutput, shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import * as GENERATORS from '../generator-list.js'; +import { GENERATOR_JDL } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +const mockedGeneratorsNames: typeof GENERATORS = {} as any; +for (const key of Object.keys(GENERATORS)) { + mockedGeneratorsNames[key] = `jhipster:${GENERATORS[key]}`; +} + +const { + GENERATOR_APP: MOCKED_APP, + GENERATOR_DOCKER_COMPOSE: MOCKED_DOCKER_COMPOSE, + GENERATOR_ENTITIES: MOCKED_ENTITIES, + GENERATOR_WORKSPACES: MOCKED_WORKSPACES, +} = mockedGeneratorsNames; +const mockedGenerators = [MOCKED_APP, MOCKED_ENTITIES, MOCKED_DOCKER_COMPOSE, MOCKED_WORKSPACES]; + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('help', () => { + it('should print expected information', async () => { + expect(await getCommandHelpOutput(generator)).toMatchSnapshot(); + }); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('for entities only jdl', () => { + it('without databaseType should reject', async () => { + await expect( + helpers.runJHipster(GENERATOR_JDL).withOptions({ + inline: 'entity Foo {}', + baseName: 'jhipster', + }), + ).rejects.toThrow("The JDL object, the application's name, and its the database type are mandatory."); + }); + it('without baseName should reject', async () => { + await expect( + helpers.runJHipster(GENERATOR_JDL).withOptions({ + inline: 'entity Foo {}', + db: 'postgresql', + }), + ).rejects.toThrow("The JDL object, the application's name, and its the database type are mandatory."); + }); + + describe('with valid parameters', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + inline: 'entity Foo {}', + db: 'postgresql', + baseName: 'jhipster', + }); + }); + + it('should not compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + + it('should compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([['Foo'], expect.any(Object)]); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + + describe('with valid config', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withJHipsterConfig().withMockedGenerators(mockedGenerators).withOptions({ + inline: 'entity Foo {}', + }); + }); + + it('should not compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + + it('should compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([['Foo'], expect.any(Object)]); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + }); + + describe('for application jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + inline: 'application { }', + }); + }); + + it('should not compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([[], expect.not.objectContaining({ applicationWithEntities: expect.any(Object) })]); + }); + it('should write expected files', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + + describe('with blueprint jdl with blueprint config', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + inline: 'application { config { blueprints [foo, bar] } config(foo) { config fooValue } config(bar) { config barValue } }', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toMatchInlineSnapshot(` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "applicationIndex": 0, + "baseName": "jhipster", + "blueprints": [ + { + "name": "generator-jhipster-foo" + }, + { + "name": "generator-jhipster-bar" + } + ], + "entities": [] + }, + "generator-jhipster-bar": { + "config": "barValue" + } +} +", + "stateCleared": "modified", + }, +} +`); + }); + }); + }); + + describe('for one application and entity jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + inline: 'application { entities Foo } entity Foo {}', + }); + }); + + it('should not compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([[], expect.not.objectContaining({ applicationWithEntities: expect.any(Object) })]); + }); + it('should write expected files', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + + describe('with --ignore-application option', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + ignoreApplication: true, + inline: 'application { entities Foo } entity Foo {}', + }); + }); + + it('should write entity files', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + }); + + describe('for two applications and entity jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', + }); + }); + + it('should not compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should not compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should compose with workspaces', () => { + const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([ + [], + expect.objectContaining({ workspacesFolders: ['jhipster', 'jhipster2'], generateApplications: expect.any(Function) }), + ]); + }); + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({}); + }); + }); + + describe('with --ignore-application option', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ + ignoreApplication: true, + inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', + }); + }); + + it('should compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(2); + expect(mock.lastCall.args).toStrictEqual([['Bar'], expect.any(Object)]); + }); + it('should not compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should not compose with workspaces', () => { + const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should write expected files', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + }); + + describe('--json-only option', () => { + describe('for entities only jdl', () => { + describe('with valid parameters', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + inline: 'entity Foo {}', + db: 'postgresql', + baseName: 'jhipster', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + + describe('with valid config', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withJHipsterConfig().withOptions({ + jsonOnly: true, + inline: 'entity Foo {}', + }); + }); + + it('should match files snapshot', () => { + expect(runResult.getSnapshot()).toEqual({ + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + }); + + describe('for application jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + inline: 'application { }', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.yo-rc.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + }); + + describe('for one application and entity jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + inline: 'application { entities Foo } entity Foo {}', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.yo-rc.json': expect.objectContaining({ contents: expect.stringMatching(/"entities": \[\s*"Foo"\s*]/g) }), + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + + describe('with --ignore-application option', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + ignoreApplication: true, + inline: 'application { entities Foo } entity Foo {}', + }); + }); + + it('should write entity files', () => { + expect(runResult.getSnapshot()).toEqual({ + '.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + }); + + describe('for two applications and entity jdl', () => { + describe('with valid jdl', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + + describe('with --ignore-application option', () => { + let runResult: RunResult; + + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_JDL).withOptions({ + jsonOnly: true, + ignoreApplication: true, + inline: 'application { entities Foo } entity Foo {} application { config { baseName jhipster2 } entities Bar } entity Bar', + }); + }); + + it('should write expected files', () => { + expect(runResult.getSnapshot()).toEqual({ + 'jhipster/.jhipster/Foo.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + 'jhipster2/.jhipster/Bar.json': expect.objectContaining({ contents: expect.any(String), stateCleared: 'modified' }), + }); + }); + }); + }); + }); + describe('with a microservices stack', () => { + const jdl = ` +application { entities Foo } +entity Foo {} +application { config { baseName gatewayApp applicationType gateway } entities * } +entity Bar +`; + describe('generating the stack', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ inline: jdl }); + }); + + it('should not compose with entities', () => { + const mock = runResult.mockedGenerators[MOCKED_ENTITIES] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should not compose with app', () => { + const mock = runResult.mockedGenerators[MOCKED_APP] as SinonSpy; + expect(mock.callCount).toBe(0); + }); + it('should compose with workspaces', () => { + const mock = runResult.mockedGenerators[MOCKED_WORKSPACES] as SinonSpy; + expect(mock.callCount).toBe(1); + expect(mock.lastCall.args).toStrictEqual([ + [], + expect.objectContaining({ workspacesFolders: ['gatewayApp', 'jhipster'], generateApplications: expect.any(Function) }), + ]); + }); + }); + describe('generating json', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_JDL).withMockedGenerators(mockedGenerators).withOptions({ inline: jdl, jsonOnly: true }); + }); + + it('should generate expected config', () => { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/jdl/generator.ts b/generators/jdl/generator.ts new file mode 100644 index 000000000000..15b4e2ca626e --- /dev/null +++ b/generators/jdl/generator.ts @@ -0,0 +1,330 @@ +/** + * Copyright 2013-2023 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 { extname } from 'path'; +import { QueuedAdapter } from '@yeoman/adapter'; +import * as _ from 'lodash-es'; +import { create as createMemFs, type Store as MemFs } from 'mem-fs'; +import { create as createMemFsEditor, type MemFsEditor } from 'mem-fs-editor'; + +import { readFile } from 'fs/promises'; +import BaseGenerator from '../base/index.js'; +import command from './command.js'; +import { downloadJdlFile } from '../../cli/download.mjs'; +import EnvironmentBuilder from '../../cli/environment-builder.mjs'; +import { CLI_NAME } from '../../cli/utils.mjs'; +import { GENERATOR_APP, GENERATOR_ENTITIES, GENERATOR_WORKSPACES } from '../generator-list.js'; +import { ApplicationWithEntities, createImporterFromContent } from '../../jdl/jdl-importer.js'; +import { GENERATOR_JHIPSTER, JHIPSTER_CONFIG_DIR } from '../generator-constants.js'; +import statistics from '../statistics.js'; +import { addApplicationIndex, allNewApplications, customizeForMicroservices } from './internal/index.js'; +import { mergeYoRcContent } from '../../jdl/index.js'; +import { normalizeBlueprintName } from '../base/internal/blueprint.js'; +import { updateApplicationEntitiesTransform } from '../base-application/support/update-application-entities-transform.js'; + +const { upperFirst } = _; + +/** + * Add jdl extension to the file + */ +const toJdlFile = file => { + if (!extname(file)) { + return `${file}.jdl`; + } + return file; +}; + +type ApplicationWithEntitiesAndPath = ApplicationWithEntities & { folder?: string; sharedFs?: MemFs }; + +export default class JdlGenerator extends BaseGenerator { + jdlFiles?: string[]; + inline?: string; + jdlContents: string[] = []; + + interactive?: boolean; + jsonOnly?: boolean; + ignoreApplication?: boolean; + ignoreDeployments?: boolean; + skipSampleRepository?: boolean; + force?: boolean; + reproducible?: boolean; + createEnvBuilder = EnvironmentBuilder.createDefaultBuilder; + existingProject?: boolean; + + applications!: ApplicationWithEntitiesAndPath[]; + exportedApplicationsWithEntities!: Record; + exportedEntities!: any[]; + exportedDeployments!: any[]; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints('jdl'); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadArguments() { + this.parseJHipsterArguments(command.arguments); + if (this.jdlFiles) { + this.log.verboseInfo('Generating jdls', ...this.jdlFiles); + } + }, + loadOptions() { + this.parseJHipsterOptions(command.options); + }, + existingProject() { + this.existingProject = this.jhipsterConfig.baseName !== undefined && (this.config as any).existed; + }, + checkOptions() { + if (!this.inline && !this.jdlFiles?.length) { + throw new Error('At least one jdl file is required.'); + } + }, + }); + } + + get [BaseGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get configuring() { + return this.asConfiguringTaskGroup({ + insight() { + statistics.sendSubGenEvent('generator', 'import-jdl'); + }, + async downloadJdlFiles() { + if (this.jdlFiles) { + this.jdlFiles = await Promise.all( + this.jdlFiles.map(toJdlFile).map(async filename => { + try { + this.readDestination(filename); + } catch { + this.log.warn(`File not found: ${filename}. Attempting download from jdl-samples repository`); + const downloadedFile = await downloadJdlFile(filename, { skipSampleRepository: this.skipSampleRepository }); + // The file has null content at mem-fs, update with actual content. + this.writeDestination(downloadedFile, (await readFile(downloadedFile)).toString()); + return downloadedFile; + } + return filename; + }), + ); + } + }, + readJdlFiles() { + if (this.inline) { + this.jdlContents.push(this.inline); + } + for (const jdlFile of this.jdlFiles ?? []) { + this.jdlContents.push(this.readDestination(jdlFile)?.toString() ?? ''); + } + }, + async parseJDL() { + const configuration = { + applicationName: this.options.baseName ?? (this.existingProject ? this.jhipsterConfig.baseName : undefined), + databaseType: this.options.db ?? (this.existingProject ? this.jhipsterConfigWithDefaults.prodDatabaseType : undefined), + applicationType: this.options.applicationType, + skipUserManagement: this.options.skipUserManagement, + }; + + const importer = createImporterFromContent(this.jdlContents.join('\n'), configuration); + + const importState = importer.import(); + + this.exportedDeployments = importState.exportedDeployments; + this.exportedEntities = importState.exportedEntities; + this.exportedApplicationsWithEntities = importState.exportedApplicationsWithEntities; + + const applicationsWithEntities = Object.values(importState.exportedApplicationsWithEntities); + this.applications = + applicationsWithEntities.length === 1 + ? applicationsWithEntities + : [ + ...applicationsWithEntities.filter((app: ApplicationWithEntitiesAndPath) => app.config.applicationType === 'gateway'), + ...applicationsWithEntities.filter((app: ApplicationWithEntitiesAndPath) => app.config.applicationType !== 'gateway'), + ]; + }, + configure() { + const nrApplications = this.applications.length; + const allNew = allNewApplications(this.applications); + const interactiveFallback = !allNew; + + this.interactive = this.interactive ?? interactiveFallback; + this.force = this.options.force ?? (nrApplications > 0 && allNew) ? true : undefined; + this.reproducible = allNew; + }, + customizeApplication() { + for (const app of this.applications) { + app.config.entities = app.entities.map(entity => entity.name); + } + if (this.applications.length > 1) { + for (const app of this.applications) { + app.folder = app.config.baseName; + if (!this.interactive && !this.jsonOnly && !this.ignoreApplication) { + app.sharedFs = createMemFs(); + } + } + } + addApplicationIndex(this.applications); + customizeForMicroservices(this.exportedApplicationsWithEntities); + }, + async generateJson() { + if (this.applications.length === 0) { + this.writeConfig({ entities: this.exportedEntities }); + await this.env.sharedFs.pipeline( + { refresh: true }, + updateApplicationEntitiesTransform({ destinationPath: this.destinationPath(), throwOnMissingConfig: false }), + ); + } else { + this.writeConfig(...this.applications.map(app => (this.ignoreApplication ? { ...app, config: undefined } : app))); + } + }, + async generate() { + if (this.jsonOnly) { + return; + } + + const generatorOptions: any = { defaults: true, reproducible: this.reproducible, force: this.force }; + + if (this.ignoreApplication || this.applications.length === 0) { + if (this.applications.length === 0) { + const entities = this.exportedEntities; + await this.composeWithJHipster(GENERATOR_ENTITIES, { + generatorArgs: entities.map(entity => entity.name), + generatorOptions, + }); + } else { + for (const app of this.applications) { + await this.composeWithJHipster(GENERATOR_ENTITIES, { + generatorArgs: app.entities.map(entity => entity.name), + generatorOptions: { + ...generatorOptions, + destinationRoot: app.folder ? this.destinationPath(app.folder) : undefined, + }, + }); + } + } + } else if (this.applications.length === 1) { + this.log.info('Generating 1 application'); + await this.composeWithJHipster(GENERATOR_APP, { generatorOptions }); + } else { + this.log.info(`Generating ${this.applications.length} applications`); + await this.composeWithJHipster(GENERATOR_WORKSPACES, { + generatorOptions: { + workspacesFolders: this.applications.map(app => app.folder), + generateApplications: async () => this.runNonInteractive(this.applications, generatorOptions), + } as any, + }); + } + }, + }); + } + + get [BaseGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get end() { + return this.asEndTaskGroup({ + async generateDeployments() { + if (!this.exportedDeployments || this.exportedDeployments.length === 0) { + this.log.info('No deployment configured'); + return; + } + if (this.ignoreDeployments) { + this.log.info(`Ignoring ${this.exportedDeployments.length} deployments`); + return; + } + + this.log.info(`Generating ${this.exportedDeployments.length} deployments`); + for (const deployment of this.exportedDeployments) { + const deploymentConfig = deployment[GENERATOR_JHIPSTER]; + const deploymentType = deploymentConfig.deploymentType; + this.log.debug(`Generating deployment: ${JSON.stringify(deploymentConfig, null, 2)}`); + + await this.composeWithJHipster(deploymentType, { + generatorOptions: { + destinationRoot: this.destinationPath(deploymentType), + force: true, + skipPrompts: true, + } as any, + }); + } + }, + }); + } + + get [BaseGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + async runNonInteractive(applications: ApplicationWithEntitiesAndPath[], options) { + await Promise.all( + applications.map(async application => { + const rootCwd = this.destinationPath(); + const cwd = application.folder ? this.destinationPath(application.folder) : rootCwd; + const adapter = (this.env.adapter as QueuedAdapter).newAdapter(); + const envOptions: any = { cwd, logCwd: rootCwd, sharedFs: application.sharedFs, adapter }; + const generatorOptions = { ...this.options, ...options, skipPriorities: ['prompting'] }; + + // We should not reuse sharedData at non interactive runs + delete generatorOptions.sharedData; + + // Install should happen at the root of the monorepository. Force skip install at childs. + if (this.options.monorepository) { + generatorOptions.skipInstall = true; + } + const envBuilder = await this.createEnvBuilder(envOptions); + const env = envBuilder.getEnvironment(); + await env.run([`${CLI_NAME}:${GENERATOR_APP}`], generatorOptions); + }), + ); + } + + writeConfig(...applications: Partial[]) { + for (const application of applications) { + const { folder = '', entities = [], sharedFs } = application; + let { config, namespaceConfigs } = application; + + const appPath = folder ? `${folder}/` : folder; + const fs: MemFsEditor = sharedFs ? createMemFsEditor(sharedFs) : this.fs; + if (config) { + const configFile = this.destinationPath(`${appPath}.yo-rc.json`); + const oldConfig: any = fs.readJSON(configFile, {}); + if (Array.isArray(config.blueprints)) { + config = { + ...config, + blueprints: config.blueprints.map(({ name, ...remaining }) => ({ ...remaining, name: normalizeBlueprintName(name) })), + }; + } + if (namespaceConfigs) { + namespaceConfigs = Object.fromEntries( + Object.entries(namespaceConfigs).map(([ns, config]) => [normalizeBlueprintName(ns), config]), + ); + } + + fs.writeJSON(configFile, mergeYoRcContent(oldConfig, { ...namespaceConfigs, [GENERATOR_JHIPSTER]: config })); + } + for (const entity of entities) { + const configFile = this.destinationPath(`${appPath}${JHIPSTER_CONFIG_DIR}/${upperFirst(entity.name)}.json`); + const oldConfig: any = fs.readJSON(configFile, {}); + fs.writeJSON(configFile, { ...oldConfig, ...entity }); + } + } + } +} diff --git a/generators/jdl/index.mts b/generators/jdl/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/jdl/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/jdl/index.ts b/generators/jdl/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/jdl/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/jdl/internal/application.mts b/generators/jdl/internal/application.ts similarity index 100% rename from generators/jdl/internal/application.mts rename to generators/jdl/internal/application.ts diff --git a/generators/jdl/internal/index.mts b/generators/jdl/internal/index.mts deleted file mode 100644 index bfb44cf85ecc..000000000000 --- a/generators/jdl/internal/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './application.mjs'; -export * from './utils.mjs'; diff --git a/generators/jdl/internal/index.ts b/generators/jdl/internal/index.ts new file mode 100644 index 000000000000..8ed37c9cfe88 --- /dev/null +++ b/generators/jdl/internal/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './application.js'; +export * from './utils.js'; diff --git a/generators/jdl/internal/utils.mjs b/generators/jdl/internal/utils.js similarity index 100% rename from generators/jdl/internal/utils.mjs rename to generators/jdl/internal/utils.js diff --git a/generators/kubernetes-helm/__snapshots__/kubernetes.helm.spec.mts.snap b/generators/kubernetes-helm/__snapshots__/kubernetes.helm.spec.ts.snap similarity index 100% rename from generators/kubernetes-helm/__snapshots__/kubernetes.helm.spec.mts.snap rename to generators/kubernetes-helm/__snapshots__/kubernetes.helm.spec.ts.snap diff --git a/generators/kubernetes-helm/files.js b/generators/kubernetes-helm/files.js new file mode 100644 index 000000000000..627f53d787e6 --- /dev/null +++ b/generators/kubernetes-helm/files.js @@ -0,0 +1,123 @@ +/** + * Copyright 2013-2023 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 { + applicationTypes, + authenticationTypes, + kubernetesPlatformTypes, + monitoringTypes, + searchEngineTypes, + serviceDiscoveryTypes, +} from '../../jdl/jhipster/index.js'; + +const { ELASTICSEARCH } = searchEngineTypes; +const { GATEWAY, MONOLITH } = applicationTypes; +const { JWT } = authenticationTypes; +const { PROMETHEUS } = monitoringTypes; +const { CONSUL, EUREKA } = serviceDiscoveryTypes; +const { ServiceTypes } = kubernetesPlatformTypes; + +const { INGRESS } = ServiceTypes; + +export default { + writeFiles, +}; + +export function writeFiles() { + const suffix = 'helm'; + return { + writeAppChart() { + const kubernetesSubgenPath = this.fetchFromInstalledJHipster('kubernetes/templates'); + if (this.kubernetesNamespace !== 'default') { + this.writeFile(`${kubernetesSubgenPath}/namespace.yml.ejs`, 'namespace.yml'); + } + for (let i = 0; i < this.appConfigs.length; i++) { + const appName = this.appConfigs[i].baseName.toLowerCase(); + const appOut = appName.concat('-', suffix); + this.app = this.appConfigs[i]; + + this.writeFile(`${kubernetesSubgenPath}/deployment.yml.ejs`, `${appOut}/templates/${appName}-deployment.yml`); + this.writeFile(`${kubernetesSubgenPath}/service.yml.ejs`, `${appOut}/templates/${appName}-service.yml`); + this.writeFile('app/values.yml.ejs', `${appOut}/values.yaml`); + this.writeFile('app/Chart.yml.ejs', `${appOut}/Chart.yaml`); + this.writeFile('app/requirements.yml.ejs', `${appOut}/requirements.yaml`); + this.writeFile('app/helpers.tpl.ejs', `${appOut}/templates/_helpers.tpl`); + + if (this.app.searchEngine === ELASTICSEARCH) { + this.writeFile(`${kubernetesSubgenPath}/db/elasticsearch.yml.ejs`, `${appOut}/templates/${appName}-elasticsearch.yml`); + } + if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { + if (this.istio) { + this.writeFile(`${kubernetesSubgenPath}/istio/gateway.yml.ejs`, `${appOut}/templates/${appName}-gateway.yml`); + } else if (this.kubernetesServiceType === INGRESS) { + this.writeFile(`${kubernetesSubgenPath}/ingress.yml.ejs`, `${appOut}/templates/${appName}-ingress.yml`); + } + } + if (!this.app.serviceDiscoveryAny && this.app.authenticationType === JWT) { + this.writeFile(`${kubernetesSubgenPath}/secret/jwt-secret.yml.ejs`, `${appOut}/templates/jwt-secret.yml`); + } + if (this.app.databaseTypeCouchbase) { + this.writeFile(`${kubernetesSubgenPath}/secret/couchbase-secret.yml.ejs`, `${appOut}/templates/couchbase-secret.yml`); + } + if (this.istio) { + this.writeFile(`${kubernetesSubgenPath}/istio/destination-rule.yml.ejs`, `${appOut}/templates/${appName}-destination-rule.yml`); + this.writeFile(`${kubernetesSubgenPath}/istio/virtual-service.yml.ejs`, `${appOut}/templates/${appName}-virtual-service.yml`); + } + } + }, + + writeCommonServiceChart() { + const k8s = this.fetchFromInstalledJHipster('kubernetes/templates'); + const csOut = 'csvc'.concat('-', suffix); + if (this.useKafka || this.monitoring === PROMETHEUS || this.serviceDiscoveryType === EUREKA || this.serviceDiscoveryType === CONSUL) { + this.writeFile('csvc/values.yml.ejs', `${csOut}/values.yaml`); + this.writeFile('csvc/Chart.yml.ejs', `${csOut}/Chart.yaml`); + this.writeFile('csvc/requirements.yml.ejs', `${csOut}/requirements.yaml`); + this.writeFile('csvc/helpers.tpl.ejs', `${csOut}/templates/_helpers.tpl`); + } + if (this.monitoring === PROMETHEUS) { + if (this.istio && this.kubernetesServiceType === INGRESS) { + this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${csOut}/templates/jhipster-grafana-gateway.yml`); + } + } + if (this.serviceDiscoveryType === EUREKA) { + this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${csOut}/templates/jhipster-registry.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); + } + if (this.serviceDiscoveryType === CONSUL) { + this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${csOut}/templates/consul.yml`); + this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${csOut}/templates/consul-config-loader.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); + } + if (this.istio) { + this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${csOut}/templates/grafana-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${csOut}/templates/zipkin-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${csOut}/templates/kiali-gateway.yml`); + } + }, + + writeReadme() { + this.writeFile('README-KUBERNETES-HELM.md.ejs', 'HELM-README.md'); + }, + + writeConfigRunFile() { + this.writeFile('helm-apply.sh.ejs', 'helm-apply.sh'); + this.writeFile('helm-upgrade.sh.ejs', 'helm-upgrade.sh'); + }, + }; +} diff --git a/generators/kubernetes-helm/files.mjs b/generators/kubernetes-helm/files.mjs deleted file mode 100644 index 4c7f2c24d195..000000000000 --- a/generators/kubernetes-helm/files.mjs +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright 2013-2023 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 { - applicationTypes, - authenticationTypes, - kubernetesPlatformTypes, - monitoringTypes, - searchEngineTypes, - serviceDiscoveryTypes, -} from '../../jdl/jhipster/index.mjs'; - -const { ELASTICSEARCH } = searchEngineTypes; -const { GATEWAY, MONOLITH } = applicationTypes; -const { JWT } = authenticationTypes; -const { PROMETHEUS } = monitoringTypes; -const { CONSUL, EUREKA } = serviceDiscoveryTypes; -const { ServiceTypes } = kubernetesPlatformTypes; - -const { INGRESS } = ServiceTypes; - -export default { - writeFiles, -}; - -export function writeFiles() { - const suffix = 'helm'; - return { - writeAppChart() { - const kubernetesSubgenPath = this.fetchFromInstalledJHipster('kubernetes/templates'); - if (this.kubernetesNamespace !== 'default') { - this.writeFile(`${kubernetesSubgenPath}/namespace.yml.ejs`, 'namespace.yml'); - } - for (let i = 0; i < this.appConfigs.length; i++) { - const appName = this.appConfigs[i].baseName.toLowerCase(); - const appOut = appName.concat('-', suffix); - this.app = this.appConfigs[i]; - - this.writeFile(`${kubernetesSubgenPath}/deployment.yml.ejs`, `${appOut}/templates/${appName}-deployment.yml`); - this.writeFile(`${kubernetesSubgenPath}/service.yml.ejs`, `${appOut}/templates/${appName}-service.yml`); - this.writeFile('app/values.yml.ejs', `${appOut}/values.yaml`); - this.writeFile('app/Chart.yml.ejs', `${appOut}/Chart.yaml`); - this.writeFile('app/requirements.yml.ejs', `${appOut}/requirements.yaml`); - this.writeFile('app/helpers.tpl.ejs', `${appOut}/templates/_helpers.tpl`); - - if (this.app.searchEngine === ELASTICSEARCH) { - this.writeFile(`${kubernetesSubgenPath}/db/elasticsearch.yml.ejs`, `${appOut}/templates/${appName}-elasticsearch.yml`); - } - if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { - if (this.istio) { - this.writeFile(`${kubernetesSubgenPath}/istio/gateway.yml.ejs`, `${appOut}/templates/${appName}-gateway.yml`); - } else if (this.kubernetesServiceType === INGRESS) { - this.writeFile(`${kubernetesSubgenPath}/ingress.yml.ejs`, `${appOut}/templates/${appName}-ingress.yml`); - } - } - if (!this.app.serviceDiscoveryAny && this.app.authenticationType === JWT) { - this.writeFile(`${kubernetesSubgenPath}/secret/jwt-secret.yml.ejs`, `${appOut}/templates/jwt-secret.yml`); - } - if (this.app.databaseTypeCouchbase) { - this.writeFile(`${kubernetesSubgenPath}/secret/couchbase-secret.yml.ejs`, `${appOut}/templates/couchbase-secret.yml`); - } - if (this.istio) { - this.writeFile(`${kubernetesSubgenPath}/istio/destination-rule.yml.ejs`, `${appOut}/templates/${appName}-destination-rule.yml`); - this.writeFile(`${kubernetesSubgenPath}/istio/virtual-service.yml.ejs`, `${appOut}/templates/${appName}-virtual-service.yml`); - } - } - }, - - writeCommonServiceChart() { - const k8s = this.fetchFromInstalledJHipster('kubernetes/templates'); - const csOut = 'csvc'.concat('-', suffix); - if (this.useKafka || this.monitoring === PROMETHEUS || this.serviceDiscoveryType === EUREKA || this.serviceDiscoveryType === CONSUL) { - this.writeFile('csvc/values.yml.ejs', `${csOut}/values.yaml`); - this.writeFile('csvc/Chart.yml.ejs', `${csOut}/Chart.yaml`); - this.writeFile('csvc/requirements.yml.ejs', `${csOut}/requirements.yaml`); - this.writeFile('csvc/helpers.tpl.ejs', `${csOut}/templates/_helpers.tpl`); - } - if (this.monitoring === PROMETHEUS) { - if (this.istio && this.kubernetesServiceType === INGRESS) { - this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${csOut}/templates/jhipster-grafana-gateway.yml`); - } - } - if (this.serviceDiscoveryType === EUREKA) { - this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${csOut}/templates/jhipster-registry.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); - } - if (this.serviceDiscoveryType === CONSUL) { - this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${csOut}/templates/consul.yml`); - this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${csOut}/templates/consul-config-loader.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); - } - if (this.istio) { - this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${csOut}/templates/grafana-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${csOut}/templates/zipkin-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${csOut}/templates/kiali-gateway.yml`); - } - }, - - writeReadme() { - this.writeFile('README-KUBERNETES-HELM.md.ejs', 'HELM-README.md'); - }, - - writeConfigRunFile() { - this.writeFile('helm-apply.sh.ejs', 'helm-apply.sh'); - this.writeFile('helm-upgrade.sh.ejs', 'helm-upgrade.sh'); - }, - }; -} diff --git a/generators/kubernetes-helm/generator.js b/generators/kubernetes-helm/generator.js new file mode 100644 index 000000000000..acd3b773cc8d --- /dev/null +++ b/generators/kubernetes-helm/generator.js @@ -0,0 +1,228 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return, import/no-named-as-default-member */ +import fs from 'fs'; +import chalk from 'chalk'; + +import BaseWorkspacesGenerator from '../base-workspaces/index.js'; + +import prompts from '../kubernetes/prompts.js'; +import { writeFiles } from './files.js'; +import { GENERATOR_KUBERNETES_HELM } from '../generator-list.js'; +import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.js'; +import { + checkKubernetes, + checkHelm, + loadConfig, + setupKubernetesConstants, + setupHelmConstants, + derivedKubernetesPlatformProperties, +} from '../kubernetes/kubernetes-base.js'; +import statistics from '../statistics.js'; +import { messageBrokerTypes } from '../../jdl/jhipster/index.js'; +import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.js'; +import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.js'; +import { checkDocker } from '../docker/support/index.js'; +import { loadDerivedServerConfig } from '../server/support/index.js'; +import { loadDerivedAppConfig } from '../app/support/index.js'; + +const { KAFKA } = messageBrokerTypes; + +/** + * @class + * @extends {BaseWorkspacesGenerator} + */ +export default class KubernetesHelmGenerator extends BaseWorkspacesGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_KUBERNETES_HELM); + } + } + + get initializing() { + return { + sayHello() { + this.log.log(chalk.white(`${chalk.bold('⎈')} Welcome to the JHipster Kubernetes Helm Generator ${chalk.bold('⎈')}`)); + this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); + }, + existingDeployment() { + this.regenerate = this.regenerate || this.config.existed; + }, + loadDockerDependenciesTask, + checkDocker, + checkKubernetes, + checkHelm, + loadConfig, + setupKubernetesConstants, + setupHelmConstants, + }; + } + + get [BaseWorkspacesGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return { + askForApplicationType: prompts.askForApplicationType, + askForPath: prompts.askForPath, + askForApps: prompts.askForApps, + askForMonitoring: prompts.askForMonitoring, + askForClustersMode: prompts.askForClustersMode, + askForServiceDiscovery: prompts.askForServiceDiscovery, + askForAdminPassword: prompts.askForAdminPassword, + askForKubernetesNamespace: prompts.askForKubernetesNamespace, + askForDockerRepositoryName: prompts.askForDockerRepositoryName, + askForDockerPushCommand: prompts.askForDockerPushCommand, + askForIstioSupport: prompts.askForIstioSupport, + askForKubernetesServiceType: prompts.askForKubernetesServiceType, + askForIngressType: prompts.askForIngressType, + askForIngressDomain: prompts.askForIngressDomain, + }; + } + + get [BaseWorkspacesGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get configuring() { + return { + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES_HELM); + }, + + generateJwtSecret, + }; + } + + get [BaseWorkspacesGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get loading() { + return { + loadFromYoRc, + loadSharedConfig() { + this.appConfigs.forEach(element => { + loadDerivedAppConfig({ application: element }); + loadDerivedServerConfig({ application: element }); + }); + loadDeploymentConfig.call(this); + derivedKubernetesPlatformProperties(this); + }, + }; + } + + get [BaseWorkspacesGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return { + configureImageNames, + + setPostPromptProp() { + this.appConfigs.forEach(element => { + element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); + if (element.messageBroker === KAFKA) { + this.useKafka = true; + } + }); + this.useKeycloak = false; + }, + }; + } + + get [BaseWorkspacesGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return writeFiles(); + } + + get [BaseWorkspacesGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get end() { + return { + checkImages, + deploy() { + if (this.hasWarning) { + this.log.warn('Helm configuration generated, but no Jib cache found'); + this.log.warn('If you forgot to generate the Docker image for this application, please run:'); + this.log.warn(this.warningMessage); + } else { + this.log.verboseInfo(`\n${chalk.bold.green('Helm configuration successfully generated!')}`); + } + this.log.warn( + 'You will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', + ); + for (let i = 0; i < this.appsFolders.length; i++) { + const originalImageName = this.appConfigs[i].baseName.toLowerCase(); + const targetImageName = this.appConfigs[i].targetImageName; + if (originalImageName !== targetImageName) { + this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); + } + this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); + } + this.log.log('\nYou can deploy all your apps by running the following script:'); + this.log.verboseInfo(` ${chalk.cyan('bash helm-apply.sh')}`); + this.log.log('\nYou can upgrade (after any changes) all your apps by running the following script:'); + this.log.verboseInfo(` ${chalk.cyan('bash helm-upgrade.sh')}`); + // Make the apply script executable + try { + fs.chmodSync('helm-apply.sh', '755'); + fs.chmodSync('helm-upgrade.sh', '755'); + } catch (err) { + this.log.warn( + "Failed to make 'helm-apply.sh', 'helm-upgrade.sh' executable, you may need to run 'chmod +x helm-apply.sh helm-upgrade.sh", + ); + } + }, + }; + } + + get [BaseWorkspacesGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * @private + * Returns the JDBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getJDBCUrl(databaseType, options = {}) { + return getJdbcUrl(databaseType, options); + } + + /** + * @private + * Returns the R2DBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getR2DBCUrl(databaseType, options = {}) { + return getR2dbcUrl(databaseType, options); + } +} diff --git a/generators/kubernetes-helm/generator.mjs b/generators/kubernetes-helm/generator.mjs deleted file mode 100644 index ea224f59e845..000000000000 --- a/generators/kubernetes-helm/generator.mjs +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return, import/no-named-as-default-member */ -import fs from 'fs'; -import chalk from 'chalk'; - -import BaseWorkspacesGenerator from '../base-workspaces/index.mjs'; - -import prompts from '../kubernetes/prompts.mjs'; -import { writeFiles } from './files.mjs'; -import { GENERATOR_KUBERNETES_HELM } from '../generator-list.mjs'; -import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.mjs'; -import { - checkKubernetes, - checkHelm, - loadConfig, - setupKubernetesConstants, - setupHelmConstants, - derivedKubernetesPlatformProperties, -} from '../kubernetes/kubernetes-base.mjs'; -import statistics from '../statistics.mjs'; -import { messageBrokerTypes } from '../../jdl/jhipster/index.mjs'; -import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.mjs'; -import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.mjs'; -import { checkDocker } from '../docker/support/index.mjs'; -import { loadDerivedServerConfig } from '../server/support/index.mjs'; -import { loadDerivedAppConfig } from '../app/support/index.mjs'; - -const { KAFKA } = messageBrokerTypes; - -/** - * @class - * @extends {BaseWorkspacesGenerator} - */ -export default class KubernetesHelmGenerator extends BaseWorkspacesGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_KUBERNETES_HELM); - } - } - - get initializing() { - return { - sayHello() { - this.log.log(chalk.white(`${chalk.bold('⎈')} Welcome to the JHipster Kubernetes Helm Generator ${chalk.bold('⎈')}`)); - this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); - }, - existingDeployment() { - this.regenerate = this.regenerate || this.config.existed; - }, - loadDockerDependenciesTask, - checkDocker, - checkKubernetes, - checkHelm, - loadConfig, - setupKubernetesConstants, - setupHelmConstants, - }; - } - - get [BaseWorkspacesGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return { - askForApplicationType: prompts.askForApplicationType, - askForPath: prompts.askForPath, - askForApps: prompts.askForApps, - askForMonitoring: prompts.askForMonitoring, - askForClustersMode: prompts.askForClustersMode, - askForServiceDiscovery: prompts.askForServiceDiscovery, - askForAdminPassword: prompts.askForAdminPassword, - askForKubernetesNamespace: prompts.askForKubernetesNamespace, - askForDockerRepositoryName: prompts.askForDockerRepositoryName, - askForDockerPushCommand: prompts.askForDockerPushCommand, - askForIstioSupport: prompts.askForIstioSupport, - askForKubernetesServiceType: prompts.askForKubernetesServiceType, - askForIngressType: prompts.askForIngressType, - askForIngressDomain: prompts.askForIngressDomain, - }; - } - - get [BaseWorkspacesGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get configuring() { - return { - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES_HELM); - }, - - generateJwtSecret, - }; - } - - get [BaseWorkspacesGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get loading() { - return { - loadFromYoRc, - loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); - loadDeploymentConfig.call(this); - derivedKubernetesPlatformProperties(this); - }, - }; - } - - get [BaseWorkspacesGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return { - configureImageNames, - - setPostPromptProp() { - this.appConfigs.forEach(element => { - element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); - if (element.messageBroker === KAFKA) { - this.useKafka = true; - } - }); - this.useKeycloak = false; - }, - }; - } - - get [BaseWorkspacesGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return writeFiles(); - } - - get [BaseWorkspacesGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get end() { - return { - checkImages, - deploy() { - if (this.hasWarning) { - this.log.warn('Helm configuration generated, but no Jib cache found'); - this.log.warn('If you forgot to generate the Docker image for this application, please run:'); - this.log.warn(this.warningMessage); - } else { - this.log.verboseInfo(`\n${chalk.bold.green('Helm configuration successfully generated!')}`); - } - this.log.warn( - 'You will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', - ); - for (let i = 0; i < this.appsFolders.length; i++) { - const originalImageName = this.appConfigs[i].baseName.toLowerCase(); - const targetImageName = this.appConfigs[i].targetImageName; - if (originalImageName !== targetImageName) { - this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); - } - this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); - } - this.log.log('\nYou can deploy all your apps by running the following script:'); - this.log.verboseInfo(` ${chalk.cyan('bash helm-apply.sh')}`); - this.log.log('\nYou can upgrade (after any changes) all your apps by running the following script:'); - this.log.verboseInfo(` ${chalk.cyan('bash helm-upgrade.sh')}`); - // Make the apply script executable - try { - fs.chmodSync('helm-apply.sh', '755'); - fs.chmodSync('helm-upgrade.sh', '755'); - } catch (err) { - this.log.warn( - "Failed to make 'helm-apply.sh', 'helm-upgrade.sh' executable, you may need to run 'chmod +x helm-apply.sh helm-upgrade.sh", - ); - } - }, - }; - } - - get [BaseWorkspacesGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * @private - * Returns the JDBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getJDBCUrl(databaseType, options = {}) { - return getJdbcUrl(databaseType, options); - } - - /** - * @private - * Returns the R2DBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getR2DBCUrl(databaseType, options = {}) { - return getR2dbcUrl(databaseType, options); - } -} diff --git a/generators/kubernetes-helm/generator.spec.js b/generators/kubernetes-helm/generator.spec.js new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/kubernetes-helm/generator.spec.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/kubernetes-helm/generator.spec.mjs b/generators/kubernetes-helm/generator.spec.mjs deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/kubernetes-helm/generator.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/kubernetes-helm/index.mts b/generators/kubernetes-helm/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/kubernetes-helm/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/kubernetes-helm/index.ts b/generators/kubernetes-helm/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/kubernetes-helm/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/kubernetes-helm/kubernetes.helm.spec.mts b/generators/kubernetes-helm/kubernetes.helm.spec.mts deleted file mode 100644 index 07e3c5a1ab65..000000000000 --- a/generators/kubernetes-helm/kubernetes.helm.spec.mts +++ /dev/null @@ -1,560 +0,0 @@ -import { expect } from 'esmocha'; - -import { basicHelpers as helpers, getGenerator } from '../../test/support/index.mjs'; -import { GENERATOR_KUBERNETES_HELM } from '../generator-list.mjs'; - -const expectedFiles = { - csvcfiles: ['./csvc-helm/Chart.yaml', './csvc-helm/requirements.yaml', './csvc-helm/values.yaml', './csvc-helm/templates/_helpers.tpl'], - eurekaregistry: ['./csvc-helm/templates/jhipster-registry.yml', './csvc-helm/templates/application-configmap.yml'], - consulregistry: [ - './csvc-helm/templates/consul.yml', - './csvc-helm/templates/consul-config-loader.yml', - './csvc-helm/templates/application-configmap.yml', - ], - jhgate: [ - './jhgate-helm/templates/jhgate-deployment.yml', - './jhgate-helm/templates/jhgate-service.yml', - './jhgate-helm/Chart.yaml', - './jhgate-helm/requirements.yaml', - './jhgate-helm/values.yaml', - './jhgate-helm/templates/_helpers.tpl', - ], - jhgateingress: ['./jhgate-helm/templates/jhgate-ingress.yml'], - customnamespace: ['./namespace.yml'], - msmysql: [ - './msmysql-helm/Chart.yaml', - './msmysql-helm/requirements.yaml', - './msmysql-helm/values.yaml', - './msmysql-helm/templates/_helpers.tpl', - './msmysql-helm/templates/msmysql-deployment.yml', - './msmysql-helm/templates/msmysql-service.yml', - ], - mspsql: [ - './mspsql-helm/Chart.yaml', - './mspsql-helm/requirements.yaml', - './mspsql-helm/values.yaml', - './mspsql-helm/templates/_helpers.tpl', - './mspsql-helm/templates/mspsql-deployment.yml', - './mspsql-helm/templates/mspsql-service.yml', - ], - msmongodb: [ - './msmongodb-helm/Chart.yaml', - './msmongodb-helm/requirements.yaml', - './msmongodb-helm/values.yaml', - './msmongodb-helm/templates/_helpers.tpl', - './msmongodb-helm/templates/msmongodb-deployment.yml', - './msmongodb-helm/templates/msmongodb-service.yml', - ], - msmariadb: [ - './msmariadb-helm/Chart.yaml', - './msmariadb-helm/requirements.yaml', - './msmariadb-helm/values.yaml', - './msmariadb-helm/templates/_helpers.tpl', - './msmariadb-helm/templates/msmariadb-deployment.yml', - './msmariadb-helm/templates/msmariadb-service.yml', - ], - monolith: [ - './samplemysql-helm/Chart.yaml', - './samplemysql-helm/requirements.yaml', - './samplemysql-helm/values.yaml', - './samplemysql-helm/templates/_helpers.tpl', - './samplemysql-helm/templates/samplemysql-deployment.yml', - './samplemysql-helm/templates/samplemysql-service.yml', - './samplemysql-helm/templates/samplemysql-elasticsearch.yml', - ], - kafka: ['./samplekafka-helm/templates/samplekafka-deployment.yml', './samplekafka-helm/templates/samplekafka-service.yml'], - jhgategateway: [ - './jhgate-helm/templates/jhgate-gateway.yml', - './jhgate-helm/templates/jhgate-destination-rule.yml', - './jhgate-helm/templates/jhgate-virtual-service.yml', - ], - applyScript: ['./helm-apply.sh', './helm-upgrade.sh'], -}; - -describe('generator - Kubernetes Helm', () => { - describe('only gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - adminPassword: 'meetup', - dockerRepositoryName: 'jhipsterrepository', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'jhipsternamespace', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files and content', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected gateway files and content', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFileContent('./jhgate-helm/requirements.yaml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and mysql microservice', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - jhipsterConsole: true, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and ingress', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - istio: false, - kubernetesServiceType: 'Ingress', - ingressType: 'gke', - ingressDomain: 'example.com', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected ingress files', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFile(expectedFiles.csvcfiles); - runResult.assertFile(expectedFiles.jhgateingress); - runResult.assertFileContent('./jhgate-helm/requirements.yaml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('MySQL and PostgreSQL microservices without gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql', '03-psql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it("doesn't creates gateway files", () => { - runResult.assertNoFile(expectedFiles.jhgate); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - runResult.assertFileContent('./mspsql-helm/requirements.yaml', /name: postgresql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway, mysql, psql, mongodb, mariadb microservices', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - istio: false, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - runResult.assertFileContent('./mspsql-helm/requirements.yaml', /name: postgresql/); - }); - it('creates expected mongodb files', () => { - runResult.assertFile(expectedFiles.msmongodb); - runResult.assertFileContent('./msmongodb-helm/requirements.yaml', /name: mongodb-replicaset/); - }); - it('creates expected mariadb files', () => { - runResult.assertFile(expectedFiles.msmariadb); - runResult.assertFileContent('./msmariadb-helm/requirements.yaml', /name: mariadb/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('monolith application', () => { - let runResult; - before(async () => { - const chosenApps = ['08-monolith']; - - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'monolith', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it("doesn't creates registry files", () => { - runResult.assertNoFile(expectedFiles.consulregistry); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('./samplemysql-helm/requirements.yaml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('Kafka application', () => { - let runResult; - before(async () => { - const chosenApps = ['09-kafka']; - - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'monolith', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.csvcfiles); - runResult.assertFile(expectedFiles.kafka); - runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: kafka/); - runResult.assertFileContent('./samplekafka-helm/requirements.yaml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - monitoring: 'prometheus', - kubernetesServiceType: 'LoadBalancer', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); - }); - it('creates expected prometheus files', () => { - runResult.assertFile(expectedFiles.csvcfiles); - runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: prometheus/); - runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: grafana/); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway with istio', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_HELM)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - ingressDomain: 'example.com', - clusteredDbApps: [], - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected service gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFile(expectedFiles.csvcfiles); - }); - it('creates expected routing gateway and istio files', () => { - runResult.assertFile(expectedFiles.jhgategateway); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); -}); diff --git a/generators/kubernetes-helm/kubernetes.helm.spec.ts b/generators/kubernetes-helm/kubernetes.helm.spec.ts new file mode 100644 index 000000000000..ef43fbaffab7 --- /dev/null +++ b/generators/kubernetes-helm/kubernetes.helm.spec.ts @@ -0,0 +1,560 @@ +import { expect } from 'esmocha'; + +import { basicHelpers as helpers, getGenerator } from '../../test/support/index.js'; +import { GENERATOR_KUBERNETES_HELM } from '../generator-list.js'; + +const expectedFiles = { + csvcfiles: ['./csvc-helm/Chart.yaml', './csvc-helm/requirements.yaml', './csvc-helm/values.yaml', './csvc-helm/templates/_helpers.tpl'], + eurekaregistry: ['./csvc-helm/templates/jhipster-registry.yml', './csvc-helm/templates/application-configmap.yml'], + consulregistry: [ + './csvc-helm/templates/consul.yml', + './csvc-helm/templates/consul-config-loader.yml', + './csvc-helm/templates/application-configmap.yml', + ], + jhgate: [ + './jhgate-helm/templates/jhgate-deployment.yml', + './jhgate-helm/templates/jhgate-service.yml', + './jhgate-helm/Chart.yaml', + './jhgate-helm/requirements.yaml', + './jhgate-helm/values.yaml', + './jhgate-helm/templates/_helpers.tpl', + ], + jhgateingress: ['./jhgate-helm/templates/jhgate-ingress.yml'], + customnamespace: ['./namespace.yml'], + msmysql: [ + './msmysql-helm/Chart.yaml', + './msmysql-helm/requirements.yaml', + './msmysql-helm/values.yaml', + './msmysql-helm/templates/_helpers.tpl', + './msmysql-helm/templates/msmysql-deployment.yml', + './msmysql-helm/templates/msmysql-service.yml', + ], + mspsql: [ + './mspsql-helm/Chart.yaml', + './mspsql-helm/requirements.yaml', + './mspsql-helm/values.yaml', + './mspsql-helm/templates/_helpers.tpl', + './mspsql-helm/templates/mspsql-deployment.yml', + './mspsql-helm/templates/mspsql-service.yml', + ], + msmongodb: [ + './msmongodb-helm/Chart.yaml', + './msmongodb-helm/requirements.yaml', + './msmongodb-helm/values.yaml', + './msmongodb-helm/templates/_helpers.tpl', + './msmongodb-helm/templates/msmongodb-deployment.yml', + './msmongodb-helm/templates/msmongodb-service.yml', + ], + msmariadb: [ + './msmariadb-helm/Chart.yaml', + './msmariadb-helm/requirements.yaml', + './msmariadb-helm/values.yaml', + './msmariadb-helm/templates/_helpers.tpl', + './msmariadb-helm/templates/msmariadb-deployment.yml', + './msmariadb-helm/templates/msmariadb-service.yml', + ], + monolith: [ + './samplemysql-helm/Chart.yaml', + './samplemysql-helm/requirements.yaml', + './samplemysql-helm/values.yaml', + './samplemysql-helm/templates/_helpers.tpl', + './samplemysql-helm/templates/samplemysql-deployment.yml', + './samplemysql-helm/templates/samplemysql-service.yml', + './samplemysql-helm/templates/samplemysql-elasticsearch.yml', + ], + kafka: ['./samplekafka-helm/templates/samplekafka-deployment.yml', './samplekafka-helm/templates/samplekafka-service.yml'], + jhgategateway: [ + './jhgate-helm/templates/jhgate-gateway.yml', + './jhgate-helm/templates/jhgate-destination-rule.yml', + './jhgate-helm/templates/jhgate-virtual-service.yml', + ], + applyScript: ['./helm-apply.sh', './helm-upgrade.sh'], +}; + +describe('generator - Kubernetes Helm', () => { + describe('only gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + adminPassword: 'meetup', + dockerRepositoryName: 'jhipsterrepository', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'jhipsternamespace', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files and content', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected gateway files and content', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFileContent('./jhgate-helm/requirements.yaml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and mysql microservice', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + jhipsterConsole: true, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and ingress', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + istio: false, + kubernetesServiceType: 'Ingress', + ingressType: 'gke', + ingressDomain: 'example.com', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected ingress files', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFile(expectedFiles.csvcfiles); + runResult.assertFile(expectedFiles.jhgateingress); + runResult.assertFileContent('./jhgate-helm/requirements.yaml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('MySQL and PostgreSQL microservices without gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql', '03-psql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it("doesn't creates gateway files", () => { + runResult.assertNoFile(expectedFiles.jhgate); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + runResult.assertFileContent('./mspsql-helm/requirements.yaml', /name: postgresql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway, mysql, psql, mongodb, mariadb microservices', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + istio: false, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + runResult.assertFileContent('./mspsql-helm/requirements.yaml', /name: postgresql/); + }); + it('creates expected mongodb files', () => { + runResult.assertFile(expectedFiles.msmongodb); + runResult.assertFileContent('./msmongodb-helm/requirements.yaml', /name: mongodb-replicaset/); + }); + it('creates expected mariadb files', () => { + runResult.assertFile(expectedFiles.msmariadb); + runResult.assertFileContent('./msmariadb-helm/requirements.yaml', /name: mariadb/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('monolith application', () => { + let runResult; + before(async () => { + const chosenApps = ['08-monolith']; + + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'monolith', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it("doesn't creates registry files", () => { + runResult.assertNoFile(expectedFiles.consulregistry); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.monolith); + runResult.assertFileContent('./samplemysql-helm/requirements.yaml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('Kafka application', () => { + let runResult; + before(async () => { + const chosenApps = ['09-kafka']; + + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'monolith', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.csvcfiles); + runResult.assertFile(expectedFiles.kafka); + runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: kafka/); + runResult.assertFileContent('./samplekafka-helm/requirements.yaml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + monitoring: 'prometheus', + kubernetesServiceType: 'LoadBalancer', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent('./msmysql-helm/requirements.yaml', /name: mysql/); + }); + it('creates expected prometheus files', () => { + runResult.assertFile(expectedFiles.csvcfiles); + runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: prometheus/); + runResult.assertFileContent('./csvc-helm/requirements.yaml', /name: grafana/); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway with istio', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_HELM)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + ingressDomain: 'example.com', + clusteredDbApps: [], + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected service gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFile(expectedFiles.csvcfiles); + }); + it('creates expected routing gateway and istio files', () => { + runResult.assertFile(expectedFiles.jhgategateway); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); +}); diff --git a/generators/kubernetes-knative/__snapshots__/knative.spec.mts.snap b/generators/kubernetes-knative/__snapshots__/knative.spec.ts.snap similarity index 100% rename from generators/kubernetes-knative/__snapshots__/knative.spec.mts.snap rename to generators/kubernetes-knative/__snapshots__/knative.spec.ts.snap diff --git a/generators/kubernetes-knative/files.js b/generators/kubernetes-knative/files.js new file mode 100644 index 000000000000..953abb8f20a4 --- /dev/null +++ b/generators/kubernetes-knative/files.js @@ -0,0 +1,168 @@ +/** + * Copyright 2013-2023 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 { + applicationTypes, + authenticationTypes, + databaseTypes, + kubernetesPlatformTypes, + monitoringTypes, + searchEngineTypes, + serviceDiscoveryTypes, +} from '../../jdl/jhipster/index.js'; + +const { ELASTICSEARCH } = searchEngineTypes; +const { GATEWAY, MONOLITH } = applicationTypes; +const { JWT } = authenticationTypes; +const { PROMETHEUS } = monitoringTypes; +const { CONSUL, EUREKA } = serviceDiscoveryTypes; +const { COUCHBASE } = databaseTypes; +const NO_DATABASE_TYPE = databaseTypes.NO; +const { GeneratorTypes } = kubernetesPlatformTypes; + +const { K8S } = GeneratorTypes; + +// eslint-disable-next-line import/prefer-default-export +export function writeFiles() { + const suffix = 'knative'; + return { + writeGeneratorFiles() { + const k8s = this.fetchFromInstalledJHipster('kubernetes/templates'); + const helm = this.fetchFromInstalledJHipster('kubernetes-helm/templates'); + if (this.kubernetesNamespace !== 'default') { + this.writeFile(`${k8s}/namespace.yml.ejs`, 'namespace.yml'); + } + this.writeFile('README-KUBERNETES-KNATIVE.md.ejs', 'KNATIVE-README.md'); + if (this.generatorType === K8S) { + for (let i = 0; i < this.appConfigs.length; i++) { + this.app = this.appConfigs[i]; + const appName = this.app.baseName.toLowerCase(); + const appOut = appName.concat('-', suffix); + + this.writeFile('service.yml.ejs', `${appOut}/${appName}-service.yml`); + // If we choose microservice with no DB, it is trying to move _no.yml as prodDatabaseType is getting tagged as 'string' type + if (this.app.databaseType !== NO_DATABASE_TYPE) { + const databaseType = this.app.prodDatabaseType ?? this.app.databaseType; + this.writeFile(`${k8s}/db/${databaseType}.yml.ejs`, `${appOut}/${appName}-${databaseType}.yml`); + } + if (this.app.searchEngine === ELASTICSEARCH) { + this.writeFile(`${k8s}/db/elasticsearch.yml.ejs`, `${appOut}/${appName}-elasticsearch.yml`); + } + if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { + this.writeFile('istio/gateway.yml.ejs', `${appOut}/${appName}-gateway.yml`); + } + if (!this.app.serviceDiscoveryType && this.app.authenticationType === JWT) { + this.writeFile(`${k8s}/secret/jwt-secret.yml.ejs`, `${appOut}/jwt-secret.yml`); + } + if (this.monitoring === PROMETHEUS) { + this.writeFile(`${k8s}/monitoring/jhipster-prometheus-sm.yml.ejs`, `${appOut}/${appName}-prometheus-sm.yml`); + } + this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/${appName}-destination-rule.yml`); + this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/${appName}-virtual-service.yml`); + } + + if (this.useKafka) { + this.writeFile(`${k8s}/messagebroker/kafka.yml.ejs`, `messagebroker-${suffix}/kafka.yml`); + } + + if (this.monitoring === PROMETHEUS) { + const monitOut = 'monitoring'.concat('-', suffix); + this.writeFile(`${k8s}/monitoring/jhipster-prometheus-crd.yml.ejs`, `${monitOut}/jhipster-prometheus-crd.yml`); + this.writeFile(`${k8s}/monitoring/jhipster-prometheus-cr.yml.ejs`, `${monitOut}/jhipster-prometheus-cr.yml`); + this.writeFile(`${k8s}/monitoring/jhipster-grafana.yml.ejs`, `${monitOut}/jhipster-grafana.yml`); + this.writeFile(`${k8s}/monitoring/jhipster-grafana-dashboard.yml.ejs`, `${monitOut}/jhipster-grafana-dashboard.yml`); + this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${monitOut}/jhipster-grafana-gateway.yml`); + } + + const registryOut = 'registry'.concat('-', suffix); + if (this.serviceDiscoveryType === EUREKA) { + this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${registryOut}/jhipster-registry.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${registryOut}/application-configmap.yml`); + } else if (this.serviceDiscoveryType === CONSUL) { + this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${registryOut}/consul.yml`); + this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${registryOut}/consul-config-loader.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${registryOut}/application-configmap.yml`); + } + + const istioOut = 'istio'.concat('-', suffix); + this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${istioOut}/grafana-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${istioOut}/zipkin-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${istioOut}/kiali-gateway.yml`); + this.writeFile('kubectl-apply.sh.ejs', 'kubectl-knative-apply.sh'); + } else { + for (let i = 0; i < this.appConfigs.length; i++) { + this.app = this.appConfigs[i]; + const appName = this.app.baseName.toLowerCase(); + const appOut = appName.concat('-', suffix); + + this.writeFile('service.yml.ejs', `${appOut}/templates/${appName}-service.yml`); + this.writeFile(`${helm}/app/values.yml.ejs`, `${appOut}/values.yml`); + this.writeFile(`${helm}/app/Chart.yml.ejs`, `${appOut}/Chart.yaml`); + this.writeFile(`${helm}/app/requirements.yml.ejs`, `${appOut}/requirements.yml`); + this.writeFile(`${helm}/app/helpers.tpl.ejs`, `${appOut}/templates/_helpers.tpl`); + + if (this.app.databaseType === COUCHBASE) { + this.writeFile(`${k8s}/db/${this.app.databaseType}.yml.ejs`, `${appOut}/templates/${appName}-${this.app.databaseType}.yml`); + } + + if (this.app.searchEngine === ELASTICSEARCH) { + this.writeFile(`${k8s}/db/elasticsearch.yml.ejs`, `${appOut}/templates/${appName}-elasticsearch.yml`); + } + if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { + this.writeFile('istio/gateway.yml.ejs', `${appOut}/templates/${appName}-gateway.yml`); + } + if (!this.app.serviceDiscoveryType && this.app.authenticationType === JWT) { + this.writeFile(`${k8s}/secret/jwt-secret.yml.ejs`, `${appOut}/templates/jwt-secret.yml`); + } + this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/templates/${appName}-destination-rule.yml`); + this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/templates/${appName}-virtual-service.yml`); + } + + const csOut = 'csvc'.concat('-', suffix); + if ( + this.useKafka || + this.monitoring === PROMETHEUS || + this.serviceDiscoveryType === EUREKA || + this.serviceDiscoveryType === CONSUL + ) { + this.writeFile(`${helm}/csvc/values.yml.ejs`, `${csOut}/values.yml`); + this.writeFile(`${helm}/csvc/Chart.yml.ejs`, `${csOut}/Chart.yaml`); + this.writeFile(`${helm}/csvc/requirements.yml.ejs`, `${csOut}/requirements.yml`); + this.writeFile(`${helm}/csvc/helpers.tpl.ejs`, `${csOut}/templates/_helpers.tpl`); + } + if (this.monitoring === PROMETHEUS) { + this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${csOut}/templates/jhipster-grafana-gateway.yml`); + } + if (this.serviceDiscoveryType === EUREKA) { + this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${csOut}/templates/jhipster-registry.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); + } + if (this.serviceDiscoveryType === CONSUL) { + this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${csOut}/templates/consul.yml`); + this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${csOut}/templates/consul-config-loader.yml`); + this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); + } + this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${csOut}/templates/grafana-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${csOut}/templates/zipkin-gateway.yml`); + this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${csOut}/templates/kiali-gateway.yml`); + this.writeFile('helm-apply.sh.ejs', 'helm-knative-apply.sh'); + this.writeFile('helm-upgrade.sh.ejs', 'helm-knative-upgrade.sh'); + } + }, + }; +} diff --git a/generators/kubernetes-knative/files.mjs b/generators/kubernetes-knative/files.mjs deleted file mode 100644 index 9422a211d108..000000000000 --- a/generators/kubernetes-knative/files.mjs +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright 2013-2023 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 { - applicationTypes, - authenticationTypes, - databaseTypes, - kubernetesPlatformTypes, - monitoringTypes, - searchEngineTypes, - serviceDiscoveryTypes, -} from '../../jdl/jhipster/index.mjs'; - -const { ELASTICSEARCH } = searchEngineTypes; -const { GATEWAY, MONOLITH } = applicationTypes; -const { JWT } = authenticationTypes; -const { PROMETHEUS } = monitoringTypes; -const { CONSUL, EUREKA } = serviceDiscoveryTypes; -const { COUCHBASE } = databaseTypes; -const NO_DATABASE_TYPE = databaseTypes.NO; -const { GeneratorTypes } = kubernetesPlatformTypes; - -const { K8S } = GeneratorTypes; - -// eslint-disable-next-line import/prefer-default-export -export function writeFiles() { - const suffix = 'knative'; - return { - writeGeneratorFiles() { - const k8s = this.fetchFromInstalledJHipster('kubernetes/templates'); - const helm = this.fetchFromInstalledJHipster('kubernetes-helm/templates'); - if (this.kubernetesNamespace !== 'default') { - this.writeFile(`${k8s}/namespace.yml.ejs`, 'namespace.yml'); - } - this.writeFile('README-KUBERNETES-KNATIVE.md.ejs', 'KNATIVE-README.md'); - if (this.generatorType === K8S) { - for (let i = 0; i < this.appConfigs.length; i++) { - this.app = this.appConfigs[i]; - const appName = this.app.baseName.toLowerCase(); - const appOut = appName.concat('-', suffix); - - this.writeFile('service.yml.ejs', `${appOut}/${appName}-service.yml`); - // If we choose microservice with no DB, it is trying to move _no.yml as prodDatabaseType is getting tagged as 'string' type - if (this.app.databaseType !== NO_DATABASE_TYPE) { - const databaseType = this.app.prodDatabaseType ?? this.app.databaseType; - this.writeFile(`${k8s}/db/${databaseType}.yml.ejs`, `${appOut}/${appName}-${databaseType}.yml`); - } - if (this.app.searchEngine === ELASTICSEARCH) { - this.writeFile(`${k8s}/db/elasticsearch.yml.ejs`, `${appOut}/${appName}-elasticsearch.yml`); - } - if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { - this.writeFile('istio/gateway.yml.ejs', `${appOut}/${appName}-gateway.yml`); - } - if (!this.app.serviceDiscoveryType && this.app.authenticationType === JWT) { - this.writeFile(`${k8s}/secret/jwt-secret.yml.ejs`, `${appOut}/jwt-secret.yml`); - } - if (this.monitoring === PROMETHEUS) { - this.writeFile(`${k8s}/monitoring/jhipster-prometheus-sm.yml.ejs`, `${appOut}/${appName}-prometheus-sm.yml`); - } - this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/${appName}-destination-rule.yml`); - this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/${appName}-virtual-service.yml`); - } - - if (this.useKafka) { - this.writeFile(`${k8s}/messagebroker/kafka.yml.ejs`, `messagebroker-${suffix}/kafka.yml`); - } - - if (this.monitoring === PROMETHEUS) { - const monitOut = 'monitoring'.concat('-', suffix); - this.writeFile(`${k8s}/monitoring/jhipster-prometheus-crd.yml.ejs`, `${monitOut}/jhipster-prometheus-crd.yml`); - this.writeFile(`${k8s}/monitoring/jhipster-prometheus-cr.yml.ejs`, `${monitOut}/jhipster-prometheus-cr.yml`); - this.writeFile(`${k8s}/monitoring/jhipster-grafana.yml.ejs`, `${monitOut}/jhipster-grafana.yml`); - this.writeFile(`${k8s}/monitoring/jhipster-grafana-dashboard.yml.ejs`, `${monitOut}/jhipster-grafana-dashboard.yml`); - this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${monitOut}/jhipster-grafana-gateway.yml`); - } - - const registryOut = 'registry'.concat('-', suffix); - if (this.serviceDiscoveryType === EUREKA) { - this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${registryOut}/jhipster-registry.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${registryOut}/application-configmap.yml`); - } else if (this.serviceDiscoveryType === CONSUL) { - this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${registryOut}/consul.yml`); - this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${registryOut}/consul-config-loader.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${registryOut}/application-configmap.yml`); - } - - const istioOut = 'istio'.concat('-', suffix); - this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${istioOut}/grafana-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${istioOut}/zipkin-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${istioOut}/kiali-gateway.yml`); - this.writeFile('kubectl-apply.sh.ejs', 'kubectl-knative-apply.sh'); - } else { - for (let i = 0; i < this.appConfigs.length; i++) { - this.app = this.appConfigs[i]; - const appName = this.app.baseName.toLowerCase(); - const appOut = appName.concat('-', suffix); - - this.writeFile('service.yml.ejs', `${appOut}/templates/${appName}-service.yml`); - this.writeFile(`${helm}/app/values.yml.ejs`, `${appOut}/values.yml`); - this.writeFile(`${helm}/app/Chart.yml.ejs`, `${appOut}/Chart.yaml`); - this.writeFile(`${helm}/app/requirements.yml.ejs`, `${appOut}/requirements.yml`); - this.writeFile(`${helm}/app/helpers.tpl.ejs`, `${appOut}/templates/_helpers.tpl`); - - if (this.app.databaseType === COUCHBASE) { - this.writeFile(`${k8s}/db/${this.app.databaseType}.yml.ejs`, `${appOut}/templates/${appName}-${this.app.databaseType}.yml`); - } - - if (this.app.searchEngine === ELASTICSEARCH) { - this.writeFile(`${k8s}/db/elasticsearch.yml.ejs`, `${appOut}/templates/${appName}-elasticsearch.yml`); - } - if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { - this.writeFile('istio/gateway.yml.ejs', `${appOut}/templates/${appName}-gateway.yml`); - } - if (!this.app.serviceDiscoveryType && this.app.authenticationType === JWT) { - this.writeFile(`${k8s}/secret/jwt-secret.yml.ejs`, `${appOut}/templates/jwt-secret.yml`); - } - this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/templates/${appName}-destination-rule.yml`); - this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/templates/${appName}-virtual-service.yml`); - } - - const csOut = 'csvc'.concat('-', suffix); - if ( - this.useKafka || - this.monitoring === PROMETHEUS || - this.serviceDiscoveryType === EUREKA || - this.serviceDiscoveryType === CONSUL - ) { - this.writeFile(`${helm}/csvc/values.yml.ejs`, `${csOut}/values.yml`); - this.writeFile(`${helm}/csvc/Chart.yml.ejs`, `${csOut}/Chart.yaml`); - this.writeFile(`${helm}/csvc/requirements.yml.ejs`, `${csOut}/requirements.yml`); - this.writeFile(`${helm}/csvc/helpers.tpl.ejs`, `${csOut}/templates/_helpers.tpl`); - } - if (this.monitoring === PROMETHEUS) { - this.writeFile(`${k8s}/istio/gateway/jhipster-grafana-gateway.yml.ejs`, `${csOut}/templates/jhipster-grafana-gateway.yml`); - } - if (this.serviceDiscoveryType === EUREKA) { - this.writeFile(`${k8s}/registry/jhipster-registry.yml.ejs`, `${csOut}/templates/jhipster-registry.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); - } - if (this.serviceDiscoveryType === CONSUL) { - this.writeFile(`${k8s}/registry/consul.yml.ejs`, `${csOut}/templates/consul.yml`); - this.writeFile(`${k8s}/registry/consul-config-loader.yml.ejs`, `${csOut}/templates/consul-config-loader.yml`); - this.writeFile(`${k8s}/registry/application-configmap.yml.ejs`, `${csOut}/templates/application-configmap.yml`); - } - this.writeFile(`${k8s}/istio/gateway/grafana-gateway.yml.ejs`, `${csOut}/templates/grafana-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/zipkin-gateway.yml.ejs`, `${csOut}/templates/zipkin-gateway.yml`); - this.writeFile(`${k8s}/istio/gateway/kiali-gateway.yml.ejs`, `${csOut}/templates/kiali-gateway.yml`); - this.writeFile('helm-apply.sh.ejs', 'helm-knative-apply.sh'); - this.writeFile('helm-upgrade.sh.ejs', 'helm-knative-upgrade.sh'); - } - }, - }; -} diff --git a/generators/kubernetes-knative/generator.js b/generators/kubernetes-knative/generator.js new file mode 100644 index 000000000000..5719e7e4e733 --- /dev/null +++ b/generators/kubernetes-knative/generator.js @@ -0,0 +1,262 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import fs from 'fs'; +import chalk from 'chalk'; + +import BaseWorkspacesGenerator from '../base-workspaces/index.js'; + +import prompts from './prompts.js'; +import { writeFiles } from './files.js'; +import { GENERATOR_KUBERNETES_KNATIVE } from '../generator-list.js'; +import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.js'; +import { + checkHelm, + checkKubernetes, + loadConfig, + setupKubernetesConstants, + setupHelmConstants, + derivedKubernetesPlatformProperties, +} from '../kubernetes/kubernetes-base.js'; +import statistics from '../statistics.js'; +import { kubernetesPlatformTypes, buildToolTypes, messageBrokerTypes } from '../../jdl/jhipster/index.js'; +import { getJdbcUrl } from '../spring-data-relational/support/index.js'; +import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.js'; +import { checkDocker } from '../docker/support/index.js'; +import { loadDerivedServerConfig } from '../server/support/index.js'; +import { loadDerivedAppConfig } from '../app/support/index.js'; + +const { GeneratorTypes } = kubernetesPlatformTypes; +const { MAVEN } = buildToolTypes; +const { KAFKA } = messageBrokerTypes; + +const { K8S } = GeneratorTypes; + +/** + * @class + * @extends {BaseWorkspacesGenerator} + */ +export default class KubernetesKnativeGenerator extends BaseWorkspacesGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_KUBERNETES_KNATIVE); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + sayHello() { + this.log.log(chalk.white(`${chalk.bold('☸')} Welcome to the JHipster Kubernetes Knative Generator ${chalk.bold('☸')}`)); + this.log.log(chalk.white(`Files will be generated in the folder: ${chalk.yellow(this.destinationRoot())}`)); + }, + existingDeployment() { + this.regenerate = this.regenerate || this.config.existed; + }, + loadDockerDependenciesTask, + checkDocker, + checkKubernetes, + checkHelm, + async checkKnative() { + if (this.skipChecks) return; + try { + await this.spawnCommand( + 'kubectl get deploy -n knative-serving --label-columns=serving.knative.dev/release | grep -E "v0\\.[8-9]{1,3}\\.[0-9]*', + ); + } catch (error) { + this.log.warn( + 'Knative 0.8.* or later is not installed on your computer.\n' + + 'Make sure you have Knative and Istio installed. Read https://knative.dev/docs/install/\n', + ); + } + }, + loadConfig, + localInit() { + this.deploymentApplicationType = 'microservice'; + this.istio = true; + }, + setupKubernetesConstants, + setupHelmConstants, + }); + } + + get [BaseWorkspacesGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return { + askForPath: prompts.askForPath, + askForApps: prompts.askForApps, + askForGeneratorType: prompts.askForGeneratorType, + askForMonitoring: prompts.askForMonitoring, + askForClustersMode: prompts.askForClustersMode, + askForServiceDiscovery: prompts.askForServiceDiscovery, + askForAdminPassword: prompts.askForAdminPassword, + askForKubernetesNamespace: prompts.askForKubernetesNamespace, + askForDockerRepositoryName: prompts.askForDockerRepositoryName, + askForDockerPushCommand: prompts.askForDockerPushCommand, + askForIngressDomain: prompts.askForIngressDomain, + }; + } + + get [BaseWorkspacesGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get configuring() { + return { + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES_KNATIVE); + }, + + generateJwtSecret, + }; + } + + get [BaseWorkspacesGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get loading() { + return { + loadFromYoRc, + loadSharedConfig() { + this.appConfigs.forEach(element => { + loadDerivedAppConfig({ application: element }); + loadDerivedServerConfig({ application: element }); + }); + loadDeploymentConfig.call(this); + derivedKubernetesPlatformProperties(this); + }, + }; + } + + get [BaseWorkspacesGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return { + configureImageNames, + + setPostPromptProp() { + this.appConfigs.forEach(element => { + element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); + if (element.messageBroker === KAFKA) { + this.useKafka = true; + } + }); + this.useKeycloak = false; + }, + }; + } + + get [BaseWorkspacesGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return writeFiles(); + } + + get [BaseWorkspacesGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get end() { + return { + checkImages, + deploy() { + if (this.hasWarning) { + this.log.warn('Kubernetes Knative configuration generated, but no Jib cache found'); + this.log.warn('If you forgot to generate the Docker image for this application, please run:'); + this.log.warn(this.warningMessage); + } else { + this.log.verboseInfo(`\n${chalk.bold.green('Kubernetes Knative configuration successfully generated!')}`); + } + this.log.warn( + '\nYou will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', + ); + for (let i = 0; i < this.appsFolders.length; i++) { + const originalImageName = this.appConfigs[i].baseName.toLowerCase(); + const targetImageName = this.appConfigs[i].targetImageName; + if (originalImageName !== targetImageName) { + this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); + } + this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); + } + if (this.dockerRepositoryName) { + this.log.log('\nAlternatively, you can use Jib to build and push image directly to a remote registry:'); + this.appsFolders.forEach((appsFolder, index) => { + const appConfig = this.appConfigs[index]; + let runCommand = ''; + if (appConfig.buildTool === MAVEN) { + runCommand = `./mvnw -ntp -Pprod verify jib:build${ + process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : '' + } -Djib.to.image=${appConfig.targetImageName}`; + } else { + runCommand = `./gradlew bootJar -Pprod jibBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''} -Djib.to.image=${ + appConfig.targetImageName + }`; + } + this.log.log(`${chalk.cyan(`${runCommand}`)} in ${this.destinationPath(this.directoryPath + appsFolder)}`); + }); + } + this.log.log('\nYou can deploy all your apps by running the following script:'); + if (this.generatorType === K8S) { + this.log.verboseInfo(` ${chalk.cyan('bash kubectl-knative-apply.sh')}`); + // Make the apply script executable + try { + fs.chmodSync('kubectl-knative-apply.sh', '755'); + } catch (err) { + this.log.warn("Failed to make 'kubectl-knative-apply.sh' executable, you may need to run 'chmod +x kubectl-knative-apply.sh'"); + } + } else { + this.log.verboseInfo(` ${chalk.cyan('bash helm-knative-apply.sh or ./helm-knative-apply.sh')}`); + this.log.log('\nYou can upgrade (after any changes) all your apps by running the following script:'); + this.log.verboseInfo(` ${chalk.cyan('bash helm-knative-upgrade.sh or ./helm-knative-upgrade.sh')}`); + // Make the apply script executable + try { + fs.chmodSync('helm-knative-apply.sh', '755'); + fs.chmodSync('helm-knative-upgrade.sh', '755'); + } catch (err) { + this.log.warn( + "Failed to make 'helm-knative-apply.sh', 'helm-knative-upgrade.sh' executable, you may need to run 'chmod +x helm-knative-apply.sh helm-knative-upgrade.sh", + ); + } + } + }, + }; + } + + get [BaseWorkspacesGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * @private + * Returns the JDBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getJDBCUrl(databaseType, options = {}) { + return getJdbcUrl(databaseType, options); + } +} diff --git a/generators/kubernetes-knative/generator.mjs b/generators/kubernetes-knative/generator.mjs deleted file mode 100644 index 5a23a539c01c..000000000000 --- a/generators/kubernetes-knative/generator.mjs +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import fs from 'fs'; -import chalk from 'chalk'; - -import BaseWorkspacesGenerator from '../base-workspaces/index.mjs'; - -import prompts from './prompts.mjs'; -import { writeFiles } from './files.mjs'; -import { GENERATOR_KUBERNETES_KNATIVE } from '../generator-list.mjs'; -import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.mjs'; -import { - checkHelm, - checkKubernetes, - loadConfig, - setupKubernetesConstants, - setupHelmConstants, - derivedKubernetesPlatformProperties, -} from '../kubernetes/kubernetes-base.mjs'; -import statistics from '../statistics.mjs'; -import { kubernetesPlatformTypes, buildToolTypes, messageBrokerTypes } from '../../jdl/jhipster/index.mjs'; -import { getJdbcUrl } from '../spring-data-relational/support/index.mjs'; -import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.mjs'; -import { checkDocker } from '../docker/support/index.mjs'; -import { loadDerivedServerConfig } from '../server/support/index.mjs'; -import { loadDerivedAppConfig } from '../app/support/index.mjs'; - -const { GeneratorTypes } = kubernetesPlatformTypes; -const { MAVEN } = buildToolTypes; -const { KAFKA } = messageBrokerTypes; - -const { K8S } = GeneratorTypes; - -/** - * @class - * @extends {BaseWorkspacesGenerator} - */ -export default class KubernetesKnativeGenerator extends BaseWorkspacesGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_KUBERNETES_KNATIVE); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - sayHello() { - this.log.log(chalk.white(`${chalk.bold('☸')} Welcome to the JHipster Kubernetes Knative Generator ${chalk.bold('☸')}`)); - this.log.log(chalk.white(`Files will be generated in the folder: ${chalk.yellow(this.destinationRoot())}`)); - }, - existingDeployment() { - this.regenerate = this.regenerate || this.config.existed; - }, - loadDockerDependenciesTask, - checkDocker, - checkKubernetes, - checkHelm, - async checkKnative() { - if (this.skipChecks) return; - try { - await this.spawnCommand( - 'kubectl get deploy -n knative-serving --label-columns=serving.knative.dev/release | grep -E "v0\\.[8-9]{1,3}\\.[0-9]*', - ); - } catch (error) { - this.log.warn( - 'Knative 0.8.* or later is not installed on your computer.\n' + - 'Make sure you have Knative and Istio installed. Read https://knative.dev/docs/install/\n', - ); - } - }, - loadConfig, - localInit() { - this.deploymentApplicationType = 'microservice'; - this.istio = true; - }, - setupKubernetesConstants, - setupHelmConstants, - }); - } - - get [BaseWorkspacesGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return { - askForPath: prompts.askForPath, - askForApps: prompts.askForApps, - askForGeneratorType: prompts.askForGeneratorType, - askForMonitoring: prompts.askForMonitoring, - askForClustersMode: prompts.askForClustersMode, - askForServiceDiscovery: prompts.askForServiceDiscovery, - askForAdminPassword: prompts.askForAdminPassword, - askForKubernetesNamespace: prompts.askForKubernetesNamespace, - askForDockerRepositoryName: prompts.askForDockerRepositoryName, - askForDockerPushCommand: prompts.askForDockerPushCommand, - askForIngressDomain: prompts.askForIngressDomain, - }; - } - - get [BaseWorkspacesGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get configuring() { - return { - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES_KNATIVE); - }, - - generateJwtSecret, - }; - } - - get [BaseWorkspacesGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get loading() { - return { - loadFromYoRc, - loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); - loadDeploymentConfig.call(this); - derivedKubernetesPlatformProperties(this); - }, - }; - } - - get [BaseWorkspacesGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return { - configureImageNames, - - setPostPromptProp() { - this.appConfigs.forEach(element => { - element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); - if (element.messageBroker === KAFKA) { - this.useKafka = true; - } - }); - this.useKeycloak = false; - }, - }; - } - - get [BaseWorkspacesGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return writeFiles(); - } - - get [BaseWorkspacesGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get end() { - return { - checkImages, - deploy() { - if (this.hasWarning) { - this.log.warn('Kubernetes Knative configuration generated, but no Jib cache found'); - this.log.warn('If you forgot to generate the Docker image for this application, please run:'); - this.log.warn(this.warningMessage); - } else { - this.log.verboseInfo(`\n${chalk.bold.green('Kubernetes Knative configuration successfully generated!')}`); - } - this.log.warn( - '\nYou will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', - ); - for (let i = 0; i < this.appsFolders.length; i++) { - const originalImageName = this.appConfigs[i].baseName.toLowerCase(); - const targetImageName = this.appConfigs[i].targetImageName; - if (originalImageName !== targetImageName) { - this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); - } - this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); - } - if (this.dockerRepositoryName) { - this.log.log('\nAlternatively, you can use Jib to build and push image directly to a remote registry:'); - this.appsFolders.forEach((appsFolder, index) => { - const appConfig = this.appConfigs[index]; - let runCommand = ''; - if (appConfig.buildTool === MAVEN) { - runCommand = `./mvnw -ntp -Pprod verify jib:build${ - process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : '' - } -Djib.to.image=${appConfig.targetImageName}`; - } else { - runCommand = `./gradlew bootJar -Pprod jibBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''} -Djib.to.image=${ - appConfig.targetImageName - }`; - } - this.log.log(`${chalk.cyan(`${runCommand}`)} in ${this.destinationPath(this.directoryPath + appsFolder)}`); - }); - } - this.log.log('\nYou can deploy all your apps by running the following script:'); - if (this.generatorType === K8S) { - this.log.verboseInfo(` ${chalk.cyan('bash kubectl-knative-apply.sh')}`); - // Make the apply script executable - try { - fs.chmodSync('kubectl-knative-apply.sh', '755'); - } catch (err) { - this.log.warn("Failed to make 'kubectl-knative-apply.sh' executable, you may need to run 'chmod +x kubectl-knative-apply.sh'"); - } - } else { - this.log.verboseInfo(` ${chalk.cyan('bash helm-knative-apply.sh or ./helm-knative-apply.sh')}`); - this.log.log('\nYou can upgrade (after any changes) all your apps by running the following script:'); - this.log.verboseInfo(` ${chalk.cyan('bash helm-knative-upgrade.sh or ./helm-knative-upgrade.sh')}`); - // Make the apply script executable - try { - fs.chmodSync('helm-knative-apply.sh', '755'); - fs.chmodSync('helm-knative-upgrade.sh', '755'); - } catch (err) { - this.log.warn( - "Failed to make 'helm-knative-apply.sh', 'helm-knative-upgrade.sh' executable, you may need to run 'chmod +x helm-knative-apply.sh helm-knative-upgrade.sh", - ); - } - } - }, - }; - } - - get [BaseWorkspacesGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * @private - * Returns the JDBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getJDBCUrl(databaseType, options = {}) { - return getJdbcUrl(databaseType, options); - } -} diff --git a/generators/kubernetes-knative/generator.spec.js b/generators/kubernetes-knative/generator.spec.js new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/kubernetes-knative/generator.spec.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/kubernetes-knative/generator.spec.mjs b/generators/kubernetes-knative/generator.spec.mjs deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/kubernetes-knative/generator.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/kubernetes-knative/index.mts b/generators/kubernetes-knative/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/kubernetes-knative/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/kubernetes-knative/index.ts b/generators/kubernetes-knative/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/kubernetes-knative/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/kubernetes-knative/knative.spec.mts b/generators/kubernetes-knative/knative.spec.mts deleted file mode 100644 index 47c2f9113241..000000000000 --- a/generators/kubernetes-knative/knative.spec.mts +++ /dev/null @@ -1,893 +0,0 @@ -import { expect } from 'esmocha'; - -import { dryRunHelpers as helpers, getGenerator } from '../../test/support/index.mjs'; -import { GENERATOR_KUBERNETES_KNATIVE } from '../generator-list.mjs'; - -const expectedFiles = { - eurekaregistry: ['./registry-knative/jhipster-registry.yml', './registry-knative/application-configmap.yml'], - consulregistry: [ - './registry-knative/consul.yml', - './registry-knative/consul-config-loader.yml', - './registry-knative/application-configmap.yml', - ], - jhgate: ['./jhgate-knative/jhgate-mysql.yml', './jhgate-knative/jhgate-service.yml'], - jhgateingress: ['./jhgate-knative/jhgate-ingress.yml'], - customnamespace: ['./namespace.yml'], - msmysql: ['./msmysql-knative/msmysql-service.yml', './msmysql-knative/msmysql-mysql.yml'], - mspsql: [ - './mspsql-knative/mspsql-service.yml', - './mspsql-knative/mspsql-postgresql.yml', - './mspsql-knative/mspsql-service.yml', - './mspsql-knative/mspsql-elasticsearch.yml', - ], - msmongodb: ['./msmongodb-knative/msmongodb-service.yml', './msmongodb-knative/msmongodb-mongodb.yml'], - msmariadb: ['./msmariadb-knative/msmariadb-service.yml', './msmariadb-knative/msmariadb-mariadb.yml'], - msmssqldb: ['./msmssqldb-knative/msmssqldb-service.yml', './msmssqldb-knative/msmssqldb-mssql.yml'], - prometheusmonit: [ - './monitoring-knative/jhipster-prometheus-crd.yml', - './monitoring-knative/jhipster-prometheus-cr.yml', - './monitoring-knative/jhipster-grafana.yml', - './monitoring-knative/jhipster-grafana-dashboard.yml', - ], - jhgategateway: [ - './jhgate-knative/jhgate-gateway.yml', - './jhgate-knative/jhgate-destination-rule.yml', - './jhgate-knative/jhgate-virtual-service.yml', - ], - applyScript: ['./kubectl-knative-apply.sh'], -}; - -const helmExpectedFiles = { - csvcfiles: [ - './csvc-knative/Chart.yaml', - './csvc-knative/requirements.yml', - './csvc-knative/values.yml', - './csvc-knative/templates/_helpers.tpl', - ], - eurekaregistry: ['./csvc-knative/templates/jhipster-registry.yml', './csvc-knative/templates/application-configmap.yml'], - consulregistry: [ - './csvc-knative/templates/consul.yml', - './csvc-knative/templates/consul-config-loader.yml', - './csvc-knative/templates/application-configmap.yml', - ], - jhgate: [ - './jhgate-knative/templates/jhgate-service.yml', - './jhgate-knative/Chart.yaml', - './jhgate-knative/requirements.yml', - './jhgate-knative/values.yml', - './jhgate-knative/templates/_helpers.tpl', - ], - customnamespace: ['./namespace.yml'], - msmysql: [ - './msmysql-knative/Chart.yaml', - './msmysql-knative/requirements.yml', - './msmysql-knative/values.yml', - './msmysql-knative/templates/_helpers.tpl', - './msmysql-knative/templates/msmysql-service.yml', - ], - mspsql: [ - './mspsql-knative/Chart.yaml', - './mspsql-knative/requirements.yml', - './mspsql-knative/values.yml', - './mspsql-knative/templates/_helpers.tpl', - './mspsql-knative/templates/mspsql-service.yml', - ], - msmongodb: [ - './msmongodb-knative/Chart.yaml', - './msmongodb-knative/requirements.yml', - './msmongodb-knative/values.yml', - './msmongodb-knative/templates/_helpers.tpl', - './msmongodb-knative/templates/msmongodb-service.yml', - ], - msmariadb: [ - './msmariadb-knative/Chart.yaml', - './msmariadb-knative/requirements.yml', - './msmariadb-knative/values.yml', - './msmariadb-knative/templates/_helpers.tpl', - './msmariadb-knative/templates/msmariadb-service.yml', - './msmariadb-knative/templates/msmariadb-service.yml', - ], - kafka: ['./samplekafka-knative/templates/samplekafka-service.yml', './samplekafka-knative/templates/samplekafka-service.yml'], - jhgategateway: [ - './jhgate-knative/templates/jhgate-gateway.yml', - './jhgate-knative/templates/jhgate-destination-rule.yml', - './jhgate-knative/templates/jhgate-virtual-service.yml', - ], - applyScript: ['./helm-knative-apply.sh', './helm-knative-upgrade.sh'], -}; - -describe('generator - Knative', () => { - describe('Using K8s generator type', () => { - describe('only gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - adminPassword: 'meetup', - dockerRepositoryName: 'jhipsterrepository', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'jhipsternamespace', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files and content', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFileContent('./registry-knative/consul.yml', /a 24 chars base64 encoded string/); - }); - it('creates expected gateway files and content', () => { - runResult.assertFile(expectedFiles.jhgate); - // runResult.assertFileContent('./jhgate-knative/jhgate-service.yml', /image: jhipsterrepository\/jhgate/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and mysql microservice', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - jhipsterConsole: true, - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and ingress', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - kubernetesServiceType: 'Ingress', - ingressDomain: 'example.com', - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('MySQL and PostgreSQL microservices without gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql', '03-psql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it("doesn't creates gateway files", () => { - runResult.assertNoFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway, mysql, psql, mongodb, mariadb, mssql microservices', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - }); - it('creates expected mongodb files', () => { - runResult.assertFile(expectedFiles.msmongodb); - }); - it('creates expected mariadb files', () => { - runResult.assertFile(expectedFiles.msmariadb); - }); - it('creates expected mssql files', () => { - runResult.assertFile(expectedFiles.msmssqldb); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - monitoring: 'prometheus', - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected prometheus files', () => { - runResult.assertFile(expectedFiles.prometheusmonit); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway with istio routing files', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - ingressDomain: 'example.com', - clusteredDbApps: [], - generatorType: 'k8s', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected service gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected routing gateway and istio files', () => { - runResult.assertFile(expectedFiles.jhgategateway); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - }); - - describe('Using Helm generator type', () => { - describe('only gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - adminPassword: 'meetup', - dockerRepositoryName: 'jhipsterrepository', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'jhipsternamespace', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files and content', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected gateway files and content', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - runResult.assertFileContent('./jhgate-knative/requirements.yml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('gateway and mysql microservice', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(helmExpectedFiles.msmysql); - runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - jhipsterConsole: true, - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(helmExpectedFiles.msmysql); - runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); - }); - it('creates expected namespace file', () => { - runResult.assertFile(helmExpectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('gateway and ingress', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - ingressType: 'gke', - ingressDomain: 'example.com', - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected ingress files', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - runResult.assertFile(helmExpectedFiles.csvcfiles); - runResult.assertFileContent('./jhgate-knative/requirements.yml', /name: mysql/); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('MySQL and PostgreSQL microservices without gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql', '03-psql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it("doesn't creates gateway files", () => { - runResult.assertNoFile(helmExpectedFiles.jhgate); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(helmExpectedFiles.msmysql); - runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); - }); - it('creates expected psql files', () => { - runResult.assertFile(helmExpectedFiles.mspsql); - runResult.assertFileContent('./mspsql-knative/requirements.yml', /name: postgresql/); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('gateway, mysql, psql, mongodb, mariadb microservices', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected gateway files', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected mysql files', () => { - runResult.assertFile(helmExpectedFiles.msmysql); - runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); - }); - it('creates expected psql files', () => { - runResult.assertFile(helmExpectedFiles.mspsql); - runResult.assertFileContent('./mspsql-knative/requirements.yml', /name: postgresql/); - }); - it('creates expected mongodb files', () => { - runResult.assertFile(helmExpectedFiles.msmongodb); - runResult.assertFileContent('./msmongodb-knative/requirements.yml', /name: mongodb-replicaset/); - }); - it('creates expected mariadb files', () => { - runResult.assertFile(helmExpectedFiles.msmariadb); - runResult.assertFileContent('./msmariadb-knative/requirements.yml', /name: mariadb/); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - monitoring: 'prometheus', - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(helmExpectedFiles.msmysql); - runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); - }); - it('creates expected prometheus files', () => { - runResult.assertFile(helmExpectedFiles.csvcfiles); - runResult.assertFileContent('./csvc-knative/requirements.yml', /name: prometheus/); - runResult.assertFileContent('./csvc-knative/requirements.yml', /name: grafana/); - }); - it('creates expected namespace file', () => { - runResult.assertFile(helmExpectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - - describe('gateway with istio routing files', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - ingressDomain: 'example.com', - clusteredDbApps: [], - generatorType: 'helm', - istio: true, - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(helmExpectedFiles.consulregistry); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected service gateway files', () => { - runResult.assertFile(helmExpectedFiles.jhgate); - runResult.assertFile(helmExpectedFiles.csvcfiles); - }); - it('creates expected routing gateway and istio files', () => { - runResult.assertFile(helmExpectedFiles.jhgategateway); - }); - it('create the apply script', () => { - runResult.assertFile(helmExpectedFiles.applyScript); - }); - }); - }); -}); diff --git a/generators/kubernetes-knative/knative.spec.ts b/generators/kubernetes-knative/knative.spec.ts new file mode 100644 index 000000000000..05a162cbd6d5 --- /dev/null +++ b/generators/kubernetes-knative/knative.spec.ts @@ -0,0 +1,893 @@ +import { expect } from 'esmocha'; + +import { dryRunHelpers as helpers, getGenerator } from '../../test/support/index.js'; +import { GENERATOR_KUBERNETES_KNATIVE } from '../generator-list.js'; + +const expectedFiles = { + eurekaregistry: ['./registry-knative/jhipster-registry.yml', './registry-knative/application-configmap.yml'], + consulregistry: [ + './registry-knative/consul.yml', + './registry-knative/consul-config-loader.yml', + './registry-knative/application-configmap.yml', + ], + jhgate: ['./jhgate-knative/jhgate-mysql.yml', './jhgate-knative/jhgate-service.yml'], + jhgateingress: ['./jhgate-knative/jhgate-ingress.yml'], + customnamespace: ['./namespace.yml'], + msmysql: ['./msmysql-knative/msmysql-service.yml', './msmysql-knative/msmysql-mysql.yml'], + mspsql: [ + './mspsql-knative/mspsql-service.yml', + './mspsql-knative/mspsql-postgresql.yml', + './mspsql-knative/mspsql-service.yml', + './mspsql-knative/mspsql-elasticsearch.yml', + ], + msmongodb: ['./msmongodb-knative/msmongodb-service.yml', './msmongodb-knative/msmongodb-mongodb.yml'], + msmariadb: ['./msmariadb-knative/msmariadb-service.yml', './msmariadb-knative/msmariadb-mariadb.yml'], + msmssqldb: ['./msmssqldb-knative/msmssqldb-service.yml', './msmssqldb-knative/msmssqldb-mssql.yml'], + prometheusmonit: [ + './monitoring-knative/jhipster-prometheus-crd.yml', + './monitoring-knative/jhipster-prometheus-cr.yml', + './monitoring-knative/jhipster-grafana.yml', + './monitoring-knative/jhipster-grafana-dashboard.yml', + ], + jhgategateway: [ + './jhgate-knative/jhgate-gateway.yml', + './jhgate-knative/jhgate-destination-rule.yml', + './jhgate-knative/jhgate-virtual-service.yml', + ], + applyScript: ['./kubectl-knative-apply.sh'], +}; + +const helmExpectedFiles = { + csvcfiles: [ + './csvc-knative/Chart.yaml', + './csvc-knative/requirements.yml', + './csvc-knative/values.yml', + './csvc-knative/templates/_helpers.tpl', + ], + eurekaregistry: ['./csvc-knative/templates/jhipster-registry.yml', './csvc-knative/templates/application-configmap.yml'], + consulregistry: [ + './csvc-knative/templates/consul.yml', + './csvc-knative/templates/consul-config-loader.yml', + './csvc-knative/templates/application-configmap.yml', + ], + jhgate: [ + './jhgate-knative/templates/jhgate-service.yml', + './jhgate-knative/Chart.yaml', + './jhgate-knative/requirements.yml', + './jhgate-knative/values.yml', + './jhgate-knative/templates/_helpers.tpl', + ], + customnamespace: ['./namespace.yml'], + msmysql: [ + './msmysql-knative/Chart.yaml', + './msmysql-knative/requirements.yml', + './msmysql-knative/values.yml', + './msmysql-knative/templates/_helpers.tpl', + './msmysql-knative/templates/msmysql-service.yml', + ], + mspsql: [ + './mspsql-knative/Chart.yaml', + './mspsql-knative/requirements.yml', + './mspsql-knative/values.yml', + './mspsql-knative/templates/_helpers.tpl', + './mspsql-knative/templates/mspsql-service.yml', + ], + msmongodb: [ + './msmongodb-knative/Chart.yaml', + './msmongodb-knative/requirements.yml', + './msmongodb-knative/values.yml', + './msmongodb-knative/templates/_helpers.tpl', + './msmongodb-knative/templates/msmongodb-service.yml', + ], + msmariadb: [ + './msmariadb-knative/Chart.yaml', + './msmariadb-knative/requirements.yml', + './msmariadb-knative/values.yml', + './msmariadb-knative/templates/_helpers.tpl', + './msmariadb-knative/templates/msmariadb-service.yml', + './msmariadb-knative/templates/msmariadb-service.yml', + ], + kafka: ['./samplekafka-knative/templates/samplekafka-service.yml', './samplekafka-knative/templates/samplekafka-service.yml'], + jhgategateway: [ + './jhgate-knative/templates/jhgate-gateway.yml', + './jhgate-knative/templates/jhgate-destination-rule.yml', + './jhgate-knative/templates/jhgate-virtual-service.yml', + ], + applyScript: ['./helm-knative-apply.sh', './helm-knative-upgrade.sh'], +}; + +describe('generator - Knative', () => { + describe('Using K8s generator type', () => { + describe('only gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + adminPassword: 'meetup', + dockerRepositoryName: 'jhipsterrepository', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'jhipsternamespace', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files and content', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFileContent('./registry-knative/consul.yml', /a 24 chars base64 encoded string/); + }); + it('creates expected gateway files and content', () => { + runResult.assertFile(expectedFiles.jhgate); + // runResult.assertFileContent('./jhgate-knative/jhgate-service.yml', /image: jhipsterrepository\/jhgate/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and mysql microservice', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + jhipsterConsole: true, + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and ingress', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + kubernetesServiceType: 'Ingress', + ingressDomain: 'example.com', + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('MySQL and PostgreSQL microservices without gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql', '03-psql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it("doesn't creates gateway files", () => { + runResult.assertNoFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway, mysql, psql, mongodb, mariadb, mssql microservices', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + }); + it('creates expected mongodb files', () => { + runResult.assertFile(expectedFiles.msmongodb); + }); + it('creates expected mariadb files', () => { + runResult.assertFile(expectedFiles.msmariadb); + }); + it('creates expected mssql files', () => { + runResult.assertFile(expectedFiles.msmssqldb); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + monitoring: 'prometheus', + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected prometheus files', () => { + runResult.assertFile(expectedFiles.prometheusmonit); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway with istio routing files', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + ingressDomain: 'example.com', + clusteredDbApps: [], + generatorType: 'k8s', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected service gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected routing gateway and istio files', () => { + runResult.assertFile(expectedFiles.jhgategateway); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + }); + + describe('Using Helm generator type', () => { + describe('only gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + adminPassword: 'meetup', + dockerRepositoryName: 'jhipsterrepository', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'jhipsternamespace', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files and content', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected gateway files and content', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + runResult.assertFileContent('./jhgate-knative/requirements.yml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('gateway and mysql microservice', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(helmExpectedFiles.msmysql); + runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + jhipsterConsole: true, + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(helmExpectedFiles.msmysql); + runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); + }); + it('creates expected namespace file', () => { + runResult.assertFile(helmExpectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('gateway and ingress', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + ingressType: 'gke', + ingressDomain: 'example.com', + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected ingress files', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + runResult.assertFile(helmExpectedFiles.csvcfiles); + runResult.assertFileContent('./jhgate-knative/requirements.yml', /name: mysql/); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('MySQL and PostgreSQL microservices without gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql', '03-psql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it("doesn't creates gateway files", () => { + runResult.assertNoFile(helmExpectedFiles.jhgate); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(helmExpectedFiles.msmysql); + runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); + }); + it('creates expected psql files', () => { + runResult.assertFile(helmExpectedFiles.mspsql); + runResult.assertFileContent('./mspsql-knative/requirements.yml', /name: postgresql/); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('gateway, mysql, psql, mongodb, mariadb microservices', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected gateway files', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected mysql files', () => { + runResult.assertFile(helmExpectedFiles.msmysql); + runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); + }); + it('creates expected psql files', () => { + runResult.assertFile(helmExpectedFiles.mspsql); + runResult.assertFileContent('./mspsql-knative/requirements.yml', /name: postgresql/); + }); + it('creates expected mongodb files', () => { + runResult.assertFile(helmExpectedFiles.msmongodb); + runResult.assertFileContent('./msmongodb-knative/requirements.yml', /name: mongodb-replicaset/); + }); + it('creates expected mariadb files', () => { + runResult.assertFile(helmExpectedFiles.msmariadb); + runResult.assertFileContent('./msmariadb-knative/requirements.yml', /name: mariadb/); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + monitoring: 'prometheus', + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(helmExpectedFiles.msmysql); + runResult.assertFileContent('./msmysql-knative/requirements.yml', /name: mysql/); + }); + it('creates expected prometheus files', () => { + runResult.assertFile(helmExpectedFiles.csvcfiles); + runResult.assertFileContent('./csvc-knative/requirements.yml', /name: prometheus/); + runResult.assertFileContent('./csvc-knative/requirements.yml', /name: grafana/); + }); + it('creates expected namespace file', () => { + runResult.assertFile(helmExpectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + + describe('gateway with istio routing files', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES_KNATIVE)) + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + ingressDomain: 'example.com', + clusteredDbApps: [], + generatorType: 'helm', + istio: true, + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(helmExpectedFiles.consulregistry); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected service gateway files', () => { + runResult.assertFile(helmExpectedFiles.jhgate); + runResult.assertFile(helmExpectedFiles.csvcfiles); + }); + it('creates expected routing gateway and istio files', () => { + runResult.assertFile(helmExpectedFiles.jhgategateway); + }); + it('create the apply script', () => { + runResult.assertFile(helmExpectedFiles.applyScript); + }); + }); + }); +}); diff --git a/generators/kubernetes-knative/prompts.js b/generators/kubernetes-knative/prompts.js new file mode 100644 index 000000000000..a2914c79811d --- /dev/null +++ b/generators/kubernetes-knative/prompts.js @@ -0,0 +1,55 @@ +/** + * Copyright 2013-2023 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 k8sPrompts from '../kubernetes/prompts.js'; +import { kubernetesPlatformTypes } from '../../jdl/jhipster/index.js'; +import { generatorDefaultConfig } from '../kubernetes/kubernetes-constants.js'; + +const { GeneratorTypes } = kubernetesPlatformTypes; +const { HELM, K8S } = GeneratorTypes; + +export default { + askForGeneratorType, + ...k8sPrompts, +}; + +async function askForGeneratorType() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'list', + name: 'generatorType', + message: 'Which *type* of generator would you like to base this on?', + choices: [ + { + value: K8S, + name: 'Kubernetes generator', + }, + { + value: HELM, + name: 'Helm Kubernetes generator', + }, + ], + default: this.generatorType ? this.generatorType : generatorDefaultConfig.generatorType, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.generatorType = props.generatorType; +} diff --git a/generators/kubernetes-knative/prompts.mjs b/generators/kubernetes-knative/prompts.mjs deleted file mode 100644 index 46944de40d12..000000000000 --- a/generators/kubernetes-knative/prompts.mjs +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2013-2023 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 k8sPrompts from '../kubernetes/prompts.mjs'; -import { kubernetesPlatformTypes } from '../../jdl/jhipster/index.mjs'; -import { generatorDefaultConfig } from '../kubernetes/kubernetes-constants.mjs'; - -const { GeneratorTypes } = kubernetesPlatformTypes; -const { HELM, K8S } = GeneratorTypes; - -export default { - askForGeneratorType, - ...k8sPrompts, -}; - -async function askForGeneratorType() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'list', - name: 'generatorType', - message: 'Which *type* of generator would you like to base this on?', - choices: [ - { - value: K8S, - name: 'Kubernetes generator', - }, - { - value: HELM, - name: 'Helm Kubernetes generator', - }, - ], - default: this.generatorType ? this.generatorType : generatorDefaultConfig.generatorType, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.generatorType = props.generatorType; -} diff --git a/generators/kubernetes/__snapshots__/kubernetes.spec.mts.snap b/generators/kubernetes/__snapshots__/kubernetes.spec.ts.snap similarity index 100% rename from generators/kubernetes/__snapshots__/kubernetes.spec.mts.snap rename to generators/kubernetes/__snapshots__/kubernetes.spec.ts.snap diff --git a/generators/kubernetes/files.js b/generators/kubernetes/files.js new file mode 100644 index 000000000000..24db966c176c --- /dev/null +++ b/generators/kubernetes/files.js @@ -0,0 +1,187 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; + +import { + applicationTypes, + authenticationTypes, + databaseTypes, + monitoringTypes, + searchEngineTypes, + serviceDiscoveryTypes, + ingressTypes, +} from '../../jdl/jhipster/index.js'; + +const { ELASTICSEARCH } = searchEngineTypes; +const { GATEWAY, MONOLITH } = applicationTypes; +const { JWT } = authenticationTypes; +const { PROMETHEUS } = monitoringTypes; +const { CONSUL, EUREKA } = serviceDiscoveryTypes; +const { GKE } = ingressTypes; + +const NO_DATABASE = databaseTypes.NO; + +export default { + writeFiles, +}; + +export function writeFiles() { + const suffix = 'k8s'; + return { + writeDeployments() { + for (let i = 0; i < this.appConfigs.length; i++) { + const appName = this.appConfigs[i].baseName.toLowerCase(); + const appOut = appName.concat('-', suffix); + this.app = this.appConfigs[i]; + this.writeFile('deployment.yml.ejs', `${appOut}/${appName}-deployment.yml`); + this.writeFile('service.yml.ejs', `${appOut}/${appName}-service.yml`); + // If we choose microservice with no DB, it is trying to move _no.yml as prodDatabaseType is getting tagged as 'string' type + if (this.app.databaseType !== NO_DATABASE) { + const databaseType = this.app.prodDatabaseType ?? this.app.databaseType; + this.writeFile(`db/${databaseType}.yml.ejs`, `${appOut}/${appName}-${databaseType}.yml`); + } + if (this.app.searchEngine === ELASTICSEARCH) { + this.writeFile('db/elasticsearch.yml.ejs', `${appOut}/${appName}-elasticsearch.yml`); + } + if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { + if (this.istio) { + this.writeFile('istio/gateway.yml.ejs', `${appOut}/${appName}-gateway.yml`); + } else if (this.kubernetesServiceType === 'Ingress') { + this.writeFile('ingress.yml.ejs', `${appOut}/${appName}-ingress.yml`); + } + } + if (!this.app.serviceDiscoveryAny && this.app.authenticationType === JWT) { + this.writeFile('secret/jwt-secret.yml.ejs', `${appOut}/jwt-secret.yml`); + } + if (this.app.databaseTypeCouchbase) { + this.writeFile('secret/couchbase-secret.yml.ejs', `${appOut}/templates/couchbase-secret.yml`); + } + if (this.monitoring === PROMETHEUS) { + this.writeFile('monitoring/jhipster-prometheus-sm.yml.ejs', `${appOut}/${appName}-prometheus-sm.yml`); + } + if (this.istio) { + this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/${appName}-destination-rule.yml`); + this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/${appName}-virtual-service.yml`); + } + } + }, + + writeReadme() { + this.writeFile('README-KUBERNETES.md.ejs', 'K8S-README.md'); + }, + + writeNamespace() { + if (this.kubernetesNamespace !== 'default') { + this.writeFile('namespace.yml.ejs', 'namespace.yml'); + } + }, + + writeMessagingBroker() { + if (!this.useKafka) return; + this.writeFile('messagebroker/kafka.yml.ejs', `messagebroker-${suffix}/kafka.yml`); + }, + + writeKeycloak() { + if (!this.useKeycloak) return; + const keycloakOut = 'keycloak'.concat('-', suffix); + this.entryPort = '8080'; + this.keycloakRedirectUris = ''; + this.appConfigs.forEach(appConfig => { + // Add application configuration + if (appConfig.applicationType === GATEWAY || appConfig.applicationType === MONOLITH) { + this.entryPort = appConfig.composePort; + if (this.ingressDomain) { + this.keycloakRedirectUris += `"http://${appConfig.baseName.toLowerCase()}.${this.kubernetesNamespace}.${this.ingressDomain}/*", + "https://${appConfig.baseName.toLowerCase()}.${this.kubernetesNamespace}.${this.ingressDomain}/*", `; + } else { + this.keycloakRedirectUris += `"http://${appConfig.baseName.toLowerCase()}:${appConfig.composePort}/*", + "https://${appConfig.baseName.toLowerCase()}:${appConfig.composePort}/*", `; + } + + this.keycloakRedirectUris += `"http://localhost:${appConfig.composePort}/*", + "https://localhost:${appConfig.composePort}/*", `; + + if (appConfig.devServerPort !== undefined) { + this.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPort}/*", `; + } + + this.debug(chalk.red.bold(`${appConfig.baseName} has redirect URIs ${this.keycloakRedirectUris}`)); + this.debug(chalk.red.bold(`AppConfig is ${JSON.stringify(appConfig)}`)); + } + }); + this.writeFile('keycloak/keycloak-configmap.yml.ejs', `${keycloakOut}/keycloak-configmap.yml`); + this.writeFile('keycloak/keycloak-postgresql.yml.ejs', `${keycloakOut}/keycloak-postgresql.yml`); + this.writeFile('keycloak/keycloak.yml.ejs', `${keycloakOut}/keycloak.yml`); + if (this.ingressType === GKE) { + this.writeFile('cert-manager/letsencrypt-staging-ca-secret.yml.ejs', 'cert-manager/letsencrypt-staging-ca-secret.yml'); + this.writeFile('cert-manager/letsencrypt-staging-issuer.yml.ejs', 'cert-manager/letsencrypt-staging-issuer.yml'); + } + }, + + writePrometheusGrafanaFiles() { + const monitOut = 'monitoring'.concat('-', suffix); + if (this.monitoring === PROMETHEUS) { + this.writeFile('monitoring/jhipster-prometheus-crd.yml.ejs', `${monitOut}/jhipster-prometheus-crd.yml`); + this.writeFile('monitoring/jhipster-prometheus-cr.yml.ejs', `${monitOut}/jhipster-prometheus-cr.yml`); + this.writeFile('monitoring/jhipster-grafana.yml.ejs', `${monitOut}/jhipster-grafana.yml`); + this.writeFile('monitoring/jhipster-grafana-dashboard.yml.ejs', `${monitOut}/jhipster-grafana-dashboard.yml`); + if (this.istio) { + this.writeFile('istio/gateway/jhipster-grafana-gateway.yml.ejs', `${monitOut}/jhipster-grafana-gateway.yml`); + } + } + }, + + writeRegistryFiles() { + const registryOut = 'registry'.concat('-', suffix); + if (this.serviceDiscoveryType === EUREKA) { + this.writeFile('registry/jhipster-registry.yml.ejs', `${registryOut}/jhipster-registry.yml`); + this.writeFile('registry/application-configmap.yml.ejs', `${registryOut}/application-configmap.yml`); + } else if (this.serviceDiscoveryType === CONSUL) { + this.writeFile('registry/consul.yml.ejs', `${registryOut}/consul.yml`); + this.writeFile('registry/consul-config-loader.yml.ejs', `${registryOut}/consul-config-loader.yml`); + this.writeFile('registry/application-configmap.yml.ejs', `${registryOut}/application-configmap.yml`); + } + }, + + writeConfigRunFile() { + this.writeFile('kubectl-apply.sh.ejs', 'kubectl-apply.sh'); + }, + + writeObservabilityGatewayFiles() { + if (!this.istio) return; + const istioOut = 'istio'.concat('-', suffix); + this.writeFile('istio/gateway/grafana-gateway.yml.ejs', `${istioOut}/grafana-gateway.yml`); + this.writeFile('istio/gateway/zipkin-gateway.yml.ejs', `${istioOut}/zipkin-gateway.yml`); + this.writeFile('istio/gateway/kiali-gateway.yml.ejs', `${istioOut}/kiali-gateway.yml`); + }, + + writeKustomize() { + const patchOut = 'patch'.concat('-', suffix); + this.writeFile('kustomize/kustomization.yml.ejs', 'kustomization.yml'); + if (this.istio) { + this.writeFile('kustomize/patch/istio-label.yml.ejs', `${patchOut}/istio-label.yml`); + this.writeFile('kustomize/patch/istio-namespace.yml.ejs', `${patchOut}/istio-namespace.yml`); + } + }, + + writeSkaffold() { + this.writeFile('skaffold/skaffold.yml.ejs', 'skaffold.yml'); + }, + }; +} diff --git a/generators/kubernetes/files.mjs b/generators/kubernetes/files.mjs deleted file mode 100644 index 69a26069bd3c..000000000000 --- a/generators/kubernetes/files.mjs +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; - -import { - applicationTypes, - authenticationTypes, - databaseTypes, - monitoringTypes, - searchEngineTypes, - serviceDiscoveryTypes, - ingressTypes, -} from '../../jdl/jhipster/index.mjs'; - -const { ELASTICSEARCH } = searchEngineTypes; -const { GATEWAY, MONOLITH } = applicationTypes; -const { JWT } = authenticationTypes; -const { PROMETHEUS } = monitoringTypes; -const { CONSUL, EUREKA } = serviceDiscoveryTypes; -const { GKE } = ingressTypes; - -const NO_DATABASE = databaseTypes.NO; - -export default { - writeFiles, -}; - -export function writeFiles() { - const suffix = 'k8s'; - return { - writeDeployments() { - for (let i = 0; i < this.appConfigs.length; i++) { - const appName = this.appConfigs[i].baseName.toLowerCase(); - const appOut = appName.concat('-', suffix); - this.app = this.appConfigs[i]; - this.writeFile('deployment.yml.ejs', `${appOut}/${appName}-deployment.yml`); - this.writeFile('service.yml.ejs', `${appOut}/${appName}-service.yml`); - // If we choose microservice with no DB, it is trying to move _no.yml as prodDatabaseType is getting tagged as 'string' type - if (this.app.databaseType !== NO_DATABASE) { - const databaseType = this.app.prodDatabaseType ?? this.app.databaseType; - this.writeFile(`db/${databaseType}.yml.ejs`, `${appOut}/${appName}-${databaseType}.yml`); - } - if (this.app.searchEngine === ELASTICSEARCH) { - this.writeFile('db/elasticsearch.yml.ejs', `${appOut}/${appName}-elasticsearch.yml`); - } - if (this.app.applicationType === GATEWAY || this.app.applicationType === MONOLITH) { - if (this.istio) { - this.writeFile('istio/gateway.yml.ejs', `${appOut}/${appName}-gateway.yml`); - } else if (this.kubernetesServiceType === 'Ingress') { - this.writeFile('ingress.yml.ejs', `${appOut}/${appName}-ingress.yml`); - } - } - if (!this.app.serviceDiscoveryAny && this.app.authenticationType === JWT) { - this.writeFile('secret/jwt-secret.yml.ejs', `${appOut}/jwt-secret.yml`); - } - if (this.app.databaseTypeCouchbase) { - this.writeFile('secret/couchbase-secret.yml.ejs', `${appOut}/templates/couchbase-secret.yml`); - } - if (this.monitoring === PROMETHEUS) { - this.writeFile('monitoring/jhipster-prometheus-sm.yml.ejs', `${appOut}/${appName}-prometheus-sm.yml`); - } - if (this.istio) { - this.writeFile('istio/destination-rule.yml.ejs', `${appOut}/${appName}-destination-rule.yml`); - this.writeFile('istio/virtual-service.yml.ejs', `${appOut}/${appName}-virtual-service.yml`); - } - } - }, - - writeReadme() { - this.writeFile('README-KUBERNETES.md.ejs', 'K8S-README.md'); - }, - - writeNamespace() { - if (this.kubernetesNamespace !== 'default') { - this.writeFile('namespace.yml.ejs', 'namespace.yml'); - } - }, - - writeMessagingBroker() { - if (!this.useKafka) return; - this.writeFile('messagebroker/kafka.yml.ejs', `messagebroker-${suffix}/kafka.yml`); - }, - - writeKeycloak() { - if (!this.useKeycloak) return; - const keycloakOut = 'keycloak'.concat('-', suffix); - this.entryPort = '8080'; - this.keycloakRedirectUris = ''; - this.appConfigs.forEach(appConfig => { - // Add application configuration - if (appConfig.applicationType === GATEWAY || appConfig.applicationType === MONOLITH) { - this.entryPort = appConfig.composePort; - if (this.ingressDomain) { - this.keycloakRedirectUris += `"http://${appConfig.baseName.toLowerCase()}.${this.kubernetesNamespace}.${this.ingressDomain}/*", - "https://${appConfig.baseName.toLowerCase()}.${this.kubernetesNamespace}.${this.ingressDomain}/*", `; - } else { - this.keycloakRedirectUris += `"http://${appConfig.baseName.toLowerCase()}:${appConfig.composePort}/*", - "https://${appConfig.baseName.toLowerCase()}:${appConfig.composePort}/*", `; - } - - this.keycloakRedirectUris += `"http://localhost:${appConfig.composePort}/*", - "https://localhost:${appConfig.composePort}/*", `; - - if (appConfig.devServerPort !== undefined) { - this.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPort}/*", `; - } - - this.debug(chalk.red.bold(`${appConfig.baseName} has redirect URIs ${this.keycloakRedirectUris}`)); - this.debug(chalk.red.bold(`AppConfig is ${JSON.stringify(appConfig)}`)); - } - }); - this.writeFile('keycloak/keycloak-configmap.yml.ejs', `${keycloakOut}/keycloak-configmap.yml`); - this.writeFile('keycloak/keycloak-postgresql.yml.ejs', `${keycloakOut}/keycloak-postgresql.yml`); - this.writeFile('keycloak/keycloak.yml.ejs', `${keycloakOut}/keycloak.yml`); - if (this.ingressType === GKE) { - this.writeFile('cert-manager/letsencrypt-staging-ca-secret.yml.ejs', 'cert-manager/letsencrypt-staging-ca-secret.yml'); - this.writeFile('cert-manager/letsencrypt-staging-issuer.yml.ejs', 'cert-manager/letsencrypt-staging-issuer.yml'); - } - }, - - writePrometheusGrafanaFiles() { - const monitOut = 'monitoring'.concat('-', suffix); - if (this.monitoring === PROMETHEUS) { - this.writeFile('monitoring/jhipster-prometheus-crd.yml.ejs', `${monitOut}/jhipster-prometheus-crd.yml`); - this.writeFile('monitoring/jhipster-prometheus-cr.yml.ejs', `${monitOut}/jhipster-prometheus-cr.yml`); - this.writeFile('monitoring/jhipster-grafana.yml.ejs', `${monitOut}/jhipster-grafana.yml`); - this.writeFile('monitoring/jhipster-grafana-dashboard.yml.ejs', `${monitOut}/jhipster-grafana-dashboard.yml`); - if (this.istio) { - this.writeFile('istio/gateway/jhipster-grafana-gateway.yml.ejs', `${monitOut}/jhipster-grafana-gateway.yml`); - } - } - }, - - writeRegistryFiles() { - const registryOut = 'registry'.concat('-', suffix); - if (this.serviceDiscoveryType === EUREKA) { - this.writeFile('registry/jhipster-registry.yml.ejs', `${registryOut}/jhipster-registry.yml`); - this.writeFile('registry/application-configmap.yml.ejs', `${registryOut}/application-configmap.yml`); - } else if (this.serviceDiscoveryType === CONSUL) { - this.writeFile('registry/consul.yml.ejs', `${registryOut}/consul.yml`); - this.writeFile('registry/consul-config-loader.yml.ejs', `${registryOut}/consul-config-loader.yml`); - this.writeFile('registry/application-configmap.yml.ejs', `${registryOut}/application-configmap.yml`); - } - }, - - writeConfigRunFile() { - this.writeFile('kubectl-apply.sh.ejs', 'kubectl-apply.sh'); - }, - - writeObservabilityGatewayFiles() { - if (!this.istio) return; - const istioOut = 'istio'.concat('-', suffix); - this.writeFile('istio/gateway/grafana-gateway.yml.ejs', `${istioOut}/grafana-gateway.yml`); - this.writeFile('istio/gateway/zipkin-gateway.yml.ejs', `${istioOut}/zipkin-gateway.yml`); - this.writeFile('istio/gateway/kiali-gateway.yml.ejs', `${istioOut}/kiali-gateway.yml`); - }, - - writeKustomize() { - const patchOut = 'patch'.concat('-', suffix); - this.writeFile('kustomize/kustomization.yml.ejs', 'kustomization.yml'); - if (this.istio) { - this.writeFile('kustomize/patch/istio-label.yml.ejs', `${patchOut}/istio-label.yml`); - this.writeFile('kustomize/patch/istio-namespace.yml.ejs', `${patchOut}/istio-namespace.yml`); - } - }, - - writeSkaffold() { - this.writeFile('skaffold/skaffold.yml.ejs', 'skaffold.yml'); - }, - }; -} diff --git a/generators/kubernetes/generator.js b/generators/kubernetes/generator.js new file mode 100644 index 000000000000..9d0d12c34f08 --- /dev/null +++ b/generators/kubernetes/generator.js @@ -0,0 +1,252 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return, import/no-named-as-default-member */ +import fs from 'fs'; +import chalk from 'chalk'; + +import BaseWorkspacesGenerator from '../base-workspaces/index.js'; + +import prompts from './prompts.js'; +import { writeFiles } from './files.js'; +import { buildToolTypes, messageBrokerTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_KUBERNETES } from '../generator-list.js'; +import statistics from '../statistics.js'; + +import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.js'; +import { checkKubernetes, loadConfig, setupKubernetesConstants, derivedKubernetesPlatformProperties } from './kubernetes-base.js'; +import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.js'; +import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.js'; +import { checkDocker } from '../docker/support/index.js'; +import { loadDerivedServerConfig } from '../server/support/index.js'; +import { loadDerivedAppConfig } from '../app/support/index.js'; + +const { KAFKA } = messageBrokerTypes; +const { MAVEN } = buildToolTypes; + +/** + * @class + * @extends {BaseWorkspacesGenerator} + */ +export default class KubernetesGenerator extends BaseWorkspacesGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_KUBERNETES); + } + } + + get initializing() { + return { + sayHello() { + this.log.log(chalk.white(`${chalk.bold('⎈')} Welcome to the JHipster Kubernetes Generator ${chalk.bold('⎈')}`)); + this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); + }, + existingDeployment() { + this.regenerate = this.regenerate || this.config.existed; + }, + loadDockerDependenciesTask, + checkDocker, + checkKubernetes, + loadConfig, + setupKubernetesConstants, + }; + } + + get [BaseWorkspacesGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return { + askForApplicationType: prompts.askForApplicationType, + askForPath: prompts.askForPath, + askForApps: prompts.askForApps, + askForMonitoring: prompts.askForMonitoring, + askForClustersMode: prompts.askForClustersMode, + askForServiceDiscovery: prompts.askForServiceDiscovery, + askForAdminPassword: prompts.askForAdminPassword, + askForKubernetesNamespace: prompts.askForKubernetesNamespace, + askForDockerRepositoryName: prompts.askForDockerRepositoryName, + askForDockerPushCommand: prompts.askForDockerPushCommand, + askForIstioSupport: prompts.askForIstioSupport, + askForKubernetesServiceType: prompts.askForKubernetesServiceType, + askForIngressType: prompts.askForIngressType, + askForIngressDomain: prompts.askForIngressDomain, + askForPersistentStorage: prompts.askForPersistentStorage, + askForStorageClassName: prompts.askForStorageClassName, + }; + } + + get [BaseWorkspacesGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get configuring() { + return { + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES); + }, + + generateJwtSecret, + }; + } + + get [BaseWorkspacesGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get loading() { + return { + loadFromYoRc, + loadSharedConfig() { + this.appConfigs.forEach(element => { + loadDerivedAppConfig({ application: element }); + loadDerivedServerConfig({ application: element }); + }); + loadDeploymentConfig.call(this); + derivedKubernetesPlatformProperties(this); + }, + }; + } + + get [BaseWorkspacesGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return { + configureImageNames, + + setPostPromptProp() { + this.appConfigs.forEach(element => { + element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); + if (element.messageBroker === KAFKA) { + this.useKafka = true; + } + }); + this.usesOauth2 = this.appConfigs.some(appConfig => appConfig.authenticationTypeOauth2); + this.usesIngress = this.kubernetesServiceType === 'Ingress'; + this.useKeycloak = this.usesOauth2 && this.usesIngress; + }, + }; + } + + get [BaseWorkspacesGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return writeFiles(); + } + + get [BaseWorkspacesGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get end() { + return { + checkImages, + deploy() { + if (this.hasWarning) { + this.log.warn(`${chalk.yellow.bold('WARNING!')} Kubernetes configuration generated, but no Jib cache found`); + this.log.warn('If you forgot to generate the Docker image for this application, please run:'); + this.log.warn(this.warningMessage); + } else { + this.log.verboseInfo(`\n${chalk.bold.green('Kubernetes configuration successfully generated!')}`); + } + + this.log.warn( + '\nYou will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', + ); + for (let i = 0; i < this.appsFolders.length; i++) { + const originalImageName = this.appConfigs[i].baseName.toLowerCase(); + const targetImageName = this.appConfigs[i].targetImageName; + if (originalImageName !== targetImageName) { + this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); + } + this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); + } + + if (this.dockerRepositoryName) { + this.log.log('\nAlternatively, you can use Jib to build and push image directly to a remote registry:'); + this.appsFolders.forEach((appsFolder, index) => { + const appConfig = this.appConfigs[index]; + let runCommand = ''; + if (appConfig.buildTool === MAVEN) { + runCommand = `./mvnw -ntp -Pprod verify jib:build${ + process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : '' + } -Djib.to.image=${appConfig.targetImageName}`; + } else { + runCommand = `./gradlew bootJar -Pprod jib${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''} -Djib.to.image=${ + appConfig.targetImageName + }`; + } + this.log.verboseInfo(` ${chalk.cyan(`${runCommand}`)} in ${this.destinationPath(this.directoryPath + appsFolder)}`); + }); + } + this.log.log('\nYou can deploy all your apps by running the following kubectl command:'); + this.log.verboseInfo(` ${chalk.cyan('bash kubectl-apply.sh -f')}`); + this.log.log('\n[OR]'); + this.log.log('\nIf you want to use kustomize configuration, then run the following command:'); + this.log.verboseInfo(` ${chalk.cyan('bash kubectl-apply.sh -k')}`); + if (this.gatewayNb + this.monolithicNb >= 1) { + const namespaceSuffix = this.kubernetesNamespace === 'default' ? '' : ` -n ${this.kubernetesNamespace}`; + this.log.verboseInfo("\nUse these commands to find your application's IP addresses:"); + for (let i = 0; i < this.appsFolders.length; i++) { + if (this.appConfigs[i].applicationType === 'gateway' || this.appConfigs[i].applicationType === 'monolith') { + this.log.verboseInfo(` ${chalk.cyan(`kubectl get svc ${this.appConfigs[i].baseName.toLowerCase()}${namespaceSuffix}`)}`); + } + } + this.log.log(); + } + // Make the apply script executable + try { + fs.chmodSync('kubectl-apply.sh', '755'); + } catch (err) { + this.log.warn("Failed to make 'kubectl-apply.sh' executable, you may need to run 'chmod +x kubectl-apply.sh'"); + } + }, + }; + } + + get [BaseWorkspacesGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * @private + * Returns the JDBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getJDBCUrl(databaseType, options = {}) { + return getJdbcUrl(databaseType, options); + } + + /** + * @private + * Returns the R2DBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getR2DBCUrl(databaseType, options = {}) { + return getR2dbcUrl(databaseType, options); + } +} diff --git a/generators/kubernetes/generator.mjs b/generators/kubernetes/generator.mjs deleted file mode 100644 index cd38933e2bbe..000000000000 --- a/generators/kubernetes/generator.mjs +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return, import/no-named-as-default-member */ -import fs from 'fs'; -import chalk from 'chalk'; - -import BaseWorkspacesGenerator from '../base-workspaces/index.mjs'; - -import prompts from './prompts.mjs'; -import { writeFiles } from './files.mjs'; -import { buildToolTypes, messageBrokerTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_KUBERNETES } from '../generator-list.mjs'; -import statistics from '../statistics.mjs'; - -import { checkImages, generateJwtSecret, configureImageNames, loadFromYoRc } from '../base-workspaces/internal/docker-base.mjs'; -import { checkKubernetes, loadConfig, setupKubernetesConstants, derivedKubernetesPlatformProperties } from './kubernetes-base.mjs'; -import { getJdbcUrl, getR2dbcUrl } from '../spring-data-relational/support/index.mjs'; -import { loadDeploymentConfig, loadDockerDependenciesTask } from '../base-workspaces/internal/index.mjs'; -import { checkDocker } from '../docker/support/index.mjs'; -import { loadDerivedServerConfig } from '../server/support/index.mjs'; -import { loadDerivedAppConfig } from '../app/support/index.mjs'; - -const { KAFKA } = messageBrokerTypes; -const { MAVEN } = buildToolTypes; - -/** - * @class - * @extends {BaseWorkspacesGenerator} - */ -export default class KubernetesGenerator extends BaseWorkspacesGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_KUBERNETES); - } - } - - get initializing() { - return { - sayHello() { - this.log.log(chalk.white(`${chalk.bold('⎈')} Welcome to the JHipster Kubernetes Generator ${chalk.bold('⎈')}`)); - this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`)); - }, - existingDeployment() { - this.regenerate = this.regenerate || this.config.existed; - }, - loadDockerDependenciesTask, - checkDocker, - checkKubernetes, - loadConfig, - setupKubernetesConstants, - }; - } - - get [BaseWorkspacesGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return { - askForApplicationType: prompts.askForApplicationType, - askForPath: prompts.askForPath, - askForApps: prompts.askForApps, - askForMonitoring: prompts.askForMonitoring, - askForClustersMode: prompts.askForClustersMode, - askForServiceDiscovery: prompts.askForServiceDiscovery, - askForAdminPassword: prompts.askForAdminPassword, - askForKubernetesNamespace: prompts.askForKubernetesNamespace, - askForDockerRepositoryName: prompts.askForDockerRepositoryName, - askForDockerPushCommand: prompts.askForDockerPushCommand, - askForIstioSupport: prompts.askForIstioSupport, - askForKubernetesServiceType: prompts.askForKubernetesServiceType, - askForIngressType: prompts.askForIngressType, - askForIngressDomain: prompts.askForIngressDomain, - askForPersistentStorage: prompts.askForPersistentStorage, - askForStorageClassName: prompts.askForStorageClassName, - }; - } - - get [BaseWorkspacesGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get configuring() { - return { - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_KUBERNETES); - }, - - generateJwtSecret, - }; - } - - get [BaseWorkspacesGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get loading() { - return { - loadFromYoRc, - loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); - loadDeploymentConfig.call(this); - derivedKubernetesPlatformProperties(this); - }, - }; - } - - get [BaseWorkspacesGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return { - configureImageNames, - - setPostPromptProp() { - this.appConfigs.forEach(element => { - element.clusteredDb ? (element.dbPeerCount = 3) : (element.dbPeerCount = 1); - if (element.messageBroker === KAFKA) { - this.useKafka = true; - } - }); - this.usesOauth2 = this.appConfigs.some(appConfig => appConfig.authenticationTypeOauth2); - this.usesIngress = this.kubernetesServiceType === 'Ingress'; - this.useKeycloak = this.usesOauth2 && this.usesIngress; - }, - }; - } - - get [BaseWorkspacesGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return writeFiles(); - } - - get [BaseWorkspacesGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get end() { - return { - checkImages, - deploy() { - if (this.hasWarning) { - this.log.warn(`${chalk.yellow.bold('WARNING!')} Kubernetes configuration generated, but no Jib cache found`); - this.log.warn('If you forgot to generate the Docker image for this application, please run:'); - this.log.warn(this.warningMessage); - } else { - this.log.verboseInfo(`\n${chalk.bold.green('Kubernetes configuration successfully generated!')}`); - } - - this.log.warn( - '\nYou will need to push your image to a registry. If you have not done so, use the following commands to tag and push the images:', - ); - for (let i = 0; i < this.appsFolders.length; i++) { - const originalImageName = this.appConfigs[i].baseName.toLowerCase(); - const targetImageName = this.appConfigs[i].targetImageName; - if (originalImageName !== targetImageName) { - this.log.verboseInfo(` ${chalk.cyan(`docker image tag ${originalImageName} ${targetImageName}`)}`); - } - this.log.verboseInfo(` ${chalk.cyan(`${this.dockerPushCommand} ${targetImageName}`)}`); - } - - if (this.dockerRepositoryName) { - this.log.log('\nAlternatively, you can use Jib to build and push image directly to a remote registry:'); - this.appsFolders.forEach((appsFolder, index) => { - const appConfig = this.appConfigs[index]; - let runCommand = ''; - if (appConfig.buildTool === MAVEN) { - runCommand = `./mvnw -ntp -Pprod verify jib:build${ - process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : '' - } -Djib.to.image=${appConfig.targetImageName}`; - } else { - runCommand = `./gradlew bootJar -Pprod jib${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''} -Djib.to.image=${ - appConfig.targetImageName - }`; - } - this.log.verboseInfo(` ${chalk.cyan(`${runCommand}`)} in ${this.destinationPath(this.directoryPath + appsFolder)}`); - }); - } - this.log.log('\nYou can deploy all your apps by running the following kubectl command:'); - this.log.verboseInfo(` ${chalk.cyan('bash kubectl-apply.sh -f')}`); - this.log.log('\n[OR]'); - this.log.log('\nIf you want to use kustomize configuration, then run the following command:'); - this.log.verboseInfo(` ${chalk.cyan('bash kubectl-apply.sh -k')}`); - if (this.gatewayNb + this.monolithicNb >= 1) { - const namespaceSuffix = this.kubernetesNamespace === 'default' ? '' : ` -n ${this.kubernetesNamespace}`; - this.log.verboseInfo("\nUse these commands to find your application's IP addresses:"); - for (let i = 0; i < this.appsFolders.length; i++) { - if (this.appConfigs[i].applicationType === 'gateway' || this.appConfigs[i].applicationType === 'monolith') { - this.log.verboseInfo(` ${chalk.cyan(`kubectl get svc ${this.appConfigs[i].baseName.toLowerCase()}${namespaceSuffix}`)}`); - } - } - this.log.log(); - } - // Make the apply script executable - try { - fs.chmodSync('kubectl-apply.sh', '755'); - } catch (err) { - this.log.warn("Failed to make 'kubectl-apply.sh' executable, you may need to run 'chmod +x kubectl-apply.sh'"); - } - }, - }; - } - - get [BaseWorkspacesGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * @private - * Returns the JDBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getJDBCUrl(databaseType, options = {}) { - return getJdbcUrl(databaseType, options); - } - - /** - * @private - * Returns the R2DBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getR2DBCUrl(databaseType, options = {}) { - return getR2dbcUrl(databaseType, options); - } -} diff --git a/generators/kubernetes/generator.spec.js b/generators/kubernetes/generator.spec.js new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/kubernetes/generator.spec.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/kubernetes/generator.spec.mjs b/generators/kubernetes/generator.spec.mjs deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/kubernetes/generator.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/kubernetes/index.mts b/generators/kubernetes/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/kubernetes/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/kubernetes/index.ts b/generators/kubernetes/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/kubernetes/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/kubernetes/kubernetes-base.js b/generators/kubernetes/kubernetes-base.js new file mode 100644 index 000000000000..cbd4fb51dc90 --- /dev/null +++ b/generators/kubernetes/kubernetes-base.js @@ -0,0 +1,162 @@ +/** + * Copyright 2013-2023 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 crypto from 'crypto'; +import * as _ from 'lodash-es'; + +import { defaultKubernetesConfig } from './kubernetes-constants.js'; +import { loadFromYoRc } from '../base-workspaces/internal/docker-base.js'; +import { + KUBERNETES_CORE_API_VERSION, + KUBERNETES_BATCH_API_VERSION, + KUBERNETES_DEPLOYMENT_API_VERSION, + KUBERNETES_STATEFULSET_API_VERSION, + KUBERNETES_INGRESS_API_VERSION, + KUBERNETES_ISTIO_NETWORKING_API_VERSION, + KUBERNETES_RBAC_API_VERSION, + HELM_KAFKA, + HELM_ELASTICSEARCH, + HELM_PROMETHEUS, + HELM_GRAFANA, + HELM_MARIADB, + HELM_MYSQL, + HELM_POSTGRESQL, + HELM_MONGODB_REPLICASET, + HELM_COUCHBASE_OPERATOR, +} from '../generator-constants.js'; +import { applicationTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.js'; + +const { MICROSERVICE } = applicationTypes; +const { GeneratorTypes, IngressTypes, ServiceTypes } = kubernetesPlatformTypes; + +const { INGRESS } = ServiceTypes; +const { GKE, NGINX } = IngressTypes; +const { K8S, HELM } = GeneratorTypes; + +export const checkKubernetes = async function () { + if (this.skipChecks) return; + + try { + await this.spawnCommand('kubectl version'); + } catch { + this.log.warn( + 'kubectl 1.2 or later is not installed on your computer.\n' + + 'Make sure you have Kubernetes installed. Read https://kubernetes.io/docs/setup/\n', + ); + } +}; + +export const checkHelm = async function () { + if (this.skipChecks) return; + + try { + await this.spawnCommand('helm version --client | grep -E "(v2\\.1[2-9]{1,2}\\.[0-9]{1,3})|(v3\\.[0-9]{1,2}\\.[0-9]{1,3})"'); + } catch { + this.log.warn( + 'helm 2.12.x or later is not installed on your computer.\n' + + 'Make sure you have helm installed. Read https://github.com/helm/helm/\n', + ); + } +}; + +export function loadConfig() { + loadFromYoRc.call(this); + if (!this.jhipsterConfig.dbRandomPassword) { + this.jhipsterConfig.dbRandomPassword = this.options.reproducibleTests ? 'SECRET-PASSWORD' : crypto.randomBytes(30).toString('hex'); + } + + const kubernetesWithDefaults = _.defaults({}, this.jhipsterConfig, defaultKubernetesConfig); + this.kubernetesNamespace = kubernetesWithDefaults.kubernetesNamespace; + this.kubernetesServiceType = kubernetesWithDefaults.kubernetesServiceType; + this.ingressType = kubernetesWithDefaults.ingressType; + this.ingressDomain = kubernetesWithDefaults.ingressDomain; + this.istio = kubernetesWithDefaults.istio; + this.dbRandomPassword = kubernetesWithDefaults.dbRandomPassword; + this.kubernetesUseDynamicStorage = kubernetesWithDefaults.kubernetesUseDynamicStorage; + this.kubernetesStorageClassName = kubernetesWithDefaults.kubernetesStorageClassName; + this.generatorType = kubernetesWithDefaults.generatorType; +} + +export function saveConfig() { + this.config.set( + _.defaults( + { + appsFolders: this.appsFolders, + directoryPath: this.directoryPath, + clusteredDbApps: this.clusteredDbApps, + serviceDiscoveryType: this.serviceDiscoveryType, + jwtSecretKey: this.jwtSecretKey, + dockerRepositoryName: this.dockerRepositoryName, + dockerPushCommand: this.dockerPushCommand, + kubernetesNamespace: this.kubernetesNamespace, + kubernetesServiceType: this.kubernetesServiceType, + kubernetesUseDynamicStorage: this.kubernetesUseDynamicStorage, + kubernetesStorageClassName: this.kubernetesStorageClassName, + generatorType: this.generatorType, + ingressType: this.ingressType, + ingressDomain: this.ingressDomain, + monitoring: this.monitoring, + istio: this.istio, + }, + defaultKubernetesConfig, + ), + ); +} + +export function setupKubernetesConstants() { + // Make constants available in templates + this.KUBERNETES_CORE_API_VERSION = KUBERNETES_CORE_API_VERSION; + this.KUBERNETES_BATCH_API_VERSION = KUBERNETES_BATCH_API_VERSION; + this.KUBERNETES_DEPLOYMENT_API_VERSION = KUBERNETES_DEPLOYMENT_API_VERSION; + this.KUBERNETES_STATEFULSET_API_VERSION = KUBERNETES_STATEFULSET_API_VERSION; + this.KUBERNETES_INGRESS_API_VERSION = KUBERNETES_INGRESS_API_VERSION; + this.KUBERNETES_ISTIO_NETWORKING_API_VERSION = KUBERNETES_ISTIO_NETWORKING_API_VERSION; + this.KUBERNETES_RBAC_API_VERSION = KUBERNETES_RBAC_API_VERSION; +} + +export function derivedKubernetesPlatformProperties(dest = this) { + dest.deploymentApplicationTypeMicroservice = dest.deploymentApplicationType === MICROSERVICE; + dest.ingressTypeNginx = dest.ingressType === NGINX; + dest.ingressTypeGke = dest.ingressType === GKE; + dest.kubernetesServiceTypeIngress = dest.kubernetesServiceType === INGRESS; + dest.kubernetesNamespaceDefault = dest.kubernetesNamespace === 'default'; + dest.generatorTypeK8s = dest.generatorType === K8S; + dest.generatorTypeHelm = dest.generatorType === HELM; +} + +export function setupHelmConstants() { + this.HELM_KAFKA = HELM_KAFKA; + this.HELM_ELASTICSEARCH = HELM_ELASTICSEARCH; + this.HELM_PROMETHEUS = HELM_PROMETHEUS; + this.HELM_GRAFANA = HELM_GRAFANA; + this.HELM_MARIADB = HELM_MARIADB; + this.HELM_MYSQL = HELM_MYSQL; + this.HELM_POSTGRESQL = HELM_POSTGRESQL; + this.HELM_MONGODB_REPLICASET = HELM_MONGODB_REPLICASET; + this.HELM_COUCHBASE_OPERATOR = HELM_COUCHBASE_OPERATOR; +} + +export default { + checkKubernetes, + checkHelm, + loadConfig, + saveConfig, + setupKubernetesConstants, + setupHelmConstants, + derivedKubernetesPlatformProperties, +}; diff --git a/generators/kubernetes/kubernetes-base.mjs b/generators/kubernetes/kubernetes-base.mjs deleted file mode 100644 index 962800fa68ff..000000000000 --- a/generators/kubernetes/kubernetes-base.mjs +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2013-2023 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 crypto from 'crypto'; -import * as _ from 'lodash-es'; - -import { defaultKubernetesConfig } from './kubernetes-constants.mjs'; -import { loadFromYoRc } from '../base-workspaces/internal/docker-base.mjs'; -import { - KUBERNETES_CORE_API_VERSION, - KUBERNETES_BATCH_API_VERSION, - KUBERNETES_DEPLOYMENT_API_VERSION, - KUBERNETES_STATEFULSET_API_VERSION, - KUBERNETES_INGRESS_API_VERSION, - KUBERNETES_ISTIO_NETWORKING_API_VERSION, - KUBERNETES_RBAC_API_VERSION, - HELM_KAFKA, - HELM_ELASTICSEARCH, - HELM_PROMETHEUS, - HELM_GRAFANA, - HELM_MARIADB, - HELM_MYSQL, - HELM_POSTGRESQL, - HELM_MONGODB_REPLICASET, - HELM_COUCHBASE_OPERATOR, -} from '../generator-constants.mjs'; -import { applicationTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.mjs'; - -const { MICROSERVICE } = applicationTypes; -const { GeneratorTypes, IngressTypes, ServiceTypes } = kubernetesPlatformTypes; - -const { INGRESS } = ServiceTypes; -const { GKE, NGINX } = IngressTypes; -const { K8S, HELM } = GeneratorTypes; - -export const checkKubernetes = async function () { - if (this.skipChecks) return; - - try { - await this.spawnCommand('kubectl version'); - } catch { - this.log.warn( - 'kubectl 1.2 or later is not installed on your computer.\n' + - 'Make sure you have Kubernetes installed. Read https://kubernetes.io/docs/setup/\n', - ); - } -}; - -export const checkHelm = async function () { - if (this.skipChecks) return; - - try { - await this.spawnCommand('helm version --client | grep -E "(v2\\.1[2-9]{1,2}\\.[0-9]{1,3})|(v3\\.[0-9]{1,2}\\.[0-9]{1,3})"'); - } catch { - this.log.warn( - 'helm 2.12.x or later is not installed on your computer.\n' + - 'Make sure you have helm installed. Read https://github.com/helm/helm/\n', - ); - } -}; - -export function loadConfig() { - loadFromYoRc.call(this); - if (!this.jhipsterConfig.dbRandomPassword) { - this.jhipsterConfig.dbRandomPassword = this.options.reproducibleTests ? 'SECRET-PASSWORD' : crypto.randomBytes(30).toString('hex'); - } - - const kubernetesWithDefaults = _.defaults({}, this.jhipsterConfig, defaultKubernetesConfig); - this.kubernetesNamespace = kubernetesWithDefaults.kubernetesNamespace; - this.kubernetesServiceType = kubernetesWithDefaults.kubernetesServiceType; - this.ingressType = kubernetesWithDefaults.ingressType; - this.ingressDomain = kubernetesWithDefaults.ingressDomain; - this.istio = kubernetesWithDefaults.istio; - this.dbRandomPassword = kubernetesWithDefaults.dbRandomPassword; - this.kubernetesUseDynamicStorage = kubernetesWithDefaults.kubernetesUseDynamicStorage; - this.kubernetesStorageClassName = kubernetesWithDefaults.kubernetesStorageClassName; - this.generatorType = kubernetesWithDefaults.generatorType; -} - -export function saveConfig() { - this.config.set( - _.defaults( - { - appsFolders: this.appsFolders, - directoryPath: this.directoryPath, - clusteredDbApps: this.clusteredDbApps, - serviceDiscoveryType: this.serviceDiscoveryType, - jwtSecretKey: this.jwtSecretKey, - dockerRepositoryName: this.dockerRepositoryName, - dockerPushCommand: this.dockerPushCommand, - kubernetesNamespace: this.kubernetesNamespace, - kubernetesServiceType: this.kubernetesServiceType, - kubernetesUseDynamicStorage: this.kubernetesUseDynamicStorage, - kubernetesStorageClassName: this.kubernetesStorageClassName, - generatorType: this.generatorType, - ingressType: this.ingressType, - ingressDomain: this.ingressDomain, - monitoring: this.monitoring, - istio: this.istio, - }, - defaultKubernetesConfig, - ), - ); -} - -export function setupKubernetesConstants() { - // Make constants available in templates - this.KUBERNETES_CORE_API_VERSION = KUBERNETES_CORE_API_VERSION; - this.KUBERNETES_BATCH_API_VERSION = KUBERNETES_BATCH_API_VERSION; - this.KUBERNETES_DEPLOYMENT_API_VERSION = KUBERNETES_DEPLOYMENT_API_VERSION; - this.KUBERNETES_STATEFULSET_API_VERSION = KUBERNETES_STATEFULSET_API_VERSION; - this.KUBERNETES_INGRESS_API_VERSION = KUBERNETES_INGRESS_API_VERSION; - this.KUBERNETES_ISTIO_NETWORKING_API_VERSION = KUBERNETES_ISTIO_NETWORKING_API_VERSION; - this.KUBERNETES_RBAC_API_VERSION = KUBERNETES_RBAC_API_VERSION; -} - -export function derivedKubernetesPlatformProperties(dest = this) { - dest.deploymentApplicationTypeMicroservice = dest.deploymentApplicationType === MICROSERVICE; - dest.ingressTypeNginx = dest.ingressType === NGINX; - dest.ingressTypeGke = dest.ingressType === GKE; - dest.kubernetesServiceTypeIngress = dest.kubernetesServiceType === INGRESS; - dest.kubernetesNamespaceDefault = dest.kubernetesNamespace === 'default'; - dest.generatorTypeK8s = dest.generatorType === K8S; - dest.generatorTypeHelm = dest.generatorType === HELM; -} - -export function setupHelmConstants() { - this.HELM_KAFKA = HELM_KAFKA; - this.HELM_ELASTICSEARCH = HELM_ELASTICSEARCH; - this.HELM_PROMETHEUS = HELM_PROMETHEUS; - this.HELM_GRAFANA = HELM_GRAFANA; - this.HELM_MARIADB = HELM_MARIADB; - this.HELM_MYSQL = HELM_MYSQL; - this.HELM_POSTGRESQL = HELM_POSTGRESQL; - this.HELM_MONGODB_REPLICASET = HELM_MONGODB_REPLICASET; - this.HELM_COUCHBASE_OPERATOR = HELM_COUCHBASE_OPERATOR; -} - -export default { - checkKubernetes, - checkHelm, - loadConfig, - saveConfig, - setupKubernetesConstants, - setupHelmConstants, - derivedKubernetesPlatformProperties, -}; diff --git a/generators/kubernetes/kubernetes-constants.js b/generators/kubernetes/kubernetes-constants.js new file mode 100644 index 000000000000..5529865b0ca9 --- /dev/null +++ b/generators/kubernetes/kubernetes-constants.js @@ -0,0 +1,51 @@ +/** + * Copyright 2013-2023 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 { monitoringTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.js'; + +const { NO } = monitoringTypes; +const { ServiceTypes, IngressTypes, GeneratorTypes } = kubernetesPlatformTypes; + +const { LOAD_BALANCER } = ServiceTypes; +const { NGINX } = IngressTypes; +const { K8S } = GeneratorTypes; + +export const kubernetesDefaultConfig = { + kubernetesNamespace: 'default', + kubernetesServiceType: LOAD_BALANCER, + monitoring: NO, + istio: false, +}; + +export const ingressDefaultConfig = { + ingressType: NGINX, +}; + +export const generatorDefaultConfig = { + generatorType: K8S, +}; + +export const defaultKubernetesConfig = { + ...kubernetesDefaultConfig, +}; + +export default { + defaultKubernetesConfig, + ingressDefaultConfig, + generatorDefaultConfig, +}; diff --git a/generators/kubernetes/kubernetes-constants.mjs b/generators/kubernetes/kubernetes-constants.mjs deleted file mode 100644 index 7b8888d0734e..000000000000 --- a/generators/kubernetes/kubernetes-constants.mjs +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2013-2023 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 { monitoringTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.mjs'; - -const { NO } = monitoringTypes; -const { ServiceTypes, IngressTypes, GeneratorTypes } = kubernetesPlatformTypes; - -const { LOAD_BALANCER } = ServiceTypes; -const { NGINX } = IngressTypes; -const { K8S } = GeneratorTypes; - -export const kubernetesDefaultConfig = { - kubernetesNamespace: 'default', - kubernetesServiceType: LOAD_BALANCER, - monitoring: NO, - istio: false, -}; - -export const ingressDefaultConfig = { - ingressType: NGINX, -}; - -export const generatorDefaultConfig = { - generatorType: K8S, -}; - -export const defaultKubernetesConfig = { - ...kubernetesDefaultConfig, -}; - -export default { - defaultKubernetesConfig, - ingressDefaultConfig, - generatorDefaultConfig, -}; diff --git a/generators/kubernetes/kubernetes.spec.mts b/generators/kubernetes/kubernetes.spec.mts deleted file mode 100644 index 35c31d07659e..000000000000 --- a/generators/kubernetes/kubernetes.spec.mts +++ /dev/null @@ -1,808 +0,0 @@ -import { expect } from 'esmocha'; - -import { basicHelpers as helpers, getGenerator, runResult } from '../../test/support/index.mjs'; -import { GENERATOR_KUBERNETES } from '../generator-list.mjs'; - -const expectedFiles = { - eurekaregistry: ['./registry-k8s/jhipster-registry.yml', './registry-k8s/application-configmap.yml'], - consulregistry: ['./registry-k8s/consul.yml', './registry-k8s/consul-config-loader.yml', './registry-k8s/application-configmap.yml'], - jhgate: ['./jhgate-k8s/jhgate-deployment.yml', './jhgate-k8s/jhgate-mysql.yml', './jhgate-k8s/jhgate-service.yml'], - jhgateingress: ['./jhgate-k8s/jhgate-ingress.yml'], - customnamespace: ['./namespace.yml'], - msmysql: ['./msmysql-k8s/msmysql-deployment.yml', './msmysql-k8s/msmysql-mysql.yml', './msmysql-k8s/msmysql-service.yml'], - mspsql: [ - './mspsql-k8s/mspsql-deployment.yml', - './mspsql-k8s/mspsql-postgresql.yml', - './mspsql-k8s/mspsql-service.yml', - './mspsql-k8s/mspsql-elasticsearch.yml', - ], - msmongodb: ['./msmongodb-k8s/msmongodb-deployment.yml', './msmongodb-k8s/msmongodb-mongodb.yml', './msmongodb-k8s/msmongodb-service.yml'], - msmariadb: ['./msmariadb-k8s/msmariadb-deployment.yml', './msmariadb-k8s/msmariadb-mariadb.yml', './msmariadb-k8s/msmariadb-service.yml'], - msmssqldb: ['./msmssqldb-k8s/msmssqldb-deployment.yml', './msmssqldb-k8s/msmssqldb-mssql.yml', './msmssqldb-k8s/msmssqldb-service.yml'], - monolith: [ - './samplemysql-k8s/samplemysql-deployment.yml', - './samplemysql-k8s/samplemysql-mysql.yml', - './samplemysql-k8s/samplemysql-service.yml', - './samplemysql-k8s/samplemysql-elasticsearch.yml', - ], - kafka: [ - './samplekafka-k8s/samplekafka-deployment.yml', - './samplekafka-k8s/samplekafka-mysql.yml', - './samplekafka-k8s/samplekafka-service.yml', - './messagebroker-k8s/kafka.yml', - ], - prometheusmonit: [ - './monitoring-k8s/jhipster-prometheus-crd.yml', - './monitoring-k8s/jhipster-prometheus-cr.yml', - './monitoring-k8s/jhipster-grafana.yml', - './monitoring-k8s/jhipster-grafana-dashboard.yml', - ], - jhgategateway: ['./jhgate-k8s/jhgate-gateway.yml', './jhgate-k8s/jhgate-destination-rule.yml', './jhgate-k8s/jhgate-virtual-service.yml'], - applyScript: ['./kubectl-apply.sh'], - keycloak: ['./keycloak-k8s/keycloak.yml', './keycloak-k8s/keycloak-configmap.yml', './keycloak-k8s/keycloak-postgresql.yml'], - certmanager: ['./cert-manager/letsencrypt-staging-ca-secret.yml', './cert-manager/letsencrypt-staging-issuer.yml'], -}; - -describe('generator - Kubernetes', () => { - describe('only gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'gateway', - directoryPath: './', - chosenApps, - adminPassword: 'meetup', - dockerRepositoryName: 'jhipsterrepository', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'jhipsternamespace', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files and content', () => { - runResult.assertFile(expectedFiles.consulregistry); - runResult.assertFileContent('./registry-k8s/consul.yml', /a 24 chars base64 encoded string/); - }); - it('creates expected gateway files and content', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /image: jhipsterrepository\/jhgate/); - runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /jhipsternamespace.svc.cluster/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('only gateway with eureka', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'eureka' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - directoryPath: './', - chosenApps, - adminPassword: 'meetup', - dockerRepositoryName: 'jhipsterrepository', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'jhipsternamespace', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files and content', () => { - runResult.assertFile(expectedFiles.eurekaregistry); - runResult.assertFileContent('./registry-k8s/jhipster-registry.yml', /# base64 encoded "meetup"/); - }); - it('creates expected gateway files and content', () => { - runResult.assertFile(expectedFiles.jhgate); - runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /image: jhipsterrepository\/jhgate/); - runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /jhipsternamespace.svc.cluster/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and mysql microservice', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace', () => { - before(async () => { - const chosenApps = ['02-mysql']; - - await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - jhipsterConsole: true, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and ingress', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - kubernetesServiceType: 'Ingress', - ingressDomain: 'example.com', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected gateway ingress files', () => { - runResult.assertFile(expectedFiles.jhgateingress); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and ingressType gke', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - kubernetesServiceType: 'Ingress', - ingressType: 'gke', - ingressDomain: 'example.com', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected gateway ingress files', () => { - runResult.assertFile(expectedFiles.jhgateingress); - }); - it('create the expected cert-manager files', () => { - runResult.assertFile(expectedFiles.certmanager); - }); - it('create the expected keycloak files', () => { - runResult.assertFile(expectedFiles.keycloak); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway and ingressType nginx', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - kubernetesServiceType: 'Ingress', - ingressDomain: 'example.com', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected gateway ingress files', () => { - runResult.assertFile(expectedFiles.jhgateingress); - }); - it('create the expected keycloak files', () => { - runResult.assertFile(expectedFiles.keycloak); - }); - it('create the expected cert-manager files', () => { - runResult.assertNoFile(expectedFiles.certmanager); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('MySQL and PostgreSQL microservices without gateway', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql', '03-psql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it("doesn't creates gateway files", () => { - runResult.assertNoFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway, mysql, psql, mongodb, mariadb, mssql microservices', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - }); - it('creates expected mongodb files', () => { - runResult.assertFile(expectedFiles.msmongodb); - }); - it('creates expected mariadb files', () => { - runResult.assertFile(expectedFiles.msmariadb); - }); - it('creates expected mssql files', () => { - runResult.assertFile(expectedFiles.msmssqldb); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('monolith application', () => { - let runResult; - before(async () => { - const chosenApps = ['08-monolith']; - - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'monolith', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it("doesn't creates registry files", () => { - runResult.assertNoFile(expectedFiles.eurekaregistry); - runResult.assertNoFile(expectedFiles.consulregistry); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.monolith); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('Kafka application', () => { - let runResult; - before(async () => { - const chosenApps = ['09-kafka']; - - runResult = await helpers - .generateDeploymentWorkspaces() - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'monolith', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it("doesn't creates registry files", () => { - runResult.assertNoFile(expectedFiles.eurekaregistry); - runResult.assertNoFile(expectedFiles.consulregistry); - }); - it('creates expected default files', () => { - runResult.assertFile(expectedFiles.kafka); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { - let runResult; - before(async () => { - const chosenApps = ['02-mysql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'mynamespace', - monitoring: 'prometheus', - kubernetesServiceType: 'LoadBalancer', - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - }); - it('creates expected prometheus files', () => { - runResult.assertFile(expectedFiles.prometheusmonit); - }); - it('creates expected namespace file', () => { - runResult.assertFile(expectedFiles.customnamespace); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('gateway with istio routing', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - ingressDomain: 'example.com', - clusteredDbApps: [], - istio: true, - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected service gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected routing gateway and istio files', () => { - runResult.assertFile(expectedFiles.jhgategateway); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); - - describe('mysql, psql, mongodb, mariadb, mssql microservices with dynamic storage provisioning', () => { - let runResult; - before(async () => { - const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; - - runResult = await helpers - .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) - .withWorkspacesSamples(...chosenApps) - .withGenerateWorkspaceApplications(); - - runResult = await runResult - .create(getGenerator(GENERATOR_KUBERNETES)) - .withSpawnMock() - .withOptions({ - askAnswered: true, - }) - .withAnswers({ - deploymentApplicationType: 'microservice', - directoryPath: './', - chosenApps, - dockerRepositoryName: 'jhipster', - dockerPushCommand: 'docker push', - kubernetesNamespace: 'default', - jhipsterConsole: false, - kubernetesServiceType: 'LoadBalancer', - clusteredDbApps: [], - kubernetesUseDynamicStorage: true, - kubernetesStorageClassName: '', - }) - .run(); - }); - it('should match files snapshot', function () { - expect(runResult.getSnapshot()).toMatchSnapshot(); - }); - it('should match spawn calls snapshot', function () { - expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); - }); - it('creates expected registry files', () => { - runResult.assertFile(expectedFiles.consulregistry); - }); - it('creates expected gateway files', () => { - runResult.assertFile(expectedFiles.jhgate); - }); - it('creates expected mysql files', () => { - runResult.assertFile(expectedFiles.msmysql); - runResult.assertFileContent(expectedFiles.msmysql[1], /PersistentVolumeClaim/); - runResult.assertFileContent(expectedFiles.msmysql[1], /claimName:/); - }); - - it('creates expected psql files', () => { - runResult.assertFile(expectedFiles.mspsql); - runResult.assertFileContent(expectedFiles.mspsql[1], /PersistentVolumeClaim/); - runResult.assertFileContent(expectedFiles.mspsql[1], /claimName:/); - }); - it('creates expected mongodb files', () => { - runResult.assertFile(expectedFiles.msmongodb); - runResult.assertFileContent(expectedFiles.msmongodb[1], /volumeClaimTemplates:/); - }); - it('creates expected mariadb files', () => { - runResult.assertFile(expectedFiles.msmariadb); - runResult.assertFileContent(expectedFiles.msmariadb[1], /PersistentVolumeClaim/); - runResult.assertFileContent(expectedFiles.msmariadb[1], /claimName:/); - }); - it('creates expected mssql files', () => { - runResult.assertFile(expectedFiles.msmssqldb); - runResult.assertFileContent(expectedFiles.msmssqldb[1], /PersistentVolumeClaim/); - runResult.assertFileContent(expectedFiles.msmssqldb[1], /claimName:/); - }); - it('create the apply script', () => { - runResult.assertFile(expectedFiles.applyScript); - }); - }); -}); diff --git a/generators/kubernetes/kubernetes.spec.ts b/generators/kubernetes/kubernetes.spec.ts new file mode 100644 index 000000000000..5e64ba8acde6 --- /dev/null +++ b/generators/kubernetes/kubernetes.spec.ts @@ -0,0 +1,808 @@ +import { expect } from 'esmocha'; + +import { basicHelpers as helpers, getGenerator, runResult } from '../../test/support/index.js'; +import { GENERATOR_KUBERNETES } from '../generator-list.js'; + +const expectedFiles = { + eurekaregistry: ['./registry-k8s/jhipster-registry.yml', './registry-k8s/application-configmap.yml'], + consulregistry: ['./registry-k8s/consul.yml', './registry-k8s/consul-config-loader.yml', './registry-k8s/application-configmap.yml'], + jhgate: ['./jhgate-k8s/jhgate-deployment.yml', './jhgate-k8s/jhgate-mysql.yml', './jhgate-k8s/jhgate-service.yml'], + jhgateingress: ['./jhgate-k8s/jhgate-ingress.yml'], + customnamespace: ['./namespace.yml'], + msmysql: ['./msmysql-k8s/msmysql-deployment.yml', './msmysql-k8s/msmysql-mysql.yml', './msmysql-k8s/msmysql-service.yml'], + mspsql: [ + './mspsql-k8s/mspsql-deployment.yml', + './mspsql-k8s/mspsql-postgresql.yml', + './mspsql-k8s/mspsql-service.yml', + './mspsql-k8s/mspsql-elasticsearch.yml', + ], + msmongodb: ['./msmongodb-k8s/msmongodb-deployment.yml', './msmongodb-k8s/msmongodb-mongodb.yml', './msmongodb-k8s/msmongodb-service.yml'], + msmariadb: ['./msmariadb-k8s/msmariadb-deployment.yml', './msmariadb-k8s/msmariadb-mariadb.yml', './msmariadb-k8s/msmariadb-service.yml'], + msmssqldb: ['./msmssqldb-k8s/msmssqldb-deployment.yml', './msmssqldb-k8s/msmssqldb-mssql.yml', './msmssqldb-k8s/msmssqldb-service.yml'], + monolith: [ + './samplemysql-k8s/samplemysql-deployment.yml', + './samplemysql-k8s/samplemysql-mysql.yml', + './samplemysql-k8s/samplemysql-service.yml', + './samplemysql-k8s/samplemysql-elasticsearch.yml', + ], + kafka: [ + './samplekafka-k8s/samplekafka-deployment.yml', + './samplekafka-k8s/samplekafka-mysql.yml', + './samplekafka-k8s/samplekafka-service.yml', + './messagebroker-k8s/kafka.yml', + ], + prometheusmonit: [ + './monitoring-k8s/jhipster-prometheus-crd.yml', + './monitoring-k8s/jhipster-prometheus-cr.yml', + './monitoring-k8s/jhipster-grafana.yml', + './monitoring-k8s/jhipster-grafana-dashboard.yml', + ], + jhgategateway: ['./jhgate-k8s/jhgate-gateway.yml', './jhgate-k8s/jhgate-destination-rule.yml', './jhgate-k8s/jhgate-virtual-service.yml'], + applyScript: ['./kubectl-apply.sh'], + keycloak: ['./keycloak-k8s/keycloak.yml', './keycloak-k8s/keycloak-configmap.yml', './keycloak-k8s/keycloak-postgresql.yml'], + certmanager: ['./cert-manager/letsencrypt-staging-ca-secret.yml', './cert-manager/letsencrypt-staging-issuer.yml'], +}; + +describe('generator - Kubernetes', () => { + describe('only gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'gateway', + directoryPath: './', + chosenApps, + adminPassword: 'meetup', + dockerRepositoryName: 'jhipsterrepository', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'jhipsternamespace', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files and content', () => { + runResult.assertFile(expectedFiles.consulregistry); + runResult.assertFileContent('./registry-k8s/consul.yml', /a 24 chars base64 encoded string/); + }); + it('creates expected gateway files and content', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /image: jhipsterrepository\/jhgate/); + runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /jhipsternamespace.svc.cluster/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('only gateway with eureka', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'eureka' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + directoryPath: './', + chosenApps, + adminPassword: 'meetup', + dockerRepositoryName: 'jhipsterrepository', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'jhipsternamespace', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files and content', () => { + runResult.assertFile(expectedFiles.eurekaregistry); + runResult.assertFileContent('./registry-k8s/jhipster-registry.yml', /# base64 encoded "meetup"/); + }); + it('creates expected gateway files and content', () => { + runResult.assertFile(expectedFiles.jhgate); + runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /image: jhipsterrepository\/jhgate/); + runResult.assertFileContent('./jhgate-k8s/jhgate-deployment.yml', /jhipsternamespace.svc.cluster/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and mysql microservice', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace', () => { + before(async () => { + const chosenApps = ['02-mysql']; + + await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + jhipsterConsole: true, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and ingress', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + kubernetesServiceType: 'Ingress', + ingressDomain: 'example.com', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected gateway ingress files', () => { + runResult.assertFile(expectedFiles.jhgateingress); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and ingressType gke', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + kubernetesServiceType: 'Ingress', + ingressType: 'gke', + ingressDomain: 'example.com', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected gateway ingress files', () => { + runResult.assertFile(expectedFiles.jhgateingress); + }); + it('create the expected cert-manager files', () => { + runResult.assertFile(expectedFiles.certmanager); + }); + it('create the expected keycloak files', () => { + runResult.assertFile(expectedFiles.keycloak); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway and ingressType nginx', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ authenticationType: 'oauth2' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + kubernetesServiceType: 'Ingress', + ingressDomain: 'example.com', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected gateway ingress files', () => { + runResult.assertFile(expectedFiles.jhgateingress); + }); + it('create the expected keycloak files', () => { + runResult.assertFile(expectedFiles.keycloak); + }); + it('create the expected cert-manager files', () => { + runResult.assertNoFile(expectedFiles.certmanager); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('MySQL and PostgreSQL microservices without gateway', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql', '03-psql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it("doesn't creates gateway files", () => { + runResult.assertNoFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway, mysql, psql, mongodb, mariadb, mssql microservices', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + }); + it('creates expected mongodb files', () => { + runResult.assertFile(expectedFiles.msmongodb); + }); + it('creates expected mariadb files', () => { + runResult.assertFile(expectedFiles.msmariadb); + }); + it('creates expected mssql files', () => { + runResult.assertFile(expectedFiles.msmssqldb); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('monolith application', () => { + let runResult; + before(async () => { + const chosenApps = ['08-monolith']; + + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'monolith', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it("doesn't creates registry files", () => { + runResult.assertNoFile(expectedFiles.eurekaregistry); + runResult.assertNoFile(expectedFiles.consulregistry); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.monolith); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('Kafka application', () => { + let runResult; + before(async () => { + const chosenApps = ['09-kafka']; + + runResult = await helpers + .generateDeploymentWorkspaces() + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'monolith', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it("doesn't creates registry files", () => { + runResult.assertNoFile(expectedFiles.eurekaregistry); + runResult.assertNoFile(expectedFiles.consulregistry); + }); + it('creates expected default files', () => { + runResult.assertFile(expectedFiles.kafka); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql microservice with custom namespace and jhipster prometheus monitoring', () => { + let runResult; + before(async () => { + const chosenApps = ['02-mysql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'mynamespace', + monitoring: 'prometheus', + kubernetesServiceType: 'LoadBalancer', + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + }); + it('creates expected prometheus files', () => { + runResult.assertFile(expectedFiles.prometheusmonit); + }); + it('creates expected namespace file', () => { + runResult.assertFile(expectedFiles.customnamespace); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('gateway with istio routing', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + ingressDomain: 'example.com', + clusteredDbApps: [], + istio: true, + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected service gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected routing gateway and istio files', () => { + runResult.assertFile(expectedFiles.jhgategateway); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); + + describe('mysql, psql, mongodb, mariadb, mssql microservices with dynamic storage provisioning', () => { + let runResult; + before(async () => { + const chosenApps = ['01-gateway', '02-mysql', '03-psql', '04-mongo', '07-mariadb', '11-mssql']; + + runResult = await helpers + .generateDeploymentWorkspaces({ serviceDiscoveryType: 'consul' }) + .withWorkspacesSamples(...chosenApps) + .withGenerateWorkspaceApplications(); + + runResult = await runResult + .create(getGenerator(GENERATOR_KUBERNETES)) + .withSpawnMock() + .withOptions({ + askAnswered: true, + }) + .withAnswers({ + deploymentApplicationType: 'microservice', + directoryPath: './', + chosenApps, + dockerRepositoryName: 'jhipster', + dockerPushCommand: 'docker push', + kubernetesNamespace: 'default', + jhipsterConsole: false, + kubernetesServiceType: 'LoadBalancer', + clusteredDbApps: [], + kubernetesUseDynamicStorage: true, + kubernetesStorageClassName: '', + }) + .run(); + }); + it('should match files snapshot', function () { + expect(runResult.getSnapshot()).toMatchSnapshot(); + }); + it('should match spawn calls snapshot', function () { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); + it('creates expected registry files', () => { + runResult.assertFile(expectedFiles.consulregistry); + }); + it('creates expected gateway files', () => { + runResult.assertFile(expectedFiles.jhgate); + }); + it('creates expected mysql files', () => { + runResult.assertFile(expectedFiles.msmysql); + runResult.assertFileContent(expectedFiles.msmysql[1], /PersistentVolumeClaim/); + runResult.assertFileContent(expectedFiles.msmysql[1], /claimName:/); + }); + + it('creates expected psql files', () => { + runResult.assertFile(expectedFiles.mspsql); + runResult.assertFileContent(expectedFiles.mspsql[1], /PersistentVolumeClaim/); + runResult.assertFileContent(expectedFiles.mspsql[1], /claimName:/); + }); + it('creates expected mongodb files', () => { + runResult.assertFile(expectedFiles.msmongodb); + runResult.assertFileContent(expectedFiles.msmongodb[1], /volumeClaimTemplates:/); + }); + it('creates expected mariadb files', () => { + runResult.assertFile(expectedFiles.msmariadb); + runResult.assertFileContent(expectedFiles.msmariadb[1], /PersistentVolumeClaim/); + runResult.assertFileContent(expectedFiles.msmariadb[1], /claimName:/); + }); + it('creates expected mssql files', () => { + runResult.assertFile(expectedFiles.msmssqldb); + runResult.assertFileContent(expectedFiles.msmssqldb[1], /PersistentVolumeClaim/); + runResult.assertFileContent(expectedFiles.msmssqldb[1], /claimName:/); + }); + it('create the apply script', () => { + runResult.assertFile(expectedFiles.applyScript); + }); + }); +}); diff --git a/generators/kubernetes/prompts.js b/generators/kubernetes/prompts.js new file mode 100644 index 000000000000..0d959b4847fa --- /dev/null +++ b/generators/kubernetes/prompts.js @@ -0,0 +1,268 @@ +/** + * Copyright 2013-2023 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 dockerPrompts from '../base-workspaces/internal/docker-prompts.js'; +import { defaultKubernetesConfig, ingressDefaultConfig } from './kubernetes-constants.js'; + +import { applicationTypes, databaseTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.js'; + +const { MONOLITH } = applicationTypes; +const { IngressTypes, ServiceTypes } = kubernetesPlatformTypes; + +const NO_DATABASE = databaseTypes.NO; +const { LOAD_BALANCER, INGRESS, NODE_PORT } = ServiceTypes; +const { GKE, NGINX } = IngressTypes; + +export default { + askForKubernetesNamespace, + askForKubernetesServiceType, + askForIngressType, + askForIngressDomain, + askForIstioSupport, + askForPersistentStorage, + askForStorageClassName, + ...dockerPrompts, +}; + +export async function askForKubernetesNamespace() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const prompts = [ + { + type: 'input', + name: 'kubernetesNamespace', + message: 'What should we use for the Kubernetes namespace?', + default: this.kubernetesNamespace ? this.kubernetesNamespace : defaultKubernetesConfig.kubernetesNamespace, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.kubernetesNamespace = props.kubernetesNamespace; +} + +export async function askForKubernetesServiceType() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + + const istio = this.istio; + + const prompts = [ + { + when: () => !istio, + type: 'list', + name: 'kubernetesServiceType', + message: 'Choose the Kubernetes service type for your edge services', + choices: [ + { + value: LOAD_BALANCER, + name: 'LoadBalancer - Let a Kubernetes cloud provider automatically assign an IP', + }, + { + value: NODE_PORT, + name: 'NodePort - expose the services to a random port (30000 - 32767) on all cluster nodes', + }, + { + value: INGRESS, + name: 'Ingress - create ingresses for your services. Requires a running ingress controller', + }, + ], + default: this.kubernetesServiceType ? this.kubernetesServiceType : defaultKubernetesConfig.kubernetesServiceType, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.kubernetesServiceType = props.kubernetesServiceType; +} + +export async function askForIngressType() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + const kubernetesServiceType = this.kubernetesServiceType; + + const prompts = [ + { + when: () => kubernetesServiceType === INGRESS, + type: 'list', + name: 'ingressType', + message: 'Choose the Kubernetes Ingress type', + choices: [ + { + value: NGINX, + name: 'NGINX Ingress - choose this if you are running on Minikube', + }, + { + value: GKE, + name: 'Google Kubernetes Engine Ingress - choose this if you are running on GKE', + }, + ], + default: this.ingressType ? this.ingressType : ingressDefaultConfig.ingressType, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.ingressType = props.ingressType; +} + +export async function askForIngressDomain() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + const kubernetesServiceType = this.kubernetesServiceType; + const istio = this.istio; + this.ingressDomain = this.ingressDomain && this.ingressDomain.startsWith('.') ? this.ingressDomain.substring(1) : this.ingressDomain; + + const istioIpCommand = "kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"; + let istioMessage = ''; + + let defaultValue = ''; + if (this.ingressDomain) { + defaultValue = this.ingressDomain; + } else if (istio) { + // If it's Istio, and no previous domain is configured, try to determine the default value + try { + const { stdout: istioIngressIp } = this.spawnCommandSync(istioIpCommand, { stdio: 'pipe' }); + defaultValue = `${istioIngressIp}.nip.io`; + } catch (ex) { + istioMessage = `Unable to determine Istio Ingress IP address. You can find the Istio Ingress IP address by running the command line:\n ${istioIpCommand}`; + } + } else if (this.ingressType === NGINX) { + defaultValue = '192.168.99.100.nip.io'; + } else { + defaultValue = 'none'; + } + + const examples = ['example.com', '192.168.99.100.nip.io']; + if (this.ingressType !== NGINX && !istio) { + examples.push('none'); + } + + const prompts = [ + { + when: () => kubernetesServiceType === INGRESS || istio === true, + type: 'input', + name: 'ingressDomain', + message: `${istioMessage}${istioMessage ? '\n' : ''}What is the root FQDN for your ingress services (e.g. ${examples.join(', ')})?`, + // if Ingress Type is nginx, then default to minikube ip + // else, default to empty string, because it's mostly not needed. + default: defaultValue, + validate: input => { + if (input.length === 0) { + if (this.ingressType === NGINX || istio) { + return 'domain name cannot be empty'; + } + return true; + } + if (input.charAt(0) === '.') { + return 'domain name cannot start with a "."'; + } + if (!input.match(/^[\w]+[\w.-]+[\w]{1,}$/)) { + return 'domain not valid'; + } + + return true; + }, + }, + ]; + + const props = await this.prompt(prompts, this.config); + if (props.ingressDomain === 'none') { + this.ingressDomain = ''; + } else { + this.ingressDomain = props.ingressDomain ? props.ingressDomain : ''; + } +} + +export async function askForIstioSupport() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + if (this.deploymentApplicationType === MONOLITH) { + this.istio = false; + return; + } + + const prompts = [ + { + type: 'list', + name: 'istio', + message: 'Do you want to enable Istio?', + choices: [ + { + value: false, + name: 'No', + }, + { + value: true, + name: 'Yes', + }, + ], + default: this.istio, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.istio = props.istio; +} + +export async function askForPersistentStorage() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + let usingDataBase = false; + this.appConfigs.forEach(appConfig => { + if (appConfig.prodDatabaseType !== NO_DATABASE) { + usingDataBase = true; + } + }); + + const prompts = [ + { + when: () => usingDataBase, + type: 'list', + name: 'kubernetesUseDynamicStorage', + message: 'Do you want to use dynamic storage provisioning for your stateful services?', + choices: [ + { + value: false, + name: 'No', + }, + { + value: true, + name: 'Yes', + }, + ], + default: this.kubernetesUseDynamicStorage, + }, + ]; + + const props = await this.prompt(prompts, this.config); + this.kubernetesUseDynamicStorage = props.kubernetesUseDynamicStorage; +} + +export async function askForStorageClassName() { + if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; + const kubernetesUseDynamicStorage = this.kubernetesUseDynamicStorage; + + const prompts = [ + { + when: () => kubernetesUseDynamicStorage, + type: 'input', + name: 'kubernetesStorageClassName', + message: 'Do you want to use a specific storage class? (leave empty for using the clusters default storage class)', + default: this.kubernetesStorageClassName ? this.kubernetesStorageClassName : '', + }, + ]; + + const props = await this.prompt(prompts, this.config); + // Add the StorageClass value only if dynamic storage is enabled + if (kubernetesUseDynamicStorage) { + this.kubernetesStorageClassName = props.kubernetesStorageClassName.trim(); + } +} diff --git a/generators/kubernetes/prompts.mjs b/generators/kubernetes/prompts.mjs deleted file mode 100644 index 0620463ea1a4..000000000000 --- a/generators/kubernetes/prompts.mjs +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright 2013-2023 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 dockerPrompts from '../base-workspaces/internal/docker-prompts.mjs'; -import { defaultKubernetesConfig, ingressDefaultConfig } from './kubernetes-constants.mjs'; - -import { applicationTypes, databaseTypes, kubernetesPlatformTypes } from '../../jdl/jhipster/index.mjs'; - -const { MONOLITH } = applicationTypes; -const { IngressTypes, ServiceTypes } = kubernetesPlatformTypes; - -const NO_DATABASE = databaseTypes.NO; -const { LOAD_BALANCER, INGRESS, NODE_PORT } = ServiceTypes; -const { GKE, NGINX } = IngressTypes; - -export default { - askForKubernetesNamespace, - askForKubernetesServiceType, - askForIngressType, - askForIngressDomain, - askForIstioSupport, - askForPersistentStorage, - askForStorageClassName, - ...dockerPrompts, -}; - -export async function askForKubernetesNamespace() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const prompts = [ - { - type: 'input', - name: 'kubernetesNamespace', - message: 'What should we use for the Kubernetes namespace?', - default: this.kubernetesNamespace ? this.kubernetesNamespace : defaultKubernetesConfig.kubernetesNamespace, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.kubernetesNamespace = props.kubernetesNamespace; -} - -export async function askForKubernetesServiceType() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - - const istio = this.istio; - - const prompts = [ - { - when: () => !istio, - type: 'list', - name: 'kubernetesServiceType', - message: 'Choose the Kubernetes service type for your edge services', - choices: [ - { - value: LOAD_BALANCER, - name: 'LoadBalancer - Let a Kubernetes cloud provider automatically assign an IP', - }, - { - value: NODE_PORT, - name: 'NodePort - expose the services to a random port (30000 - 32767) on all cluster nodes', - }, - { - value: INGRESS, - name: 'Ingress - create ingresses for your services. Requires a running ingress controller', - }, - ], - default: this.kubernetesServiceType ? this.kubernetesServiceType : defaultKubernetesConfig.kubernetesServiceType, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.kubernetesServiceType = props.kubernetesServiceType; -} - -export async function askForIngressType() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - const kubernetesServiceType = this.kubernetesServiceType; - - const prompts = [ - { - when: () => kubernetesServiceType === INGRESS, - type: 'list', - name: 'ingressType', - message: 'Choose the Kubernetes Ingress type', - choices: [ - { - value: NGINX, - name: 'NGINX Ingress - choose this if you are running on Minikube', - }, - { - value: GKE, - name: 'Google Kubernetes Engine Ingress - choose this if you are running on GKE', - }, - ], - default: this.ingressType ? this.ingressType : ingressDefaultConfig.ingressType, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.ingressType = props.ingressType; -} - -export async function askForIngressDomain() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - const kubernetesServiceType = this.kubernetesServiceType; - const istio = this.istio; - this.ingressDomain = this.ingressDomain && this.ingressDomain.startsWith('.') ? this.ingressDomain.substring(1) : this.ingressDomain; - - const istioIpCommand = "kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"; - let istioMessage = ''; - - let defaultValue = ''; - if (this.ingressDomain) { - defaultValue = this.ingressDomain; - } else if (istio) { - // If it's Istio, and no previous domain is configured, try to determine the default value - try { - const { stdout: istioIngressIp } = this.spawnCommandSync(istioIpCommand, { stdio: 'pipe' }); - defaultValue = `${istioIngressIp}.nip.io`; - } catch (ex) { - istioMessage = `Unable to determine Istio Ingress IP address. You can find the Istio Ingress IP address by running the command line:\n ${istioIpCommand}`; - } - } else if (this.ingressType === NGINX) { - defaultValue = '192.168.99.100.nip.io'; - } else { - defaultValue = 'none'; - } - - const examples = ['example.com', '192.168.99.100.nip.io']; - if (this.ingressType !== NGINX && !istio) { - examples.push('none'); - } - - const prompts = [ - { - when: () => kubernetesServiceType === INGRESS || istio === true, - type: 'input', - name: 'ingressDomain', - message: `${istioMessage}${istioMessage ? '\n' : ''}What is the root FQDN for your ingress services (e.g. ${examples.join(', ')})?`, - // if Ingress Type is nginx, then default to minikube ip - // else, default to empty string, because it's mostly not needed. - default: defaultValue, - validate: input => { - if (input.length === 0) { - if (this.ingressType === NGINX || istio) { - return 'domain name cannot be empty'; - } - return true; - } - if (input.charAt(0) === '.') { - return 'domain name cannot start with a "."'; - } - if (!input.match(/^[\w]+[\w.-]+[\w]{1,}$/)) { - return 'domain not valid'; - } - - return true; - }, - }, - ]; - - const props = await this.prompt(prompts, this.config); - if (props.ingressDomain === 'none') { - this.ingressDomain = ''; - } else { - this.ingressDomain = props.ingressDomain ? props.ingressDomain : ''; - } -} - -export async function askForIstioSupport() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - if (this.deploymentApplicationType === MONOLITH) { - this.istio = false; - return; - } - - const prompts = [ - { - type: 'list', - name: 'istio', - message: 'Do you want to enable Istio?', - choices: [ - { - value: false, - name: 'No', - }, - { - value: true, - name: 'Yes', - }, - ], - default: this.istio, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.istio = props.istio; -} - -export async function askForPersistentStorage() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - let usingDataBase = false; - this.appConfigs.forEach(appConfig => { - if (appConfig.prodDatabaseType !== NO_DATABASE) { - usingDataBase = true; - } - }); - - const prompts = [ - { - when: () => usingDataBase, - type: 'list', - name: 'kubernetesUseDynamicStorage', - message: 'Do you want to use dynamic storage provisioning for your stateful services?', - choices: [ - { - value: false, - name: 'No', - }, - { - value: true, - name: 'Yes', - }, - ], - default: this.kubernetesUseDynamicStorage, - }, - ]; - - const props = await this.prompt(prompts, this.config); - this.kubernetesUseDynamicStorage = props.kubernetesUseDynamicStorage; -} - -export async function askForStorageClassName() { - if (!this.options.askAnswered && (this.regenerate || this.config.existed)) return; - const kubernetesUseDynamicStorage = this.kubernetesUseDynamicStorage; - - const prompts = [ - { - when: () => kubernetesUseDynamicStorage, - type: 'input', - name: 'kubernetesStorageClassName', - message: 'Do you want to use a specific storage class? (leave empty for using the clusters default storage class)', - default: this.kubernetesStorageClassName ? this.kubernetesStorageClassName : '', - }, - ]; - - const props = await this.prompt(prompts, this.config); - // Add the StorageClass value only if dynamic storage is enabled - if (kubernetesUseDynamicStorage) { - this.kubernetesStorageClassName = props.kubernetesStorageClassName.trim(); - } -} diff --git a/generators/languages/command.mts b/generators/languages/command.mts deleted file mode 100644 index d6a60bce3cdd..000000000000 --- a/generators/languages/command.mts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - arguments: { - languages: { - description: 'Languages to generate', - type: Array, - required: false, - }, - }, - options: { - enableTranslation: { - description: 'Enable translation', - type: Boolean, - required: false, - scope: 'storage', - }, - language: { - alias: 'l', - description: 'Language to be added to application (existing languages are not removed)', - type: Array, - }, - nativeLanguage: { - alias: 'n', - description: 'Set application native language', - type: String, - required: false, - }, - regenerateLanguages: { - description: 'Regenerate languages', - type: Boolean, - scope: 'generator', - }, - }, -}; - -export default command; diff --git a/generators/languages/command.ts b/generators/languages/command.ts new file mode 100644 index 000000000000..7a7f41d91791 --- /dev/null +++ b/generators/languages/command.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + arguments: { + languages: { + description: 'Languages to generate', + type: Array, + required: false, + }, + }, + options: { + enableTranslation: { + description: 'Enable translation', + type: Boolean, + required: false, + scope: 'storage', + }, + language: { + alias: 'l', + description: 'Language to be added to application (existing languages are not removed)', + type: Array, + }, + nativeLanguage: { + alias: 'n', + description: 'Set application native language', + type: String, + required: false, + }, + regenerateLanguages: { + description: 'Regenerate languages', + type: Boolean, + scope: 'generator', + }, + }, +}; + +export default command; diff --git a/generators/languages/entity-files.js b/generators/languages/entity-files.js new file mode 100644 index 000000000000..8a04cb1acf90 --- /dev/null +++ b/generators/languages/entity-files.js @@ -0,0 +1,96 @@ +/** + * Copyright 2013-2023 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 { getEnumInfo } from '../base-application/support/index.js'; + +/** + * The default is to use a file path string. It implies use of the template method. + * For any other config an object { file:.., method:.., template:.. } can be used + */ +export const entityClientI18nFiles = { + entityBaseFiles: [ + { + templates: [ + { + sourceFile: context => `entity/i18n/entity_${context.lang}.json.ejs`, + destinationFile: context => `${context.clientSrcDir}i18n/${context.lang}/${context.entityTranslationKey}.json`, + }, + ], + }, + ], +}; + +export const enumClientI18nFiles = { + enumBaseFiles: [ + { + templates: [ + { + sourceFile: 'entity/i18n/enum.json.ejs', + destinationFile: context => `${context.clientSrcDir}i18n/${context.lang}/${context.clientRootFolder}${context.enumInstance}.json`, + }, + ], + }, + ], +}; + +export function writeEntityFiles() { + return { + async writeEnumFiles({ entities, application }) { + if (!application.enableTranslation || application.skipClient) return; + entities = entities.filter(entity => !entity.skipClient && !entity.builtIn); + const { clientSrcDir, packageName, frontendAppName } = application; + await Promise.all( + entities + .map(entity => + entity.fields + .map(field => { + if (!field.fieldIsEnum) return undefined; + return this.languagesToApply.map(lang => + this.writeFiles({ + sections: enumClientI18nFiles, + context: { + ...getEnumInfo(field, entity.clientRootFolder), + lang, + frontendAppName, + packageName, + clientSrcDir, + }, + }), + ); + }) + .flat(), + ) + .flat(), + ); + }, + + async writeClientFiles({ application, entities }) { + if (application.skipClient) return; + const entitiesToWriteTranslationFor = entities.filter(entity => !entity.skipClient && !entity.builtIn); + + // Copy each + const { clientSrcDir, frontendAppName } = application; + const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; + for (const entity of entitiesToWriteTranslationFor) { + for (const lang of languagesToApply) { + await this.writeFiles({ sections: entityClientI18nFiles, context: { ...entity, clientSrcDir, frontendAppName, lang } }); + } + } + }, + }; +} diff --git a/generators/languages/entity-files.mjs b/generators/languages/entity-files.mjs deleted file mode 100644 index e1d91be929ac..000000000000 --- a/generators/languages/entity-files.mjs +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2013-2023 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 { getEnumInfo } from '../base-application/support/index.mjs'; - -/** - * The default is to use a file path string. It implies use of the template method. - * For any other config an object { file:.., method:.., template:.. } can be used - */ -export const entityClientI18nFiles = { - entityBaseFiles: [ - { - templates: [ - { - sourceFile: context => `entity/i18n/entity_${context.lang}.json.ejs`, - destinationFile: context => `${context.clientSrcDir}i18n/${context.lang}/${context.entityTranslationKey}.json`, - }, - ], - }, - ], -}; - -export const enumClientI18nFiles = { - enumBaseFiles: [ - { - templates: [ - { - sourceFile: 'entity/i18n/enum.json.ejs', - destinationFile: context => `${context.clientSrcDir}i18n/${context.lang}/${context.clientRootFolder}${context.enumInstance}.json`, - }, - ], - }, - ], -}; - -export function writeEntityFiles() { - return { - async writeEnumFiles({ entities, application }) { - if (!application.enableTranslation || application.skipClient) return; - entities = entities.filter(entity => !entity.skipClient && !entity.builtIn); - const { clientSrcDir, packageName, frontendAppName } = application; - await Promise.all( - entities - .map(entity => - entity.fields - .map(field => { - if (!field.fieldIsEnum) return undefined; - return this.languagesToApply.map(lang => - this.writeFiles({ - sections: enumClientI18nFiles, - context: { - ...getEnumInfo(field, entity.clientRootFolder), - lang, - frontendAppName, - packageName, - clientSrcDir, - }, - }), - ); - }) - .flat(), - ) - .flat(), - ); - }, - - async writeClientFiles({ application, entities }) { - if (application.skipClient) return; - const entitiesToWriteTranslationFor = entities.filter(entity => !entity.skipClient && !entity.builtIn); - - // Copy each - const { clientSrcDir, frontendAppName } = application; - const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; - for (const entity of entitiesToWriteTranslationFor) { - for (const lang of languagesToApply) { - await this.writeFiles({ sections: entityClientI18nFiles, context: { ...entity, clientSrcDir, frontendAppName, lang } }); - } - } - }, - }; -} diff --git a/generators/languages/files.js b/generators/languages/files.js new file mode 100644 index 000000000000..85ccdf337df4 --- /dev/null +++ b/generators/languages/files.js @@ -0,0 +1,80 @@ +/** + * Copyright 2013-2023 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 { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; + +// eslint-disable-next-line import/prefer-default-export +export const clientI18nFiles = { + clientI18nFiles: [ + { + from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, + to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, + transform: false, + templates: [ + 'error.json', + 'login.json', + 'home.json', + 'password.json', + 'register.json', + 'sessions.json', + 'settings.json', + 'user-management.json', + ], + }, + { + condition: ctx => ctx.clientFrameworkVue && ctx.enableTranslation && !ctx.microfrontend, + path: `${CLIENT_MAIN_SRC_DIR}/i18n/`, + renameTo: context => `${context.clientSrcDir}/i18n/${context.lang}/${context.lang}.js`, + templates: ['index.js'], + }, + { + from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, + to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, + templates: ['activate.json', 'global.json', 'reset.json'], + }, + { + condition: context => context.withAdminUi, + from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, + to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, + transform: false, + templates: [ + 'configuration.json', + 'logs.json', + 'metrics.json', + { + transform: true, + file: 'health.json', + }, + ], + }, + { + condition: context => context.communicationSpringWebsocket, + from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, + to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, + transform: false, + templates: ['tracker.json'], + }, + { + condition: context => context.applicationTypeGateway, + from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, + to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, + transform: false, + templates: ['gateway.json'], + }, + ], +}; diff --git a/generators/languages/files.mjs b/generators/languages/files.mjs deleted file mode 100644 index f8df223989db..000000000000 --- a/generators/languages/files.mjs +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2013-2023 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 { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; - -// eslint-disable-next-line import/prefer-default-export -export const clientI18nFiles = { - clientI18nFiles: [ - { - from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, - to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, - transform: false, - templates: [ - 'error.json', - 'login.json', - 'home.json', - 'password.json', - 'register.json', - 'sessions.json', - 'settings.json', - 'user-management.json', - ], - }, - { - condition: ctx => ctx.clientFrameworkVue && ctx.enableTranslation && !ctx.microfrontend, - path: `${CLIENT_MAIN_SRC_DIR}/i18n/`, - renameTo: context => `${context.clientSrcDir}/i18n/${context.lang}/${context.lang}.js`, - templates: ['index.js'], - }, - { - from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, - to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, - templates: ['activate.json', 'global.json', 'reset.json'], - }, - { - condition: context => context.withAdminUi, - from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, - to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, - transform: false, - templates: [ - 'configuration.json', - 'logs.json', - 'metrics.json', - { - transform: true, - file: 'health.json', - }, - ], - }, - { - condition: context => context.communicationSpringWebsocket, - from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, - to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, - transform: false, - templates: ['tracker.json'], - }, - { - condition: context => context.applicationTypeGateway, - from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`, - to: context => `${context.clientSrcDir}/i18n/${context.lang}/`, - transform: false, - templates: ['gateway.json'], - }, - ], -}; diff --git a/generators/languages/generator-needles.spec.mts b/generators/languages/generator-needles.spec.mts deleted file mode 100644 index 589ee7ccfae7..000000000000 --- a/generators/languages/generator-needles.spec.mts +++ /dev/null @@ -1,54 +0,0 @@ -import { defaultHelpers as helpers, result as runResult, getGenerator } from '../../test/support/index.mjs'; - -import LanguagesGenerator from './index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; - -const generatorPath = getGenerator('languages'); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends LanguagesGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - if (!this.jhipsterContext) { - throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); - } - - this.sbsBlueprint = true; - } - - get [LanguagesGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - addEntityTranslationKey({ source }) { - source.addEntityTranslationKey?.({ translationKey: 'my_entity_key', translationValue: 'My Entity Value', language: 'en' }); - source.addEntityTranslationKey?.({ translationKey: 'ma_cle_entite', translationValue: 'Ma Valeur Entite', language: 'fr' }); - }, - }); - } -}; - -describe('needle API i18n: JHipster language generator with blueprint', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({ baseName: 'jhipster' }) - .withOptions({ ignoreNeedlesError: true }) - .withOptions({ - build: 'maven', - auth: 'jwt', - db: 'mysql', - blueprint: 'myblueprint', - nativeLanguage: 'en', - languages: ['en', 'fr'], - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:languages' }]]); - }); - - it('Assert english entity global.json contain the new key', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}i18n/en/global.json`, '"my_entity_key": "My Entity Value"'); - }); - - it('Assert french entity global.json contain the new key', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}i18n/fr/global.json`, '"ma_cle_entite": "Ma Valeur Entite"'); - }); -}); diff --git a/generators/languages/generator-needles.spec.ts b/generators/languages/generator-needles.spec.ts new file mode 100644 index 000000000000..4db3e86f3708 --- /dev/null +++ b/generators/languages/generator-needles.spec.ts @@ -0,0 +1,54 @@ +import { defaultHelpers as helpers, result as runResult, getGenerator } from '../../test/support/index.js'; + +import LanguagesGenerator from './index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; + +const generatorPath = getGenerator('languages'); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends LanguagesGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + if (!this.jhipsterContext) { + throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); + } + + this.sbsBlueprint = true; + } + + get [LanguagesGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + addEntityTranslationKey({ source }) { + source.addEntityTranslationKey?.({ translationKey: 'my_entity_key', translationValue: 'My Entity Value', language: 'en' }); + source.addEntityTranslationKey?.({ translationKey: 'ma_cle_entite', translationValue: 'Ma Valeur Entite', language: 'fr' }); + }, + }); + } +}; + +describe('needle API i18n: JHipster language generator with blueprint', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({ baseName: 'jhipster' }) + .withOptions({ ignoreNeedlesError: true }) + .withOptions({ + build: 'maven', + auth: 'jwt', + db: 'mysql', + blueprint: 'myblueprint', + nativeLanguage: 'en', + languages: ['en', 'fr'], + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:languages' }]]); + }); + + it('Assert english entity global.json contain the new key', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}i18n/en/global.json`, '"my_entity_key": "My Entity Value"'); + }); + + it('Assert french entity global.json contain the new key', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}i18n/fr/global.json`, '"ma_cle_entite": "Ma Valeur Entite"'); + }); +}); diff --git a/generators/languages/generator.js b/generators/languages/generator.js new file mode 100644 index 000000000000..dd399ff3c2a9 --- /dev/null +++ b/generators/languages/generator.js @@ -0,0 +1,423 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import { askForLanguages, askI18n } from './prompts.js'; +import statistics from '../statistics.js'; +import { GENERATOR_LANGUAGES, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import { clientI18nFiles } from './files.js'; +import { writeEntityFiles } from './entity-files.js'; +import TranslationData, { createTranslationsFileFilter, createTranslationsFilter } from './translation-data.js'; +import { findLanguageForTag, supportedLanguages } from './support/languages.js'; +import { updateLanguagesTask as updateLanguagesInAngularTask } from '../angular/support/index.js'; +import { updateLanguagesTask as updateLanguagesInReact } from '../react/support/index.js'; +import { updateLanguagesTask as updateLanguagesInVue } from '../vue/support/index.js'; +import { updateLanguagesTask as updateLanguagesInJava } from '../server/support/index.js'; +import { SERVER_MAIN_RES_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.js'; +import command from './command.js'; +import { QUEUES } from '../base-application/priorities.js'; +import { PRIORITY_NAMES } from '../base/priorities.js'; + +const { startCase } = _; + +/** + * This is the base class for a generator that generates entities. + * + * @class + * @extends {BaseApplicationGenerator} + */ +export default class LanguagesGenerator extends BaseApplicationGenerator { + translationData; + supportedLanguages; + languages; + /** + * Languages to be generated. + * Can be incremental or every language. + */ + languagesToApply; + composedBlueprints; + languageCommand; + writeJavaLanguageFiles; + regenerateLanguages; + + constructor(args, options, features) { + super(args, options, features); + + this.languageCommand = this.options.commandName === 'languages'; + } + + async beforeQueue() { + if (!this.fromBlueprint) { + this.supportedLanguages = supportedLanguages; + this.composedBlueprints = await this.composeWithBlueprints('languages', { + generatorArgs: this.options.languages, + }); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + + if ( + !this.jhipsterConfigWithDefaults.skipClient && + this.jhipsterConfigWithDefaults.clientFramework !== 'no' && + (!this.jhipsterConfig.enableTranslation || this.jhipsterConfigWithDefaults.clientFramework === 'angular') + ) { + // We must write languages files for translation process for entities only generation. + // Angular frontend uses translation files even if enableTranslation is enabled. + // As side effect, with angular frontends, translation files will be written for nativeLanguage for entity only generation. + this.setFeatures({ disableSkipPriorities: true }); + } + } + + // Public API method used by the getter and also by Blueprints + get initializing() { + return this.asInitializingTaskGroup({ + parseCli() { + this.parseJHipsterArguments(command.arguments); + this.parseJHipsterOptions(command.options); + }, + languagesToApply() { + // Validate languages passed as argument. + // Additional languages, will not replace current ones. + this.languagesToApply = [this.options.nativeLanguage, ...(this.languages ?? [])].filter(Boolean); + }, + validateSupportedLanguages() { + for (const blueprint of this.composedBlueprints) { + if (blueprint.supportedLanguages) { + this.supportedLanguages = [...this.supportedLanguages, ...blueprint.supportedLanguages]; + } + } + if (this.languagesToApply.length > 0) { + const unsupportedLanguage = this.languagesToApply.find(lang => !findLanguageForTag(lang, this.supportedLanguages)); + if (unsupportedLanguage) { + throw new Error( + `Unsupported language "${unsupportedLanguage}" passed as argument to language generator.` + + `\nSupported languages: ${this.supportedLanguages + .map(language => `\n ${_.padEnd(language.languageTag, 5)} (${language.name})`) + .join('')}`, + ); + } + } + }, + validate() { + if (this.languagesToApply.length > 0) { + if (this.jhipsterConfig.skipClient) { + this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')} for server`)); + } else if (this.jhipsterConfig.skipServer) { + this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')} for client`)); + } else { + this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')}`)); + } + } + }, + exportControl({ control }) { + control.supportedLanguages = this.supportedLanguages; + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + // Public API method used by the getter and also by Blueprints + get prompting() { + return this.asPromptingTaskGroup({ + checkPrompts({ control }) { + const { enableTranslation, languages } = this.jhipsterConfig; + const showPrompts = this.options.askAnswered || this.languageCommand; + this.askForNativeLanguage = showPrompts || (!control.existingProject && !this.jhipsterConfig.nativeLanguage); + this.askForMoreLanguages = + enableTranslation !== false && (showPrompts || (!control.existingProject && (languages?.length ?? 0) < 1)); + }, + askI18n, + askForLanguages, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + // Public API method used by the getter and also by Blueprints + get configuring() { + return this.asConfiguringTaskGroup({ + migrateLanguages() { + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.migrateLanguages({ in: 'id' }); + } + }, + defaults() { + const { nativeLanguage, enableTranslation } = this.jhipsterConfigWithDefaults; + const isLanguageConfigured = Boolean(this.jhipsterConfig.nativeLanguage); + // Prompts detects current language. Save default native language for next execution. + this.config.defaults({ nativeLanguage }); + if (!enableTranslation) { + return; + } + this.config.defaults({ languages: [] }); + if (!isLanguageConfigured && this.languagesToApply.length === 0) { + // If languages is not configured, apply defaults. + this.languagesToApply = this.jhipsterConfigWithDefaults.languages; + } + if (this.jhipsterConfig.languages.length === 0 || this.jhipsterConfig.languages[0] !== this.jhipsterConfig.nativeLanguage) { + // Set native language as first language. + this.jhipsterConfig.languages = [...new Set([nativeLanguage, ...this.jhipsterConfig.languages])]; + } + if (this.languagesToApply && this.languagesToApply.length > 0) { + // Save new languages. + this.jhipsterConfig.languages = [...new Set([...this.jhipsterConfig.languages, ...this.languagesToApply])]; + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + // Public API method used by the getter and also by Blueprints + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application, source }) { + if (application.enableTranslation) { + if (!this.languageCommand || this.regenerateLanguages) { + this.languagesToApply = application.languages; + } else { + this.languagesToApply = [...new Set(this.languagesToApply || [])]; + } + } + + source.addEntityTranslationKey = ({ translationKey, translationValue, language }) => { + this.mergeDestinationJson(`${application.clientSrcDir}i18n/${language}/global.json`, { + global: { + menu: { + entities: { + [translationKey]: translationValue, + }, + }, + }, + }); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get default() { + return this.asDefaultTaskGroup({ + async loadNativeLanguage({ application, control }) { + if (application.skipClient) return; + control.translations = control.translations ?? {}; + this.translationData = new TranslationData({ generator: this, translations: control.translations }); + const { clientSrcDir, enableTranslation, nativeLanguage } = application; + const fallbackLanguage = 'en'; + this.queueLoadLanguages({ clientSrcDir, enableTranslation, nativeLanguage, fallbackLanguage }); + const filter = createTranslationsFilter({ clientSrcDir, nativeLanguage, fallbackLanguage }); + const listener = filePath => { + if (filter(filePath)) { + this.env.sharedFs.removeListener('change', listener); + this.queueLoadLanguages({ clientSrcDir, enableTranslation, nativeLanguage, fallbackLanguage }); + } + }; + this.env.sharedFs.on('change', listener); + + control.getWebappTranslation = (...args) => this.translationData.getClientTranslation(...args); + }, + + insight() { + statistics.sendSubGenEvent('generator', GENERATOR_LANGUAGES); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + // Public API method used by the getter and also by Blueprints + get writing() { + return this.asWritingTaskGroup({ + async writeClientTranslations({ application }) { + if (application.skipClient) return; + const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; + await Promise.all( + languagesToApply.map(lang => + this.writeFiles({ + sections: clientI18nFiles, + context: { + ...application, + lang, + }, + }), + ), + ); + }, + async translateFile({ application }) { + if ( + !application.enableTranslation || + application.skipServer || + (!application.backendTypeSpringBoot && !this.writeJavaLanguageFiles) || + this.options.skipPriorities?.includes?.(PRIORITY_NAMES.POST_WRITING) + ) + return; + await Promise.all( + this.languagesToApply.map(async lang => { + const language = findLanguageForTag(lang); + if (language.javaLocaleMessageSourceSuffix) { + await this.writeFiles({ + sections: { + serverI18nFiles: [ + { + path: SERVER_MAIN_RES_DIR, + renameTo: (data, filePath) => `${data.srcMainResources}${filePath}`, + templates: [`i18n/messages_${language.javaLocaleMessageSourceSuffix}.properties`], + }, + ], + serverI18nTestFiles: [ + { + path: SERVER_TEST_RES_DIR, + renameTo: (data, filePath) => `${data.srcTestResources}${filePath}`, + condition: data => !data.skipUserManagement, + templates: [`i18n/messages_${language.javaLocaleMessageSourceSuffix}.properties`], + }, + ], + }, + context: { + ...application, + lang, + }, + }); + } + statistics.sendSubGenEvent('languages/language', language); + }), + ); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + ...writeEntityFiles(), + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + write({ application, control }) { + if (this.options.skipPriorities?.includes?.(PRIORITY_NAMES.POST_WRITING)) return; + + if (application.enableTranslation && !application.skipClient) { + if (application.clientFrameworkAngular) { + updateLanguagesInAngularTask.call(this, { application, control }); + } + if (application.clientFrameworkReact) { + updateLanguagesInReact.call(this, { application, control }); + } + if (application.clientFrameworkVue) { + updateLanguagesInVue.call(this, { application, control }); + } + } + if ( + application.enableTranslation && + application.generateUserManagement && + !application.skipServer && + application.backendTypeSpringBoot + ) { + updateLanguagesInJava.call(this, { application, control }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + addEntities({ application, entities, source }) { + if (application.skipClient) return; + const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + for (const language of languagesToApply) { + source.addEntityTranslationKey?.({ + language, + translationKey: entity.entityTranslationKeyMenu, + translationValue: entity.entityClassHumanized ?? startCase(entity.entityClass), + }); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.postWritingEntities); + } + + migrateLanguages(languagesToMigrate) { + const { languages, nativeLanguage } = this.jhipsterConfig; + if (languagesToMigrate[nativeLanguage]) { + this.jhipsterConfig.nativeLanguage = languagesToMigrate[nativeLanguage]; + } + if (languages && languages.some(lang => languagesToMigrate[lang])) { + this.jhipsterConfig.languages = languages.map(lang => languagesToMigrate[lang] ?? lang); + } + } + + queueLoadLanguages({ enableTranslation, clientSrcDir, nativeLanguage, fallbackLanguage = 'en' }) { + this.queueTask({ + method: async () => { + const filter = createTranslationsFileFilter({ clientSrcDir, nativeLanguage, fallbackLanguage }); + await this.pipeline( + { + name: 'loading translations', + filter: file => file.path.startsWith(this.destinationPath()) && filter(file), + refresh: true, + }, + this.translationData.loadFromStreamTransform({ + enableTranslation, + clientSrcDir, + nativeLanguage, + fallbackLanguage, + }), + ); + }, + taskName: 'loadingTranslations', + queueName: QUEUES.LOADING_TRANSLATIONS_QUEUE, + once: true, + }); + } +} diff --git a/generators/languages/generator.mjs b/generators/languages/generator.mjs deleted file mode 100644 index 89aa3e220726..000000000000 --- a/generators/languages/generator.mjs +++ /dev/null @@ -1,423 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { askForLanguages, askI18n } from './prompts.mjs'; -import statistics from '../statistics.mjs'; -import { GENERATOR_LANGUAGES, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import { clientI18nFiles } from './files.mjs'; -import { writeEntityFiles } from './entity-files.mjs'; -import TranslationData, { createTranslationsFileFilter, createTranslationsFilter } from './translation-data.mjs'; -import { findLanguageForTag, supportedLanguages } from './support/languages.mjs'; -import { updateLanguagesTask as updateLanguagesInAngularTask } from '../angular/support/index.mjs'; -import { updateLanguagesTask as updateLanguagesInReact } from '../react/support/index.mjs'; -import { updateLanguagesTask as updateLanguagesInVue } from '../vue/support/index.mjs'; -import { updateLanguagesTask as updateLanguagesInJava } from '../server/support/index.mjs'; -import { SERVER_MAIN_RES_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.mjs'; -import command from './command.mjs'; -import { QUEUES } from '../base-application/priorities.mjs'; -import { PRIORITY_NAMES } from '../base/priorities.mjs'; - -const { startCase } = _; - -/** - * This is the base class for a generator that generates entities. - * - * @class - * @extends {BaseApplicationGenerator} - */ -export default class LanguagesGenerator extends BaseApplicationGenerator { - translationData; - supportedLanguages; - languages; - /** - * Languages to be generated. - * Can be incremental or every language. - */ - languagesToApply; - composedBlueprints; - languageCommand; - writeJavaLanguageFiles; - regenerateLanguages; - - constructor(args, options, features) { - super(args, options, features); - - this.languageCommand = this.options.commandName === 'languages'; - } - - async beforeQueue() { - if (!this.fromBlueprint) { - this.supportedLanguages = supportedLanguages; - this.composedBlueprints = await this.composeWithBlueprints('languages', { - generatorArgs: this.options.languages, - }); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - - if ( - !this.jhipsterConfigWithDefaults.skipClient && - this.jhipsterConfigWithDefaults.clientFramework !== 'no' && - (!this.jhipsterConfig.enableTranslation || this.jhipsterConfigWithDefaults.clientFramework === 'angular') - ) { - // We must write languages files for translation process for entities only generation. - // Angular frontend uses translation files even if enableTranslation is enabled. - // As side effect, with angular frontends, translation files will be written for nativeLanguage for entity only generation. - this.setFeatures({ disableSkipPriorities: true }); - } - } - - // Public API method used by the getter and also by Blueprints - get initializing() { - return this.asInitializingTaskGroup({ - parseCli() { - this.parseJHipsterArguments(command.arguments); - this.parseJHipsterOptions(command.options); - }, - languagesToApply() { - // Validate languages passed as argument. - // Additional languages, will not replace current ones. - this.languagesToApply = [this.options.nativeLanguage, ...(this.languages ?? [])].filter(Boolean); - }, - validateSupportedLanguages() { - for (const blueprint of this.composedBlueprints) { - if (blueprint.supportedLanguages) { - this.supportedLanguages = [...this.supportedLanguages, ...blueprint.supportedLanguages]; - } - } - if (this.languagesToApply.length > 0) { - const unsupportedLanguage = this.languagesToApply.find(lang => !findLanguageForTag(lang, this.supportedLanguages)); - if (unsupportedLanguage) { - throw new Error( - `Unsupported language "${unsupportedLanguage}" passed as argument to language generator.` + - `\nSupported languages: ${this.supportedLanguages - .map(language => `\n ${_.padEnd(language.languageTag, 5)} (${language.name})`) - .join('')}`, - ); - } - } - }, - validate() { - if (this.languagesToApply.length > 0) { - if (this.jhipsterConfig.skipClient) { - this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')} for server`)); - } else if (this.jhipsterConfig.skipServer) { - this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')} for client`)); - } else { - this.log.log(chalk.bold(`\nInstalling languages: ${this.languagesToApply.join(', ')}`)); - } - } - }, - exportControl({ control }) { - control.supportedLanguages = this.supportedLanguages; - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - // Public API method used by the getter and also by Blueprints - get prompting() { - return this.asPromptingTaskGroup({ - checkPrompts({ control }) { - const { enableTranslation, languages } = this.jhipsterConfig; - const showPrompts = this.options.askAnswered || this.languageCommand; - this.askForNativeLanguage = showPrompts || (!control.existingProject && !this.jhipsterConfig.nativeLanguage); - this.askForMoreLanguages = - enableTranslation !== false && (showPrompts || (!control.existingProject && (languages?.length ?? 0) < 1)); - }, - askI18n, - askForLanguages, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - // Public API method used by the getter and also by Blueprints - get configuring() { - return this.asConfiguringTaskGroup({ - migrateLanguages() { - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.migrateLanguages({ in: 'id' }); - } - }, - defaults() { - const { nativeLanguage, enableTranslation } = this.jhipsterConfigWithDefaults; - const isLanguageConfigured = Boolean(this.jhipsterConfig.nativeLanguage); - // Prompts detects current language. Save default native language for next execution. - this.config.defaults({ nativeLanguage }); - if (!enableTranslation) { - return; - } - this.config.defaults({ languages: [] }); - if (!isLanguageConfigured && this.languagesToApply.length === 0) { - // If languages is not configured, apply defaults. - this.languagesToApply = this.jhipsterConfigWithDefaults.languages; - } - if (this.jhipsterConfig.languages.length === 0 || this.jhipsterConfig.languages[0] !== this.jhipsterConfig.nativeLanguage) { - // Set native language as first language. - this.jhipsterConfig.languages = [...new Set([nativeLanguage, ...this.jhipsterConfig.languages])]; - } - if (this.languagesToApply && this.languagesToApply.length > 0) { - // Save new languages. - this.jhipsterConfig.languages = [...new Set([...this.jhipsterConfig.languages, ...this.languagesToApply])]; - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - // Public API method used by the getter and also by Blueprints - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application, source }) { - if (application.enableTranslation) { - if (!this.languageCommand || this.regenerateLanguages) { - this.languagesToApply = application.languages; - } else { - this.languagesToApply = [...new Set(this.languagesToApply || [])]; - } - } - - source.addEntityTranslationKey = ({ translationKey, translationValue, language }) => { - this.mergeDestinationJson(`${application.clientSrcDir}i18n/${language}/global.json`, { - global: { - menu: { - entities: { - [translationKey]: translationValue, - }, - }, - }, - }); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get default() { - return this.asDefaultTaskGroup({ - async loadNativeLanguage({ application, control }) { - if (application.skipClient) return; - control.translations = control.translations ?? {}; - this.translationData = new TranslationData({ generator: this, translations: control.translations }); - const { clientSrcDir, enableTranslation, nativeLanguage } = application; - const fallbackLanguage = 'en'; - this.queueLoadLanguages({ clientSrcDir, enableTranslation, nativeLanguage, fallbackLanguage }); - const filter = createTranslationsFilter({ clientSrcDir, nativeLanguage, fallbackLanguage }); - const listener = filePath => { - if (filter(filePath)) { - this.env.sharedFs.removeListener('change', listener); - this.queueLoadLanguages({ clientSrcDir, enableTranslation, nativeLanguage, fallbackLanguage }); - } - }; - this.env.sharedFs.on('change', listener); - - control.getWebappTranslation = (...args) => this.translationData.getClientTranslation(...args); - }, - - insight() { - statistics.sendSubGenEvent('generator', GENERATOR_LANGUAGES); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - // Public API method used by the getter and also by Blueprints - get writing() { - return this.asWritingTaskGroup({ - async writeClientTranslations({ application }) { - if (application.skipClient) return; - const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; - await Promise.all( - languagesToApply.map(lang => - this.writeFiles({ - sections: clientI18nFiles, - context: { - ...application, - lang, - }, - }), - ), - ); - }, - async translateFile({ application }) { - if ( - !application.enableTranslation || - application.skipServer || - (!application.backendTypeSpringBoot && !this.writeJavaLanguageFiles) || - this.options.skipPriorities?.includes?.(PRIORITY_NAMES.POST_WRITING) - ) - return; - await Promise.all( - this.languagesToApply.map(async lang => { - const language = findLanguageForTag(lang); - if (language.javaLocaleMessageSourceSuffix) { - await this.writeFiles({ - sections: { - serverI18nFiles: [ - { - path: SERVER_MAIN_RES_DIR, - renameTo: (data, filePath) => `${data.srcMainResources}${filePath}`, - templates: [`i18n/messages_${language.javaLocaleMessageSourceSuffix}.properties`], - }, - ], - serverI18nTestFiles: [ - { - path: SERVER_TEST_RES_DIR, - renameTo: (data, filePath) => `${data.srcTestResources}${filePath}`, - condition: data => !data.skipUserManagement, - templates: [`i18n/messages_${language.javaLocaleMessageSourceSuffix}.properties`], - }, - ], - }, - context: { - ...application, - lang, - }, - }); - } - statistics.sendSubGenEvent('languages/language', language); - }), - ); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - ...writeEntityFiles(), - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - write({ application, control }) { - if (this.options.skipPriorities?.includes?.(PRIORITY_NAMES.POST_WRITING)) return; - - if (application.enableTranslation && !application.skipClient) { - if (application.clientFrameworkAngular) { - updateLanguagesInAngularTask.call(this, { application, control }); - } - if (application.clientFrameworkReact) { - updateLanguagesInReact.call(this, { application, control }); - } - if (application.clientFrameworkVue) { - updateLanguagesInVue.call(this, { application, control }); - } - } - if ( - application.enableTranslation && - application.generateUserManagement && - !application.skipServer && - application.backendTypeSpringBoot - ) { - updateLanguagesInJava.call(this, { application, control }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - addEntities({ application, entities, source }) { - if (application.skipClient) return; - const languagesToApply = application.enableTranslation ? this.languagesToApply : [...new Set([application.nativeLanguage, 'en'])]; - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - for (const language of languagesToApply) { - source.addEntityTranslationKey?.({ - language, - translationKey: entity.entityTranslationKeyMenu, - translationValue: entity.entityClassHumanized ?? startCase(entity.entityClass), - }); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.postWritingEntities); - } - - migrateLanguages(languagesToMigrate) { - const { languages, nativeLanguage } = this.jhipsterConfig; - if (languagesToMigrate[nativeLanguage]) { - this.jhipsterConfig.nativeLanguage = languagesToMigrate[nativeLanguage]; - } - if (languages && languages.some(lang => languagesToMigrate[lang])) { - this.jhipsterConfig.languages = languages.map(lang => languagesToMigrate[lang] ?? lang); - } - } - - queueLoadLanguages({ enableTranslation, clientSrcDir, nativeLanguage, fallbackLanguage = 'en' }) { - this.queueTask({ - method: async () => { - const filter = createTranslationsFileFilter({ clientSrcDir, nativeLanguage, fallbackLanguage }); - await this.pipeline( - { - name: 'loading translations', - filter: file => file.path.startsWith(this.destinationPath()) && filter(file), - refresh: true, - }, - this.translationData.loadFromStreamTransform({ - enableTranslation, - clientSrcDir, - nativeLanguage, - fallbackLanguage, - }), - ); - }, - taskName: 'loadingTranslations', - queueName: QUEUES.LOADING_TRANSLATIONS_QUEUE, - once: true, - }); - } -} diff --git a/generators/languages/generator.spec.js b/generators/languages/generator.spec.js new file mode 100644 index 000000000000..d0e494dc844a --- /dev/null +++ b/generators/languages/generator.spec.js @@ -0,0 +1,66 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { GENERATOR_LANGUAGES } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('languages migration', () => { + describe('indonesian language', () => { + before(() => + helpers + .runJHipster(GENERATOR_LANGUAGES) + .withSkipWritingPriorities() + .withJHipsterConfig({ + jhipsterVersion: '7.9.3', + enableTranslation: true, + nativeLanguage: 'in', + languages: ['in'], + baseName: 'jhipster', + }), + ); + it('should migrate in language to id', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + nativeLanguage: 'id', + languages: ['id'], + }, + }); + }); + }); + }); +}); diff --git a/generators/languages/generator.spec.mjs b/generators/languages/generator.spec.mjs deleted file mode 100644 index 2817f4150494..000000000000 --- a/generators/languages/generator.spec.mjs +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { GENERATOR_LANGUAGES } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('languages migration', () => { - describe('indonesian language', () => { - before(() => - helpers - .runJHipster(GENERATOR_LANGUAGES) - .withSkipWritingPriorities() - .withJHipsterConfig({ - jhipsterVersion: '7.9.3', - enableTranslation: true, - nativeLanguage: 'in', - languages: ['in'], - baseName: 'jhipster', - }), - ); - it('should migrate in language to id', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - nativeLanguage: 'id', - languages: ['id'], - }, - }); - }); - }); - }); -}); diff --git a/generators/languages/index.mts b/generators/languages/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/languages/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/languages/index.ts b/generators/languages/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/languages/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/languages/languages.spec.js b/generators/languages/languages.spec.js new file mode 100644 index 000000000000..6de4ec3bfe85 --- /dev/null +++ b/generators/languages/languages.spec.js @@ -0,0 +1,343 @@ +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { basicHelpers, defaultHelpers as helpers, result as runResult } from '../../test/support/index.js'; + +import { CLIENT_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { supportedLanguages } from './support/index.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generatorPath = join(__dirname, 'index.js'); + +const createClientProject = options => + basicHelpers + .runJHipster('app') + .withJHipsterConfig() + .withOptions({ + ...options, + }); + +const containsLanguageFiles = languageValue => { + it(`creates expected files for ${languageValue}`, () => { + runResult.assertFile([ + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/activate.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/configuration.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/error.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/login.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/logs.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/home.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/metrics.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/password.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/register.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/sessions.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/settings.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/reset.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/user-management.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/health.json`, + `${SERVER_MAIN_RES_DIR}i18n/messages_${languageValue.replace(/-/g, '_').replace(/_[a-z]+$/g, lang => lang.toUpperCase())}.properties`, + ]); + runResult.assertNoFile([`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/gateway.json`]); + }); + it('contains 3 needles in global.json', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, + '"jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)"', + ); + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, + '"jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)"', + ); + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, + '"jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)"', + ); + }); +}; + +const noLanguageFiles = languageValue => { + it(`should not create files for ${languageValue}`, () => { + runResult.assertNoFile([ + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/activate.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/configuration.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/error.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/login.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/logs.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/home.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/metrics.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/password.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/register.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/sessions.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/settings.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/reset.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/user-management.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, + `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/health.json`, + `${SERVER_MAIN_RES_DIR}i18n/messages_${languageValue.replace(/-/g, '_').replace(/_[a-z]+$/g, lang => lang.toUpperCase())}.properties`, + ]); + runResult.assertNoFile([`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/gateway.json`]); + }); + it('should not create global.json', () => { + runResult.assertNoFile(`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`); + }); +}; + +const containsLanguageInVueStore = languageValue => { + it(`add language ${languageValue} into translation-store.ts`, () => { + const langKey = languageValue.includes('-') ? `'${languageValue}'` : `${languageValue}`; + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/shared/config/languages.ts`, `${langKey}: { name:`); + }); +}; + +describe('generator - languages', () => { + context('Creates default i18n files', () => { + supportedLanguages.forEach(language => { + describe(`with prompts for ${language.name}`, () => { + before(() => + helpers + .create(generatorPath) + .withOptions({ ignoreNeedlesError: true }) + .withAnswers({ + enableTranslation: true, + nativeLanguage: language.languageTag, + languages: [language.languageTag], + }) + .run(), + ); + containsLanguageFiles(language.languageTag); + }); + describe(`with options for ${language.name}`, () => { + before(() => + helpers + .run(generatorPath) + .withArguments([language.languageTag]) + .withJHipsterConfig({ enableTranslation: true, nativeLanguage: language.languageTag }) + .withOptions({ ignoreNeedlesError: true }), + ); + containsLanguageFiles(language.languageTag); + }); + }); + }); + context('should not create i18n files', () => { + describe('for already generated native language', () => { + before(() => + helpers + .run(generatorPath) + .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['fr'], baseName: 'jhipster' }) + .withOptions({ commandName: 'languages', ignoreNeedlesError: true }), + ); + noLanguageFiles('fr'); + }); + describe('for already generated languages', () => { + before(() => + helpers + .run(generatorPath) + .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['en', 'fr'] }) + .withOptions({ commandName: 'languages', ignoreNeedlesError: true }), + ); + noLanguageFiles('fr'); + noLanguageFiles('en'); + }); + }); + context('should create default i18n files for the native language', () => { + describe('using prompts', () => { + before(() => + helpers.run(generatorPath).withOptions({ ignoreNeedlesError: true }).withAnswers({ + enableTranslation: true, + nativeLanguage: 'fr', + languages: [], + }), + ); + containsLanguageFiles('fr'); + }); + describe('using arguments', () => { + before(() => + helpers + .run(generatorPath) + .withLocalConfig({ enableTranslation: true }) + .withOptions({ ignoreNeedlesError: true }) + .withOptions({ nativeLanguage: 'fr', baseName: 'jhipster' }), + ); + containsLanguageFiles('fr'); + }); + describe('when regenerating', () => { + before(() => + helpers + .run(generatorPath) + .withLocalConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['fr'] }) + .withOptions({ ignoreNeedlesError: true }) + .withOptions({ skipPrompts: true, regenerate: true, baseName: 'jhipster' }), + ); + containsLanguageFiles('fr'); + }); + }); + context('should create default i18n files for the native language and an additional language', () => { + describe('by default', () => { + before(() => helpers.run(generatorPath).withJHipsterConfig().withOptions({ ignoreNeedlesError: true })); + containsLanguageFiles('en'); + }); + describe('using prompts', () => { + before(() => + helpers + .run(generatorPath) + .withOptions({ ignoreNeedlesError: true }) + .withAnswers({ + enableTranslation: true, + nativeLanguage: 'fr', + languages: ['en'], + }), + ); + it('creates expected configuration values', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + languages: ['fr', 'en'], + nativeLanguage: 'fr', + enableTranslation: true, + }, + }); + }); + containsLanguageFiles('fr'); + containsLanguageFiles('en'); + }); + describe('using arguments', () => { + before(() => + helpers + .run(generatorPath) + .withLocalConfig({ enableTranslation: true }) + .withArguments(['en']) + .withOptions({ ignoreNeedlesError: true }) + .withOptions({ nativeLanguage: 'fr', baseName: 'jhipster' }), + ); + containsLanguageFiles('fr'); + containsLanguageFiles('en'); + }); + describe('when regenerating', () => { + before(() => + helpers + .run(generatorPath) + .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['en', 'fr'] }) + .withOptions({ ignoreNeedlesError: true }) + .withOptions({ skipPrompts: true, regenerate: true, baseName: 'jhipster' }), + ); + it('creates expected configuration values', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + languages: ['fr', 'en'], + nativeLanguage: 'fr', + enableTranslation: true, + }, + }); + }); + containsLanguageFiles('fr'); + containsLanguageFiles('en'); + }); + }); + context('Creates default i18n files for more than one language', () => { + describe('with prompts', () => { + before(() => + helpers + .run(generatorPath) + .withOptions({ ignoreNeedlesError: true }) + .withAnswers({ + enableTranslation: true, + nativeLanguage: 'fr', + languages: ['fr', 'de'], + }), + ); + containsLanguageFiles('fr'); + containsLanguageFiles('de'); + }); + describe('with options', () => { + before(() => + helpers + .run(generatorPath) + .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'en' }) + .withOptions({ ignoreNeedlesError: true }) + .withArguments(['fr', 'de']) + .withOptions({ baseName: 'jhipster' }), + ); + it('creates expected configuration values', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + languages: ['en', 'fr', 'de'], + nativeLanguage: 'en', + enableTranslation: true, + }, + }); + }); + containsLanguageFiles('fr'); + containsLanguageFiles('de'); + }); + }); + + context('Creates default i18n files for Vue applications', () => { + describe('using prompts', () => { + before(async () => { + const result = await createClientProject().withJHipsterConfig({ + clientFramework: 'vue', + enableTranslation: true, + nativeLanguage: 'en', + }); + await result + .create('jhipster:languages') + .withAnswers({ + languages: ['fr', 'de'], + }) + .withOptions({ + commandName: 'languages', + }) + .run(); + }); + it('creates expected configuration values', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + languages: ['en', 'fr', 'de'], + nativeLanguage: 'en', + enableTranslation: true, + }, + }); + }); + describe('for native language translation', () => { + containsLanguageFiles('en'); + containsLanguageInVueStore('en'); + }); + describe('for additional languages translations', () => { + containsLanguageFiles('fr'); + containsLanguageInVueStore('fr'); + containsLanguageFiles('de'); + containsLanguageInVueStore('de'); + }); + }); + + describe('using arguments', () => { + before(async () => { + const result = await createClientProject().withJHipsterConfig({ + clientFramework: 'vue', + enableTranslation: true, + nativeLanguage: 'en', + }); + await result.create('jhipster:languages').withArguments(['fr', 'de']).withOptions({ baseName: 'jhipster' }).run(); + }); + it('creates expected configuration values', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + languages: ['en', 'fr', 'de'], + nativeLanguage: 'en', + enableTranslation: true, + }, + }); + }); + describe('for native language translation', () => { + containsLanguageFiles('en'); + containsLanguageInVueStore('en'); + }); + describe('for additional languages translations', () => { + containsLanguageFiles('fr'); + containsLanguageInVueStore('fr'); + containsLanguageFiles('de'); + containsLanguageInVueStore('de'); + }); + }); + }); +}); diff --git a/generators/languages/languages.spec.mjs b/generators/languages/languages.spec.mjs deleted file mode 100644 index cc4db65f8e90..000000000000 --- a/generators/languages/languages.spec.mjs +++ /dev/null @@ -1,343 +0,0 @@ -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; -import { basicHelpers, defaultHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; - -import { CLIENT_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { supportedLanguages } from './support/index.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generatorPath = join(__dirname, 'index.mjs'); - -const createClientProject = options => - basicHelpers - .runJHipster('app') - .withJHipsterConfig() - .withOptions({ - ...options, - }); - -const containsLanguageFiles = languageValue => { - it(`creates expected files for ${languageValue}`, () => { - runResult.assertFile([ - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/activate.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/configuration.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/error.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/login.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/logs.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/home.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/metrics.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/password.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/register.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/sessions.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/settings.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/reset.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/user-management.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/health.json`, - `${SERVER_MAIN_RES_DIR}i18n/messages_${languageValue.replace(/-/g, '_').replace(/_[a-z]+$/g, lang => lang.toUpperCase())}.properties`, - ]); - runResult.assertNoFile([`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/gateway.json`]); - }); - it('contains 3 needles in global.json', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, - '"jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)"', - ); - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, - '"jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)"', - ); - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, - '"jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)"', - ); - }); -}; - -const noLanguageFiles = languageValue => { - it(`should not create files for ${languageValue}`, () => { - runResult.assertNoFile([ - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/activate.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/configuration.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/error.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/login.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/logs.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/home.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/metrics.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/password.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/register.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/sessions.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/settings.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/reset.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/user-management.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`, - `${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/health.json`, - `${SERVER_MAIN_RES_DIR}i18n/messages_${languageValue.replace(/-/g, '_').replace(/_[a-z]+$/g, lang => lang.toUpperCase())}.properties`, - ]); - runResult.assertNoFile([`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/gateway.json`]); - }); - it('should not create global.json', () => { - runResult.assertNoFile(`${CLIENT_MAIN_SRC_DIR}i18n/${languageValue}/global.json`); - }); -}; - -const containsLanguageInVueStore = languageValue => { - it(`add language ${languageValue} into translation-store.ts`, () => { - const langKey = languageValue.includes('-') ? `'${languageValue}'` : `${languageValue}`; - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/shared/config/languages.ts`, `${langKey}: { name:`); - }); -}; - -describe('generator - languages', () => { - context('Creates default i18n files', () => { - supportedLanguages.forEach(language => { - describe(`with prompts for ${language.name}`, () => { - before(() => - helpers - .create(generatorPath) - .withOptions({ ignoreNeedlesError: true }) - .withAnswers({ - enableTranslation: true, - nativeLanguage: language.languageTag, - languages: [language.languageTag], - }) - .run(), - ); - containsLanguageFiles(language.languageTag); - }); - describe(`with options for ${language.name}`, () => { - before(() => - helpers - .run(generatorPath) - .withArguments([language.languageTag]) - .withJHipsterConfig({ enableTranslation: true, nativeLanguage: language.languageTag }) - .withOptions({ ignoreNeedlesError: true }), - ); - containsLanguageFiles(language.languageTag); - }); - }); - }); - context('should not create i18n files', () => { - describe('for already generated native language', () => { - before(() => - helpers - .run(generatorPath) - .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['fr'], baseName: 'jhipster' }) - .withOptions({ commandName: 'languages', ignoreNeedlesError: true }), - ); - noLanguageFiles('fr'); - }); - describe('for already generated languages', () => { - before(() => - helpers - .run(generatorPath) - .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['en', 'fr'] }) - .withOptions({ commandName: 'languages', ignoreNeedlesError: true }), - ); - noLanguageFiles('fr'); - noLanguageFiles('en'); - }); - }); - context('should create default i18n files for the native language', () => { - describe('using prompts', () => { - before(() => - helpers.run(generatorPath).withOptions({ ignoreNeedlesError: true }).withAnswers({ - enableTranslation: true, - nativeLanguage: 'fr', - languages: [], - }), - ); - containsLanguageFiles('fr'); - }); - describe('using arguments', () => { - before(() => - helpers - .run(generatorPath) - .withLocalConfig({ enableTranslation: true }) - .withOptions({ ignoreNeedlesError: true }) - .withOptions({ nativeLanguage: 'fr', baseName: 'jhipster' }), - ); - containsLanguageFiles('fr'); - }); - describe('when regenerating', () => { - before(() => - helpers - .run(generatorPath) - .withLocalConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['fr'] }) - .withOptions({ ignoreNeedlesError: true }) - .withOptions({ skipPrompts: true, regenerate: true, baseName: 'jhipster' }), - ); - containsLanguageFiles('fr'); - }); - }); - context('should create default i18n files for the native language and an additional language', () => { - describe('by default', () => { - before(() => helpers.run(generatorPath).withJHipsterConfig().withOptions({ ignoreNeedlesError: true })); - containsLanguageFiles('en'); - }); - describe('using prompts', () => { - before(() => - helpers - .run(generatorPath) - .withOptions({ ignoreNeedlesError: true }) - .withAnswers({ - enableTranslation: true, - nativeLanguage: 'fr', - languages: ['en'], - }), - ); - it('creates expected configuration values', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - languages: ['fr', 'en'], - nativeLanguage: 'fr', - enableTranslation: true, - }, - }); - }); - containsLanguageFiles('fr'); - containsLanguageFiles('en'); - }); - describe('using arguments', () => { - before(() => - helpers - .run(generatorPath) - .withLocalConfig({ enableTranslation: true }) - .withArguments(['en']) - .withOptions({ ignoreNeedlesError: true }) - .withOptions({ nativeLanguage: 'fr', baseName: 'jhipster' }), - ); - containsLanguageFiles('fr'); - containsLanguageFiles('en'); - }); - describe('when regenerating', () => { - before(() => - helpers - .run(generatorPath) - .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'fr', languages: ['en', 'fr'] }) - .withOptions({ ignoreNeedlesError: true }) - .withOptions({ skipPrompts: true, regenerate: true, baseName: 'jhipster' }), - ); - it('creates expected configuration values', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - languages: ['fr', 'en'], - nativeLanguage: 'fr', - enableTranslation: true, - }, - }); - }); - containsLanguageFiles('fr'); - containsLanguageFiles('en'); - }); - }); - context('Creates default i18n files for more than one language', () => { - describe('with prompts', () => { - before(() => - helpers - .run(generatorPath) - .withOptions({ ignoreNeedlesError: true }) - .withAnswers({ - enableTranslation: true, - nativeLanguage: 'fr', - languages: ['fr', 'de'], - }), - ); - containsLanguageFiles('fr'); - containsLanguageFiles('de'); - }); - describe('with options', () => { - before(() => - helpers - .run(generatorPath) - .withJHipsterConfig({ enableTranslation: true, nativeLanguage: 'en' }) - .withOptions({ ignoreNeedlesError: true }) - .withArguments(['fr', 'de']) - .withOptions({ baseName: 'jhipster' }), - ); - it('creates expected configuration values', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - languages: ['en', 'fr', 'de'], - nativeLanguage: 'en', - enableTranslation: true, - }, - }); - }); - containsLanguageFiles('fr'); - containsLanguageFiles('de'); - }); - }); - - context('Creates default i18n files for Vue applications', () => { - describe('using prompts', () => { - before(async () => { - const result = await createClientProject().withJHipsterConfig({ - clientFramework: 'vue', - enableTranslation: true, - nativeLanguage: 'en', - }); - await result - .create('jhipster:languages') - .withAnswers({ - languages: ['fr', 'de'], - }) - .withOptions({ - commandName: 'languages', - }) - .run(); - }); - it('creates expected configuration values', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - languages: ['en', 'fr', 'de'], - nativeLanguage: 'en', - enableTranslation: true, - }, - }); - }); - describe('for native language translation', () => { - containsLanguageFiles('en'); - containsLanguageInVueStore('en'); - }); - describe('for additional languages translations', () => { - containsLanguageFiles('fr'); - containsLanguageInVueStore('fr'); - containsLanguageFiles('de'); - containsLanguageInVueStore('de'); - }); - }); - - describe('using arguments', () => { - before(async () => { - const result = await createClientProject().withJHipsterConfig({ - clientFramework: 'vue', - enableTranslation: true, - nativeLanguage: 'en', - }); - await result.create('jhipster:languages').withArguments(['fr', 'de']).withOptions({ baseName: 'jhipster' }).run(); - }); - it('creates expected configuration values', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { - languages: ['en', 'fr', 'de'], - nativeLanguage: 'en', - enableTranslation: true, - }, - }); - }); - describe('for native language translation', () => { - containsLanguageFiles('en'); - containsLanguageInVueStore('en'); - }); - describe('for additional languages translations', () => { - containsLanguageFiles('fr'); - containsLanguageInVueStore('fr'); - containsLanguageFiles('de'); - containsLanguageInVueStore('de'); - }); - }); - }); -}); diff --git a/generators/languages/prompts.js b/generators/languages/prompts.js new file mode 100644 index 000000000000..1c9e342c4c0e --- /dev/null +++ b/generators/languages/prompts.js @@ -0,0 +1,74 @@ +/** + * Copyright 2013-2023 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 detectLanguage from './support/detect-language.js'; +import { languagesAsChoices } from './support/languages.js'; + +export async function askI18n() { + if (!this.askForMoreLanguages) return; + const nativeLanguage = this.jhipsterConfig.nativeLanguage; + const answers = await this.prompt( + [ + { + type: 'confirm', + name: 'enableTranslation', + message: 'Would you like to enable internationalization support?', + default: true, + }, + { + type: 'list', + name: 'nativeLanguage', + message: 'Please choose the native language of the application', + choices: () => languagesAsChoices(this.supportedLanguages), + default: () => (this.options.reproducible ? 'en' : detectLanguage()), + store: true, + }, + ], + this.config, + ); + if (nativeLanguage !== answers.nativeLanguage) { + this.languagesToApply.push(answers.nativeLanguage); + } +} + +export async function askForLanguages({ control }) { + if (!this.askForMoreLanguages) { + return; + } + const currentLanguages = this.jhipsterConfig.languages ?? []; + const answers = await this.prompt([ + { + type: 'checkbox', + name: 'languages', + message: 'Please choose additional languages to install', + choices: () => { + const languageOptions = this.supportedLanguages; + const nativeLanguage = this.jhipsterConfigWithDefaults.nativeLanguage; + const choices = languagesAsChoices(languageOptions.filter(l => l.languageTag !== nativeLanguage)); + const defaults = this.jhipsterConfigWithDefaults.languages ?? []; + return [...choices.filter(({ value }) => defaults.includes(value)), ...choices.filter(({ value }) => !defaults.includes(value))]; + }, + default: () => this.jhipsterConfigWithDefaults.languages, + }, + ]); + if (control.existingProject) { + this.languagesToApply.push(...answers.languages.filter(newLang => !currentLanguages.includes(newLang))); + } else { + this.languagesToApply.push(...answers.languages); + } +} diff --git a/generators/languages/prompts.mjs b/generators/languages/prompts.mjs deleted file mode 100644 index ac85fe6db3b4..000000000000 --- a/generators/languages/prompts.mjs +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2013-2023 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 detectLanguage from './support/detect-language.mjs'; -import { languagesAsChoices } from './support/languages.mjs'; - -export async function askI18n() { - if (!this.askForMoreLanguages) return; - const nativeLanguage = this.jhipsterConfig.nativeLanguage; - const answers = await this.prompt( - [ - { - type: 'confirm', - name: 'enableTranslation', - message: 'Would you like to enable internationalization support?', - default: true, - }, - { - type: 'list', - name: 'nativeLanguage', - message: 'Please choose the native language of the application', - choices: () => languagesAsChoices(this.supportedLanguages), - default: () => (this.options.reproducible ? 'en' : detectLanguage()), - store: true, - }, - ], - this.config, - ); - if (nativeLanguage !== answers.nativeLanguage) { - this.languagesToApply.push(answers.nativeLanguage); - } -} - -export async function askForLanguages({ control }) { - if (!this.askForMoreLanguages) { - return; - } - const currentLanguages = this.jhipsterConfig.languages ?? []; - const answers = await this.prompt([ - { - type: 'checkbox', - name: 'languages', - message: 'Please choose additional languages to install', - choices: () => { - const languageOptions = this.supportedLanguages; - const nativeLanguage = this.jhipsterConfigWithDefaults.nativeLanguage; - const choices = languagesAsChoices(languageOptions.filter(l => l.languageTag !== nativeLanguage)); - const defaults = this.jhipsterConfigWithDefaults.languages ?? []; - return [...choices.filter(({ value }) => defaults.includes(value)), ...choices.filter(({ value }) => !defaults.includes(value))]; - }, - default: () => this.jhipsterConfigWithDefaults.languages, - }, - ]); - if (control.existingProject) { - this.languagesToApply.push(...answers.languages.filter(newLang => !currentLanguages.includes(newLang))); - } else { - this.languagesToApply.push(...answers.languages); - } -} diff --git a/generators/languages/support/detect-language.mts b/generators/languages/support/detect-language.mts deleted file mode 100644 index 04f9c4feca37..000000000000 --- a/generators/languages/support/detect-language.mts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013-2023 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 { osLocaleSync } from 'os-locale'; -import { findLanguageForTag, Language, supportedLanguages } from './languages.mjs'; - -const detectLanguage = (languages: ReadonlyArray = supportedLanguages) => { - const locale = osLocaleSync(); - if (locale) { - const language = findLanguageForTag(locale.toLowerCase(), languages) ?? findLanguageForTag(locale.split('-')[0], languages); - if (language) { - return language.languageTag; - } - } - return 'en'; -}; - -export default detectLanguage; diff --git a/generators/languages/support/detect-language.ts b/generators/languages/support/detect-language.ts new file mode 100644 index 000000000000..459f708fb1c8 --- /dev/null +++ b/generators/languages/support/detect-language.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2013-2023 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 { osLocaleSync } from 'os-locale'; +import { findLanguageForTag, Language, supportedLanguages } from './languages.js'; + +const detectLanguage = (languages: ReadonlyArray = supportedLanguages) => { + const locale = osLocaleSync(); + if (locale) { + const language = findLanguageForTag(locale.toLowerCase(), languages) ?? findLanguageForTag(locale.split('-')[0], languages); + if (language) { + return language.languageTag; + } + } + return 'en'; +}; + +export default detectLanguage; diff --git a/generators/languages/support/index.mts b/generators/languages/support/index.mts deleted file mode 100644 index 83c11919711d..000000000000 --- a/generators/languages/support/index.mts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2013-2023 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 './languages.mjs'; -export { default as generateDateTimeFormat } from './json/dates.mjs'; -export { default as detectLanguage } from './detect-language.mjs'; -export { default as loadLanguagesConfig } from './load-config.mjs'; -export * from './translate.mjs'; diff --git a/generators/languages/support/index.ts b/generators/languages/support/index.ts new file mode 100644 index 000000000000..f4730f183371 --- /dev/null +++ b/generators/languages/support/index.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2013-2023 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 './languages.js'; +export { default as generateDateTimeFormat } from './json/dates.js'; +export { default as detectLanguage } from './detect-language.js'; +export { default as loadLanguagesConfig } from './load-config.js'; +export * from './translate.js'; diff --git a/generators/languages/support/json/dates.mjs b/generators/languages/support/json/dates.js similarity index 100% rename from generators/languages/support/json/dates.mjs rename to generators/languages/support/json/dates.js diff --git a/generators/languages/support/languages.spec.mts b/generators/languages/support/languages.spec.mts deleted file mode 100644 index 933e633f517e..000000000000 --- a/generators/languages/support/languages.spec.mts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect } from 'esmocha'; -import { findLanguageForTag, generateLanguagesWebappOptions } from './languages.mjs'; - -describe('generator - languages - support', () => { - describe('generateLanguagesWebappOptions', () => { - describe('when called with empty array', () => { - it('return empty', () => { - expect(generateLanguagesWebappOptions([])).toEqual([]); - }); - }); - describe('when called with languages array', () => { - it('return languages pipe syntax', () => { - expect(generateLanguagesWebappOptions(['en', 'fr'].map(lang => findLanguageForTag(lang)!))).toEqual([ - "'en': { name: 'English' }", - "'fr': { name: 'Français' }", - ]); - }); - }); - }); -}); diff --git a/generators/languages/support/languages.spec.ts b/generators/languages/support/languages.spec.ts new file mode 100644 index 000000000000..ccf52a110975 --- /dev/null +++ b/generators/languages/support/languages.spec.ts @@ -0,0 +1,20 @@ +import { expect } from 'esmocha'; +import { findLanguageForTag, generateLanguagesWebappOptions } from './languages.js'; + +describe('generator - languages - support', () => { + describe('generateLanguagesWebappOptions', () => { + describe('when called with empty array', () => { + it('return empty', () => { + expect(generateLanguagesWebappOptions([])).toEqual([]); + }); + }); + describe('when called with languages array', () => { + it('return languages pipe syntax', () => { + expect(generateLanguagesWebappOptions(['en', 'fr'].map(lang => findLanguageForTag(lang)!))).toEqual([ + "'en': { name: 'English' }", + "'fr': { name: 'Français' }", + ]); + }); + }); + }); +}); diff --git a/generators/languages/support/languages.mts b/generators/languages/support/languages.ts similarity index 100% rename from generators/languages/support/languages.mts rename to generators/languages/support/languages.ts diff --git a/generators/languages/support/load-config.mts b/generators/languages/support/load-config.mts deleted file mode 100644 index 773698f8b57f..000000000000 --- a/generators/languages/support/load-config.mts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2013-2023 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 I18nApplication } from '../types.mjs'; -import { findLanguageForTag, supportedLanguages as baseSupportedLanguages, type Language } from './languages.mjs'; - -/** - * Load translation config into application - */ -export default function loadConfig({ application, config, control = {} }: { application: I18nApplication; config: any; control?: any }) { - const { supportedLanguages = baseSupportedLanguages } = control; - application.enableTranslation = config.enableTranslation; - application.nativeLanguage = config.nativeLanguage; - const nativeLanguageDefinition = findLanguageForTag(config.nativeLanguage, supportedLanguages); - if (!nativeLanguageDefinition) { - throw new Error(`Native language ${config.nativeLanguage} does not exist`); - } - application.nativeLanguageDefinition = nativeLanguageDefinition; - if (application.enableTranslation) { - application.languages = config.languages; - application.languagesDefinition = application.languages - .map(lang => findLanguageForTag(lang, supportedLanguages)) - .filter(lang => lang) as Language[]; - application.enableI18nRTL = (application.languagesDefinition ?? [application.nativeLanguageDefinition]).some(language => language.rtl); - } else { - application.enableI18nRTL = application.nativeLanguageDefinition.rtl; - } -} diff --git a/generators/languages/support/load-config.ts b/generators/languages/support/load-config.ts new file mode 100644 index 000000000000..71a0b139029d --- /dev/null +++ b/generators/languages/support/load-config.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2013-2023 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 I18nApplication } from '../types.js'; +import { findLanguageForTag, supportedLanguages as baseSupportedLanguages, type Language } from './languages.js'; + +/** + * Load translation config into application + */ +export default function loadConfig({ application, config, control = {} }: { application: I18nApplication; config: any; control?: any }) { + const { supportedLanguages = baseSupportedLanguages } = control; + application.enableTranslation = config.enableTranslation; + application.nativeLanguage = config.nativeLanguage; + const nativeLanguageDefinition = findLanguageForTag(config.nativeLanguage, supportedLanguages); + if (!nativeLanguageDefinition) { + throw new Error(`Native language ${config.nativeLanguage} does not exist`); + } + application.nativeLanguageDefinition = nativeLanguageDefinition; + if (application.enableTranslation) { + application.languages = config.languages; + application.languagesDefinition = application.languages + .map(lang => findLanguageForTag(lang, supportedLanguages)) + .filter(lang => lang) as Language[]; + application.enableI18nRTL = (application.languagesDefinition ?? [application.nativeLanguageDefinition]).some(language => language.rtl); + } else { + application.enableI18nRTL = application.nativeLanguageDefinition.rtl; + } +} diff --git a/generators/languages/support/translate.spec.js b/generators/languages/support/translate.spec.js new file mode 100644 index 000000000000..b67b99e6c890 --- /dev/null +++ b/generators/languages/support/translate.spec.js @@ -0,0 +1,171 @@ +/** + * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; + +import { createJhiTransformTranslateReplacer, createJhiTransformTranslateStringifyReplacer } from './translate.js'; + +describe('generator - languages - translate', () => { + let getWebappTranslation; + + beforeEach(() => { + let value = 0; + getWebappTranslation = esmocha.fn().mockImplementation((key, interpolation = '') => { + if (interpolation) { + interpolation = `-${JSON.stringify(interpolation)}`; + } + return `${key}${interpolation}-translated-"-'-value-${value++}`; + }); + }); + + describe('jhiTransformTranslate', () => { + let jhiTransformTranslate; + + beforeEach(() => { + jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation); + }); + + it('should replace __jhiTransformTranslate__ function', () => { + const body = ` +__jhiTransformTranslate__('global') +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +global-translated-"-'-value-0 +" +`); + }); + + it('should replace __jhiTransformTranslate__ function with interpolation', () => { + const body = ` +__jhiTransformTranslate__('global', { "min":20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", +"anotherPattern": "^[a-zA-Z0-9]*$", +"dynamic": "exec()" +}) +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*$","anotherPattern":"^[a-zA-Z0-9]*$","dynamic":"exec()"}-translated-"-'-value-0 +" +`); + }); + }); + + describe('jhiTransformTranslate with escapeHtml', () => { + let jhiTransformTranslate; + + beforeEach(() => { + jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation, { escapeHtml: true }); + }); + + describe('with translation disabled', () => { + describe('.tsx files', () => { + it('should replace __jhiTransformTranslate__ function', () => { + const body = ` +__jhiTransformTranslate__('global') +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +global-translated-"-'-value-0 +" +`); + }); + + it('should replace __jhiTransformTranslate__ function with interpolation', () => { + const body = ` +__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", + "anotherPattern": "^[a-zA-Z0-9]*$", + "dynamic": "exec()" +}) +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", + "anotherPattern": "^[a-zA-Z0-9]*$", + "dynamic": "exec()" +})quot;,"anotherPattern":"^[a-zA-Z0-9]*__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", + "anotherPattern": "^[a-zA-Z0-9]*$", + "dynamic": "exec()" +})quot;,"dynamic":"exec()"}-translated-"-'-value-0 +" +`); + }); + }); + }); + }); + + describe('jhiTransformTranslate with wrapTranslation', () => { + let jhiTransformTranslate; + + beforeEach(() => { + jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation, { wrapTranslation: '"' }); + }); + + describe('with translation disabled', () => { + describe('.tsx files', () => { + it('should replace __jhiTransformTranslate__ function', () => { + const body = ` +__jhiTransformTranslate__('global') +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +"global-translated-"-'-value-0" +" +`); + }); + + it('should replace __jhiTransformTranslate__ function with interpolation', () => { + const body = ` +__jhiTransformTranslate__('global', { "min":20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", + "anotherPattern": "^[a-zA-Z0-9]*$", + "dynamic": "exec()" +}) +__jhiTransformTranslate__('logs.nbloggers', { "total": "{{ loggers.length }}" }) + +`; + expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` +" +"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*$","anotherPattern":"^[a-zA-Z0-9]*$","dynamic":"exec()"}-translated-"-'-value-0" +"logs.nbloggers-{"total":"{{ loggers.length }}"}-translated-"-'-value-1" + +" +`); + }); + }); + }); + }); + + describe('jhiTransformTranslate', () => { + let jhiTransformTranslateStringify; + + beforeEach(() => { + jhiTransformTranslateStringify = createJhiTransformTranslateStringifyReplacer(getWebappTranslation); + }); + + it('should replace __jhiTransformTranslateStringify__ function', () => { + const body = ` +__jhiTransformTranslateStringify__('global') +`; + expect(jhiTransformTranslateStringify(body)).toMatchInlineSnapshot(` +" +"global-translated-\\"-'-value-0" +" +`); + }); + }); +}); diff --git a/generators/languages/support/translate.spec.mjs b/generators/languages/support/translate.spec.mjs deleted file mode 100644 index e5e8492cc7e0..000000000000 --- a/generators/languages/support/translate.spec.mjs +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; - -import { createJhiTransformTranslateReplacer, createJhiTransformTranslateStringifyReplacer } from './translate.mjs'; - -describe('generator - languages - translate', () => { - let getWebappTranslation; - - beforeEach(() => { - let value = 0; - getWebappTranslation = esmocha.fn().mockImplementation((key, interpolation = '') => { - if (interpolation) { - interpolation = `-${JSON.stringify(interpolation)}`; - } - return `${key}${interpolation}-translated-"-'-value-${value++}`; - }); - }); - - describe('jhiTransformTranslate', () => { - let jhiTransformTranslate; - - beforeEach(() => { - jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation); - }); - - it('should replace __jhiTransformTranslate__ function', () => { - const body = ` -__jhiTransformTranslate__('global') -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -global-translated-"-'-value-0 -" -`); - }); - - it('should replace __jhiTransformTranslate__ function with interpolation', () => { - const body = ` -__jhiTransformTranslate__('global', { "min":20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", -"anotherPattern": "^[a-zA-Z0-9]*$", -"dynamic": "exec()" -}) -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*$","anotherPattern":"^[a-zA-Z0-9]*$","dynamic":"exec()"}-translated-"-'-value-0 -" -`); - }); - }); - - describe('jhiTransformTranslate with escapeHtml', () => { - let jhiTransformTranslate; - - beforeEach(() => { - jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation, { escapeHtml: true }); - }); - - describe('with translation disabled', () => { - describe('.tsx files', () => { - it('should replace __jhiTransformTranslate__ function', () => { - const body = ` -__jhiTransformTranslate__('global') -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -global-translated-"-'-value-0 -" -`); - }); - - it('should replace __jhiTransformTranslate__ function with interpolation', () => { - const body = ` -__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", - "anotherPattern": "^[a-zA-Z0-9]*$", - "dynamic": "exec()" -}) -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", - "anotherPattern": "^[a-zA-Z0-9]*$", - "dynamic": "exec()" -})quot;,"anotherPattern":"^[a-zA-Z0-9]*__jhiTransformTranslate__('global', { "min": 20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", - "anotherPattern": "^[a-zA-Z0-9]*$", - "dynamic": "exec()" -})quot;,"dynamic":"exec()"}-translated-"-'-value-0 -" -`); - }); - }); - }); - }); - - describe('jhiTransformTranslate with wrapTranslation', () => { - let jhiTransformTranslate; - - beforeEach(() => { - jhiTransformTranslate = createJhiTransformTranslateReplacer(getWebappTranslation, { wrapTranslation: '"' }); - }); - - describe('with translation disabled', () => { - describe('.tsx files', () => { - it('should replace __jhiTransformTranslate__ function', () => { - const body = ` -__jhiTransformTranslate__('global') -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -"global-translated-"-'-value-0" -" -`); - }); - - it('should replace __jhiTransformTranslate__ function with interpolation', () => { - const body = ` -__jhiTransformTranslate__('global', { "min":20, "max": 50, "pattern": "^[a-zA-Z0-9]*$", - "anotherPattern": "^[a-zA-Z0-9]*$", - "dynamic": "exec()" -}) -__jhiTransformTranslate__('logs.nbloggers', { "total": "{{ loggers.length }}" }) - -`; - expect(jhiTransformTranslate(body)).toMatchInlineSnapshot(` -" -"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*$","anotherPattern":"^[a-zA-Z0-9]*$","dynamic":"exec()"}-translated-"-'-value-0" -"logs.nbloggers-{"total":"{{ loggers.length }}"}-translated-"-'-value-1" - -" -`); - }); - }); - }); - }); - - describe('jhiTransformTranslate', () => { - let jhiTransformTranslateStringify; - - beforeEach(() => { - jhiTransformTranslateStringify = createJhiTransformTranslateStringifyReplacer(getWebappTranslation); - }); - - it('should replace __jhiTransformTranslateStringify__ function', () => { - const body = ` -__jhiTransformTranslateStringify__('global') -`; - expect(jhiTransformTranslateStringify(body)).toMatchInlineSnapshot(` -" -"global-translated-\\"-'-value-0" -" -`); - }); - }); -}); diff --git a/generators/languages/support/translate.mts b/generators/languages/support/translate.ts similarity index 100% rename from generators/languages/support/translate.mts rename to generators/languages/support/translate.ts diff --git a/generators/languages/translation-data.mjs b/generators/languages/translation-data.js similarity index 100% rename from generators/languages/translation-data.mjs rename to generators/languages/translation-data.js diff --git a/generators/languages/types.d.mts b/generators/languages/types.d.mts deleted file mode 100644 index 09223920446b..000000000000 --- a/generators/languages/types.d.mts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013-2023 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 { Language } from './support/languages.mjs'; - -export type I18nApplication = { - enableI18nRTL: boolean; - nativeLanguage: string; - nativeLanguageDefinition: Language; -} & ( - | { - enableTranslation: false; - } - | { - enableTranslation: true; - languages: string[]; - languagesDefinition: ReadonlyArray; - } -); diff --git a/generators/languages/types.d.ts b/generators/languages/types.d.ts index aab8aa8131fb..1df8ea581ff4 100644 --- a/generators/languages/types.d.ts +++ b/generators/languages/types.d.ts @@ -1,7 +1,34 @@ -export type LanguageSourceType = { - /** - * Add a new entity in the "global.json" translations. - * @param args - */ - addEntityTranslationKey?(args: { translationKey: string; translationValue: string; language: string }): void; -}; +/** + * Copyright 2013-2023 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 { Language } from './support/languages.js'; + +export type I18nApplication = { + enableI18nRTL: boolean; + nativeLanguage: string; + nativeLanguageDefinition: Language; +} & ( + | { + enableTranslation: false; + } + | { + enableTranslation: true; + languages: string[]; + languagesDefinition: ReadonlyArray; + } +); diff --git a/generators/liquibase/__snapshots__/incremental-liquibase.spec.mts.snap b/generators/liquibase/__snapshots__/incremental-liquibase.spec.ts.snap similarity index 100% rename from generators/liquibase/__snapshots__/incremental-liquibase.spec.mts.snap rename to generators/liquibase/__snapshots__/incremental-liquibase.spec.ts.snap diff --git a/generators/liquibase/changelog-files.js b/generators/liquibase/changelog-files.js new file mode 100644 index 000000000000..d44248bf32a5 --- /dev/null +++ b/generators/liquibase/changelog-files.js @@ -0,0 +1,124 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; + +export const addEntityFiles = { + dbChangelog: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/changelog/added_entity.xml', + renameTo: generator => `config/liquibase/changelog/${generator.changelogDate}_added_entity_${generator.entity.entityClass}.xml`, + }, + ], + }, + { + condition: generator => generator.entity.anyRelationshipIsOwnerSide, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/changelog/added_entity_constraints.xml', + renameTo: generator => + `config/liquibase/changelog/${generator.changelogDate}_added_entity_constraints_${generator.entity.entityClass}.xml`, + }, + ], + }, + ], +}; + +export const updateEntityFiles = { + dbChangelog: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/changelog/updated_entity.xml', + renameTo: generator => + `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_${generator.entity.entityClass}.xml`, + }, + ], + }, + ], +}; + +export const updateConstraintsFiles = { + dbChangelog: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/changelog/updated_entity_constraints.xml', + renameTo: generator => + `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_constraints_${generator.entity.entityClass}.xml`, + }, + ], + }, + ], +}; + +export const updateMigrateFiles = { + dbChangelog: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/changelog/updated_entity_migrate.xml', + renameTo: generator => + `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_migrate_${generator.entity.entityClass}.xml`, + }, + ], + }, + ], +}; + +export const fakeFiles = { + fakeData: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/fake-data/table_entity.csv', + renameTo: generator => { + if (!generator.incrementalChangelog || generator.recreateInitialChangelog) { + return `config/liquibase/fake-data/${generator.entity.entityTableName}.csv`; + } + + return `config/liquibase/fake-data/${generator.databaseChangelog.changelogDate}_entity_${generator.entity.entityTableName}.csv`; + }, + }, + ], + }, + { + condition: generator => generator.entity.anyFieldHasImageContentType || generator.entity.anyFieldIsBlobDerived, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/liquibase/fake-data/blob/hipster.png', + noEjs: true, + }, + ], + }, + { + condition: generator => generator.entity.anyFieldHasTextContentType, + path: SERVER_MAIN_RES_DIR, + templates: ['config/liquibase/fake-data/blob/hipster.txt'], + }, + ], +}; diff --git a/generators/liquibase/changelog-files.mjs b/generators/liquibase/changelog-files.mjs deleted file mode 100644 index aa599517afc3..000000000000 --- a/generators/liquibase/changelog-files.mjs +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; - -export const addEntityFiles = { - dbChangelog: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/changelog/added_entity.xml', - renameTo: generator => `config/liquibase/changelog/${generator.changelogDate}_added_entity_${generator.entity.entityClass}.xml`, - }, - ], - }, - { - condition: generator => generator.entity.anyRelationshipIsOwnerSide, - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/changelog/added_entity_constraints.xml', - renameTo: generator => - `config/liquibase/changelog/${generator.changelogDate}_added_entity_constraints_${generator.entity.entityClass}.xml`, - }, - ], - }, - ], -}; - -export const updateEntityFiles = { - dbChangelog: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/changelog/updated_entity.xml', - renameTo: generator => - `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_${generator.entity.entityClass}.xml`, - }, - ], - }, - ], -}; - -export const updateConstraintsFiles = { - dbChangelog: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/changelog/updated_entity_constraints.xml', - renameTo: generator => - `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_constraints_${generator.entity.entityClass}.xml`, - }, - ], - }, - ], -}; - -export const updateMigrateFiles = { - dbChangelog: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/changelog/updated_entity_migrate.xml', - renameTo: generator => - `config/liquibase/changelog/${generator.databaseChangelog.changelogDate}_updated_entity_migrate_${generator.entity.entityClass}.xml`, - }, - ], - }, - ], -}; - -export const fakeFiles = { - fakeData: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/fake-data/table_entity.csv', - renameTo: generator => { - if (!generator.incrementalChangelog || generator.recreateInitialChangelog) { - return `config/liquibase/fake-data/${generator.entity.entityTableName}.csv`; - } - - return `config/liquibase/fake-data/${generator.databaseChangelog.changelogDate}_entity_${generator.entity.entityTableName}.csv`; - }, - }, - ], - }, - { - condition: generator => generator.entity.anyFieldHasImageContentType || generator.entity.anyFieldIsBlobDerived, - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/liquibase/fake-data/blob/hipster.png', - noEjs: true, - }, - ], - }, - { - condition: generator => generator.entity.anyFieldHasTextContentType, - path: SERVER_MAIN_RES_DIR, - templates: ['config/liquibase/fake-data/blob/hipster.txt'], - }, - ], -}; diff --git a/generators/liquibase/command.mts b/generators/liquibase/command.mts deleted file mode 100644 index ed77a154c585..000000000000 --- a/generators/liquibase/command.mts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - skipFakeData: { - description: 'Skip generation of fake data for development', - type: Boolean, - scope: 'storage', - }, - }, -}; - -export default command; diff --git a/generators/liquibase/command.ts b/generators/liquibase/command.ts new file mode 100644 index 000000000000..f84d5814348f --- /dev/null +++ b/generators/liquibase/command.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + skipFakeData: { + description: 'Skip generation of fake data for development', + type: Boolean, + scope: 'storage', + }, + }, +}; + +export default command; diff --git a/generators/liquibase/files.mts b/generators/liquibase/files.mts deleted file mode 100644 index b455616e061f..000000000000 --- a/generators/liquibase/files.mts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.mjs'; -import type LiquibaseGenerator from './generator.mjs'; -import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir } from '../server/support/index.mjs'; -import { CommonClientServerApplication } from '../base-application/types.mjs'; - -// eslint-disable-next-line import/prefer-default-export -export const liquibaseFiles: WriteFileSection = { - liquibase: [ - { - condition: ctx => ctx.backendTypeSpringBoot, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/LiquibaseConfiguration.java'], - }, - ], - gradle: [ - { - condition: ctx => ctx.buildToolGradle, - templates: ['gradle/liquibase.gradle'], - }, - ], - serverResource: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - { - override: ctx => !ctx.incrementalChangelog || (ctx as any).recreateInitialChangelog, - file: data => `config/liquibase/changelog/initial_schema_${data.databaseType}.xml`, - renameTo: () => 'config/liquibase/changelog/00000000000000_initial_schema.xml', - }, - { - override: ctx => !ctx.incrementalChangelog || (ctx as any).recreateInitialChangelog, - file: 'config/liquibase/master.xml', - }, - ], - }, - { - condition: generator => Boolean(generator.generateUserManagement), - path: SERVER_MAIN_RES_DIR, - templates: ['config/liquibase/data/user.csv', 'config/liquibase/data/user_authority.csv'], - }, - { - condition: generator => Boolean(generator.generateBuiltInAuthorityEntity), - path: SERVER_MAIN_RES_DIR, - templates: ['config/liquibase/data/authority.csv'], - }, - ], -}; diff --git a/generators/liquibase/files.ts b/generators/liquibase/files.ts new file mode 100644 index 000000000000..e7fd76c127a5 --- /dev/null +++ b/generators/liquibase/files.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.js'; +import type LiquibaseGenerator from './generator.js'; +import { SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir } from '../server/support/index.js'; +import { CommonClientServerApplication } from '../base-application/types.js'; + +// eslint-disable-next-line import/prefer-default-export +export const liquibaseFiles: WriteFileSection = { + liquibase: [ + { + condition: ctx => ctx.backendTypeSpringBoot, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/LiquibaseConfiguration.java'], + }, + ], + gradle: [ + { + condition: ctx => ctx.buildToolGradle, + templates: ['gradle/liquibase.gradle'], + }, + ], + serverResource: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + override: ctx => !ctx.incrementalChangelog || (ctx as any).recreateInitialChangelog, + file: data => `config/liquibase/changelog/initial_schema_${data.databaseType}.xml`, + renameTo: () => 'config/liquibase/changelog/00000000000000_initial_schema.xml', + }, + { + override: ctx => !ctx.incrementalChangelog || (ctx as any).recreateInitialChangelog, + file: 'config/liquibase/master.xml', + }, + ], + }, + { + condition: generator => Boolean(generator.generateUserManagement), + path: SERVER_MAIN_RES_DIR, + templates: ['config/liquibase/data/user.csv', 'config/liquibase/data/user_authority.csv'], + }, + { + condition: generator => Boolean(generator.generateBuiltInAuthorityEntity), + path: SERVER_MAIN_RES_DIR, + templates: ['config/liquibase/data/authority.csv'], + }, + ], +}; diff --git a/generators/liquibase/generator.mts b/generators/liquibase/generator.mts deleted file mode 100644 index bcf955a50f5e..000000000000 --- a/generators/liquibase/generator.mts +++ /dev/null @@ -1,735 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; - -import BaseEntityChangesGenerator from '../base-entity-changes/index.mjs'; -import { GENERATOR_LIQUIBASE, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.mjs'; -import { liquibaseFiles } from './files.mjs'; -import { - prepareField as prepareFieldForLiquibase, - postPrepareEntity, - prepareRelationshipForLiquibase, - liquibaseComment, -} from './support/index.mjs'; -import { getFKConstraintName, getUXConstraintName, prepareEntity as prepareEntityForServer } from '../server/support/index.mjs'; -import { - prepareEntityPrimaryKeyForTemplates, - prepareRelationship, - prepareField, - prepareEntity, - loadRequiredConfigIntoEntity, -} from '../base-application/support/index.mjs'; -import mavenPlugin from './support/maven-plugin.mjs'; -import { - addLiquibaseChangelogCallback, - addLiquibaseConstraintsChangelogCallback, - addLiquibaseIncrementalChangelogCallback, -} from './internal/needles.mjs'; -import { prepareSqlApplicationProperties } from '../spring-data-relational/support/index.mjs'; -import { addEntityFiles, updateEntityFiles, updateConstraintsFiles, updateMigrateFiles, fakeFiles } from './changelog-files.mjs'; -import { fieldTypes } from '../../jdl/jhipster/index.mjs'; -import command from './command.mjs'; - -const { - CommonDBTypes: { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER }, -} = fieldTypes; - -export default class LiquibaseGenerator extends BaseEntityChangesGenerator { - recreateInitialChangelog: boolean; - numberOfRows: number; - databaseChangelogs: any[] = []; - injectBuildTool = true; - injectLogs = true; - - constructor(args: any, options: any, features: any) { - super(args, options, { skipParseOptions: false, ...features }); - - this.argument('entities', { - description: 'Which entities to generate a new changelog', - type: Array, - required: false, - }); - - this.recreateInitialChangelog = this.options.recreateInitialChangelog ?? false; - this.numberOfRows = 10; - } - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_LIQUIBASE); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadConfig() { - this.parseJHipsterOptions(command.options); - }, - }); - } - - get [BaseEntityChangesGenerator.INITIALIZING]() { - return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); - } - - get preparing() { - return this.asPreparingTaskGroup({ - preparing({ application }) { - application.liquibaseDefaultSchemaName = ''; - // Generate h2 properties at master.xml for blueprints that uses h2 for tests or others purposes. - (application as any).liquibaseAddH2Properties = - (application as any).liquibaseAddH2Properties ?? (application as any).devDatabaseTypeH2Any; - }, - checkDatabaseCompatibility({ application }) { - if (!application.databaseTypeSql && !application.databaseTypeNeo4j) { - throw new Error(`Database type ${application.databaseType} is not supported`); - } - - if (!application.databaseTypeSql) { - // Add sql related derived properties - prepareSqlApplicationProperties({ application }); - } - }, - addNeedles({ source, application }) { - source.addLiquibaseChangelog = changelog => - this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseChangelogCallback(changelog)); - source.addLiquibaseIncrementalChangelog = changelog => - this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseIncrementalChangelogCallback(changelog)); - source.addLiquibaseConstraintsChangelog = changelog => - this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseConstraintsChangelogCallback(changelog)); - }, - }); - } - - get [BaseEntityChangesGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get preparingEachEntityField() { - return this.asPreparingEachEntityFieldTaskGroup({ - prepareEntityField({ entity, field }) { - if (!field.transient) { - prepareFieldForLiquibase(entity, field); - } - }, - }); - } - - get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_FIELD]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntityField); - } - - get preparingEachEntityRelationship() { - return this.asPreparingEachEntityRelationshipTaskGroup({ - prepareEntityRelationship({ entity, relationship }) { - prepareRelationshipForLiquibase(entity, relationship); - }, - }); - } - - get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship); - } - - get postPreparingEachEntity() { - return this.asPostPreparingEachEntityTaskGroup({ - postPrepareEntity({ application, entity }) { - postPrepareEntity({ application, entity }); - }, - }); - } - - get [BaseEntityChangesGenerator.POST_PREPARING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity); - } - - get default() { - return this.asDefaultTaskGroup({ - async calculateChangelogs({ application, entities, entityChanges }) { - if (!application.databaseTypeSql || this.options.skipDbChangelog || !entityChanges) { - return; - } - - for (const databaseChangelog of entityChanges) { - if (!databaseChangelog.newEntity) { - // Previous entities are not prepared using default jhipster priorities. - // Prepare them. - const { previousEntity: entity } = databaseChangelog; - loadRequiredConfigIntoEntity(entity, this.jhipsterConfigWithDefaults); - prepareEntity(entity, this, application); - prepareEntityForServer(entity); - if (!entity.embedded && !entity.primaryKey) { - prepareEntityPrimaryKeyForTemplates.call(this, { entity, application }); - } - for (const field of entity.fields ?? []) { - prepareField(entity, field, this); - prepareFieldForLiquibase(entity, field); - } - for (const relationship of entity.relationships ?? []) { - prepareRelationship(entity, relationship, this, true); - prepareRelationshipForLiquibase(entity, relationship); - } - postPrepareEntity({ application, entity }); - } - } - - const entitiesToWrite = - this.options.entities ?? entities.filter(entity => !entity.builtIn && !entity.skipServer).map(entity => entity.name); - // Write only specified entities changelogs. - const changes = entityChanges.filter( - databaseChangelog => entitiesToWrite!.length === 0 || entitiesToWrite!.includes(databaseChangelog.entityName), - ); - - for (const databaseChangelog of changes) { - if (databaseChangelog.newEntity) { - this.databaseChangelogs.push(this.prepareChangelog({ databaseChangelog, application })); - } else if (databaseChangelog.addedFields.length > 0 || databaseChangelog.removedFields.length > 0) { - this.databaseChangelogs.push( - this.prepareChangelog({ - databaseChangelog: { - ...databaseChangelog, - fieldChangelog: true, - addedRelationships: [], - removedRelationships: [], - relationshipsToRecreateForeignKeysOnly: [], - }, - application, - }), - ); - } - } - // Relationships needs to be added later to make sure every related field is already added. - for (const databaseChangelog of changes) { - if ( - databaseChangelog.incremental && - (databaseChangelog.addedRelationships.length > 0 || databaseChangelog.removedRelationships.length > 0) - ) { - this.databaseChangelogs.push( - this.prepareChangelog({ - databaseChangelog: { - ...databaseChangelog, - relationshipChangelog: true, - addedFields: [], - removedFields: [], - }, - application, - }), - ); - } - } - this.databaseChangelogs = this.databaseChangelogs.filter(Boolean); - }, - }); - } - - get [BaseEntityChangesGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return this.asWritingTaskGroup({ - async writing({ application }) { - const context = { - ...application, - recreateInitialChangelog: this.recreateInitialChangelog, - } as any; - await this.writeFiles({ - sections: liquibaseFiles, - context, - }); - }, - }); - } - - get [BaseEntityChangesGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - writeChangelogs() { - return Promise.all(this.databaseChangelogs.map(databaseChangelog => this.writeChangelog({ databaseChangelog }))); - }, - }); - } - - get [BaseEntityChangesGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - customizeSpring({ source }) { - if (!this.injectLogs) return; - source.addLogbackMainLog?.({ name: 'liquibase', level: 'WARN' }); - source.addLogbackMainLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' }); - source.addLogbackTestLog?.({ name: 'liquibase', level: 'WARN' }); - source.addLogbackTestLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' }); - }, - customizeMaven({ source, application }) { - if (!application.buildToolMaven || !this.injectBuildTool) return; - if (!application.javaDependencies) { - throw new Error('Some application fields are be mandatory'); - } - - const applicationAny = application as any; - const databaseTypeProfile = applicationAny.devDatabaseTypeH2Any ? 'prod' : undefined; - - let liquibasePluginHibernateDialect; - let liquibasePluginJdbcDriver; - if (applicationAny.devDatabaseTypeH2Any) { - // eslint-disable-next-line no-template-curly-in-string - liquibasePluginHibernateDialect = '${liquibase-plugin.hibernate-dialect}'; - // eslint-disable-next-line no-template-curly-in-string - liquibasePluginJdbcDriver = '${liquibase-plugin.driver}'; - source.addMavenDefinition?.({ - properties: [ - { property: 'liquibase-plugin.hibernate-dialect' }, - { property: 'liquibase-plugin.driver' }, - { property: 'h2.version', value: application.javaDependencies.h2 }, - { inProfile: 'dev', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.devHibernateDialect }, - { inProfile: 'prod', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.prodHibernateDialect }, - { inProfile: 'dev', property: 'liquibase-plugin.driver', value: applicationAny.devJdbcDriver }, - { inProfile: 'prod', property: 'liquibase-plugin.driver', value: applicationAny.prodJdbcDriver }, - ], - }); - } else { - liquibasePluginHibernateDialect = applicationAny.prodHibernateDialect; - liquibasePluginJdbcDriver = applicationAny.prodJdbcDriver; - } - - source.addMavenDefinition?.({ - properties: [ - { inProfile: 'no-liquibase', property: 'profile.no-liquibase', value: ',no-liquibase' }, - { property: 'profile.no-liquibase' }, - { property: 'liquibase.version', value: application.javaDependencies.liquibase }, - { property: 'liquibase-plugin.url' }, - { property: 'liquibase-plugin.username' }, - { property: 'liquibase-plugin.password' }, - { inProfile: 'dev', property: 'liquibase-plugin.url', value: applicationAny.devLiquibaseUrl }, - { inProfile: 'dev', property: 'liquibase-plugin.username', value: applicationAny.devDatabaseUsername }, - { inProfile: 'dev', property: 'liquibase-plugin.password', value: applicationAny.devDatabasePassword }, - { inProfile: 'prod', property: 'liquibase-plugin.url', value: applicationAny.prodLiquibaseUrl }, - { inProfile: 'prod', property: 'liquibase-plugin.username', value: applicationAny.prodDatabaseUsername }, - { inProfile: 'prod', property: 'liquibase-plugin.password', value: applicationAny.prodDatabasePassword }, - ], - pluginManagement: [ - { - groupId: 'org.liquibase', - artifactId: 'liquibase-maven-plugin', - // eslint-disable-next-line no-template-curly-in-string - version: '${liquibase.version}', - additionalContent: mavenPlugin({ - backendTypeSpringBoot: application.backendTypeSpringBoot, - reactive: application.reactive, - packageName: application.packageName, - srcMainResources: application.srcMainResources, - authenticationTypeOauth2: application.authenticationTypeOauth2, - devDatabaseTypeH2Any: applicationAny.devDatabaseTypeH2Any, - driver: liquibasePluginJdbcDriver, - hibernateDialect: liquibasePluginHibernateDialect, - defaultSchemaName: application.liquibaseDefaultSchemaName, - // eslint-disable-next-line no-template-curly-in-string - url: '${liquibase-plugin.url}', - // eslint-disable-next-line no-template-curly-in-string - username: '${liquibase-plugin.username}', - // eslint-disable-next-line no-template-curly-in-string - password: '${liquibase-plugin.password}', - }), - }, - ], - dependencies: [ - { - groupId: 'org.liquibase', - artifactId: 'liquibase-core', - // eslint-disable-next-line no-template-curly-in-string - version: '${liquibase.version}', - }, - ], - }); - - if (applicationAny.prodDatabaseTypeMssql) { - source.addMavenDependency?.({ - inProfile: databaseTypeProfile, - groupId: 'org.liquibase.ext', - artifactId: 'liquibase-mssql', - // eslint-disable-next-line no-template-curly-in-string - version: '${liquibase.version}', - }); - } - - if (applicationAny.databaseTypeNeo4j) { - if (applicationAny.backendTypeSpringBoot) { - source.addMavenDependency?.([{ groupId: 'org.springframework', artifactId: 'spring-jdbc' }]); - } - source.addMavenDependency?.([ - { - groupId: 'org.liquibase.ext', - artifactId: 'liquibase-neo4j', - // eslint-disable-next-line no-template-curly-in-string - version: '${liquibase.version}', - // Exclude current neo4j driver and use the one provided by spring-data - // See: https://github.com/jhipster/generator-jhipster/pull/24241 - additionalContent: ` - - - org.neo4j.driver - neo4j-java-driver - - - org.slf4j - slf4j-jdk14 - - `, - }, - ]); - } - }, - injectGradle({ source, application }) { - if (!application.buildToolGradle || !this.injectBuildTool) return; - if (!application.javaDependencies) { - throw new Error('Some application fields are be mandatory'); - } - - source.addGradleProperty?.({ property: 'liquibaseTaskPrefix', value: 'liquibase' }); - source.addGradleProperty?.({ property: 'liquibasePluginVersion', value: application.javaDependencies['gradle-liquibase'] }); - source.addGradleProperty?.({ property: 'liquibaseVersion', value: application.javaDependencies.liquibase }); - if (application.databaseTypeSql && !application.reactive) { - source.addGradleProperty?.({ property: 'liquibaseHibernate6Version', value: application.javaDependencies.liquibase }); - } - - source.applyFromGradle?.({ script: 'gradle/liquibase.gradle' }); - source.addGradlePlugin?.({ id: 'org.liquibase.gradle' }); - // eslint-disable-next-line no-template-curly-in-string - source.addGradlePluginManagement?.({ id: 'org.liquibase.gradle', version: '${liquibasePluginVersion}' }); - }, - }); - } - - get [BaseEntityChangesGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - postWriteChangelogs({ source }) { - return Promise.all(this.databaseChangelogs.map(databaseChangelog => this.postWriteChangelog({ source, databaseChangelog }))); - }, - }); - } - - get [BaseEntityChangesGenerator.POST_WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.postWritingEntities); - } - - /* ======================================================================== */ - /* private methods use within generator */ - /* ======================================================================== */ - - isChangelogNew({ entityName, changelogDate }) { - return !fs.existsSync( - this.destinationPath(`src/main/resources/config/liquibase/changelog/${changelogDate}_added_entity_${entityName}.xml`), - ); - } - - /** - * Write files for new entities. - */ - _writeLiquibaseFiles({ context: writeContext, changelogData }) { - const promises: any[] = []; - const context = { - ...writeContext, - skipFakeData: changelogData.skipFakeData, - fields: changelogData.allFields, - allFields: changelogData.allFields, - relationships: changelogData.relationships, - }; - // Write initial liquibase files - promises.push(this.writeFiles({ sections: addEntityFiles, context })); - if (!changelogData.skipFakeData) { - promises.push(this.writeFiles({ sections: fakeFiles, context })); - } - - return Promise.all(promises); - } - - /** - * Write files for new entities. - */ - _addLiquibaseFilesReferences({ entity, databaseChangelog, source }) { - const fileName = `${databaseChangelog.changelogDate}_added_entity_${entity.entityClass}`; - source.addLiquibaseChangelog({ changelogName: fileName, section: entity.incremental ? 'incremental' : 'base' }); - - if (entity.anyRelationshipIsOwnerSide) { - const constFileName = `${databaseChangelog.changelogDate}_added_entity_constraints_${entity.entityClass}`; - source.addLiquibaseChangelog({ changelogName: constFileName, section: entity.incremental ? 'incremental' : 'constraints' }); - } - } - - /** - * Write files for updated entities. - */ - _writeUpdateFiles({ context: writeContext, changelogData }) { - const { - addedFields, - allFields, - removedFields, - addedRelationships, - removedRelationships, - hasFieldConstraint, - hasRelationshipConstraint, - shouldWriteAnyRelationship, - relationshipsToRecreateForeignKeysOnly, - } = changelogData; - - const context = { - ...writeContext, - skipFakeData: changelogData.skipFakeData, - addedFields, - removedFields, - fields: addedFields, - allFields, - hasFieldConstraint, - addedRelationships, - removedRelationships, - relationships: addedRelationships, - hasRelationshipConstraint, - shouldWriteAnyRelationship, - relationshipsToRecreateForeignKeysOnly, - }; - - const promises: Promise[] = []; - promises.push(this.writeFiles({ sections: updateEntityFiles, context })); - - if (!changelogData.skipFakeData && (changelogData.addedFields.length > 0 || shouldWriteAnyRelationship)) { - promises.push(this.writeFiles({ sections: fakeFiles, context })); - promises.push(this.writeFiles({ sections: updateMigrateFiles, context })); - } - - if (hasFieldConstraint || shouldWriteAnyRelationship) { - promises.push(this.writeFiles({ sections: updateConstraintsFiles, context })); - } - return Promise.all(promises); - } - - /** - * Write files for updated entities. - */ - _addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source }) { - source.addLiquibaseIncrementalChangelog({ changelogName: `${databaseChangelog.changelogDate}_updated_entity_${entity.entityClass}` }); - - if (!changelogData.skipFakeData && (changelogData.addedFields.length > 0 || changelogData.shouldWriteAnyRelationship)) { - source.addLiquibaseIncrementalChangelog({ - changelogName: `${databaseChangelog.changelogDate}_updated_entity_migrate_${entity.entityClass}`, - }); - } - - if (changelogData.hasFieldConstraint || changelogData.shouldWriteAnyRelationship) { - source.addLiquibaseIncrementalChangelog({ - changelogName: `${databaseChangelog.changelogDate}_updated_entity_constraints_${entity.entityClass}`, - }); - } - } - - /** - * @private - * Format As Liquibase Remarks - * - * @param {string} text - text to format - * @param {boolean} addRemarksTag - add remarks tag - * @returns formatted liquibase remarks - */ - formatAsLiquibaseRemarks(text, addRemarksTag = false) { - return liquibaseComment(text, addRemarksTag); - } - - prepareChangelog({ databaseChangelog, application }) { - if (!databaseChangelog.changelogDate) { - databaseChangelog.changelogDate = this.dateFormatForLiquibase(); - } - const entity = databaseChangelog.entity; - - if (entity.skipServer) { - return undefined; - } - - // eslint-disable-next-line no-nested-ternary - const entityChanges = databaseChangelog.changelogData; - entityChanges.skipFakeData = application.skipFakeData || entity.skipFakeData; - - entityChanges.allFields = entity.fields.filter(field => !field.transient); - - if (databaseChangelog.newEntity) { - entityChanges.fields = entityChanges.allFields; - } else { - entityChanges.addedFields = databaseChangelog.addedFields.filter(field => !field.transient); - entityChanges.removedFields = databaseChangelog.removedFields.filter(field => !field.transient); - } - - const seed = `${entity.entityClass}-liquibase`; - this.resetEntitiesFakeData(seed); - - entity.liquibaseFakeData = []; - - // fakeDataCount must be limited to the size of required unique relationships. - Object.defineProperty(entity, 'fakeDataCount', { - get: () => { - const uniqueRelationships = entity.relationships.filter(rel => rel.unique && (rel.relationshipRequired || rel.id)); - return _.min([entity.liquibaseFakeData.length, ...uniqueRelationships.map(rel => rel.otherEntity.fakeDataCount)]); - }, - configurable: true, - }); - - for (let rowNumber = 0; rowNumber < this.numberOfRows; rowNumber++) { - const rowData = {}; - const fields = databaseChangelog.newEntity - ? // generate id fields first to improve reproducibility - [...entityChanges.fields.filter(f => f.id), ...entityChanges.fields.filter(f => !f.id)] - : [...entityChanges.allFields.filter(f => f.id), ...entityChanges.addedFields.filter(f => !f.id)]; - fields.forEach(field => { - if (field.derived) { - Object.defineProperty(rowData, field.fieldName, { - get: () => { - if (!field.derivedEntity.liquibaseFakeData || rowNumber >= field.derivedEntity.liquibaseFakeData.length) { - return undefined; - } - return field.derivedEntity.liquibaseFakeData[rowNumber][field.fieldName]; - }, - }); - return; - } - let data; - if (field.id && [TYPE_INTEGER, TYPE_LONG].includes(field.fieldType)) { - data = rowNumber + 1; - } else { - data = field.generateFakeData(); - } - rowData[field.fieldName] = data; - }); - - entity.liquibaseFakeData.push(rowData); - } - - if (databaseChangelog.newEntity) { - entityChanges.relationships = entity.relationships; - } else { - entityChanges.addedRelationships = databaseChangelog.addedRelationships; - entityChanges.removedRelationships = databaseChangelog.removedRelationships; - entityChanges.relationshipsToRecreateForeignKeysOnly = databaseChangelog.relationshipsToRecreateForeignKeysOnly; - } - - /* Required by the templates */ - databaseChangelog.writeContext = { - entity, - databaseChangelog, - changelogDate: databaseChangelog.changelogDate, - databaseType: entity.databaseType, - prodDatabaseType: entity.prodDatabaseType, - authenticationType: entity.authenticationType, - jhiPrefix: entity.jhiPrefix, - reactive: application.reactive, - incrementalChangelog: application.incrementalChangelog, - recreateInitialChangelog: this.recreateInitialChangelog, - }; - - if (databaseChangelog.newEntity) { - return databaseChangelog; - } - - entityChanges.requiresUpdateChangelogs = - entityChanges.addedFields.length > 0 || - entityChanges.removedFields.length > 0 || - entityChanges.addedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) || - entityChanges.removedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable); - - if (entityChanges.requiresUpdateChangelogs) { - entityChanges.hasFieldConstraint = entityChanges.addedFields.some(field => field.unique || !field.nullable); - entityChanges.hasRelationshipConstraint = entityChanges.addedRelationships.some( - relationship => - (relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) && (relationship.unique || !relationship.nullable), - ); - entityChanges.shouldWriteAnyRelationship = entityChanges.addedRelationships.some( - relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable, - ); - } - - return databaseChangelog; - } - - writeChangelog({ databaseChangelog }) { - const { writeContext: context, changelogData } = databaseChangelog; - if (databaseChangelog.newEntity) { - return this._writeLiquibaseFiles({ context, changelogData }); - } - if (changelogData.requiresUpdateChangelogs) { - return this._writeUpdateFiles({ context, changelogData }); - } - return undefined; - } - - postWriteChangelog({ databaseChangelog, source }) { - const { entity, changelogData } = databaseChangelog; - if (entity.skipServer) { - return undefined; - } - - if (databaseChangelog.newEntity) { - return this._addLiquibaseFilesReferences({ entity, databaseChangelog, source }); - } - if (changelogData.requiresUpdateChangelogs) { - return this._addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source }); - } - return undefined; - } - - /** - * @private - * get a foreign key constraint name for tables in JHipster preferred style. - * - * @param {string} entityName - name of the entity - * @param {string} relationshipName - name of the related entity - * @param {string} prodDatabaseType - database type - * @param {boolean} noSnakeCase - do not convert names to snakecase - */ - getFKConstraintName(entityName, relationshipName, prodDatabaseType, noSnakeCase) { - const result = getFKConstraintName(entityName, relationshipName, { prodDatabaseType, noSnakeCase }); - (this as any).validateResult(result); - return result.value; - } - - /** - * @private - * get a unique constraint name for tables in JHipster preferred style. - * - * @param {string} entityName - name of the entity - * @param {string} columnName - name of the column - * @param {string} prodDatabaseType - database type - * @param {boolean} noSnakeCase - do not convert names to snakecase - */ - getUXConstraintName(entityName, columnName, prodDatabaseType, noSnakeCase) { - const result = getUXConstraintName(entityName, columnName, { prodDatabaseType, noSnakeCase }); - (this as any).validateResult(result); - return result.value; - } -} diff --git a/generators/liquibase/generator.spec.mts b/generators/liquibase/generator.spec.mts deleted file mode 100644 index 549bd327b5ed..000000000000 --- a/generators/liquibase/generator.spec.mts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); -}); diff --git a/generators/liquibase/generator.spec.ts b/generators/liquibase/generator.spec.ts new file mode 100644 index 000000000000..aed48150a103 --- /dev/null +++ b/generators/liquibase/generator.spec.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); +}); diff --git a/generators/liquibase/generator.ts b/generators/liquibase/generator.ts new file mode 100644 index 000000000000..9c3ab1b37edd --- /dev/null +++ b/generators/liquibase/generator.ts @@ -0,0 +1,735 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; + +import BaseEntityChangesGenerator from '../base-entity-changes/index.js'; +import { GENERATOR_LIQUIBASE, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.js'; +import { liquibaseFiles } from './files.js'; +import { + prepareField as prepareFieldForLiquibase, + postPrepareEntity, + prepareRelationshipForLiquibase, + liquibaseComment, +} from './support/index.js'; +import { getFKConstraintName, getUXConstraintName, prepareEntity as prepareEntityForServer } from '../server/support/index.js'; +import { + prepareEntityPrimaryKeyForTemplates, + prepareRelationship, + prepareField, + prepareEntity, + loadRequiredConfigIntoEntity, +} from '../base-application/support/index.js'; +import mavenPlugin from './support/maven-plugin.js'; +import { + addLiquibaseChangelogCallback, + addLiquibaseConstraintsChangelogCallback, + addLiquibaseIncrementalChangelogCallback, +} from './internal/needles.js'; +import { prepareSqlApplicationProperties } from '../spring-data-relational/support/index.js'; +import { addEntityFiles, updateEntityFiles, updateConstraintsFiles, updateMigrateFiles, fakeFiles } from './changelog-files.js'; +import { fieldTypes } from '../../jdl/jhipster/index.js'; +import command from './command.js'; + +const { + CommonDBTypes: { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER }, +} = fieldTypes; + +export default class LiquibaseGenerator extends BaseEntityChangesGenerator { + recreateInitialChangelog: boolean; + numberOfRows: number; + databaseChangelogs: any[] = []; + injectBuildTool = true; + injectLogs = true; + + constructor(args: any, options: any, features: any) { + super(args, options, { skipParseOptions: false, ...features }); + + this.argument('entities', { + description: 'Which entities to generate a new changelog', + type: Array, + required: false, + }); + + this.recreateInitialChangelog = this.options.recreateInitialChangelog ?? false; + this.numberOfRows = 10; + } + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_LIQUIBASE); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadConfig() { + this.parseJHipsterOptions(command.options); + }, + }); + } + + get [BaseEntityChangesGenerator.INITIALIZING]() { + return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); + } + + get preparing() { + return this.asPreparingTaskGroup({ + preparing({ application }) { + application.liquibaseDefaultSchemaName = ''; + // Generate h2 properties at master.xml for blueprints that uses h2 for tests or others purposes. + (application as any).liquibaseAddH2Properties = + (application as any).liquibaseAddH2Properties ?? (application as any).devDatabaseTypeH2Any; + }, + checkDatabaseCompatibility({ application }) { + if (!application.databaseTypeSql && !application.databaseTypeNeo4j) { + throw new Error(`Database type ${application.databaseType} is not supported`); + } + + if (!application.databaseTypeSql) { + // Add sql related derived properties + prepareSqlApplicationProperties({ application }); + } + }, + addNeedles({ source, application }) { + source.addLiquibaseChangelog = changelog => + this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseChangelogCallback(changelog)); + source.addLiquibaseIncrementalChangelog = changelog => + this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseIncrementalChangelogCallback(changelog)); + source.addLiquibaseConstraintsChangelog = changelog => + this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseConstraintsChangelogCallback(changelog)); + }, + }); + } + + get [BaseEntityChangesGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get preparingEachEntityField() { + return this.asPreparingEachEntityFieldTaskGroup({ + prepareEntityField({ entity, field }) { + if (!field.transient) { + prepareFieldForLiquibase(entity, field); + } + }, + }); + } + + get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_FIELD]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntityField); + } + + get preparingEachEntityRelationship() { + return this.asPreparingEachEntityRelationshipTaskGroup({ + prepareEntityRelationship({ entity, relationship }) { + prepareRelationshipForLiquibase(entity, relationship); + }, + }); + } + + get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship); + } + + get postPreparingEachEntity() { + return this.asPostPreparingEachEntityTaskGroup({ + postPrepareEntity({ application, entity }) { + postPrepareEntity({ application, entity }); + }, + }); + } + + get [BaseEntityChangesGenerator.POST_PREPARING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity); + } + + get default() { + return this.asDefaultTaskGroup({ + async calculateChangelogs({ application, entities, entityChanges }) { + if (!application.databaseTypeSql || this.options.skipDbChangelog || !entityChanges) { + return; + } + + for (const databaseChangelog of entityChanges) { + if (!databaseChangelog.newEntity) { + // Previous entities are not prepared using default jhipster priorities. + // Prepare them. + const { previousEntity: entity } = databaseChangelog; + loadRequiredConfigIntoEntity(entity, this.jhipsterConfigWithDefaults); + prepareEntity(entity, this, application); + prepareEntityForServer(entity); + if (!entity.embedded && !entity.primaryKey) { + prepareEntityPrimaryKeyForTemplates.call(this, { entity, application }); + } + for (const field of entity.fields ?? []) { + prepareField(entity, field, this); + prepareFieldForLiquibase(entity, field); + } + for (const relationship of entity.relationships ?? []) { + prepareRelationship(entity, relationship, this, true); + prepareRelationshipForLiquibase(entity, relationship); + } + postPrepareEntity({ application, entity }); + } + } + + const entitiesToWrite = + this.options.entities ?? entities.filter(entity => !entity.builtIn && !entity.skipServer).map(entity => entity.name); + // Write only specified entities changelogs. + const changes = entityChanges.filter( + databaseChangelog => entitiesToWrite!.length === 0 || entitiesToWrite!.includes(databaseChangelog.entityName), + ); + + for (const databaseChangelog of changes) { + if (databaseChangelog.newEntity) { + this.databaseChangelogs.push(this.prepareChangelog({ databaseChangelog, application })); + } else if (databaseChangelog.addedFields.length > 0 || databaseChangelog.removedFields.length > 0) { + this.databaseChangelogs.push( + this.prepareChangelog({ + databaseChangelog: { + ...databaseChangelog, + fieldChangelog: true, + addedRelationships: [], + removedRelationships: [], + relationshipsToRecreateForeignKeysOnly: [], + }, + application, + }), + ); + } + } + // Relationships needs to be added later to make sure every related field is already added. + for (const databaseChangelog of changes) { + if ( + databaseChangelog.incremental && + (databaseChangelog.addedRelationships.length > 0 || databaseChangelog.removedRelationships.length > 0) + ) { + this.databaseChangelogs.push( + this.prepareChangelog({ + databaseChangelog: { + ...databaseChangelog, + relationshipChangelog: true, + addedFields: [], + removedFields: [], + }, + application, + }), + ); + } + } + this.databaseChangelogs = this.databaseChangelogs.filter(Boolean); + }, + }); + } + + get [BaseEntityChangesGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return this.asWritingTaskGroup({ + async writing({ application }) { + const context = { + ...application, + recreateInitialChangelog: this.recreateInitialChangelog, + } as any; + await this.writeFiles({ + sections: liquibaseFiles, + context, + }); + }, + }); + } + + get [BaseEntityChangesGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + writeChangelogs() { + return Promise.all(this.databaseChangelogs.map(databaseChangelog => this.writeChangelog({ databaseChangelog }))); + }, + }); + } + + get [BaseEntityChangesGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + customizeSpring({ source }) { + if (!this.injectLogs) return; + source.addLogbackMainLog?.({ name: 'liquibase', level: 'WARN' }); + source.addLogbackMainLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' }); + source.addLogbackTestLog?.({ name: 'liquibase', level: 'WARN' }); + source.addLogbackTestLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' }); + }, + customizeMaven({ source, application }) { + if (!application.buildToolMaven || !this.injectBuildTool) return; + if (!application.javaDependencies) { + throw new Error('Some application fields are be mandatory'); + } + + const applicationAny = application as any; + const databaseTypeProfile = applicationAny.devDatabaseTypeH2Any ? 'prod' : undefined; + + let liquibasePluginHibernateDialect; + let liquibasePluginJdbcDriver; + if (applicationAny.devDatabaseTypeH2Any) { + // eslint-disable-next-line no-template-curly-in-string + liquibasePluginHibernateDialect = '${liquibase-plugin.hibernate-dialect}'; + // eslint-disable-next-line no-template-curly-in-string + liquibasePluginJdbcDriver = '${liquibase-plugin.driver}'; + source.addMavenDefinition?.({ + properties: [ + { property: 'liquibase-plugin.hibernate-dialect' }, + { property: 'liquibase-plugin.driver' }, + { property: 'h2.version', value: application.javaDependencies.h2 }, + { inProfile: 'dev', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.devHibernateDialect }, + { inProfile: 'prod', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.prodHibernateDialect }, + { inProfile: 'dev', property: 'liquibase-plugin.driver', value: applicationAny.devJdbcDriver }, + { inProfile: 'prod', property: 'liquibase-plugin.driver', value: applicationAny.prodJdbcDriver }, + ], + }); + } else { + liquibasePluginHibernateDialect = applicationAny.prodHibernateDialect; + liquibasePluginJdbcDriver = applicationAny.prodJdbcDriver; + } + + source.addMavenDefinition?.({ + properties: [ + { inProfile: 'no-liquibase', property: 'profile.no-liquibase', value: ',no-liquibase' }, + { property: 'profile.no-liquibase' }, + { property: 'liquibase.version', value: application.javaDependencies.liquibase }, + { property: 'liquibase-plugin.url' }, + { property: 'liquibase-plugin.username' }, + { property: 'liquibase-plugin.password' }, + { inProfile: 'dev', property: 'liquibase-plugin.url', value: applicationAny.devLiquibaseUrl }, + { inProfile: 'dev', property: 'liquibase-plugin.username', value: applicationAny.devDatabaseUsername }, + { inProfile: 'dev', property: 'liquibase-plugin.password', value: applicationAny.devDatabasePassword }, + { inProfile: 'prod', property: 'liquibase-plugin.url', value: applicationAny.prodLiquibaseUrl }, + { inProfile: 'prod', property: 'liquibase-plugin.username', value: applicationAny.prodDatabaseUsername }, + { inProfile: 'prod', property: 'liquibase-plugin.password', value: applicationAny.prodDatabasePassword }, + ], + pluginManagement: [ + { + groupId: 'org.liquibase', + artifactId: 'liquibase-maven-plugin', + // eslint-disable-next-line no-template-curly-in-string + version: '${liquibase.version}', + additionalContent: mavenPlugin({ + backendTypeSpringBoot: application.backendTypeSpringBoot, + reactive: application.reactive, + packageName: application.packageName, + srcMainResources: application.srcMainResources, + authenticationTypeOauth2: application.authenticationTypeOauth2, + devDatabaseTypeH2Any: applicationAny.devDatabaseTypeH2Any, + driver: liquibasePluginJdbcDriver, + hibernateDialect: liquibasePluginHibernateDialect, + defaultSchemaName: application.liquibaseDefaultSchemaName, + // eslint-disable-next-line no-template-curly-in-string + url: '${liquibase-plugin.url}', + // eslint-disable-next-line no-template-curly-in-string + username: '${liquibase-plugin.username}', + // eslint-disable-next-line no-template-curly-in-string + password: '${liquibase-plugin.password}', + }), + }, + ], + dependencies: [ + { + groupId: 'org.liquibase', + artifactId: 'liquibase-core', + // eslint-disable-next-line no-template-curly-in-string + version: '${liquibase.version}', + }, + ], + }); + + if (applicationAny.prodDatabaseTypeMssql) { + source.addMavenDependency?.({ + inProfile: databaseTypeProfile, + groupId: 'org.liquibase.ext', + artifactId: 'liquibase-mssql', + // eslint-disable-next-line no-template-curly-in-string + version: '${liquibase.version}', + }); + } + + if (applicationAny.databaseTypeNeo4j) { + if (applicationAny.backendTypeSpringBoot) { + source.addMavenDependency?.([{ groupId: 'org.springframework', artifactId: 'spring-jdbc' }]); + } + source.addMavenDependency?.([ + { + groupId: 'org.liquibase.ext', + artifactId: 'liquibase-neo4j', + // eslint-disable-next-line no-template-curly-in-string + version: '${liquibase.version}', + // Exclude current neo4j driver and use the one provided by spring-data + // See: https://github.com/jhipster/generator-jhipster/pull/24241 + additionalContent: ` + + + org.neo4j.driver + neo4j-java-driver + + + org.slf4j + slf4j-jdk14 + + `, + }, + ]); + } + }, + injectGradle({ source, application }) { + if (!application.buildToolGradle || !this.injectBuildTool) return; + if (!application.javaDependencies) { + throw new Error('Some application fields are be mandatory'); + } + + source.addGradleProperty?.({ property: 'liquibaseTaskPrefix', value: 'liquibase' }); + source.addGradleProperty?.({ property: 'liquibasePluginVersion', value: application.javaDependencies['gradle-liquibase'] }); + source.addGradleProperty?.({ property: 'liquibaseVersion', value: application.javaDependencies.liquibase }); + if (application.databaseTypeSql && !application.reactive) { + source.addGradleProperty?.({ property: 'liquibaseHibernate6Version', value: application.javaDependencies.liquibase }); + } + + source.applyFromGradle?.({ script: 'gradle/liquibase.gradle' }); + source.addGradlePlugin?.({ id: 'org.liquibase.gradle' }); + // eslint-disable-next-line no-template-curly-in-string + source.addGradlePluginManagement?.({ id: 'org.liquibase.gradle', version: '${liquibasePluginVersion}' }); + }, + }); + } + + get [BaseEntityChangesGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + postWriteChangelogs({ source }) { + return Promise.all(this.databaseChangelogs.map(databaseChangelog => this.postWriteChangelog({ source, databaseChangelog }))); + }, + }); + } + + get [BaseEntityChangesGenerator.POST_WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.postWritingEntities); + } + + /* ======================================================================== */ + /* private methods use within generator */ + /* ======================================================================== */ + + isChangelogNew({ entityName, changelogDate }) { + return !fs.existsSync( + this.destinationPath(`src/main/resources/config/liquibase/changelog/${changelogDate}_added_entity_${entityName}.xml`), + ); + } + + /** + * Write files for new entities. + */ + _writeLiquibaseFiles({ context: writeContext, changelogData }) { + const promises: any[] = []; + const context = { + ...writeContext, + skipFakeData: changelogData.skipFakeData, + fields: changelogData.allFields, + allFields: changelogData.allFields, + relationships: changelogData.relationships, + }; + // Write initial liquibase files + promises.push(this.writeFiles({ sections: addEntityFiles, context })); + if (!changelogData.skipFakeData) { + promises.push(this.writeFiles({ sections: fakeFiles, context })); + } + + return Promise.all(promises); + } + + /** + * Write files for new entities. + */ + _addLiquibaseFilesReferences({ entity, databaseChangelog, source }) { + const fileName = `${databaseChangelog.changelogDate}_added_entity_${entity.entityClass}`; + source.addLiquibaseChangelog({ changelogName: fileName, section: entity.incremental ? 'incremental' : 'base' }); + + if (entity.anyRelationshipIsOwnerSide) { + const constFileName = `${databaseChangelog.changelogDate}_added_entity_constraints_${entity.entityClass}`; + source.addLiquibaseChangelog({ changelogName: constFileName, section: entity.incremental ? 'incremental' : 'constraints' }); + } + } + + /** + * Write files for updated entities. + */ + _writeUpdateFiles({ context: writeContext, changelogData }) { + const { + addedFields, + allFields, + removedFields, + addedRelationships, + removedRelationships, + hasFieldConstraint, + hasRelationshipConstraint, + shouldWriteAnyRelationship, + relationshipsToRecreateForeignKeysOnly, + } = changelogData; + + const context = { + ...writeContext, + skipFakeData: changelogData.skipFakeData, + addedFields, + removedFields, + fields: addedFields, + allFields, + hasFieldConstraint, + addedRelationships, + removedRelationships, + relationships: addedRelationships, + hasRelationshipConstraint, + shouldWriteAnyRelationship, + relationshipsToRecreateForeignKeysOnly, + }; + + const promises: Promise[] = []; + promises.push(this.writeFiles({ sections: updateEntityFiles, context })); + + if (!changelogData.skipFakeData && (changelogData.addedFields.length > 0 || shouldWriteAnyRelationship)) { + promises.push(this.writeFiles({ sections: fakeFiles, context })); + promises.push(this.writeFiles({ sections: updateMigrateFiles, context })); + } + + if (hasFieldConstraint || shouldWriteAnyRelationship) { + promises.push(this.writeFiles({ sections: updateConstraintsFiles, context })); + } + return Promise.all(promises); + } + + /** + * Write files for updated entities. + */ + _addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source }) { + source.addLiquibaseIncrementalChangelog({ changelogName: `${databaseChangelog.changelogDate}_updated_entity_${entity.entityClass}` }); + + if (!changelogData.skipFakeData && (changelogData.addedFields.length > 0 || changelogData.shouldWriteAnyRelationship)) { + source.addLiquibaseIncrementalChangelog({ + changelogName: `${databaseChangelog.changelogDate}_updated_entity_migrate_${entity.entityClass}`, + }); + } + + if (changelogData.hasFieldConstraint || changelogData.shouldWriteAnyRelationship) { + source.addLiquibaseIncrementalChangelog({ + changelogName: `${databaseChangelog.changelogDate}_updated_entity_constraints_${entity.entityClass}`, + }); + } + } + + /** + * @private + * Format As Liquibase Remarks + * + * @param {string} text - text to format + * @param {boolean} addRemarksTag - add remarks tag + * @returns formatted liquibase remarks + */ + formatAsLiquibaseRemarks(text, addRemarksTag = false) { + return liquibaseComment(text, addRemarksTag); + } + + prepareChangelog({ databaseChangelog, application }) { + if (!databaseChangelog.changelogDate) { + databaseChangelog.changelogDate = this.dateFormatForLiquibase(); + } + const entity = databaseChangelog.entity; + + if (entity.skipServer) { + return undefined; + } + + // eslint-disable-next-line no-nested-ternary + const entityChanges = databaseChangelog.changelogData; + entityChanges.skipFakeData = application.skipFakeData || entity.skipFakeData; + + entityChanges.allFields = entity.fields.filter(field => !field.transient); + + if (databaseChangelog.newEntity) { + entityChanges.fields = entityChanges.allFields; + } else { + entityChanges.addedFields = databaseChangelog.addedFields.filter(field => !field.transient); + entityChanges.removedFields = databaseChangelog.removedFields.filter(field => !field.transient); + } + + const seed = `${entity.entityClass}-liquibase`; + this.resetEntitiesFakeData(seed); + + entity.liquibaseFakeData = []; + + // fakeDataCount must be limited to the size of required unique relationships. + Object.defineProperty(entity, 'fakeDataCount', { + get: () => { + const uniqueRelationships = entity.relationships.filter(rel => rel.unique && (rel.relationshipRequired || rel.id)); + return _.min([entity.liquibaseFakeData.length, ...uniqueRelationships.map(rel => rel.otherEntity.fakeDataCount)]); + }, + configurable: true, + }); + + for (let rowNumber = 0; rowNumber < this.numberOfRows; rowNumber++) { + const rowData = {}; + const fields = databaseChangelog.newEntity + ? // generate id fields first to improve reproducibility + [...entityChanges.fields.filter(f => f.id), ...entityChanges.fields.filter(f => !f.id)] + : [...entityChanges.allFields.filter(f => f.id), ...entityChanges.addedFields.filter(f => !f.id)]; + fields.forEach(field => { + if (field.derived) { + Object.defineProperty(rowData, field.fieldName, { + get: () => { + if (!field.derivedEntity.liquibaseFakeData || rowNumber >= field.derivedEntity.liquibaseFakeData.length) { + return undefined; + } + return field.derivedEntity.liquibaseFakeData[rowNumber][field.fieldName]; + }, + }); + return; + } + let data; + if (field.id && [TYPE_INTEGER, TYPE_LONG].includes(field.fieldType)) { + data = rowNumber + 1; + } else { + data = field.generateFakeData(); + } + rowData[field.fieldName] = data; + }); + + entity.liquibaseFakeData.push(rowData); + } + + if (databaseChangelog.newEntity) { + entityChanges.relationships = entity.relationships; + } else { + entityChanges.addedRelationships = databaseChangelog.addedRelationships; + entityChanges.removedRelationships = databaseChangelog.removedRelationships; + entityChanges.relationshipsToRecreateForeignKeysOnly = databaseChangelog.relationshipsToRecreateForeignKeysOnly; + } + + /* Required by the templates */ + databaseChangelog.writeContext = { + entity, + databaseChangelog, + changelogDate: databaseChangelog.changelogDate, + databaseType: entity.databaseType, + prodDatabaseType: entity.prodDatabaseType, + authenticationType: entity.authenticationType, + jhiPrefix: entity.jhiPrefix, + reactive: application.reactive, + incrementalChangelog: application.incrementalChangelog, + recreateInitialChangelog: this.recreateInitialChangelog, + }; + + if (databaseChangelog.newEntity) { + return databaseChangelog; + } + + entityChanges.requiresUpdateChangelogs = + entityChanges.addedFields.length > 0 || + entityChanges.removedFields.length > 0 || + entityChanges.addedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) || + entityChanges.removedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable); + + if (entityChanges.requiresUpdateChangelogs) { + entityChanges.hasFieldConstraint = entityChanges.addedFields.some(field => field.unique || !field.nullable); + entityChanges.hasRelationshipConstraint = entityChanges.addedRelationships.some( + relationship => + (relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) && (relationship.unique || !relationship.nullable), + ); + entityChanges.shouldWriteAnyRelationship = entityChanges.addedRelationships.some( + relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable, + ); + } + + return databaseChangelog; + } + + writeChangelog({ databaseChangelog }) { + const { writeContext: context, changelogData } = databaseChangelog; + if (databaseChangelog.newEntity) { + return this._writeLiquibaseFiles({ context, changelogData }); + } + if (changelogData.requiresUpdateChangelogs) { + return this._writeUpdateFiles({ context, changelogData }); + } + return undefined; + } + + postWriteChangelog({ databaseChangelog, source }) { + const { entity, changelogData } = databaseChangelog; + if (entity.skipServer) { + return undefined; + } + + if (databaseChangelog.newEntity) { + return this._addLiquibaseFilesReferences({ entity, databaseChangelog, source }); + } + if (changelogData.requiresUpdateChangelogs) { + return this._addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source }); + } + return undefined; + } + + /** + * @private + * get a foreign key constraint name for tables in JHipster preferred style. + * + * @param {string} entityName - name of the entity + * @param {string} relationshipName - name of the related entity + * @param {string} prodDatabaseType - database type + * @param {boolean} noSnakeCase - do not convert names to snakecase + */ + getFKConstraintName(entityName, relationshipName, prodDatabaseType, noSnakeCase) { + const result = getFKConstraintName(entityName, relationshipName, { prodDatabaseType, noSnakeCase }); + (this as any).validateResult(result); + return result.value; + } + + /** + * @private + * get a unique constraint name for tables in JHipster preferred style. + * + * @param {string} entityName - name of the entity + * @param {string} columnName - name of the column + * @param {string} prodDatabaseType - database type + * @param {boolean} noSnakeCase - do not convert names to snakecase + */ + getUXConstraintName(entityName, columnName, prodDatabaseType, noSnakeCase) { + const result = getUXConstraintName(entityName, columnName, { prodDatabaseType, noSnakeCase }); + (this as any).validateResult(result); + return result.value; + } +} diff --git a/generators/liquibase/incremental-liquibase.spec.mts b/generators/liquibase/incremental-liquibase.spec.mts deleted file mode 100644 index ca69053d491b..000000000000 --- a/generators/liquibase/incremental-liquibase.spec.mts +++ /dev/null @@ -1,1181 +0,0 @@ -import path, { basename, join } from 'path'; -import { existsSync, mkdirSync, writeFileSync } from 'fs'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { skipPrettierHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { createImporterFromContent } from '../../jdl/jdl-importer.js'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const incrementalFiles = [ - `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/00000000000000_initial_schema.xml`, -]; - -const baseName = 'JhipsterApp'; - -const jdlApplication = ` -application { - config { baseName ${baseName} } - entities * -}`; - -const jdlApplicationWithEntities = ` -${jdlApplication} -entity One { - @Id oneId Long - original String -} -entity Another { - @Id anotherId Long - original String -}`; - -const jdlApplicationWithRelationshipToUser = ` -${jdlApplicationWithEntities} -relationship ManyToOne { - One{user(login)} to User with builtInEntity -} -`; - -const jdlApplicationEntityWithByteTypes = ` -${jdlApplication} -entity Smarty { - name String required unique minlength(2) maxlength(10) - price Float required min(0) - description TextBlob required - picture ImageBlob required - specification Blob - category ProductCategory - inventory Integer required min(0) -} -enum ProductCategory { - Laptop, Desktop, Phone, Tablet, Accessory -}`; - -const jdlApplicationEntityWithoutByteTypes = ` -${jdlApplication} -entity Smarty { - name String - age Integer - height Long - income BigDecimal - expense Double - savings Float - category ProductCategory - happy Boolean - dob LocalDate - exactTime ZonedDateTime - travelTime Duration - moment Instant -} -enum ProductCategory { - Laptop, Desktop, Phone, Tablet, Accessory -}`; - -const jdlApplicationWithEntitiesAndRelationship = ` -${jdlApplicationWithEntities} -relationship OneToOne { -One to Another, -}`; - -const jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers = ` -${jdlApplicationWithEntities} -relationship ManyToOne { -One to @OnDelete("CASCADE") @OnUpdate("SET NULL") Another, -}`; - -const jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlers = ` -${jdlApplicationWithEntities} -relationship ManyToOne { -One to @OnDelete("SET NULL") @OnUpdate("CASCADE") Another, -}`; - -const jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlersAndChangedNaming = ` -${jdlApplicationWithEntities} -relationship ManyToOne { -One{anotherEnt} to @OnDelete("SET NULL") @OnUpdate("CASCADE") Another, -}`; - -const generatorPath = join(__dirname, '../server/index.mjs'); -const mockedGenerators = ['jhipster:common', 'jhipster:gradle', 'jhipster:maven']; - -describe('generator - app - --incremental-changelog', function () { - this.timeout(45000); - const options = { - creationTimestamp: '2020-01-01', - }; - const config = { - incrementalChangelog: true, - skipClient: true, - force: true, - }; - context('when creating a new application', () => { - let runResult; - before(async () => { - runResult = await helpers.run(generatorPath).withJHipsterConfig(config).withOptions(options).withMockedGenerators(mockedGenerators); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when incremental liquibase files exists', () => { - context('with default options', () => { - let runResult; - before(async () => { - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions(options) - .doInDir(cwd => { - incrementalFiles.forEach(filePath => { - filePath = join(cwd, filePath); - const dirname = path.dirname(filePath); - if (!existsSync(dirname)) { - mkdirSync(dirname, { recursive: true }); - } - writeFileSync(filePath, basename(filePath)); - }); - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - - it('should not override existing incremental files', () => { - incrementalFiles.forEach(filePath => { - runResult.assertFileContent(filePath, basename(filePath)); - }); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('with --recreate-initial-changelog', () => { - let runResult; - before(async () => { - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, recreateInitialChangelog: true }) - .doInDir(cwd => { - incrementalFiles.forEach(filePath => { - filePath = join(cwd, filePath); - const dirname = path.dirname(filePath); - if (!existsSync(dirname)) { - mkdirSync(dirname, { recursive: true }); - } - writeFileSync(filePath, basename(filePath)); - }); - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - - it('should override existing incremental files', () => { - incrementalFiles.forEach(filePath => { - runResult.assertNoFileContent(filePath, filePath); - }); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - }); - - context('regenerating the application', () => { - let runResult; - before(async () => { - const initialState = createImporterFromContent(jdlApplicationWithRelationshipToUser, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - const state = createImporterFromContent(jdlApplicationWithRelationshipToUser, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities.JhipsterApp, - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`, - `${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000200_entity_another.csv`, - ]); - }); - it('should not create the entity update changelog', () => { - runResult.assertNoFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000200_updated_entity_Another.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000200_updated_entity_constraints_Another.xml`, - ]); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when adding a field without constraints', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String -} -`, - { - ...options, - creationTimestampConfig: options.creationTimestamp, - }, - ).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(1); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String - foo String -} -`, - { - ...options, - }, - ).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities.JhipsterApp, - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'Customer.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); - }); - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'addColumn tableName="customer"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'column name="foo" type="varchar(255)"', - ); - runResult.assertNoFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'dropColump', - ); - }); - it('should not create the entity constraint update changelog', () => { - runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when adding a field with constraints', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String -} -`, - { - ...options, - creationTimestampConfig: options.creationTimestamp, - }, - ).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(1); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const regenerateState = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String - foo String required -} -`, - { - ...options, - }, - ).import(); - - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: regenerateState.exportedApplicationsWithEntities.JhipsterApp, - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'Customer.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); - }); - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'addColumn tableName="customer"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'column name="foo" type="varchar(255)"', - ); - runResult.assertNoFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'dropColump', - ); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when removing a field without constraints', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String - foo String -} -`, - { - ...options, - creationTimestampConfig: options.creationTimestamp, - }, - ).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(1); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String -} -`, - { - ...options, - }, - ).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'Customer.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); - }); - it('should create entity update changelog with dropColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'dropColumn tableName="customer"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'column name="foo"', - ); - runResult.assertNoFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'addColumn', - ); - }); - it('should not create the entity constraint update changelog', () => { - runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when removing a field with constraints', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String - foo String required -} -`, - { - ...options, - creationTimestampConfig: options.creationTimestamp, - }, - ).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(1); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent( - ` -${jdlApplication} -entity Customer { - original String -} -`, - { - ...options, - }, - ).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'Customer.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); - }); - it('should create entity update changelog with dropColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'dropColumn tableName="customer"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'column name="foo"', - ); - runResult.assertNoFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, - 'addColumn', - ); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('when adding a relationship', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntities, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationship, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'addColumn tableName="one"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'column name="another_another_id" type="bigint"', - ); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - context('when adding a relationship with on handlers', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntities, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'addColumn tableName="one"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'column name="another_another_id" type="bigint"', - ); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); - }); - it('should contain onUpdate and onDelete handlers', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onUpdate="SET NULL"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onDelete="CASCADE"', - ); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); - }); - }); - context('when modifying a relationship with on handlers, only at these handlers', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntities, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - - const thirdState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlers, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: thirdState.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-03', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'addColumn tableName="one"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'column name="another_another_id" type="bigint"', - ); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); - }); - it('should contain onUpdate and onDelete handlers', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onUpdate="SET NULL"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onDelete="CASCADE"', - ); - }); - - it('should create entity update changelog without add/dropColumn for on handler change', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`]); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'addColumn'); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'dropColumn'); - }); - - it('should create entity update changelog with dropForeignKeyConstraint', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, - 'dropForeignKeyConstraint', - ); - }); - - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`]); - }); - - it('should contain addForeignKeyConstraint with correct onUpdate and onDelete handlers', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'addForeignKeyConstraint', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'onUpdate="CASCADE"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'onDelete="SET NULL"', - ); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); - }); - }); - - context('when modifying an existing relationship', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntities, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - - const thirdState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlersAndChangedNaming, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: thirdState.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-03', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - - it('should create entity update changelog with addColumn', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'addColumn tableName="one"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'column name="another_another_id" type="bigint"', - ); - runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); - }); - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); - }); - it('should contain onUpdate and onDelete handlers', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onUpdate="SET NULL"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, - 'onDelete="CASCADE"', - ); - }); - - it('should create entity update changelog with add/dropColumn for on handler change', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`]); - - runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'addColumn'); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, - 'column name="another_ent_another_id" type="bigint"', - ); - - runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'dropColumn'); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, - 'column name="another_another_id"', - ); - }); - - it('should create entity update changelog with dropForeignKeyConstraint', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, - 'dropForeignKeyConstraint', - ); - }); - - it('should create the entity constraint update changelog', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`]); - }); - - it('should contain addForeignKeyConstraint with correct onUpdate and onDelete handlers', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'addForeignKeyConstraint', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'onUpdate="CASCADE"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, - 'onDelete="SET NULL"', - ); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); - }); - }); - - context('when initially creating an application with entities with relationships having on handlers', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should have a foreign key column in initial changelog', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - 'column name="another_another_id" type="bigint"', - ); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - it('should create entity initial constraint changelog with addForeignKeyConstraint and proper on handlers', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`]); - - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, - 'addForeignKeyConstraint', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, - 'onUpdate="SET NULL"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, - 'onDelete="CASCADE"', - ); - }); - - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); - }); - }); - - context('when removing a relationship', () => { - let runResult; - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationship, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(2); - runResult = await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - - const state = createImporterFromContent(jdlApplicationWithEntities, { - ...options, - }).import(); - runResult = await runResult - .create(generatorPath) - .withOptions({ - ...options, - applicationWithEntities: state.exportedApplicationsWithEntities[baseName], - creationTimestamp: '2020-01-02', - }) - .run(); - }); - - after(() => runResult.cleanup()); - - it('should create application', () => { - runResult.assertFile(['.yo-rc.json']); - }); - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); - }); - it('should create entity initial changelog', () => { - runResult.assertFile([ - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, - ]); - }); - it('should create entity initial fake data', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); - }); - it('should create entity update changelog with dropColumn and dropForeignKeyContraint', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'dropColumn tableName="one"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'column name="another_another_id"', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, - 'dropForeignKeyConstraint baseTableName="one" constraintName="fk_one__another_id"', - ); - }); - it('should not create an additional entity constraint update changelog', () => { - runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); - }); - it('should match snapshot', () => { - expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); - }); - }); - - context('entities with/without byte fields should create fake data', () => { - [ - { - entity: jdlApplicationEntityWithByteTypes, - bytesFields: true, - }, - { - entity: jdlApplicationEntityWithoutByteTypes, - bytesFields: false, - }, - ].forEach(eachEntityConfig => { - describe(`testing ${eachEntityConfig.bytesFields ? 'with' : 'without'} byte fields`, () => { - before(async () => { - const baseName = 'JhipsterApp'; - const initialState = createImporterFromContent(eachEntityConfig.entity, { - ...options, - creationTimestampConfig: options.creationTimestamp, - }).import(); - const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; - expect(applicationWithEntities).toBeTruthy(); - expect(applicationWithEntities.entities.length).toBe(1); - await helpers - .create(generatorPath) - .withJHipsterConfig(config) - .withOptions({ ...options, applicationWithEntities }) - .run(); - }); - - it('should create entity config file', () => { - runResult.assertFile([join('.jhipster', 'Smarty.json')]); - }); - it('should create entity initial fake data file', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_smarty.csv`]); - }); - it('should create fake data file with required content', () => { - expect( - runResult.getSnapshot(`**/${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_smarty.csv`), - ).toMatchSnapshot(); - }); - }); - }); - }); -}); diff --git a/generators/liquibase/incremental-liquibase.spec.ts b/generators/liquibase/incremental-liquibase.spec.ts new file mode 100644 index 000000000000..cfaa175ff4c7 --- /dev/null +++ b/generators/liquibase/incremental-liquibase.spec.ts @@ -0,0 +1,1181 @@ +import path, { basename, join } from 'path'; +import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { skipPrettierHelpers as helpers, runResult } from '../../test/support/index.js'; +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { createImporterFromContent } from '../../jdl/jdl-importer.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const incrementalFiles = [ + `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/00000000000000_initial_schema.xml`, +]; + +const baseName = 'JhipsterApp'; + +const jdlApplication = ` +application { + config { baseName ${baseName} } + entities * +}`; + +const jdlApplicationWithEntities = ` +${jdlApplication} +entity One { + @Id oneId Long + original String +} +entity Another { + @Id anotherId Long + original String +}`; + +const jdlApplicationWithRelationshipToUser = ` +${jdlApplicationWithEntities} +relationship ManyToOne { + One{user(login)} to User with builtInEntity +} +`; + +const jdlApplicationEntityWithByteTypes = ` +${jdlApplication} +entity Smarty { + name String required unique minlength(2) maxlength(10) + price Float required min(0) + description TextBlob required + picture ImageBlob required + specification Blob + category ProductCategory + inventory Integer required min(0) +} +enum ProductCategory { + Laptop, Desktop, Phone, Tablet, Accessory +}`; + +const jdlApplicationEntityWithoutByteTypes = ` +${jdlApplication} +entity Smarty { + name String + age Integer + height Long + income BigDecimal + expense Double + savings Float + category ProductCategory + happy Boolean + dob LocalDate + exactTime ZonedDateTime + travelTime Duration + moment Instant +} +enum ProductCategory { + Laptop, Desktop, Phone, Tablet, Accessory +}`; + +const jdlApplicationWithEntitiesAndRelationship = ` +${jdlApplicationWithEntities} +relationship OneToOne { +One to Another, +}`; + +const jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers = ` +${jdlApplicationWithEntities} +relationship ManyToOne { +One to @OnDelete("CASCADE") @OnUpdate("SET NULL") Another, +}`; + +const jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlers = ` +${jdlApplicationWithEntities} +relationship ManyToOne { +One to @OnDelete("SET NULL") @OnUpdate("CASCADE") Another, +}`; + +const jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlersAndChangedNaming = ` +${jdlApplicationWithEntities} +relationship ManyToOne { +One{anotherEnt} to @OnDelete("SET NULL") @OnUpdate("CASCADE") Another, +}`; + +const generatorPath = join(__dirname, '../server/index.js'); +const mockedGenerators = ['jhipster:common', 'jhipster:gradle', 'jhipster:maven']; + +describe('generator - app - --incremental-changelog', function () { + this.timeout(45000); + const options = { + creationTimestamp: '2020-01-01', + }; + const config = { + incrementalChangelog: true, + skipClient: true, + force: true, + }; + context('when creating a new application', () => { + let runResult; + before(async () => { + runResult = await helpers.run(generatorPath).withJHipsterConfig(config).withOptions(options).withMockedGenerators(mockedGenerators); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when incremental liquibase files exists', () => { + context('with default options', () => { + let runResult; + before(async () => { + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions(options) + .doInDir(cwd => { + incrementalFiles.forEach(filePath => { + filePath = join(cwd, filePath); + const dirname = path.dirname(filePath); + if (!existsSync(dirname)) { + mkdirSync(dirname, { recursive: true }); + } + writeFileSync(filePath, basename(filePath)); + }); + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + + it('should not override existing incremental files', () => { + incrementalFiles.forEach(filePath => { + runResult.assertFileContent(filePath, basename(filePath)); + }); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('with --recreate-initial-changelog', () => { + let runResult; + before(async () => { + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, recreateInitialChangelog: true }) + .doInDir(cwd => { + incrementalFiles.forEach(filePath => { + filePath = join(cwd, filePath); + const dirname = path.dirname(filePath); + if (!existsSync(dirname)) { + mkdirSync(dirname, { recursive: true }); + } + writeFileSync(filePath, basename(filePath)); + }); + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + + it('should override existing incremental files', () => { + incrementalFiles.forEach(filePath => { + runResult.assertNoFileContent(filePath, filePath); + }); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + }); + + context('regenerating the application', () => { + let runResult; + before(async () => { + const initialState = createImporterFromContent(jdlApplicationWithRelationshipToUser, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + const state = createImporterFromContent(jdlApplicationWithRelationshipToUser, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities.JhipsterApp, + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`, + `${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000200_entity_another.csv`, + ]); + }); + it('should not create the entity update changelog', () => { + runResult.assertNoFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000200_updated_entity_Another.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000200_updated_entity_constraints_Another.xml`, + ]); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when adding a field without constraints', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String +} +`, + { + ...options, + creationTimestampConfig: options.creationTimestamp, + }, + ).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(1); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String + foo String +} +`, + { + ...options, + }, + ).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities.JhipsterApp, + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'Customer.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); + }); + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'addColumn tableName="customer"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'column name="foo" type="varchar(255)"', + ); + runResult.assertNoFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'dropColump', + ); + }); + it('should not create the entity constraint update changelog', () => { + runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when adding a field with constraints', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String +} +`, + { + ...options, + creationTimestampConfig: options.creationTimestamp, + }, + ).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(1); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const regenerateState = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String + foo String required +} +`, + { + ...options, + }, + ).import(); + + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: regenerateState.exportedApplicationsWithEntities.JhipsterApp, + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'Customer.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); + }); + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'addColumn tableName="customer"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'column name="foo" type="varchar(255)"', + ); + runResult.assertNoFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'dropColump', + ); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when removing a field without constraints', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String + foo String +} +`, + { + ...options, + creationTimestampConfig: options.creationTimestamp, + }, + ).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(1); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String +} +`, + { + ...options, + }, + ).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'Customer.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); + }); + it('should create entity update changelog with dropColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'dropColumn tableName="customer"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'column name="foo"', + ); + runResult.assertNoFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'addColumn', + ); + }); + it('should not create the entity constraint update changelog', () => { + runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when removing a field with constraints', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String + foo String required +} +`, + { + ...options, + creationTimestampConfig: options.creationTimestamp, + }, + ).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(1); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent( + ` +${jdlApplication} +entity Customer { + original String +} +`, + { + ...options, + }, + ).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'Customer.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_Customer.xml`]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_customer.csv`]); + }); + it('should create entity update changelog with dropColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'dropColumn tableName="customer"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'column name="foo"', + ); + runResult.assertNoFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_Customer.xml`, + 'addColumn', + ); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_Customer.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('when adding a relationship', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntities, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationship, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'addColumn tableName="one"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'column name="another_another_id" type="bigint"', + ); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + context('when adding a relationship with on handlers', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntities, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'addColumn tableName="one"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'column name="another_another_id" type="bigint"', + ); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); + }); + it('should contain onUpdate and onDelete handlers', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onUpdate="SET NULL"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onDelete="CASCADE"', + ); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); + }); + }); + context('when modifying a relationship with on handlers, only at these handlers', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntities, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + + const thirdState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlers, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: thirdState.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-03', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'addColumn tableName="one"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'column name="another_another_id" type="bigint"', + ); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); + }); + it('should contain onUpdate and onDelete handlers', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onUpdate="SET NULL"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onDelete="CASCADE"', + ); + }); + + it('should create entity update changelog without add/dropColumn for on handler change', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`]); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'addColumn'); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'dropColumn'); + }); + + it('should create entity update changelog with dropForeignKeyConstraint', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, + 'dropForeignKeyConstraint', + ); + }); + + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`]); + }); + + it('should contain addForeignKeyConstraint with correct onUpdate and onDelete handlers', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'addForeignKeyConstraint', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'onUpdate="CASCADE"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'onDelete="SET NULL"', + ); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); + }); + }); + + context('when modifying an existing relationship', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntities, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + + const thirdState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithChangedOnHandlersAndChangedNaming, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: thirdState.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-03', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + + it('should create entity update changelog with addColumn', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'addColumn tableName="one"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'column name="another_another_id" type="bigint"', + ); + runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, 'dropColumn'); + }); + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); + }); + it('should contain onUpdate and onDelete handlers', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onUpdate="SET NULL"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`, + 'onDelete="CASCADE"', + ); + }); + + it('should create entity update changelog with add/dropColumn for on handler change', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`]); + + runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'addColumn'); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, + 'column name="another_ent_another_id" type="bigint"', + ); + + runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, 'dropColumn'); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, + 'column name="another_another_id"', + ); + }); + + it('should create entity update changelog with dropForeignKeyConstraint', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_One.xml`, + 'dropForeignKeyConstraint', + ); + }); + + it('should create the entity constraint update changelog', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`]); + }); + + it('should contain addForeignKeyConstraint with correct onUpdate and onDelete handlers', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'addForeignKeyConstraint', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'onUpdate="CASCADE"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200103000100_updated_entity_constraints_One.xml`, + 'onDelete="SET NULL"', + ); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); + }); + }); + + context('when initially creating an application with entities with relationships having on handlers', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationshipsWithOnHandlers, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should have a foreign key column in initial changelog', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + 'column name="another_another_id" type="bigint"', + ); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + it('should create entity initial constraint changelog with addForeignKeyConstraint and proper on handlers', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`]); + + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, + 'addForeignKeyConstraint', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, + 'onUpdate="SET NULL"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_constraints_One.xml`, + 'onDelete="CASCADE"', + ); + }); + + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/changelog/**')).toMatchSnapshot(); + }); + }); + + context('when removing a relationship', () => { + let runResult; + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(jdlApplicationWithEntitiesAndRelationship, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(2); + runResult = await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + + const state = createImporterFromContent(jdlApplicationWithEntities, { + ...options, + }).import(); + runResult = await runResult + .create(generatorPath) + .withOptions({ + ...options, + applicationWithEntities: state.exportedApplicationsWithEntities[baseName], + creationTimestamp: '2020-01-02', + }) + .run(); + }); + + after(() => runResult.cleanup()); + + it('should create application', () => { + runResult.assertFile(['.yo-rc.json']); + }); + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'One.json'), join('.jhipster', 'Another.json')]); + }); + it('should create entity initial changelog', () => { + runResult.assertFile([ + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000100_added_entity_One.xml`, + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200101000200_added_entity_Another.xml`, + ]); + }); + it('should create entity initial fake data', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_one.csv`]); + }); + it('should create entity update changelog with dropColumn and dropForeignKeyContraint', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`]); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'dropColumn tableName="one"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'column name="another_another_id"', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_One.xml`, + 'dropForeignKeyConstraint baseTableName="one" constraintName="fk_one__another_id"', + ); + }); + it('should not create an additional entity constraint update changelog', () => { + runResult.assertNoFile([`${SERVER_MAIN_RES_DIR}config/liquibase/changelog/20200102000100_updated_entity_constraints_One.xml`]); + }); + it('should match snapshot', () => { + expect(runResult.getSnapshot('**/src/main/resources/config/liquibase/**')).toMatchSnapshot(); + }); + }); + + context('entities with/without byte fields should create fake data', () => { + [ + { + entity: jdlApplicationEntityWithByteTypes, + bytesFields: true, + }, + { + entity: jdlApplicationEntityWithoutByteTypes, + bytesFields: false, + }, + ].forEach(eachEntityConfig => { + describe(`testing ${eachEntityConfig.bytesFields ? 'with' : 'without'} byte fields`, () => { + before(async () => { + const baseName = 'JhipsterApp'; + const initialState = createImporterFromContent(eachEntityConfig.entity, { + ...options, + creationTimestampConfig: options.creationTimestamp, + }).import(); + const applicationWithEntities = initialState.exportedApplicationsWithEntities[baseName]; + expect(applicationWithEntities).toBeTruthy(); + expect(applicationWithEntities.entities.length).toBe(1); + await helpers + .create(generatorPath) + .withJHipsterConfig(config) + .withOptions({ ...options, applicationWithEntities }) + .run(); + }); + + it('should create entity config file', () => { + runResult.assertFile([join('.jhipster', 'Smarty.json')]); + }); + it('should create entity initial fake data file', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_smarty.csv`]); + }); + it('should create fake data file with required content', () => { + expect( + runResult.getSnapshot(`**/${SERVER_MAIN_RES_DIR}config/liquibase/fake-data/20200101000100_entity_smarty.csv`), + ).toMatchSnapshot(); + }); + }); + }); + }); +}); diff --git a/generators/liquibase/index.mts b/generators/liquibase/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/liquibase/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/liquibase/index.ts b/generators/liquibase/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/liquibase/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/liquibase/internal/needles.mts b/generators/liquibase/internal/needles.mts deleted file mode 100644 index 95001386be98..000000000000 --- a/generators/liquibase/internal/needles.mts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2013-2023 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 { createNeedleCallback } from '../../base/support/needles.mjs'; -import { LiquibaseChangelog, LiquibaseChangelogSection } from '../types.mjs'; - -const changelogType = { - base: 'liquibase-add-changelog', - incremental: 'liquibase-add-incremental-changelog', - constraints: 'liquibase-add-constraints-changelog', -}; - -const addLiquibaseChangelogToMasterCallback = ({ changelogName, needle }: LiquibaseChangelog & { needle: string }) => - createNeedleCallback({ - needle, - contentToAdd: ``, - }); - -export const addLiquibaseChangelogCallback = ({ changelogName, section = 'base' }: LiquibaseChangelogSection) => - addLiquibaseChangelogToMasterCallback({ needle: changelogType[section], changelogName }); - -export const addLiquibaseIncrementalChangelogCallback = ({ changelogName }: LiquibaseChangelog) => - addLiquibaseChangelogCallback({ changelogName, section: 'incremental' }); - -export const addLiquibaseConstraintsChangelogCallback = ({ changelogName }: LiquibaseChangelog) => - addLiquibaseChangelogCallback({ changelogName, section: 'constraints' }); diff --git a/generators/liquibase/internal/needles.ts b/generators/liquibase/internal/needles.ts new file mode 100644 index 000000000000..7cb84ae21089 --- /dev/null +++ b/generators/liquibase/internal/needles.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2013-2023 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 { createNeedleCallback } from '../../base/support/needles.js'; +import { LiquibaseChangelog, LiquibaseChangelogSection } from '../types.js'; + +const changelogType = { + base: 'liquibase-add-changelog', + incremental: 'liquibase-add-incremental-changelog', + constraints: 'liquibase-add-constraints-changelog', +}; + +const addLiquibaseChangelogToMasterCallback = ({ changelogName, needle }: LiquibaseChangelog & { needle: string }) => + createNeedleCallback({ + needle, + contentToAdd: ``, + }); + +export const addLiquibaseChangelogCallback = ({ changelogName, section = 'base' }: LiquibaseChangelogSection) => + addLiquibaseChangelogToMasterCallback({ needle: changelogType[section], changelogName }); + +export const addLiquibaseIncrementalChangelogCallback = ({ changelogName }: LiquibaseChangelog) => + addLiquibaseChangelogCallback({ changelogName, section: 'incremental' }); + +export const addLiquibaseConstraintsChangelogCallback = ({ changelogName }: LiquibaseChangelog) => + addLiquibaseChangelogCallback({ changelogName, section: 'constraints' }); diff --git a/generators/liquibase/needles.spec.mts b/generators/liquibase/needles.spec.mts deleted file mode 100644 index e268f0661d43..000000000000 --- a/generators/liquibase/needles.spec.mts +++ /dev/null @@ -1,74 +0,0 @@ -import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { GENERATOR_LIQUIBASE } from '../generator-list.mjs'; - -class mockBlueprintSubGen extends BaseApplicationGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - addChangelogStep({ source }) { - source.addLiquibaseChangelog?.({ changelogName: 'aNewChangeLog' }); - source.addLiquibaseConstraintsChangelog?.({ changelogName: 'aNewConstraintsChangeLog' }); - }, - addIncrementalChangelog({ source }) { - source.addLiquibaseIncrementalChangelog?.({ changelogName: 'incrementalChangeLogWithNeedle' }); - source.addLiquibaseIncrementalChangelog?.({ changelogName: 'incrementalChangeLogWithNeedle2' }); - }, - }); - } -} - -describe('generator - liquibase - needles', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_LIQUIBASE) - .withFiles({ - 'src/main/resources/config/liquibase/master.xml': ` - - - - - -`, - }) - .withJHipsterConfig({ - blueprint: 'myblueprint', - clientFramework: 'no', - }) - .withOptions({ - skipPriorities: ['writing'], - }) - .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:liquibase']]); - }); - - it('Assert changelog is added to master.xml', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, - '', - ); - }); - - it('Assert incremental changelog is added to master.xml', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, - '', - ); - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, - '', - ); - }); - - it('Assert constraints changelog is added to master.xml', () => { - runResult.assertFileContent( - `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, - '', - ); - }); -}); diff --git a/generators/liquibase/needles.spec.ts b/generators/liquibase/needles.spec.ts new file mode 100644 index 000000000000..f786544d62fe --- /dev/null +++ b/generators/liquibase/needles.spec.ts @@ -0,0 +1,74 @@ +import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { GENERATOR_LIQUIBASE } from '../generator-list.js'; + +class mockBlueprintSubGen extends BaseApplicationGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + addChangelogStep({ source }) { + source.addLiquibaseChangelog?.({ changelogName: 'aNewChangeLog' }); + source.addLiquibaseConstraintsChangelog?.({ changelogName: 'aNewConstraintsChangeLog' }); + }, + addIncrementalChangelog({ source }) { + source.addLiquibaseIncrementalChangelog?.({ changelogName: 'incrementalChangeLogWithNeedle' }); + source.addLiquibaseIncrementalChangelog?.({ changelogName: 'incrementalChangeLogWithNeedle2' }); + }, + }); + } +} + +describe('generator - liquibase - needles', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_LIQUIBASE) + .withFiles({ + 'src/main/resources/config/liquibase/master.xml': ` + + + + + +`, + }) + .withJHipsterConfig({ + blueprint: 'myblueprint', + clientFramework: 'no', + }) + .withOptions({ + skipPriorities: ['writing'], + }) + .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:liquibase']]); + }); + + it('Assert changelog is added to master.xml', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, + '', + ); + }); + + it('Assert incremental changelog is added to master.xml', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, + '', + ); + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, + '', + ); + }); + + it('Assert constraints changelog is added to master.xml', () => { + runResult.assertFileContent( + `${SERVER_MAIN_RES_DIR}config/liquibase/master.xml`, + '', + ); + }); +}); diff --git a/generators/liquibase/support/formatting.js b/generators/liquibase/support/formatting.js new file mode 100644 index 000000000000..dc0f6e4a7913 --- /dev/null +++ b/generators/liquibase/support/formatting.js @@ -0,0 +1,52 @@ +/** + * Copyright 2013-2023 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 { formatDocAsSingleLine } from '../../base-application/support/index.js'; + +const htmlEncode = text => { + let htmLifiedText = text; + // escape & to & + htmLifiedText = htmLifiedText.replace(/&/g, '&'); + // escape " to " + htmLifiedText = htmLifiedText.replace(/"/g, '"'); + // escape ' to ' + htmLifiedText = htmLifiedText.replace(/'/g, '''); + // escape < to < + htmLifiedText = htmLifiedText.replace(/ to > + htmLifiedText = htmLifiedText.replace(/>/g, '>'); + return htmLifiedText; +}; + +/** + * Format As Liquibase Remarks + * + * @param {string} text - text to format + * @param {boolean} addRemarksTag - add remarks tag + * @returns formatted liquibase remarks + */ +const formatAsLiquibaseRemarks = (text, addRemarksTag = false) => { + if (!text) { + return addRemarksTag ? '' : text; + } + + const description = htmlEncode(formatDocAsSingleLine(text)); + return addRemarksTag ? ` remarks="${description}"` : description; +}; + +export default formatAsLiquibaseRemarks; diff --git a/generators/liquibase/support/formatting.mjs b/generators/liquibase/support/formatting.mjs deleted file mode 100644 index 84d001e3302b..000000000000 --- a/generators/liquibase/support/formatting.mjs +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2013-2023 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 { formatDocAsSingleLine } from '../../base-application/support/index.mjs'; - -const htmlEncode = text => { - let htmLifiedText = text; - // escape & to & - htmLifiedText = htmLifiedText.replace(/&/g, '&'); - // escape " to " - htmLifiedText = htmLifiedText.replace(/"/g, '"'); - // escape ' to ' - htmLifiedText = htmLifiedText.replace(/'/g, '''); - // escape < to < - htmLifiedText = htmLifiedText.replace(/ to > - htmLifiedText = htmLifiedText.replace(/>/g, '>'); - return htmLifiedText; -}; - -/** - * Format As Liquibase Remarks - * - * @param {string} text - text to format - * @param {boolean} addRemarksTag - add remarks tag - * @returns formatted liquibase remarks - */ -const formatAsLiquibaseRemarks = (text, addRemarksTag = false) => { - if (!text) { - return addRemarksTag ? '' : text; - } - - const description = htmlEncode(formatDocAsSingleLine(text)); - return addRemarksTag ? ` remarks="${description}"` : description; -}; - -export default formatAsLiquibaseRemarks; diff --git a/generators/liquibase/support/formatting.spec.mts b/generators/liquibase/support/formatting.spec.mts deleted file mode 100644 index 05dabcc950f1..000000000000 --- a/generators/liquibase/support/formatting.spec.mts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect } from 'esmocha'; -import formatAsLiquibaseRemarks from './formatting.mjs'; - -describe('generator - liquibase - support - formatting', () => { - describe('formatAsLiquibaseRemarks', () => { - describe('when formatting a nil text', () => { - it('returns it', () => { - // @ts-expect-error - expect(formatAsLiquibaseRemarks()).toEqual(undefined); - }); - }); - describe('when formatting an empty text', () => { - it('returns it', () => { - expect(formatAsLiquibaseRemarks('')).toEqual(''); - }); - }); - describe('when formatting normal texts', () => { - describe('when having empty lines', () => { - it('discards them', () => { - expect(formatAsLiquibaseRemarks('First line\n \nSecond line\n\nThird line')).toEqual('First line Second line Third line'); - }); - }); - describe('when having a plain text', () => { - it('puts a space before each line', () => { - expect(formatAsLiquibaseRemarks('JHipster is\na great generator')).toEqual('JHipster is a great generator'); - }); - }); - describe('when having ampersand', () => { - it('formats the text to escape it', () => { - expect(formatAsLiquibaseRemarks('JHipster uses Spring & Hibernate')).toEqual('JHipster uses Spring & Hibernate'); - }); - }); - describe('when having quotes', () => { - it('formats the text to escape it', () => { - expect(formatAsLiquibaseRemarks('JHipster is "the" best')).toEqual('JHipster is "the" best'); - }); - }); - describe('when having apostrophe', () => { - it('formats the text to escape it', () => { - expect(formatAsLiquibaseRemarks("JHipster is 'the' best")).toEqual('JHipster is 'the' best'); - }); - }); - describe('when having HTML tags < and >', () => { - it('formats the text to escape it', () => { - expect(formatAsLiquibaseRemarks('Not boldy\nboldy')).toEqual('Not boldy<b>boldy</b>'); - }); - }); - }); - }); -}); diff --git a/generators/liquibase/support/formatting.spec.ts b/generators/liquibase/support/formatting.spec.ts new file mode 100644 index 000000000000..db772f16fa1d --- /dev/null +++ b/generators/liquibase/support/formatting.spec.ts @@ -0,0 +1,50 @@ +import { expect } from 'esmocha'; +import formatAsLiquibaseRemarks from './formatting.js'; + +describe('generator - liquibase - support - formatting', () => { + describe('formatAsLiquibaseRemarks', () => { + describe('when formatting a nil text', () => { + it('returns it', () => { + // @ts-expect-error + expect(formatAsLiquibaseRemarks()).toEqual(undefined); + }); + }); + describe('when formatting an empty text', () => { + it('returns it', () => { + expect(formatAsLiquibaseRemarks('')).toEqual(''); + }); + }); + describe('when formatting normal texts', () => { + describe('when having empty lines', () => { + it('discards them', () => { + expect(formatAsLiquibaseRemarks('First line\n \nSecond line\n\nThird line')).toEqual('First line Second line Third line'); + }); + }); + describe('when having a plain text', () => { + it('puts a space before each line', () => { + expect(formatAsLiquibaseRemarks('JHipster is\na great generator')).toEqual('JHipster is a great generator'); + }); + }); + describe('when having ampersand', () => { + it('formats the text to escape it', () => { + expect(formatAsLiquibaseRemarks('JHipster uses Spring & Hibernate')).toEqual('JHipster uses Spring & Hibernate'); + }); + }); + describe('when having quotes', () => { + it('formats the text to escape it', () => { + expect(formatAsLiquibaseRemarks('JHipster is "the" best')).toEqual('JHipster is "the" best'); + }); + }); + describe('when having apostrophe', () => { + it('formats the text to escape it', () => { + expect(formatAsLiquibaseRemarks("JHipster is 'the' best")).toEqual('JHipster is 'the' best'); + }); + }); + describe('when having HTML tags < and >', () => { + it('formats the text to escape it', () => { + expect(formatAsLiquibaseRemarks('Not boldy\nboldy')).toEqual('Not boldy<b>boldy</b>'); + }); + }); + }); + }); +}); diff --git a/generators/liquibase/support/index.mts b/generators/liquibase/support/index.mts deleted file mode 100644 index 0f2a4c394158..000000000000 --- a/generators/liquibase/support/index.mts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2023 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 as liquibaseComment } from './formatting.mjs'; -export { default as postPrepareEntity } from './post-prepare-entity.mjs'; -export { default as prepareField } from './prepare-field.mjs'; -export * from './relationship.mjs'; diff --git a/generators/liquibase/support/index.ts b/generators/liquibase/support/index.ts new file mode 100644 index 000000000000..cbec397a1059 --- /dev/null +++ b/generators/liquibase/support/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2013-2023 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 as liquibaseComment } from './formatting.js'; +export { default as postPrepareEntity } from './post-prepare-entity.js'; +export { default as prepareField } from './prepare-field.js'; +export * from './relationship.js'; diff --git a/generators/liquibase/support/maven-plugin.mts b/generators/liquibase/support/maven-plugin.ts similarity index 100% rename from generators/liquibase/support/maven-plugin.mts rename to generators/liquibase/support/maven-plugin.ts diff --git a/generators/liquibase/support/post-prepare-entity.mts b/generators/liquibase/support/post-prepare-entity.mts deleted file mode 100644 index 4ae07564e59f..000000000000 --- a/generators/liquibase/support/post-prepare-entity.mts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.mjs'; -import { LiquibaseEntity } from '../types.mjs'; -import { GeneratorDefinition } from '../../base-application/generator.mjs'; - -const { CommonDBTypes } = fieldTypes; -const { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER } = CommonDBTypes; - -export default function postPrepareEntity({ - application, - entity, -}: Pick) { - const { relationships, builtIn, name, primaryKey } = entity; - if (builtIn && name === 'User') { - const userIdType = primaryKey.type; - const idField = primaryKey.fields[0]; - const idFieldName = idField.fieldName ?? 'id'; - const liquibaseFakeData = application.generateUserManagement - ? [ - { [idFieldName]: [TYPE_INTEGER, TYPE_LONG].includes(userIdType) ? 1 : idField.generateFakeData() }, - { [idFieldName]: [TYPE_INTEGER, TYPE_LONG].includes(userIdType) ? 2 : idField.generateFakeData() }, - ] - : []; - (entity as LiquibaseEntity).liquibaseFakeData = liquibaseFakeData; - (entity as LiquibaseEntity).fakeDataCount = liquibaseFakeData.length; - } - - (entity as LiquibaseEntity).anyRelationshipIsOwnerSide = relationships.some(relationship => relationship.ownerSide); -} diff --git a/generators/liquibase/support/post-prepare-entity.ts b/generators/liquibase/support/post-prepare-entity.ts new file mode 100644 index 000000000000..b2fd1cda96dc --- /dev/null +++ b/generators/liquibase/support/post-prepare-entity.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2013-2023 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 { fieldTypes } from '../../../jdl/jhipster/index.js'; +import { LiquibaseEntity } from '../types.js'; +import { GeneratorDefinition } from '../../base-application/generator.js'; + +const { CommonDBTypes } = fieldTypes; +const { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER } = CommonDBTypes; + +export default function postPrepareEntity({ + application, + entity, +}: Pick) { + const { relationships, builtIn, name, primaryKey } = entity; + if (builtIn && name === 'User') { + const userIdType = primaryKey.type; + const idField = primaryKey.fields[0]; + const idFieldName = idField.fieldName ?? 'id'; + const liquibaseFakeData = application.generateUserManagement + ? [ + { [idFieldName]: [TYPE_INTEGER, TYPE_LONG].includes(userIdType) ? 1 : idField.generateFakeData() }, + { [idFieldName]: [TYPE_INTEGER, TYPE_LONG].includes(userIdType) ? 2 : idField.generateFakeData() }, + ] + : []; + (entity as LiquibaseEntity).liquibaseFakeData = liquibaseFakeData; + (entity as LiquibaseEntity).fakeDataCount = liquibaseFakeData.length; + } + + (entity as LiquibaseEntity).anyRelationshipIsOwnerSide = relationships.some(relationship => relationship.ownerSide); +} diff --git a/generators/liquibase/support/prepare-field.js b/generators/liquibase/support/prepare-field.js new file mode 100644 index 000000000000..81e891995afd --- /dev/null +++ b/generators/liquibase/support/prepare-field.js @@ -0,0 +1,153 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; + +import { databaseTypes, fieldTypes } from '../../../jdl/jhipster/index.js'; + +const { MYSQL, MARIADB } = databaseTypes; +const { CommonDBTypes, RelationalOnlyDBTypes, BlobTypes } = fieldTypes; + +const { STRING, INTEGER, LONG, BIG_DECIMAL, FLOAT, DOUBLE, UUID, BOOLEAN, LOCAL_DATE, ZONED_DATE_TIME, INSTANT, DURATION } = CommonDBTypes; +const { BYTES } = RelationalOnlyDBTypes; +const { TEXT } = BlobTypes; + +function parseLiquibaseColumnType(entity, field) { + const fieldType = field.fieldType; + if (fieldType === STRING || field.fieldIsEnum) { + return `varchar(${field.fieldValidateRulesMaxlength || 255})`; + } + + if (fieldType === INTEGER) { + return 'integer'; + } + + if (fieldType === LONG) { + return 'bigint'; + } + + if (fieldType === FLOAT) { + // eslint-disable-next-line no-template-curly-in-string + return '${floatType}'; + } + + if (fieldType === DOUBLE) { + return 'double'; + } + + if (fieldType === BIG_DECIMAL) { + return 'decimal(21,2)'; + } + + if (fieldType === LOCAL_DATE) { + return 'date'; + } + + if (fieldType === INSTANT) { + // eslint-disable-next-line no-template-curly-in-string + return '${datetimeType}'; + } + + if (fieldType === ZONED_DATE_TIME) { + // eslint-disable-next-line no-template-curly-in-string + return '${datetimeType}'; + } + + if (fieldType === DURATION) { + return 'bigint'; + } + + if (fieldType === UUID) { + // eslint-disable-next-line no-template-curly-in-string + return '${uuidType}'; + } + + if (fieldType === BYTES && field.fieldTypeBlobContent !== TEXT) { + // eslint-disable-next-line no-template-curly-in-string + return '${blobType}'; + } + + if (field.fieldTypeBlobContent === TEXT) { + // eslint-disable-next-line no-template-curly-in-string + return '${clobType}'; + } + + if (fieldType === BOOLEAN) { + return 'boolean'; + } + + return undefined; +} + +function parseLiquibaseLoadColumnType(entity, field) { + const columnType = field.columnType; + // eslint-disable-next-line no-template-curly-in-string + if (['integer', 'bigint', 'double', 'decimal(21,2)', '${floatType}'].includes(columnType)) { + return 'numeric'; + } + + if (field.fieldIsEnum) { + return 'string'; + } + + // eslint-disable-next-line no-template-curly-in-string + if (['date', '${datetimeType}'].includes(columnType)) { + return 'date'; + } + + if (columnType === 'boolean') { + return columnType; + } + + // eslint-disable-next-line no-template-curly-in-string + if (columnType === '${blobType}') { + return 'blob'; + } + + // eslint-disable-next-line no-template-curly-in-string + if (columnType === '${clobType}') { + return 'clob'; + } + + const { prodDatabaseType } = entity; + if ( + // eslint-disable-next-line no-template-curly-in-string + columnType === '${uuidType}' && + prodDatabaseType !== MYSQL && + prodDatabaseType !== MARIADB + ) { + // eslint-disable-next-line no-template-curly-in-string + return '${uuidType}'; + } + + return 'string'; +} + +export default function prepareField(entity, field) { + _.defaults(field, { + columnType: parseLiquibaseColumnType(entity, field), + shouldDropDefaultValue: field.fieldType === ZONED_DATE_TIME || field.fieldType === INSTANT, + shouldCreateContentType: field.fieldType === BYTES && field.fieldTypeBlobContent !== TEXT, + nullable: !(field.fieldValidate === true && field.fieldValidateRules.includes('required')), + }); + _.defaults(field, { + loadColumnType: parseLiquibaseLoadColumnType(entity, field), + }); + return field; +} diff --git a/generators/liquibase/support/prepare-field.mjs b/generators/liquibase/support/prepare-field.mjs deleted file mode 100644 index 217d0792d86c..000000000000 --- a/generators/liquibase/support/prepare-field.mjs +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; - -import { databaseTypes, fieldTypes } from '../../../jdl/jhipster/index.mjs'; - -const { MYSQL, MARIADB } = databaseTypes; -const { CommonDBTypes, RelationalOnlyDBTypes, BlobTypes } = fieldTypes; - -const { STRING, INTEGER, LONG, BIG_DECIMAL, FLOAT, DOUBLE, UUID, BOOLEAN, LOCAL_DATE, ZONED_DATE_TIME, INSTANT, DURATION } = CommonDBTypes; -const { BYTES } = RelationalOnlyDBTypes; -const { TEXT } = BlobTypes; - -function parseLiquibaseColumnType(entity, field) { - const fieldType = field.fieldType; - if (fieldType === STRING || field.fieldIsEnum) { - return `varchar(${field.fieldValidateRulesMaxlength || 255})`; - } - - if (fieldType === INTEGER) { - return 'integer'; - } - - if (fieldType === LONG) { - return 'bigint'; - } - - if (fieldType === FLOAT) { - // eslint-disable-next-line no-template-curly-in-string - return '${floatType}'; - } - - if (fieldType === DOUBLE) { - return 'double'; - } - - if (fieldType === BIG_DECIMAL) { - return 'decimal(21,2)'; - } - - if (fieldType === LOCAL_DATE) { - return 'date'; - } - - if (fieldType === INSTANT) { - // eslint-disable-next-line no-template-curly-in-string - return '${datetimeType}'; - } - - if (fieldType === ZONED_DATE_TIME) { - // eslint-disable-next-line no-template-curly-in-string - return '${datetimeType}'; - } - - if (fieldType === DURATION) { - return 'bigint'; - } - - if (fieldType === UUID) { - // eslint-disable-next-line no-template-curly-in-string - return '${uuidType}'; - } - - if (fieldType === BYTES && field.fieldTypeBlobContent !== TEXT) { - // eslint-disable-next-line no-template-curly-in-string - return '${blobType}'; - } - - if (field.fieldTypeBlobContent === TEXT) { - // eslint-disable-next-line no-template-curly-in-string - return '${clobType}'; - } - - if (fieldType === BOOLEAN) { - return 'boolean'; - } - - return undefined; -} - -function parseLiquibaseLoadColumnType(entity, field) { - const columnType = field.columnType; - // eslint-disable-next-line no-template-curly-in-string - if (['integer', 'bigint', 'double', 'decimal(21,2)', '${floatType}'].includes(columnType)) { - return 'numeric'; - } - - if (field.fieldIsEnum) { - return 'string'; - } - - // eslint-disable-next-line no-template-curly-in-string - if (['date', '${datetimeType}'].includes(columnType)) { - return 'date'; - } - - if (columnType === 'boolean') { - return columnType; - } - - // eslint-disable-next-line no-template-curly-in-string - if (columnType === '${blobType}') { - return 'blob'; - } - - // eslint-disable-next-line no-template-curly-in-string - if (columnType === '${clobType}') { - return 'clob'; - } - - const { prodDatabaseType } = entity; - if ( - // eslint-disable-next-line no-template-curly-in-string - columnType === '${uuidType}' && - prodDatabaseType !== MYSQL && - prodDatabaseType !== MARIADB - ) { - // eslint-disable-next-line no-template-curly-in-string - return '${uuidType}'; - } - - return 'string'; -} - -export default function prepareField(entity, field) { - _.defaults(field, { - columnType: parseLiquibaseColumnType(entity, field), - shouldDropDefaultValue: field.fieldType === ZONED_DATE_TIME || field.fieldType === INSTANT, - shouldCreateContentType: field.fieldType === BYTES && field.fieldTypeBlobContent !== TEXT, - nullable: !(field.fieldValidate === true && field.fieldValidateRules.includes('required')), - }); - _.defaults(field, { - loadColumnType: parseLiquibaseLoadColumnType(entity, field), - }); - return field; -} diff --git a/generators/liquibase/support/relationship.mts b/generators/liquibase/support/relationship.mts deleted file mode 100644 index 276c51984e34..000000000000 --- a/generators/liquibase/support/relationship.mts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { getFKConstraintName } from '../../server/support/index.mjs'; - -function relationshipBaseDataEquals(relationshipA, relationshipB) { - return ( - // name is the same - relationshipA.relationshipName === relationshipB.relationshipName && - relationshipA.relationshipType === relationshipB.relationshipType && - // related entities same - relationshipA.entityName === relationshipB.entityName && - relationshipA.otherEntityName === relationshipB.otherEntityName - ); -} - -/** - * Whether the two relationships are absolutely equal - * @param relationshipA - * @param relationshipB - * @returns - */ -export function relationshipEquals(relationshipA, relationshipB) { - return ( - relationshipBaseDataEquals(relationshipA, relationshipB) && - // relevant options the very same - relationshipA.options?.onDelete === relationshipB.options?.onDelete && - relationshipA.options?.onUpdate === relationshipB.options?.onUpdate - ); -} - -/** - * Whether the two relationships are equal, except for the foreign key on handlers, indicating that foreign key recreation is sufficient - * @param relationshipA - * @param relationshipB - * @returns - */ -export function relationshipNeedsForeignKeyRecreationOnly(relationshipA, relationshipB) { - return ( - relationshipBaseDataEquals(relationshipA, relationshipB) && - (relationshipA.options?.onDelete !== relationshipB.options?.onDelete || - relationshipA.options?.onUpdate !== relationshipB.options?.onUpdate) - ); -} - -export function prepareRelationshipForLiquibase(entity, relationship) { - relationship.shouldWriteRelationship = - relationship.relationshipType === 'many-to-one' || (relationship.relationshipType === 'one-to-one' && relationship.ownerSide === true); - - if (relationship.shouldWriteJoinTable) { - const joinTableName = relationship.joinTable.name; - const prodDatabaseType = entity.prodDatabaseType; - _.defaults(relationship.joinTable, { - constraintName: getFKConstraintName(joinTableName, entity.entityTableName, { prodDatabaseType }).value, - otherConstraintName: getFKConstraintName(joinTableName, relationship.columnName, { prodDatabaseType }).value, - }); - } - - relationship.columnDataType = relationship.otherEntity.columnType; - return relationship; -} diff --git a/generators/liquibase/support/relationship.ts b/generators/liquibase/support/relationship.ts new file mode 100644 index 000000000000..ce925dfde8d7 --- /dev/null +++ b/generators/liquibase/support/relationship.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { getFKConstraintName } from '../../server/support/index.js'; + +function relationshipBaseDataEquals(relationshipA, relationshipB) { + return ( + // name is the same + relationshipA.relationshipName === relationshipB.relationshipName && + relationshipA.relationshipType === relationshipB.relationshipType && + // related entities same + relationshipA.entityName === relationshipB.entityName && + relationshipA.otherEntityName === relationshipB.otherEntityName + ); +} + +/** + * Whether the two relationships are absolutely equal + * @param relationshipA + * @param relationshipB + * @returns + */ +export function relationshipEquals(relationshipA, relationshipB) { + return ( + relationshipBaseDataEquals(relationshipA, relationshipB) && + // relevant options the very same + relationshipA.options?.onDelete === relationshipB.options?.onDelete && + relationshipA.options?.onUpdate === relationshipB.options?.onUpdate + ); +} + +/** + * Whether the two relationships are equal, except for the foreign key on handlers, indicating that foreign key recreation is sufficient + * @param relationshipA + * @param relationshipB + * @returns + */ +export function relationshipNeedsForeignKeyRecreationOnly(relationshipA, relationshipB) { + return ( + relationshipBaseDataEquals(relationshipA, relationshipB) && + (relationshipA.options?.onDelete !== relationshipB.options?.onDelete || + relationshipA.options?.onUpdate !== relationshipB.options?.onUpdate) + ); +} + +export function prepareRelationshipForLiquibase(entity, relationship) { + relationship.shouldWriteRelationship = + relationship.relationshipType === 'many-to-one' || (relationship.relationshipType === 'one-to-one' && relationship.ownerSide === true); + + if (relationship.shouldWriteJoinTable) { + const joinTableName = relationship.joinTable.name; + const prodDatabaseType = entity.prodDatabaseType; + _.defaults(relationship.joinTable, { + constraintName: getFKConstraintName(joinTableName, entity.entityTableName, { prodDatabaseType }).value, + otherConstraintName: getFKConstraintName(joinTableName, relationship.columnName, { prodDatabaseType }).value, + }); + } + + relationship.columnDataType = relationship.otherEntity.columnType; + return relationship; +} diff --git a/generators/liquibase/types.d.mts b/generators/liquibase/types.d.mts deleted file mode 100644 index a9185450f8a7..000000000000 --- a/generators/liquibase/types.d.mts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Entity } from '../base-application/index.mjs'; - -export type LiquibaseChangelog = { changelogName: string }; -export type LiquibaseChangelogSection = LiquibaseChangelog & { section?: 'base' | 'incremental' | 'constraints' }; - -export type LiquibaseSourceType = { - addLiquibaseChangelog?(changelog: LiquibaseChangelogSection): void; - addLiquibaseIncrementalChangelog?(changelog: LiquibaseChangelog): void; - addLiquibaseConstraintsChangelog?(changelog: LiquibaseChangelog): void; -}; - -export type LiquibaseEntity = Entity & { - anyRelationshipIsOwnerSide: boolean; - liquibaseFakeData: Record[]; - fakeDataCount: number; -}; diff --git a/generators/liquibase/types.d.ts b/generators/liquibase/types.d.ts new file mode 100644 index 000000000000..a02b481c5bd6 --- /dev/null +++ b/generators/liquibase/types.d.ts @@ -0,0 +1,16 @@ +import type { Entity } from '../base-application/index.js'; + +export type LiquibaseChangelog = { changelogName: string }; +export type LiquibaseChangelogSection = LiquibaseChangelog & { section?: 'base' | 'incremental' | 'constraints' }; + +export type LiquibaseSourceType = { + addLiquibaseChangelog?(changelog: LiquibaseChangelogSection): void; + addLiquibaseIncrementalChangelog?(changelog: LiquibaseChangelog): void; + addLiquibaseConstraintsChangelog?(changelog: LiquibaseChangelog): void; +}; + +export type LiquibaseEntity = Entity & { + anyRelationshipIsOwnerSide: boolean; + liquibaseFakeData: Record[]; + fakeDataCount: number; +}; diff --git a/generators/maven/__snapshots__/generator.spec.mts.snap b/generators/maven/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/maven/__snapshots__/generator.spec.mts.snap rename to generators/maven/__snapshots__/generator.spec.ts.snap diff --git a/generators/maven/__snapshots__/needles.spec.mts.snap b/generators/maven/__snapshots__/needles.spec.ts.snap similarity index 100% rename from generators/maven/__snapshots__/needles.spec.mts.snap rename to generators/maven/__snapshots__/needles.spec.ts.snap diff --git a/generators/maven/cleanup.mts b/generators/maven/cleanup.mts deleted file mode 100644 index b5ad2cbd2e6e..000000000000 --- a/generators/maven/cleanup.mts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2013-2023 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 CoreGenerator from '../base-core/index.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupOldServerFilesTask(this: CoreGenerator) { - if (this.isJhipsterVersionLessThan('7.7.1')) { - this.removeFile('.mvn/wrapper/MavenWrapperDownloader.java'); - } -} diff --git a/generators/maven/cleanup.ts b/generators/maven/cleanup.ts new file mode 100644 index 000000000000..0bfd04417a62 --- /dev/null +++ b/generators/maven/cleanup.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2013-2023 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 CoreGenerator from '../base-core/index.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupOldServerFilesTask(this: CoreGenerator) { + if (this.isJhipsterVersionLessThan('7.7.1')) { + this.removeFile('.mvn/wrapper/MavenWrapperDownloader.java'); + } +} diff --git a/generators/maven/constants.mjs b/generators/maven/constants.js similarity index 100% rename from generators/maven/constants.mjs rename to generators/maven/constants.js diff --git a/generators/maven/files.mjs b/generators/maven/files.js similarity index 100% rename from generators/maven/files.mjs rename to generators/maven/files.js diff --git a/generators/maven/generator.mts b/generators/maven/generator.mts deleted file mode 100644 index 6c529d31fff0..000000000000 --- a/generators/maven/generator.mts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import assert from 'assert/strict'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; - -import { GENERATOR_MAVEN, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.mjs'; -import files from './files.mjs'; -import { MAVEN } from './constants.mjs'; -import cleanupOldServerFilesTask from './cleanup.mjs'; -import { type GeneratorDefinition as SpringBootGeneratorDefinition } from '../server/index.mjs'; -import { createPomStorage, type PomStorage } from './support/index.mjs'; - -export default class MavenGenerator extends BaseApplicationGenerator { - pomStorage!: PomStorage; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_MAVEN); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - pomStorage() { - this.pomStorage = createPomStorage(this); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get configuring() { - return this.asConfiguringTaskGroup({ - configure() { - if (this.jhipsterConfigWithDefaults.buildTool !== MAVEN) { - this.config.defaults({ - buildTool: MAVEN, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get preparing() { - return this.asPreparingTaskGroup({ - async verify({ application }) { - assert.equal(application.buildTool, MAVEN); - }, - addSourceNeddles({ source }) { - function createForEach(callback: (arg: T) => any): (arg: T | T[]) => void { - return (arg: T | T[]): void => { - const argArray = Array.isArray(arg) ? arg : [arg]; - for (const item of argArray) { - callback(item); - } - }; - } - source.addMavenAnnotationProcessor = createForEach(artifact => this.pomStorage.addAnnotationProcessor(artifact)); - source.addMavenDependency = createForEach(artifact => this.pomStorage.addDependency(artifact)); - source.addMavenDependencyManagement = createForEach(artifact => this.pomStorage.addDependencyManagement(artifact)); - source.addMavenDistributionManagement = createForEach(artifact => this.pomStorage.addDistributionManagement(artifact)); - source.addMavenPlugin = createForEach(plugin => this.pomStorage.addPlugin(plugin)); - source.addMavenPluginManagement = createForEach(plugin => this.pomStorage.addPluginManagement(plugin)); - source.addMavenPluginRepository = createForEach(repository => this.pomStorage.addPluginRepository(repository)); - source.addMavenProfile = createForEach(profile => this.pomStorage.addProfile(profile)); - source.addMavenProperty = createForEach(property => this.pomStorage.addProperty(property)); - source.addMavenRepository = createForEach(repository => this.pomStorage.addRepository(repository)); - - source.addMavenDefinition = definition => { - // profiles should be added first due to inProfile - definition.profiles?.forEach(profile => this.pomStorage.addProfile(profile)); - // annotationProcessors may depend on pluginManagement - definition.pluginManagement?.forEach(plugin => this.pomStorage.addPluginManagement(plugin)); - - definition.dependencies?.forEach(dependency => this.pomStorage.addDependency(dependency)); - definition.dependencyManagement?.forEach(dependency => this.pomStorage.addDependencyManagement(dependency)); - definition.distributionManagement?.forEach(distribution => this.pomStorage.addDistributionManagement(distribution)); - definition.plugins?.forEach(plugin => this.pomStorage.addPlugin(plugin)); - definition.pluginRepositories?.forEach(repository => this.pomStorage.addPluginRepository(repository)); - definition.properties?.forEach(property => this.pomStorage.addProperty(property)); - definition.repositories?.forEach(repository => this.pomStorage.addRepository(repository)); - definition.annotationProcessors?.forEach(annotation => this.pomStorage.addAnnotationProcessor(annotation)); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupOldServerFilesTask, - async writeFiles({ application }) { - await this.writeFiles({ sections: files, context: application }); - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - sortPom() { - this.pomStorage.save(); - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/maven/generator.spec.mts b/generators/maven/generator.spec.mts deleted file mode 100644 index 44086664f3c3..000000000000 --- a/generators/maven/generator.spec.mts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import { GENERATOR_MAVEN } from '../generator-list.mjs'; -import MavenGenerator from './generator.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', () => { - expect(GENERATOR_MAVEN).toBe(generator); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - describe('with valid configuration', () => { - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig({ - baseName: 'existing', - packageName: 'tech.jhipster', - }); - }); - it('should generate only maven files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - describe('with empty configuration', () => { - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig(); - }); - it('should generate only maven files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - - describe('needles', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_MAVEN) - .withJHipsterConfig({ - blueprints: [{ name: 'blueprint' }], - }) - .withGenerators([ - [ - class extends MavenGenerator { - constructor(args, options, features) { - super(args, options, { ...features, sbsBlueprint: true }); - } - - get [MavenGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - addProperty({ source }) { - source.addMavenDependency?.({ groupId: 'group', artifactId: 'artifact', version: 'initial' }); - source.addMavenDependency?.({ groupId: 'group', artifactId: 'artifact' }); - source.addMavenDependency?.({ - groupId: 'group', - artifactId: 'artifact2', - additionalContent: ` - - - exclusionGroupId - exclusionArtifactId - -`, - }); - source.addMavenProperty?.({ property: 'foo', value: 'initial' }); - source.addMavenProperty?.({ property: 'foo', value: 'bar' }); - source.addMavenProperty?.({ property: 'foo2', value: 'bar2' }); - source.addMavenProfile?.({ id: 'profileId', content: 'initial' }); - source.addMavenProfile?.({ id: 'profileId', content: 'bar' }); - source.addMavenProfile?.({ id: 'profileId2', content: 'bar2' }); - }, - }); - } - }, - 'jhipster-blueprint:maven', - ], - ]); - }); - it('should add properties', () => { - runResult.assertFileContent('pom.xml', 'bar'); - runResult.assertFileContent('pom.xml', 'bar2'); - }); - it('should add dependencies', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - group - artifact - -`, - ); - runResult.assertFileContent( - 'pom.xml', - ` - - group - artifact2 - - - exclusionGroupId - exclusionArtifactId - - - -`, - ); - }); - it('should add profiles', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - profileId - bar - -`, - ); - runResult.assertFileContent( - 'pom.xml', - ` - - profileId2 - bar2 - -`, - ); - }); - it('should match generated pom', () => { - expect(runResult.getSnapshot('**/pom.xml')).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/maven/generator.spec.ts b/generators/maven/generator.spec.ts new file mode 100644 index 000000000000..a2df7e71717d --- /dev/null +++ b/generators/maven/generator.spec.ts @@ -0,0 +1,156 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import { GENERATOR_MAVEN } from '../generator-list.js'; +import MavenGenerator from './generator.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', () => { + expect(GENERATOR_MAVEN).toBe(generator); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + describe('with valid configuration', () => { + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig({ + baseName: 'existing', + packageName: 'tech.jhipster', + }); + }); + it('should generate only maven files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + describe('with empty configuration', () => { + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig(); + }); + it('should generate only maven files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + + describe('needles', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_MAVEN) + .withJHipsterConfig({ + blueprints: [{ name: 'blueprint' }], + }) + .withGenerators([ + [ + class extends MavenGenerator { + constructor(args, options, features) { + super(args, options, { ...features, sbsBlueprint: true }); + } + + get [MavenGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + addProperty({ source }) { + source.addMavenDependency?.({ groupId: 'group', artifactId: 'artifact', version: 'initial' }); + source.addMavenDependency?.({ groupId: 'group', artifactId: 'artifact' }); + source.addMavenDependency?.({ + groupId: 'group', + artifactId: 'artifact2', + additionalContent: ` + + + exclusionGroupId + exclusionArtifactId + +`, + }); + source.addMavenProperty?.({ property: 'foo', value: 'initial' }); + source.addMavenProperty?.({ property: 'foo', value: 'bar' }); + source.addMavenProperty?.({ property: 'foo2', value: 'bar2' }); + source.addMavenProfile?.({ id: 'profileId', content: 'initial' }); + source.addMavenProfile?.({ id: 'profileId', content: 'bar' }); + source.addMavenProfile?.({ id: 'profileId2', content: 'bar2' }); + }, + }); + } + }, + 'jhipster-blueprint:maven', + ], + ]); + }); + it('should add properties', () => { + runResult.assertFileContent('pom.xml', 'bar'); + runResult.assertFileContent('pom.xml', 'bar2'); + }); + it('should add dependencies', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + group + artifact + +`, + ); + runResult.assertFileContent( + 'pom.xml', + ` + + group + artifact2 + + + exclusionGroupId + exclusionArtifactId + + + +`, + ); + }); + it('should add profiles', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + profileId + bar + +`, + ); + runResult.assertFileContent( + 'pom.xml', + ` + + profileId2 + bar2 + +`, + ); + }); + it('should match generated pom', () => { + expect(runResult.getSnapshot('**/pom.xml')).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/maven/generator.ts b/generators/maven/generator.ts new file mode 100644 index 000000000000..213fb1479a37 --- /dev/null +++ b/generators/maven/generator.ts @@ -0,0 +1,144 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import assert from 'assert/strict'; + +import BaseApplicationGenerator from '../base-application/index.js'; + +import { GENERATOR_MAVEN, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.js'; +import files from './files.js'; +import { MAVEN } from './constants.js'; +import cleanupOldServerFilesTask from './cleanup.js'; +import { type GeneratorDefinition as SpringBootGeneratorDefinition } from '../server/index.js'; +import { createPomStorage, type PomStorage } from './support/index.js'; + +export default class MavenGenerator extends BaseApplicationGenerator { + pomStorage!: PomStorage; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_MAVEN); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + pomStorage() { + this.pomStorage = createPomStorage(this); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get configuring() { + return this.asConfiguringTaskGroup({ + configure() { + if (this.jhipsterConfigWithDefaults.buildTool !== MAVEN) { + this.config.defaults({ + buildTool: MAVEN, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get preparing() { + return this.asPreparingTaskGroup({ + async verify({ application }) { + assert.equal(application.buildTool, MAVEN); + }, + addSourceNeddles({ source }) { + function createForEach(callback: (arg: T) => any): (arg: T | T[]) => void { + return (arg: T | T[]): void => { + const argArray = Array.isArray(arg) ? arg : [arg]; + for (const item of argArray) { + callback(item); + } + }; + } + source.addMavenAnnotationProcessor = createForEach(artifact => this.pomStorage.addAnnotationProcessor(artifact)); + source.addMavenDependency = createForEach(artifact => this.pomStorage.addDependency(artifact)); + source.addMavenDependencyManagement = createForEach(artifact => this.pomStorage.addDependencyManagement(artifact)); + source.addMavenDistributionManagement = createForEach(artifact => this.pomStorage.addDistributionManagement(artifact)); + source.addMavenPlugin = createForEach(plugin => this.pomStorage.addPlugin(plugin)); + source.addMavenPluginManagement = createForEach(plugin => this.pomStorage.addPluginManagement(plugin)); + source.addMavenPluginRepository = createForEach(repository => this.pomStorage.addPluginRepository(repository)); + source.addMavenProfile = createForEach(profile => this.pomStorage.addProfile(profile)); + source.addMavenProperty = createForEach(property => this.pomStorage.addProperty(property)); + source.addMavenRepository = createForEach(repository => this.pomStorage.addRepository(repository)); + + source.addMavenDefinition = definition => { + // profiles should be added first due to inProfile + definition.profiles?.forEach(profile => this.pomStorage.addProfile(profile)); + // annotationProcessors may depend on pluginManagement + definition.pluginManagement?.forEach(plugin => this.pomStorage.addPluginManagement(plugin)); + + definition.dependencies?.forEach(dependency => this.pomStorage.addDependency(dependency)); + definition.dependencyManagement?.forEach(dependency => this.pomStorage.addDependencyManagement(dependency)); + definition.distributionManagement?.forEach(distribution => this.pomStorage.addDistributionManagement(distribution)); + definition.plugins?.forEach(plugin => this.pomStorage.addPlugin(plugin)); + definition.pluginRepositories?.forEach(repository => this.pomStorage.addPluginRepository(repository)); + definition.properties?.forEach(property => this.pomStorage.addProperty(property)); + definition.repositories?.forEach(repository => this.pomStorage.addRepository(repository)); + definition.annotationProcessors?.forEach(annotation => this.pomStorage.addAnnotationProcessor(annotation)); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupOldServerFilesTask, + async writeFiles({ application }) { + await this.writeFiles({ sections: files, context: application }); + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + sortPom() { + this.pomStorage.save(); + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/maven/index.mts b/generators/maven/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/maven/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/maven/index.ts b/generators/maven/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/maven/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/maven/internal/xml-store.mts b/generators/maven/internal/xml-store.ts similarity index 100% rename from generators/maven/internal/xml-store.mts rename to generators/maven/internal/xml-store.ts diff --git a/generators/maven/needles.spec.mts b/generators/maven/needles.spec.mts deleted file mode 100644 index ea5600b22329..000000000000 --- a/generators/maven/needles.spec.mts +++ /dev/null @@ -1,368 +0,0 @@ -import { expect } from 'esmocha'; - -import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_MAVEN } from '../generator-list.mjs'; - -class mockBlueprintSubGen extends BaseApplicationGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - mavenStep({ source }) { - const inProfile = this.options.profile; - function asItemOrArray(item: T): T | T[] { - return inProfile ? [item] : item; - } - source.addMavenDependencyManagement?.( - asItemOrArray({ - inProfile, - groupId: 'dependencyManagementGroupId', - artifactId: 'dependencyManagementArtifactId', - version: 'version', - type: 'type', - scope: 'scope', - additionalContent: ` - - - exclusionGroupId - exclusionArtifactId - - `, - }), - ); - - source.addMavenDistributionManagement?.( - asItemOrArray({ - inProfile, - snapshotsId: 'snapshotsId', - snapshotsUrl: 'snapshotsUrl', - releasesId: 'releasesId', - releasesUrl: 'releasesUrl', - }), - ); - source.addMavenProperty?.(asItemOrArray({ inProfile, property: 'propertyName.dotted', value: 'propertyValue' })); - source.addMavenDependency?.( - asItemOrArray({ - inProfile, - groupId: 'dependencyGroupId', - artifactId: 'dependencyArtifactId', - version: 'version', - additionalContent: ` - - - exclusionGroupId - exclusionArtifactId - - `, - }), - ); - source.addMavenPlugin?.( - asItemOrArray({ - inProfile, - groupId: 'mavenPluginGroupId', - artifactId: 'mavenPluginArtifactId', - version: 'version', - additionalContent: ` - - - exclusionGroupId - exclusionArtifactId - - `, - }), - ); - source.addMavenPluginManagement?.( - asItemOrArray({ - inProfile, - groupId: 'mavenPluginManagementGroupId', - artifactId: 'mavenPluginManagementArtifactId', - version: 'version', - additionalContent: ` - - exclusionGroupId - exclusionArtifactId - - `, - }), - ); - source.addMavenAnnotationProcessor?.( - asItemOrArray({ - inProfile, - groupId: 'annotationProcessorGroupId', - artifactId: 'annotationProcessorArtifactId', - version: 'annotationProcessorVersion', - }), - ); - source.addMavenProfile?.(asItemOrArray({ id: 'profileId', content: ' other' })); - source.addMavenRepository?.( - asItemOrArray({ - id: 'repositoryId', - url: 'repositoryUrl', - releasesEnabled: true, - snapshotsEnabled: false, - }), - ); - source.addMavenPluginRepository?.( - asItemOrArray({ - id: 'repositoryId', - url: 'repositoryUrl', - }), - ); - }, - }); - } -} - -describe('generator - maven - needles', () => { - describe('no profile', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_MAVEN) - .withJHipsterConfig({ - blueprint: 'myblueprint', - clientFramework: 'no', - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:maven' }]]); - }); - - it('Assert pom.xml has the dependency management added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - dependencyManagementGroupId - dependencyManagementArtifactId - version - type - scope - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the distributionManagement added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - snapshotsId - snapshotsUrl - - - releasesId - releasesUrl - - `, - ); - }); - - it('Assert pom.xml has the property added', () => { - runResult.assertFileContent('pom.xml', 'propertyValue'); - }); - - it('Assert pom.xml has the dependency added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - dependencyGroupId - dependencyArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - it('Assert pom.xml has the maven plugin added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - mavenPluginGroupId - mavenPluginArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the maven plugin management added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - mavenPluginManagementGroupId - mavenPluginManagementArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the annotation processor added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - annotationProcessorGroupId - annotationProcessorArtifactId - annotationProcessorVersion - `, - ); - }); - - it('Assert pom.xml has the profile added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - profileId - other - `, - ); - }); - }); - describe('prod profile', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_MAVEN) - .withJHipsterConfig({ - blueprint: 'myblueprint', - clientFramework: 'no', - }) - .withOptions({ - profile: 'prod', - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:maven' }]]); - }); - - it('Assert pom.xml has the dependency management added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - dependencyManagementGroupId - dependencyManagementArtifactId - version - type - scope - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the distributionManagement added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - - snapshotsId - snapshotsUrl - - - releasesId - releasesUrl - - `, - ); - }); - - it('Assert pom.xml has the property added', () => { - runResult.assertFileContent('pom.xml', 'propertyValue'); - }); - - it('Assert pom.xml has the dependency added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - dependencyGroupId - dependencyArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - it('Assert pom.xml has the maven plugin added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - mavenPluginGroupId - mavenPluginArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the maven plugin management added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - mavenPluginManagementGroupId - mavenPluginManagementArtifactId - version - - - exclusionGroupId - exclusionArtifactId - - - `, - ); - }); - - it('Assert pom.xml has the annotation processor added', () => { - runResult.assertFileContent( - 'pom.xml', - ` - - annotationProcessorGroupId - annotationProcessorArtifactId - annotationProcessorVersion - `, - ); - }); - - it('should match generated pom', () => { - expect(runResult.getSnapshot('**/pom.xml')).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/maven/needles.spec.ts b/generators/maven/needles.spec.ts new file mode 100644 index 000000000000..53856bf41add --- /dev/null +++ b/generators/maven/needles.spec.ts @@ -0,0 +1,368 @@ +import { expect } from 'esmocha'; + +import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_MAVEN } from '../generator-list.js'; + +class mockBlueprintSubGen extends BaseApplicationGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + mavenStep({ source }) { + const inProfile = this.options.profile; + function asItemOrArray(item: T): T | T[] { + return inProfile ? [item] : item; + } + source.addMavenDependencyManagement?.( + asItemOrArray({ + inProfile, + groupId: 'dependencyManagementGroupId', + artifactId: 'dependencyManagementArtifactId', + version: 'version', + type: 'type', + scope: 'scope', + additionalContent: ` + + + exclusionGroupId + exclusionArtifactId + + `, + }), + ); + + source.addMavenDistributionManagement?.( + asItemOrArray({ + inProfile, + snapshotsId: 'snapshotsId', + snapshotsUrl: 'snapshotsUrl', + releasesId: 'releasesId', + releasesUrl: 'releasesUrl', + }), + ); + source.addMavenProperty?.(asItemOrArray({ inProfile, property: 'propertyName.dotted', value: 'propertyValue' })); + source.addMavenDependency?.( + asItemOrArray({ + inProfile, + groupId: 'dependencyGroupId', + artifactId: 'dependencyArtifactId', + version: 'version', + additionalContent: ` + + + exclusionGroupId + exclusionArtifactId + + `, + }), + ); + source.addMavenPlugin?.( + asItemOrArray({ + inProfile, + groupId: 'mavenPluginGroupId', + artifactId: 'mavenPluginArtifactId', + version: 'version', + additionalContent: ` + + + exclusionGroupId + exclusionArtifactId + + `, + }), + ); + source.addMavenPluginManagement?.( + asItemOrArray({ + inProfile, + groupId: 'mavenPluginManagementGroupId', + artifactId: 'mavenPluginManagementArtifactId', + version: 'version', + additionalContent: ` + + exclusionGroupId + exclusionArtifactId + + `, + }), + ); + source.addMavenAnnotationProcessor?.( + asItemOrArray({ + inProfile, + groupId: 'annotationProcessorGroupId', + artifactId: 'annotationProcessorArtifactId', + version: 'annotationProcessorVersion', + }), + ); + source.addMavenProfile?.(asItemOrArray({ id: 'profileId', content: ' other' })); + source.addMavenRepository?.( + asItemOrArray({ + id: 'repositoryId', + url: 'repositoryUrl', + releasesEnabled: true, + snapshotsEnabled: false, + }), + ); + source.addMavenPluginRepository?.( + asItemOrArray({ + id: 'repositoryId', + url: 'repositoryUrl', + }), + ); + }, + }); + } +} + +describe('generator - maven - needles', () => { + describe('no profile', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_MAVEN) + .withJHipsterConfig({ + blueprint: 'myblueprint', + clientFramework: 'no', + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:maven' }]]); + }); + + it('Assert pom.xml has the dependency management added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + dependencyManagementGroupId + dependencyManagementArtifactId + version + type + scope + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the distributionManagement added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + snapshotsId + snapshotsUrl + + + releasesId + releasesUrl + + `, + ); + }); + + it('Assert pom.xml has the property added', () => { + runResult.assertFileContent('pom.xml', 'propertyValue'); + }); + + it('Assert pom.xml has the dependency added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + dependencyGroupId + dependencyArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + it('Assert pom.xml has the maven plugin added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + mavenPluginGroupId + mavenPluginArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the maven plugin management added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + mavenPluginManagementGroupId + mavenPluginManagementArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the annotation processor added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + annotationProcessorGroupId + annotationProcessorArtifactId + annotationProcessorVersion + `, + ); + }); + + it('Assert pom.xml has the profile added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + profileId + other + `, + ); + }); + }); + describe('prod profile', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_MAVEN) + .withJHipsterConfig({ + blueprint: 'myblueprint', + clientFramework: 'no', + }) + .withOptions({ + profile: 'prod', + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:maven' }]]); + }); + + it('Assert pom.xml has the dependency management added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + dependencyManagementGroupId + dependencyManagementArtifactId + version + type + scope + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the distributionManagement added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + + snapshotsId + snapshotsUrl + + + releasesId + releasesUrl + + `, + ); + }); + + it('Assert pom.xml has the property added', () => { + runResult.assertFileContent('pom.xml', 'propertyValue'); + }); + + it('Assert pom.xml has the dependency added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + dependencyGroupId + dependencyArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + it('Assert pom.xml has the maven plugin added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + mavenPluginGroupId + mavenPluginArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the maven plugin management added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + mavenPluginManagementGroupId + mavenPluginManagementArtifactId + version + + + exclusionGroupId + exclusionArtifactId + + + `, + ); + }); + + it('Assert pom.xml has the annotation processor added', () => { + runResult.assertFileContent( + 'pom.xml', + ` + + annotationProcessorGroupId + annotationProcessorArtifactId + annotationProcessorVersion + `, + ); + }); + + it('should match generated pom', () => { + expect(runResult.getSnapshot('**/pom.xml')).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/maven/support/index.mts b/generators/maven/support/index.mts deleted file mode 100644 index 893587f01bf8..000000000000 --- a/generators/maven/support/index.mts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2013-2023 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 as PomStorage } from './pom-store.mjs'; -export * from './pom-store.mjs'; diff --git a/generators/maven/support/index.ts b/generators/maven/support/index.ts new file mode 100644 index 000000000000..98e00889cb60 --- /dev/null +++ b/generators/maven/support/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2013-2023 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 as PomStorage } from './pom-store.js'; +export * from './pom-store.js'; diff --git a/generators/maven/support/pom-store.mts b/generators/maven/support/pom-store.mts deleted file mode 100644 index cf6688dc33f3..000000000000 --- a/generators/maven/support/pom-store.mts +++ /dev/null @@ -1,352 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import sortKeys from 'sort-keys'; - -import CoreGenerator from '../../base-core/index.mjs'; -import XmlStorage from '../internal/xml-store.mjs'; -import { - MavenAnnotationProcessor, - MavenArtifact, - MavenDependency, - MavenDistributionManagement, - MavenPlugin, - MavenProfile, - MavenProperty, - MavenRepository, -} from '../types.mjs'; - -const { set, get } = _; - -const rootOrder = [ - 'modelVersion', - 'groupId', - 'artifactId', - 'version', - 'packaging', - 'name', - 'description', - 'parent', - 'repositories', - 'pluginRepositories', - 'distributionManagement', - 'properties', - 'dependencyManagement', - 'dependencies', - 'build', - 'profiles', -]; - -const propertiesOrder = [ - 'maven.version', - 'java.version', - 'node.version', - 'npm.version', - 'project.build.sourceEncoding', - 'project.reporting.outputEncoding', - 'maven.build.timestamp.format', - 'maven.compiler.source', - 'maven.compiler.target', - 'start-class', - 'argLine', - 'm2e.apt.activation', - 'run.addResources', - 'jhipster-dependencies.version', - 'spring-boot.version', -]; - -const formatFirstXmlLevel = content => - content.replace( - /(\n {4}<(?:groupId|distributionManagement|repositories|pluginRepositories|properties|dependencyManagement|dependencies|build|profiles)>)/g, - '\n$1', - ); - -const isComment = name => name.startsWith('#'); - -const toMaxInt = nr => (nr === -1 ? Number.MAX_SAFE_INTEGER : nr); - -const sortWithTemplate = (template: string[], a: string, b: string) => { - if (isComment(a)) return -1; - if (isComment(b)) return 1; - const indexOfA = toMaxInt(template.findIndex(item => item === a)); - const indexOfB = toMaxInt(template.findIndex(item => item === b)); - if (indexOfA === indexOfB) { - return a.localeCompare(b); - } - return indexOfA - indexOfB; -}; - -const comparator = (order: string[]) => (a: string, b: string) => sortWithTemplate(order, a, b); - -const sortProperties = properties => sortKeys(properties, { compare: comparator(propertiesOrder) }); - -const artifactEquals = (a: MavenArtifact, b: MavenArtifact) => { - return a.groupId === b.groupId && a.artifactId === b.artifactId; -}; - -const dependencyEquals = (a: MavenDependency, b: MavenDependency) => { - return artifactEquals(a, b) && a.scope === b.scope && a.type === b.type; -}; - -const idEquals = (a: { id: string }, b: { id: string }) => { - return a.id === b.id; -}; - -const ensureChildIsArray = (node, childPath) => { - let dependencyArray = get(node, childPath); - if (!dependencyArray) { - dependencyArray = []; - set(node, childPath, dependencyArray); - } else if (!Array.isArray(dependencyArray)) { - // Convert to array - dependencyArray = [dependencyArray]; - set(node, childPath, dependencyArray); - } - return dependencyArray; -}; - -function appendOrReplace(array: T[], item: T, equals: (a: T, b: T) => boolean) { - const dependencyIndex = array.findIndex(existing => equals(existing, item)); - if (dependencyIndex === -1) { - array.push(item); - } else { - array[dependencyIndex] = item; - } -} - -function appendOrGet(array: T[], item: T, equals: (a: T, b: T) => boolean) { - const child = array.find(existing => equals(existing, item)); - if (child) { - return child; - } - array.push(item); - return item; -} - -const ensureProfile = (project, profileId: string) => { - const profileArray = ensureChildIsArray(project, 'profiles.profile'); - return appendOrGet(profileArray, { id: profileId }, idEquals); -}; - -const groupIdOrder = ['tech.jhipster', 'org.springframework.boot', 'org.springframework.security', 'org.springdoc']; - -const sortArtifacts = (artifacts: MavenArtifact[]) => - artifacts.sort((a: MavenArtifact, b: MavenArtifact) => { - if (a.groupId !== b.groupId) { - if (a.groupId === undefined) { - return -1; - } - if (b.groupId === undefined) { - return 1; - } - const groupIdCompared = sortWithTemplate(groupIdOrder, a.groupId, b.groupId); - if (groupIdCompared) return groupIdCompared; - } - return a.artifactId.localeCompare(b.artifactId); - }); - -const sortProfiles = (profiles: MavenProfile[]) => profiles.sort((a, b) => a.id.localeCompare(b.id)); - -const ensureChildPath = (node: any, childPath) => { - let child = get(node, childPath); - if (child) return child; - child = {}; - set(node, childPath, child); - return child; -}; - -const ensureChild = (current: any, ...childPath) => { - for (const node of childPath) { - if (typeof node === 'string') { - current = ensureChildPath(current, node); - } else if (typeof node === 'function') { - current = node(current); - } else { - throw new Error(`Path section not supported ${node}`); - } - if (!current) { - return undefined; - } - } - return current; -}; - -export default class PomStorage extends XmlStorage { - constructor({ saveFile, loadFile }: { saveFile: (string) => void; loadFile: () => string }) { - super({ saveFile, loadFile }); - } - - public addProperty({ inProfile, property, value = null }: MavenProperty) { - const node = this.getNode({ nodePath: 'properties', profile: inProfile }); - node[property] = value; - this.persist(); - } - - public addDependency({ inProfile, ...dependency }: MavenDependency): void { - this.addDependencyAt(this.getNode({ profile: inProfile }), dependency); - this.persist(); - } - - public addDependencyManagement({ inProfile, ...dependency }: MavenDependency): void { - this.addDependencyAt(this.getNode({ profile: inProfile, nodePath: 'dependencyManagement' }), dependency); - this.persist(); - } - - public addDistributionManagement({ inProfile, snapshotsId, snapshotsUrl, releasesId, releasesUrl }: MavenDistributionManagement) { - const store = this.getNode({ profile: inProfile }); - store.distributionManagement = { - snapshotRepository: { - id: snapshotsId, - url: snapshotsUrl, - }, - repository: { - id: releasesId, - url: releasesUrl, - }, - }; - this.persist(); - } - - public addProfile({ content, ...profile }: MavenProfile): void { - const profileArray = ensureChildIsArray(this.getNode(), 'profiles.profile'); - appendOrReplace(profileArray, this.mergeContent(profile, content), idEquals); - this.persist(); - } - - public addPlugin({ inProfile, ...plugin }: MavenPlugin): void { - this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build' }), plugin); - this.persist(); - } - - public addPluginManagement({ inProfile, ...plugin }: MavenPlugin): void { - this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build.pluginManagement' }), plugin); - this.persist(); - } - - public addRepository({ inProfile, ...repository }: MavenRepository): void { - this.addRepositoryAt(this.getNode({ profile: inProfile }), repository); - this.persist(); - } - - public addPluginRepository({ inProfile, ...repository }: MavenRepository): void { - this.addPluginRepositoryAt(this.getNode({ profile: inProfile }), repository); - this.persist(); - } - - public addAnnotationProcessor({ inProfile, ...artifact }: MavenAnnotationProcessor) { - const node = this.getNode({ profile: inProfile }); - const plugins = ensureChildIsArray(node, 'build.pluginManagement.plugins.plugin'); - const annotationProcessorPaths = ensureChild( - plugins, - pluginArray => { - return appendOrGet( - pluginArray, - { - groupId: 'org.apache.maven.plugins', - artifactId: 'maven-compiler-plugin', - }, - artifactEquals, - ); - }, - 'configuration.annotationProcessorPaths', - ); - const paths = ensureChildIsArray(annotationProcessorPaths, 'path'); - appendOrReplace(paths, artifact, artifactEquals); - this.persist(); - } - - protected getNode({ profile, nodePath }: { profile?: string; nodePath?: string } = {}): any { - const node = profile ? ensureProfile(this.store.project, profile) : this.store.project; - if (nodePath) { - return ensureChild(node, nodePath); - } - return node; - } - - protected addDependencyAt(node, { additionalContent, ...dependency }: MavenDependency) { - const dependencyArray = ensureChildIsArray(node, 'dependencies.dependency'); - appendOrReplace(dependencyArray, this.mergeContent(dependency, additionalContent), dependencyEquals); - } - - protected addPluginAt(node, { additionalContent, ...artifact }: MavenPlugin) { - const artifactArray = ensureChildIsArray(node, 'plugins.plugin'); - appendOrReplace(artifactArray, this.mergeContent(artifact, additionalContent), artifactEquals); - } - - protected addRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }: MavenRepository): void { - const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled }; - const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled }; - const repositoryArray = ensureChildIsArray(node, 'repositories.repository'); - appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals); - } - - protected addPluginRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }: MavenRepository): void { - const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled }; - const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled }; - const repositoryArray = ensureChildIsArray(node, 'pluginRepositories.pluginRepository'); - appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals); - } - - protected sort() { - if (this.store.project) { - const project = sortKeys(this.store.project, { compare: comparator(rootOrder) }); - this.store.project = project; - if (project.properties) { - project.properties = sortProperties(project.properties); - } - if (Array.isArray(project.dependencies?.dependency)) { - project.dependencies.dependency = sortArtifacts(project.dependencies.dependency); - } - if (Array.isArray(project.dependencyManagement?.dependencies?.dependency)) { - project.dependencyManagement.dependencies.dependency = sortArtifacts(project.dependencyManagement.dependencies.dependency); - } - if (Array.isArray(project.build?.plugins?.plugin)) { - project.build.plugins.plugin = sortArtifacts(project.build.plugins.plugin); - } - if (Array.isArray(project.build?.pluginManagement?.plugins?.plugin)) { - project.build.pluginManagement.plugins.plugin = sortArtifacts(project.build.pluginManagement.plugins.plugin); - } - if (Array.isArray(project.profiles?.profile)) { - project.profiles.profile = sortProfiles(project.profiles.profile); - } - } - } -} - -const emptyPomFile = ` - - 4.0.0 - -`; - -export const createPomStorage = (generator: CoreGenerator) => { - const loadFile = () => generator.readDestination('pom.xml', { defaults: emptyPomFile })?.toString() ?? ''; - const pomStorage = new PomStorage({ - loadFile, - saveFile: content => generator.writeDestination('pom.xml', formatFirstXmlLevel(content)), - }); - generator.fs.store.on('change', filename => { - if (filename === generator.destinationPath('pom.xml')) { - pomStorage.clearCache(); - } - }); - return pomStorage; -}; diff --git a/generators/maven/support/pom-store.ts b/generators/maven/support/pom-store.ts new file mode 100644 index 000000000000..7cea3be72991 --- /dev/null +++ b/generators/maven/support/pom-store.ts @@ -0,0 +1,352 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import sortKeys from 'sort-keys'; + +import CoreGenerator from '../../base-core/index.js'; +import XmlStorage from '../internal/xml-store.js'; +import { + MavenAnnotationProcessor, + MavenArtifact, + MavenDependency, + MavenDistributionManagement, + MavenPlugin, + MavenProfile, + MavenProperty, + MavenRepository, +} from '../types.js'; + +const { set, get } = _; + +const rootOrder = [ + 'modelVersion', + 'groupId', + 'artifactId', + 'version', + 'packaging', + 'name', + 'description', + 'parent', + 'repositories', + 'pluginRepositories', + 'distributionManagement', + 'properties', + 'dependencyManagement', + 'dependencies', + 'build', + 'profiles', +]; + +const propertiesOrder = [ + 'maven.version', + 'java.version', + 'node.version', + 'npm.version', + 'project.build.sourceEncoding', + 'project.reporting.outputEncoding', + 'maven.build.timestamp.format', + 'maven.compiler.source', + 'maven.compiler.target', + 'start-class', + 'argLine', + 'm2e.apt.activation', + 'run.addResources', + 'jhipster-dependencies.version', + 'spring-boot.version', +]; + +const formatFirstXmlLevel = content => + content.replace( + /(\n {4}<(?:groupId|distributionManagement|repositories|pluginRepositories|properties|dependencyManagement|dependencies|build|profiles)>)/g, + '\n$1', + ); + +const isComment = name => name.startsWith('#'); + +const toMaxInt = nr => (nr === -1 ? Number.MAX_SAFE_INTEGER : nr); + +const sortWithTemplate = (template: string[], a: string, b: string) => { + if (isComment(a)) return -1; + if (isComment(b)) return 1; + const indexOfA = toMaxInt(template.findIndex(item => item === a)); + const indexOfB = toMaxInt(template.findIndex(item => item === b)); + if (indexOfA === indexOfB) { + return a.localeCompare(b); + } + return indexOfA - indexOfB; +}; + +const comparator = (order: string[]) => (a: string, b: string) => sortWithTemplate(order, a, b); + +const sortProperties = properties => sortKeys(properties, { compare: comparator(propertiesOrder) }); + +const artifactEquals = (a: MavenArtifact, b: MavenArtifact) => { + return a.groupId === b.groupId && a.artifactId === b.artifactId; +}; + +const dependencyEquals = (a: MavenDependency, b: MavenDependency) => { + return artifactEquals(a, b) && a.scope === b.scope && a.type === b.type; +}; + +const idEquals = (a: { id: string }, b: { id: string }) => { + return a.id === b.id; +}; + +const ensureChildIsArray = (node, childPath) => { + let dependencyArray = get(node, childPath); + if (!dependencyArray) { + dependencyArray = []; + set(node, childPath, dependencyArray); + } else if (!Array.isArray(dependencyArray)) { + // Convert to array + dependencyArray = [dependencyArray]; + set(node, childPath, dependencyArray); + } + return dependencyArray; +}; + +function appendOrReplace(array: T[], item: T, equals: (a: T, b: T) => boolean) { + const dependencyIndex = array.findIndex(existing => equals(existing, item)); + if (dependencyIndex === -1) { + array.push(item); + } else { + array[dependencyIndex] = item; + } +} + +function appendOrGet(array: T[], item: T, equals: (a: T, b: T) => boolean) { + const child = array.find(existing => equals(existing, item)); + if (child) { + return child; + } + array.push(item); + return item; +} + +const ensureProfile = (project, profileId: string) => { + const profileArray = ensureChildIsArray(project, 'profiles.profile'); + return appendOrGet(profileArray, { id: profileId }, idEquals); +}; + +const groupIdOrder = ['tech.jhipster', 'org.springframework.boot', 'org.springframework.security', 'org.springdoc']; + +const sortArtifacts = (artifacts: MavenArtifact[]) => + artifacts.sort((a: MavenArtifact, b: MavenArtifact) => { + if (a.groupId !== b.groupId) { + if (a.groupId === undefined) { + return -1; + } + if (b.groupId === undefined) { + return 1; + } + const groupIdCompared = sortWithTemplate(groupIdOrder, a.groupId, b.groupId); + if (groupIdCompared) return groupIdCompared; + } + return a.artifactId.localeCompare(b.artifactId); + }); + +const sortProfiles = (profiles: MavenProfile[]) => profiles.sort((a, b) => a.id.localeCompare(b.id)); + +const ensureChildPath = (node: any, childPath) => { + let child = get(node, childPath); + if (child) return child; + child = {}; + set(node, childPath, child); + return child; +}; + +const ensureChild = (current: any, ...childPath) => { + for (const node of childPath) { + if (typeof node === 'string') { + current = ensureChildPath(current, node); + } else if (typeof node === 'function') { + current = node(current); + } else { + throw new Error(`Path section not supported ${node}`); + } + if (!current) { + return undefined; + } + } + return current; +}; + +export default class PomStorage extends XmlStorage { + constructor({ saveFile, loadFile }: { saveFile: (string) => void; loadFile: () => string }) { + super({ saveFile, loadFile }); + } + + public addProperty({ inProfile, property, value = null }: MavenProperty) { + const node = this.getNode({ nodePath: 'properties', profile: inProfile }); + node[property] = value; + this.persist(); + } + + public addDependency({ inProfile, ...dependency }: MavenDependency): void { + this.addDependencyAt(this.getNode({ profile: inProfile }), dependency); + this.persist(); + } + + public addDependencyManagement({ inProfile, ...dependency }: MavenDependency): void { + this.addDependencyAt(this.getNode({ profile: inProfile, nodePath: 'dependencyManagement' }), dependency); + this.persist(); + } + + public addDistributionManagement({ inProfile, snapshotsId, snapshotsUrl, releasesId, releasesUrl }: MavenDistributionManagement) { + const store = this.getNode({ profile: inProfile }); + store.distributionManagement = { + snapshotRepository: { + id: snapshotsId, + url: snapshotsUrl, + }, + repository: { + id: releasesId, + url: releasesUrl, + }, + }; + this.persist(); + } + + public addProfile({ content, ...profile }: MavenProfile): void { + const profileArray = ensureChildIsArray(this.getNode(), 'profiles.profile'); + appendOrReplace(profileArray, this.mergeContent(profile, content), idEquals); + this.persist(); + } + + public addPlugin({ inProfile, ...plugin }: MavenPlugin): void { + this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build' }), plugin); + this.persist(); + } + + public addPluginManagement({ inProfile, ...plugin }: MavenPlugin): void { + this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build.pluginManagement' }), plugin); + this.persist(); + } + + public addRepository({ inProfile, ...repository }: MavenRepository): void { + this.addRepositoryAt(this.getNode({ profile: inProfile }), repository); + this.persist(); + } + + public addPluginRepository({ inProfile, ...repository }: MavenRepository): void { + this.addPluginRepositoryAt(this.getNode({ profile: inProfile }), repository); + this.persist(); + } + + public addAnnotationProcessor({ inProfile, ...artifact }: MavenAnnotationProcessor) { + const node = this.getNode({ profile: inProfile }); + const plugins = ensureChildIsArray(node, 'build.pluginManagement.plugins.plugin'); + const annotationProcessorPaths = ensureChild( + plugins, + pluginArray => { + return appendOrGet( + pluginArray, + { + groupId: 'org.apache.maven.plugins', + artifactId: 'maven-compiler-plugin', + }, + artifactEquals, + ); + }, + 'configuration.annotationProcessorPaths', + ); + const paths = ensureChildIsArray(annotationProcessorPaths, 'path'); + appendOrReplace(paths, artifact, artifactEquals); + this.persist(); + } + + protected getNode({ profile, nodePath }: { profile?: string; nodePath?: string } = {}): any { + const node = profile ? ensureProfile(this.store.project, profile) : this.store.project; + if (nodePath) { + return ensureChild(node, nodePath); + } + return node; + } + + protected addDependencyAt(node, { additionalContent, ...dependency }: MavenDependency) { + const dependencyArray = ensureChildIsArray(node, 'dependencies.dependency'); + appendOrReplace(dependencyArray, this.mergeContent(dependency, additionalContent), dependencyEquals); + } + + protected addPluginAt(node, { additionalContent, ...artifact }: MavenPlugin) { + const artifactArray = ensureChildIsArray(node, 'plugins.plugin'); + appendOrReplace(artifactArray, this.mergeContent(artifact, additionalContent), artifactEquals); + } + + protected addRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }: MavenRepository): void { + const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled }; + const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled }; + const repositoryArray = ensureChildIsArray(node, 'repositories.repository'); + appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals); + } + + protected addPluginRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }: MavenRepository): void { + const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled }; + const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled }; + const repositoryArray = ensureChildIsArray(node, 'pluginRepositories.pluginRepository'); + appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals); + } + + protected sort() { + if (this.store.project) { + const project = sortKeys(this.store.project, { compare: comparator(rootOrder) }); + this.store.project = project; + if (project.properties) { + project.properties = sortProperties(project.properties); + } + if (Array.isArray(project.dependencies?.dependency)) { + project.dependencies.dependency = sortArtifacts(project.dependencies.dependency); + } + if (Array.isArray(project.dependencyManagement?.dependencies?.dependency)) { + project.dependencyManagement.dependencies.dependency = sortArtifacts(project.dependencyManagement.dependencies.dependency); + } + if (Array.isArray(project.build?.plugins?.plugin)) { + project.build.plugins.plugin = sortArtifacts(project.build.plugins.plugin); + } + if (Array.isArray(project.build?.pluginManagement?.plugins?.plugin)) { + project.build.pluginManagement.plugins.plugin = sortArtifacts(project.build.pluginManagement.plugins.plugin); + } + if (Array.isArray(project.profiles?.profile)) { + project.profiles.profile = sortProfiles(project.profiles.profile); + } + } + } +} + +const emptyPomFile = ` + + 4.0.0 + +`; + +export const createPomStorage = (generator: CoreGenerator) => { + const loadFile = () => generator.readDestination('pom.xml', { defaults: emptyPomFile })?.toString() ?? ''; + const pomStorage = new PomStorage({ + loadFile, + saveFile: content => generator.writeDestination('pom.xml', formatFirstXmlLevel(content)), + }); + generator.fs.store.on('change', filename => { + if (filename === generator.destinationPath('pom.xml')) { + pomStorage.clearCache(); + } + }); + return pomStorage; +}; diff --git a/generators/maven/types.d.mts b/generators/maven/types.d.ts similarity index 100% rename from generators/maven/types.d.mts rename to generators/maven/types.d.ts diff --git a/generators/needle-api.js b/generators/needle-api.js new file mode 100644 index 000000000000..beba69fda608 --- /dev/null +++ b/generators/needle-api.js @@ -0,0 +1,33 @@ +/** + * Copyright 2013-2023 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 Base from './needle-base.js'; +import Client from './client/needle-api/needle-client.js'; +import ClientAngular from './angular/needle-api/needle-client-angular.js'; +import ClientReact from './react/needle-api/needle-client-react.js'; +import ClientVue from './client/needle-api/needle-client-vue.js'; + +export default class NeedleApi { + constructor(generator) { + this.base = new Base(generator); + this.client = new Client(generator); + this.clientAngular = new ClientAngular(generator); + this.clientReact = new ClientReact(generator); + this.clientVue = new ClientVue(generator); + } +} diff --git a/generators/needle-api.mjs b/generators/needle-api.mjs deleted file mode 100644 index 3f40e1080440..000000000000 --- a/generators/needle-api.mjs +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2013-2023 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 Base from './needle-base.mjs'; -import Client from './client/needle-api/needle-client.mjs'; -import ClientAngular from './angular/needle-api/needle-client-angular.mjs'; -import ClientReact from './react/needle-api/needle-client-react.mjs'; -import ClientVue from './client/needle-api/needle-client-vue.mjs'; - -export default class NeedleApi { - constructor(generator) { - this.base = new Base(generator); - this.client = new Client(generator); - this.clientAngular = new ClientAngular(generator); - this.clientReact = new ClientReact(generator); - this.clientVue = new ClientVue(generator); - } -} diff --git a/generators/needle-base.mts b/generators/needle-base.mts deleted file mode 100644 index 9dbc3259f422..000000000000 --- a/generators/needle-base.mts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import BaseGenerator from './base/index.mjs'; -import { createNeedleCallback, NeedleInsertion } from './base/support/needles.mjs'; - -export type NeedleFileModel = { - /** - * file path for logging purposes. - */ - file: string; - /** - * needle to be looked for - */ - needle: string; - /** - * content to be added. - */ - splicable: string | string[]; - - path?: string; - /** - * apply prettier aware expressions before looking for applied needles. - */ - prettierAware?: boolean; - /** - * use another content to looking for applied needles. - */ - regexp?: RegExp | string; - /** - * file content - */ - haystack?: string; -}; - -export default class { - generator: BaseGenerator; - - constructor(generator: BaseGenerator) { - this.generator = generator; - } - - /** - * @deprecated - */ - get clientSrcDir(): string { - return (this.generator.sharedData.getApplication() as any).clientSrcDir; - } - - /** - * @deprecated - */ - get clientFramework(): string { - return (this.generator.sharedData.getApplication() as any).clientFramework; - } - - /** - * @deprecated use editFile - * @param rewriteFileModel - * @param errorMessage - */ - addBlockContentToFile(rewriteFileModel: NeedleFileModel, errorMessage?: string): void { - const ignoreNonExisting = errorMessage ?? true; - const { path: rewritePath, file } = rewriteFileModel; - let fullPath; - if (rewritePath) { - fullPath = this.generator.destinationPath(rewritePath, file); - } else { - fullPath = this.generator.destinationPath(file); - } - this.generator.editFile( - fullPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: rewriteFileModel.needle, - contentToAdd: rewriteFileModel.splicable, - contentToCheck: rewriteFileModel.regexp, - ignoreWhitespaces: rewriteFileModel.prettierAware, - autoIndent: false, - }), - ); - } - - editFile(fullPath, errorMessage: string, needleData: NeedleInsertion): void { - const ignoreNonExisting = errorMessage ?? true; - this.generator.editFile(fullPath, { ignoreNonExisting }, createNeedleCallback(needleData)); - } - - logNeedleNotFound(exception: Error, message?: string, fullPath?: string): void { - if (!message) { - message = 'File rewrite failed.'; - } - this.generator.log(chalk.yellow('\nUnable to find ') + fullPath + chalk.yellow(` or missing required jhipster-needle. ${message}\n`)); - this.generator.log.debug('Error:', exception); - } - - /** - * @deprecated - */ - generateFileModelWithPath(aPath: string, aFile: string, needleTag: string, ...content: string[]): NeedleFileModel { - return Object.assign(this.generateFileModel(aFile, needleTag, ...content), { path: aPath }); - } - - /** - * @deprecated - */ - generateFileModel(aFile: string, needleTag: string, ...content: string[]): NeedleFileModel { - return { - file: aFile, - needle: needleTag, - splicable: content, - }; - } -} diff --git a/generators/needle-base.ts b/generators/needle-base.ts new file mode 100644 index 000000000000..7f673c688f7d --- /dev/null +++ b/generators/needle-base.ts @@ -0,0 +1,130 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import BaseGenerator from './base/index.js'; +import { createNeedleCallback, NeedleInsertion } from './base/support/needles.js'; + +export type NeedleFileModel = { + /** + * file path for logging purposes. + */ + file: string; + /** + * needle to be looked for + */ + needle: string; + /** + * content to be added. + */ + splicable: string | string[]; + + path?: string; + /** + * apply prettier aware expressions before looking for applied needles. + */ + prettierAware?: boolean; + /** + * use another content to looking for applied needles. + */ + regexp?: RegExp | string; + /** + * file content + */ + haystack?: string; +}; + +export default class { + generator: BaseGenerator; + + constructor(generator: BaseGenerator) { + this.generator = generator; + } + + /** + * @deprecated + */ + get clientSrcDir(): string { + return (this.generator.sharedData.getApplication() as any).clientSrcDir; + } + + /** + * @deprecated + */ + get clientFramework(): string { + return (this.generator.sharedData.getApplication() as any).clientFramework; + } + + /** + * @deprecated use editFile + * @param rewriteFileModel + * @param errorMessage + */ + addBlockContentToFile(rewriteFileModel: NeedleFileModel, errorMessage?: string): void { + const ignoreNonExisting = errorMessage ?? true; + const { path: rewritePath, file } = rewriteFileModel; + let fullPath; + if (rewritePath) { + fullPath = this.generator.destinationPath(rewritePath, file); + } else { + fullPath = this.generator.destinationPath(file); + } + this.generator.editFile( + fullPath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: rewriteFileModel.needle, + contentToAdd: rewriteFileModel.splicable, + contentToCheck: rewriteFileModel.regexp, + ignoreWhitespaces: rewriteFileModel.prettierAware, + autoIndent: false, + }), + ); + } + + editFile(fullPath, errorMessage: string, needleData: NeedleInsertion): void { + const ignoreNonExisting = errorMessage ?? true; + this.generator.editFile(fullPath, { ignoreNonExisting }, createNeedleCallback(needleData)); + } + + logNeedleNotFound(exception: Error, message?: string, fullPath?: string): void { + if (!message) { + message = 'File rewrite failed.'; + } + this.generator.log(chalk.yellow('\nUnable to find ') + fullPath + chalk.yellow(` or missing required jhipster-needle. ${message}\n`)); + this.generator.log.debug('Error:', exception); + } + + /** + * @deprecated + */ + generateFileModelWithPath(aPath: string, aFile: string, needleTag: string, ...content: string[]): NeedleFileModel { + return Object.assign(this.generateFileModel(aFile, needleTag, ...content), { path: aPath }); + } + + /** + * @deprecated + */ + generateFileModel(aFile: string, needleTag: string, ...content: string[]): NeedleFileModel { + return { + file: aFile, + needle: needleTag, + splicable: content, + }; + } +} diff --git a/generators/project-name/command.mts b/generators/project-name/command.mts deleted file mode 100644 index add324a277b4..000000000000 --- a/generators/project-name/command.mts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2013-2023 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 { BASE_NAME, BASE_NAME_DESCRIPTION } from './constants.mjs'; -import { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - [BASE_NAME]: { - description: BASE_NAME_DESCRIPTION, - type: String, - scope: 'storage', - }, - }, -}; - -export default command; diff --git a/generators/project-name/command.ts b/generators/project-name/command.ts new file mode 100644 index 000000000000..e26f7b68483a --- /dev/null +++ b/generators/project-name/command.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2013-2023 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 { BASE_NAME, BASE_NAME_DESCRIPTION } from './constants.js'; +import { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + [BASE_NAME]: { + description: BASE_NAME_DESCRIPTION, + type: String, + scope: 'storage', + }, + }, +}; + +export default command; diff --git a/generators/project-name/constants.mjs b/generators/project-name/constants.js similarity index 100% rename from generators/project-name/constants.mjs rename to generators/project-name/constants.js diff --git a/generators/project-name/generator.js b/generators/project-name/generator.js new file mode 100644 index 000000000000..65bfc998dc45 --- /dev/null +++ b/generators/project-name/generator.js @@ -0,0 +1,140 @@ +/** + * Copyright 2013-2023 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. + */ +/* eslint-disable consistent-return */ +import * as _ from 'lodash-es'; +import { getDefaultAppName } from './support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; + +import { GENERATOR_PROJECT_NAME } from '../generator-list.js'; +import { BASE_NAME } from './constants.js'; +import { getHipster } from '../base/support/index.js'; +import command from './command.js'; +import { validateProjectName } from './support/name-resolver.js'; + +/** + * @class + * @extends {BaseApplicationGenerator} + */ +export default class ProjectNameGenerator extends BaseApplicationGenerator { + javaApplication; + + async beforeQueue() { + this.sharedData.getControl().existingProject = + this.options.defaults || this.options.applicationWithConfig || (this.jhipsterConfig.baseName !== undefined && this.config.existed); + + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_PROJECT_NAME); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadOptions() { + this.parseJHipsterOptions(command.options); + }, + parseOptions() { + if (this.options.defaults) { + if (!this.jhipsterConfig.baseName) { + this.jhipsterConfig.baseName = getDefaultAppName({ + reproducible: this.options.reproducible, + javaApplication: this.javaApplication, + }); + } + } + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get prompting() { + return { + async showPrompts() { + await this.prompt( + [ + { + name: BASE_NAME, + type: 'input', + validate: input => this.validateBaseName(input), + message: 'What is the base name of your application?', + default: () => getDefaultAppName({ reproducible: this.options.reproducible, javaApplication: this.javaApplication }), + }, + ], + this.config, + ); + }, + }; + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.delegateTasksToBlueprint(() => this.prompting); + } + + get loading() { + return this.asLoadingTaskGroup({ + load({ application }) { + const { baseName, projectDescription } = this.jhipsterConfig; + application.baseName = baseName; + application.projectDescription = projectDescription; + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return this.asPreparingTaskGroup({ + preparing({ application }) { + const { baseName, upperFirstCamelCaseBaseName } = application; + const humanizedBaseName = baseName.toLowerCase() === 'jhipster' ? 'JHipster' : _.startCase(baseName); + _.defaults(application, { + humanizedBaseName, + camelizedBaseName: _.camelCase(baseName), + hipster: getHipster(baseName), + capitalizedBaseName: _.upperFirst(baseName), + dasherizedBaseName: _.kebabCase(baseName), + lowercaseBaseName: baseName.toLowerCase(), + upperFirstCamelCaseBaseName, + projectDescription: `Description for ${humanizedBaseName}`, + }); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + /* + * Start of local public API, blueprints may override to customize the generator behavior. + */ + + /** + * Validates baseName + * @param {String} input - Base name to be checked + * @returns Boolean + */ + validateBaseName(input) { + return validateProjectName(input, { javaApplication: this.javaApplication }); + } +} diff --git a/generators/project-name/generator.mjs b/generators/project-name/generator.mjs deleted file mode 100644 index 7e2a5ce213e4..000000000000 --- a/generators/project-name/generator.mjs +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ -/* eslint-disable consistent-return */ -import * as _ from 'lodash-es'; -import { getDefaultAppName } from './support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; - -import { GENERATOR_PROJECT_NAME } from '../generator-list.mjs'; -import { BASE_NAME } from './constants.mjs'; -import { getHipster } from '../base/support/index.mjs'; -import command from './command.mjs'; -import { validateProjectName } from './support/name-resolver.mjs'; - -/** - * @class - * @extends {BaseApplicationGenerator} - */ -export default class ProjectNameGenerator extends BaseApplicationGenerator { - javaApplication; - - async beforeQueue() { - this.sharedData.getControl().existingProject = - this.options.defaults || this.options.applicationWithConfig || (this.jhipsterConfig.baseName !== undefined && this.config.existed); - - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_PROJECT_NAME); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadOptions() { - this.parseJHipsterOptions(command.options); - }, - parseOptions() { - if (this.options.defaults) { - if (!this.jhipsterConfig.baseName) { - this.jhipsterConfig.baseName = getDefaultAppName({ - reproducible: this.options.reproducible, - javaApplication: this.javaApplication, - }); - } - } - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get prompting() { - return { - async showPrompts() { - await this.prompt( - [ - { - name: BASE_NAME, - type: 'input', - validate: input => this.validateBaseName(input), - message: 'What is the base name of your application?', - default: () => getDefaultAppName({ reproducible: this.options.reproducible, javaApplication: this.javaApplication }), - }, - ], - this.config, - ); - }, - }; - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.delegateTasksToBlueprint(() => this.prompting); - } - - get loading() { - return this.asLoadingTaskGroup({ - load({ application }) { - const { baseName, projectDescription } = this.jhipsterConfig; - application.baseName = baseName; - application.projectDescription = projectDescription; - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return this.asPreparingTaskGroup({ - preparing({ application }) { - const { baseName, upperFirstCamelCaseBaseName } = application; - const humanizedBaseName = baseName.toLowerCase() === 'jhipster' ? 'JHipster' : _.startCase(baseName); - _.defaults(application, { - humanizedBaseName, - camelizedBaseName: _.camelCase(baseName), - hipster: getHipster(baseName), - capitalizedBaseName: _.upperFirst(baseName), - dasherizedBaseName: _.kebabCase(baseName), - lowercaseBaseName: baseName.toLowerCase(), - upperFirstCamelCaseBaseName, - projectDescription: `Description for ${humanizedBaseName}`, - }); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - /* - * Start of local public API, blueprints may override to customize the generator behavior. - */ - - /** - * Validates baseName - * @param {String} input - Base name to be checked - * @returns Boolean - */ - validateBaseName(input) { - return validateProjectName(input, { javaApplication: this.javaApplication }); - } -} diff --git a/generators/project-name/generator.spec.mts b/generators/project-name/generator.spec.mts deleted file mode 100644 index 3d557ac42a47..000000000000 --- a/generators/project-name/generator.spec.mts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { basicTests, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { GENERATOR_PROJECT_NAME } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorPath = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', () => { - expect(GENERATOR_PROJECT_NAME).toBe(generator); - }); - describe('blueprint support', () => testBlueprintSupport(generator)); - basicTests({ - requiredConfig: {}, - defaultConfig: {}, - customPrompts: { - baseName: 'BeautifulProject', - }, - generatorPath, - }); -}); diff --git a/generators/project-name/generator.spec.ts b/generators/project-name/generator.spec.ts new file mode 100644 index 000000000000..4641a80e8751 --- /dev/null +++ b/generators/project-name/generator.spec.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { basicTests, testBlueprintSupport } from '../../test/support/tests.js'; +import { GENERATOR_PROJECT_NAME } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorPath = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', () => { + expect(GENERATOR_PROJECT_NAME).toBe(generator); + }); + describe('blueprint support', () => testBlueprintSupport(generator)); + basicTests({ + requiredConfig: {}, + defaultConfig: {}, + customPrompts: { + baseName: 'BeautifulProject', + }, + generatorPath, + }); +}); diff --git a/generators/project-name/index.mts b/generators/project-name/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/project-name/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/project-name/index.ts b/generators/project-name/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/project-name/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/project-name/support/index.mts b/generators/project-name/support/index.mts deleted file mode 100644 index b662d3485e31..000000000000 --- a/generators/project-name/support/index.mts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2013-2023 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. - */ - -// eslint-disable-next-line import/prefer-default-export -export { default as getDefaultAppName } from './name-resolver.mjs'; diff --git a/generators/project-name/support/index.ts b/generators/project-name/support/index.ts new file mode 100644 index 000000000000..88d018420316 --- /dev/null +++ b/generators/project-name/support/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2013-2023 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. + */ + +// eslint-disable-next-line import/prefer-default-export +export { default as getDefaultAppName } from './name-resolver.js'; diff --git a/generators/project-name/support/name-resolver.mts b/generators/project-name/support/name-resolver.mts deleted file mode 100644 index 57e118c6f0f1..000000000000 --- a/generators/project-name/support/name-resolver.mts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2013-2023 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 path from 'path'; - -import { camelCase } from 'lodash-es'; -import { isReproducible } from '../../base/support/index.mjs'; - -const defaultName = 'jhipster'; - -export const validateJavaApplicationName = (name: string) => { - if (!/^([\w]*)$/.test(name)) { - return 'Your base name cannot contain special characters or a blank space'; - } - if (/_/.test(name)) { - return 'Your base name cannot contain underscores as this does not meet the URI spec'; - } - if (name.toLowerCase() === 'application') { - return "Your base name cannot be named 'application' as this is a reserved name for Spring Boot"; - } - return true; -}; - -export const validateNpmProjectName = (name: string) => { - if (!/^([\w-.]*)$/.test(name)) { - return 'Your base name cannot contain special characters or a blank space'; - } - return true; -}; - -export const validateProjectName = (name: string, { javaApplication }: { javaApplication?: boolean } = {}) => - javaApplication ? validateJavaApplicationName(name) : validateNpmProjectName(name); - -const getDefaultName = (generator: { reproducible?: boolean; javaApplication?: boolean } | any) => { - if (generator?.options && isReproducible(generator)) { - return defaultName; - } - let projectName = path.basename(process.cwd()); - - const { reproducible = false, javaApplication = false } = generator && !generator.options ? generator : {}; - if (reproducible) { - return defaultName; - } - if (javaApplication) { - projectName = camelCase(projectName); - } else { - projectName = projectName.replace('generator-jhipster-', ''); - } - return validateProjectName(projectName, { javaApplication }) === true ? projectName : defaultName; -}; - -export default getDefaultName; diff --git a/generators/project-name/support/name-resolver.ts b/generators/project-name/support/name-resolver.ts new file mode 100644 index 000000000000..b636207c49d8 --- /dev/null +++ b/generators/project-name/support/name-resolver.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2013-2023 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 path from 'path'; + +import { camelCase } from 'lodash-es'; +import { isReproducible } from '../../base/support/index.js'; + +const defaultName = 'jhipster'; + +export const validateJavaApplicationName = (name: string) => { + if (!/^([\w]*)$/.test(name)) { + return 'Your base name cannot contain special characters or a blank space'; + } + if (/_/.test(name)) { + return 'Your base name cannot contain underscores as this does not meet the URI spec'; + } + if (name.toLowerCase() === 'application') { + return "Your base name cannot be named 'application' as this is a reserved name for Spring Boot"; + } + return true; +}; + +export const validateNpmProjectName = (name: string) => { + if (!/^([\w-.]*)$/.test(name)) { + return 'Your base name cannot contain special characters or a blank space'; + } + return true; +}; + +export const validateProjectName = (name: string, { javaApplication }: { javaApplication?: boolean } = {}) => + javaApplication ? validateJavaApplicationName(name) : validateNpmProjectName(name); + +const getDefaultName = (generator: { reproducible?: boolean; javaApplication?: boolean } | any) => { + if (generator?.options && isReproducible(generator)) { + return defaultName; + } + let projectName = path.basename(process.cwd()); + + const { reproducible = false, javaApplication = false } = generator && !generator.options ? generator : {}; + if (reproducible) { + return defaultName; + } + if (javaApplication) { + projectName = camelCase(projectName); + } else { + projectName = projectName.replace('generator-jhipster-', ''); + } + return validateProjectName(projectName, { javaApplication }) === true ? projectName : defaultName; +}; + +export default getDefaultName; diff --git a/generators/react/__snapshots__/generator.spec.mts.snap b/generators/react/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/react/__snapshots__/generator.spec.mts.snap rename to generators/react/__snapshots__/generator.spec.ts.snap diff --git a/generators/react/application/entities/index.mts b/generators/react/application/entities/index.ts similarity index 100% rename from generators/react/application/entities/index.mts rename to generators/react/application/entities/index.ts diff --git a/generators/react/cleanup.mjs b/generators/react/cleanup.js similarity index 100% rename from generators/react/cleanup.mjs rename to generators/react/cleanup.js diff --git a/generators/react/entity-files-react.js b/generators/react/entity-files-react.js new file mode 100644 index 000000000000..c721b46ee8a6 --- /dev/null +++ b/generators/react/entity-files-react.js @@ -0,0 +1,85 @@ +/** + * Copyright 2013-2023 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 { clientApplicationTemplatesBlock } from '../client/support/files.js'; + +export const reactFiles = { + client: [ + { + condition: generator => !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/_entityFolder_/_entityFile_-detail.tsx', + 'entities/_entityFolder_/_entityFile_.tsx', + 'entities/_entityFolder_/_entityFile_.reducer.ts', + 'entities/_entityFolder_/index.tsx', + ], + }, + { + ...clientApplicationTemplatesBlock(), + renameTo: data => `${data.clientSrcDir}app/shared/model/${data.entityModelFileName}.model.ts`, + templates: ['entities/_entityFolder_/_entityModel_.model.ts'], + }, + { + condition: generator => !generator.readOnly && !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: ['entities/_entityFolder_/_entityFile_-delete-dialog.tsx', 'entities/_entityFolder_/_entityFile_-update.tsx'], + }, + ], + test: [ + { + condition: generator => !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: ['entities/_entityFolder_/_entityFile_-reducer.spec.ts'], + }, + ], +}; + +export async function writeEntitiesFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + await this.writeFiles({ + sections: reactFiles, + context: { ...application, ...entity }, + }); + } +} + +export async function postWriteEntitiesFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + if (!entity.embedded) { + const { entityInstance, entityClass, entityAngularName, entityFolderName, entityFileName } = entity; + + const { applicationTypeMicroservice, clientSrcDir } = application; + this.needleApi.clientReact.addEntityToModule(entityInstance, entityClass, entityAngularName, entityFolderName, entityFileName, { + applicationTypeMicroservice, + clientSrcDir, + }); + this.addEntityToMenu(entity.entityPage, application.enableTranslation, entity.entityTranslationKeyMenu, entity.entityClassHumanized); + } + } +} + +export function cleanupEntitiesFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + const { entityFolderName, entityFileName } = entity; + + if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { + this.removeFile(`${application.clientTestDir}spec/app/entities/${entityFolderName}/${entityFileName}-reducer.spec.ts`); + } + } +} diff --git a/generators/react/entity-files-react.mjs b/generators/react/entity-files-react.mjs deleted file mode 100644 index 97fd094a132e..000000000000 --- a/generators/react/entity-files-react.mjs +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2013-2023 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 { clientApplicationTemplatesBlock } from '../client/support/files.mjs'; - -export const reactFiles = { - client: [ - { - condition: generator => !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/_entityFolder_/_entityFile_-detail.tsx', - 'entities/_entityFolder_/_entityFile_.tsx', - 'entities/_entityFolder_/_entityFile_.reducer.ts', - 'entities/_entityFolder_/index.tsx', - ], - }, - { - ...clientApplicationTemplatesBlock(), - renameTo: data => `${data.clientSrcDir}app/shared/model/${data.entityModelFileName}.model.ts`, - templates: ['entities/_entityFolder_/_entityModel_.model.ts'], - }, - { - condition: generator => !generator.readOnly && !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: ['entities/_entityFolder_/_entityFile_-delete-dialog.tsx', 'entities/_entityFolder_/_entityFile_-update.tsx'], - }, - ], - test: [ - { - condition: generator => !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: ['entities/_entityFolder_/_entityFile_-reducer.spec.ts'], - }, - ], -}; - -export async function writeEntitiesFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - await this.writeFiles({ - sections: reactFiles, - context: { ...application, ...entity }, - }); - } -} - -export async function postWriteEntitiesFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - if (!entity.embedded) { - const { entityInstance, entityClass, entityAngularName, entityFolderName, entityFileName } = entity; - - const { applicationTypeMicroservice, clientSrcDir } = application; - this.needleApi.clientReact.addEntityToModule(entityInstance, entityClass, entityAngularName, entityFolderName, entityFileName, { - applicationTypeMicroservice, - clientSrcDir, - }); - this.addEntityToMenu(entity.entityPage, application.enableTranslation, entity.entityTranslationKeyMenu, entity.entityClassHumanized); - } - } -} - -export function cleanupEntitiesFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - const { entityFolderName, entityFileName } = entity; - - if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { - this.removeFile(`${application.clientTestDir}spec/app/entities/${entityFolderName}/${entityFileName}-reducer.spec.ts`); - } - } -} diff --git a/generators/react/files-react.js b/generators/react/files-react.js new file mode 100644 index 000000000000..1cabc9fbc50c --- /dev/null +++ b/generators/react/files-react.js @@ -0,0 +1,316 @@ +/** + * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.js'; + +export const files = { + common: [ + clientRootTemplatesBlock({ + templates: [ + 'package.json', + '.eslintrc.json', + 'tsconfig.json', + 'tsconfig.test.json', + 'jest.conf.js', + 'webpack/environment.js', + 'webpack/webpack.common.js', + 'webpack/webpack.dev.js', + 'webpack/webpack.prod.js', + 'webpack/utils.js', + 'webpack/logo-jhipster.png', + ], + }), + ], + sass: [ + clientRootTemplatesBlock({ + templates: ['postcss.config.js'], + }), + ], + reactApp: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'app.tsx', + 'index.tsx', + 'routes.tsx', + 'setup-tests.ts', + 'typings.d.ts', + 'config/constants.ts', + 'config/dayjs.ts', + 'config/axios-interceptor.ts', + 'config/error-middleware.ts', + 'config/logger-middleware.ts', + 'config/notification-middleware.ts', + 'config/store.ts', + 'config/icon-loader.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['config/translation.ts'], + }, + { + condition: generator => generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: ['config/websocket-middleware.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + templates: ['app.scss', '_bootstrap-variables.scss'], + }, + ], + reactEntities: [ + { + ...clientApplicationTemplatesBlock(), + templates: ['entities/reducers.ts', 'entities/menu.tsx', 'entities/routes.tsx'], + }, + ], + reactMain: [ + { + ...clientApplicationTemplatesBlock(), + templates: ['modules/home/home.tsx', 'modules/login/logout.tsx'], + }, + { + condition: generator => !generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['modules/login/login.tsx', 'modules/login/login-modal.tsx'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['modules/login/login-redirect.tsx'], + }, + { + ...clientApplicationTemplatesBlock(), + templates: ['modules/home/home.scss'], + }, + ], + reducers: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'shared/reducers/index.ts', + 'shared/reducers/reducer.utils.ts', + 'shared/reducers/authentication.ts', + 'shared/reducers/application-profile.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['shared/reducers/locale.ts'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['shared/reducers/user-management.ts'], + }, + ], + accountModule: [ + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'modules/account/index.tsx', + 'modules/account/activate/activate.tsx', + 'modules/account/password/password.tsx', + 'modules/account/register/register.tsx', + 'modules/account/password-reset/init/password-reset-init.tsx', + 'modules/account/password-reset/finish/password-reset-finish.tsx', + 'modules/account/settings/settings.tsx', + 'modules/account/register/register.reducer.ts', + 'modules/account/activate/activate.reducer.ts', + 'modules/account/password-reset/password-reset.reducer.ts', + 'modules/account/password/password.reducer.ts', + 'modules/account/settings/settings.reducer.ts', + ], + }, + { + condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: ['modules/account/sessions/sessions.tsx', 'modules/account/sessions/sessions.reducer.ts'], + }, + ], + adminModule: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'modules/administration/index.tsx', + 'modules/administration/administration.reducer.ts', + 'modules/administration/docs/docs.tsx', + 'modules/administration/docs/docs.scss', + ], + }, + { + condition: generator => generator.withAdminUi, + ...clientApplicationTemplatesBlock(), + templates: [ + 'modules/administration/configuration/configuration.tsx', + 'modules/administration/health/health.tsx', + 'modules/administration/health/health-modal.tsx', + 'modules/administration/logs/logs.tsx', + 'modules/administration/metrics/metrics.tsx', + ], + }, + { + condition: generator => generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: ['modules/administration/tracker/tracker.tsx'], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'modules/administration/user-management/index.tsx', + 'modules/administration/user-management/user-management.tsx', + 'modules/administration/user-management/user-management-update.tsx', + 'modules/administration/user-management/user-management-detail.tsx', + 'modules/administration/user-management/user-management-delete-dialog.tsx', + 'modules/administration/user-management/user-management.reducer.ts', + ], + }, + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, + ...clientApplicationTemplatesBlock(), + templates: ['modules/administration/gateway/gateway.tsx'], + }, + ], + reactShared: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + // layouts + 'shared/layout/footer/footer.tsx', + 'shared/layout/header/header.tsx', + 'shared/layout/header/header-components.tsx', + 'shared/layout/menus/index.ts', + 'shared/layout/menus/admin.tsx', + 'shared/layout/menus/account.tsx', + 'shared/layout/menus/entities.tsx', + 'shared/layout/menus/menu-components.tsx', + 'shared/layout/menus/menu-item.tsx', + 'shared/layout/password/password-strength-bar.tsx', + // util + 'shared/util/date-utils.ts', + 'shared/util/pagination.constants.ts', + 'shared/util/entity-utils.ts', + // components + 'shared/auth/private-route.tsx', + 'shared/error/error-boundary.tsx', + 'shared/error/error-boundary-routes.tsx', + 'shared/error/page-not-found.tsx', + 'shared/DurationFormat.tsx', + // model + 'shared/model/user.model.ts', + ], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['shared/layout/menus/locale.tsx'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['shared/util/url-utils.ts'], + }, + { + condition: generator => generator.authenticationTypeSession && generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: ['shared/util/cookie-utils.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'shared/layout/header/header.scss', + 'shared/layout/footer/footer.scss', + 'shared/layout/password/password-strength-bar.scss', + ], + }, + ], + microfrontend: [ + clientRootTemplatesBlock({ + condition: generator => generator.microfrontend, + templates: ['webpack/webpack.microfrontend.js.jhi.react'], + }), + { + condition: generator => generator.microfrontend, + ...clientApplicationTemplatesBlock(), + templates: ['main.tsx', 'shared/error/error-loading.tsx'], + }, + { + condition: generator => generator.microfrontend && generator.applicationTypeGateway, + ...clientSrcTemplatesBlock(), + templates: ['microfrontends/entities-menu.tsx', 'microfrontends/entities-routes.tsx'], + }, + ], + clientTestFw: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'config/axios-interceptor.spec.ts', + 'config/notification-middleware.spec.ts', + 'shared/reducers/application-profile.spec.ts', + 'shared/reducers/authentication.spec.ts', + 'shared/util/entity-utils.spec.ts', + 'shared/auth/private-route.spec.tsx', + 'shared/error/error-boundary.spec.tsx', + 'shared/error/error-boundary-routes.spec.tsx', + 'shared/layout/header/header.spec.tsx', + 'shared/layout/menus/account.spec.tsx', + 'modules/administration/administration.reducer.spec.ts', + ], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + // 'spec/app/modules/account/register/register.spec.tsx', + 'modules/account/register/register.reducer.spec.ts', + 'modules/account/activate/activate.reducer.spec.ts', + 'modules/account/password/password.reducer.spec.ts', + 'modules/account/settings/settings.reducer.spec.ts', + ], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: ['modules/administration/user-management/user-management.reducer.spec.ts'], + }, + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['shared/reducers/locale.spec.ts'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['shared/reducers/user-management.spec.ts'], + }, + ], +}; + +export async function writeFiles({ application }) { + if (!application.clientFrameworkReact) return; + + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/react/files-react.mjs b/generators/react/files-react.mjs deleted file mode 100644 index 5f936d139f83..000000000000 --- a/generators/react/files-react.mjs +++ /dev/null @@ -1,316 +0,0 @@ -/** - * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.mjs'; - -export const files = { - common: [ - clientRootTemplatesBlock({ - templates: [ - 'package.json', - '.eslintrc.json', - 'tsconfig.json', - 'tsconfig.test.json', - 'jest.conf.js', - 'webpack/environment.js', - 'webpack/webpack.common.js', - 'webpack/webpack.dev.js', - 'webpack/webpack.prod.js', - 'webpack/utils.js', - 'webpack/logo-jhipster.png', - ], - }), - ], - sass: [ - clientRootTemplatesBlock({ - templates: ['postcss.config.js'], - }), - ], - reactApp: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'app.tsx', - 'index.tsx', - 'routes.tsx', - 'setup-tests.ts', - 'typings.d.ts', - 'config/constants.ts', - 'config/dayjs.ts', - 'config/axios-interceptor.ts', - 'config/error-middleware.ts', - 'config/logger-middleware.ts', - 'config/notification-middleware.ts', - 'config/store.ts', - 'config/icon-loader.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['config/translation.ts'], - }, - { - condition: generator => generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: ['config/websocket-middleware.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - templates: ['app.scss', '_bootstrap-variables.scss'], - }, - ], - reactEntities: [ - { - ...clientApplicationTemplatesBlock(), - templates: ['entities/reducers.ts', 'entities/menu.tsx', 'entities/routes.tsx'], - }, - ], - reactMain: [ - { - ...clientApplicationTemplatesBlock(), - templates: ['modules/home/home.tsx', 'modules/login/logout.tsx'], - }, - { - condition: generator => !generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['modules/login/login.tsx', 'modules/login/login-modal.tsx'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['modules/login/login-redirect.tsx'], - }, - { - ...clientApplicationTemplatesBlock(), - templates: ['modules/home/home.scss'], - }, - ], - reducers: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'shared/reducers/index.ts', - 'shared/reducers/reducer.utils.ts', - 'shared/reducers/authentication.ts', - 'shared/reducers/application-profile.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['shared/reducers/locale.ts'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['shared/reducers/user-management.ts'], - }, - ], - accountModule: [ - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'modules/account/index.tsx', - 'modules/account/activate/activate.tsx', - 'modules/account/password/password.tsx', - 'modules/account/register/register.tsx', - 'modules/account/password-reset/init/password-reset-init.tsx', - 'modules/account/password-reset/finish/password-reset-finish.tsx', - 'modules/account/settings/settings.tsx', - 'modules/account/register/register.reducer.ts', - 'modules/account/activate/activate.reducer.ts', - 'modules/account/password-reset/password-reset.reducer.ts', - 'modules/account/password/password.reducer.ts', - 'modules/account/settings/settings.reducer.ts', - ], - }, - { - condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: ['modules/account/sessions/sessions.tsx', 'modules/account/sessions/sessions.reducer.ts'], - }, - ], - adminModule: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'modules/administration/index.tsx', - 'modules/administration/administration.reducer.ts', - 'modules/administration/docs/docs.tsx', - 'modules/administration/docs/docs.scss', - ], - }, - { - condition: generator => generator.withAdminUi, - ...clientApplicationTemplatesBlock(), - templates: [ - 'modules/administration/configuration/configuration.tsx', - 'modules/administration/health/health.tsx', - 'modules/administration/health/health-modal.tsx', - 'modules/administration/logs/logs.tsx', - 'modules/administration/metrics/metrics.tsx', - ], - }, - { - condition: generator => generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: ['modules/administration/tracker/tracker.tsx'], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'modules/administration/user-management/index.tsx', - 'modules/administration/user-management/user-management.tsx', - 'modules/administration/user-management/user-management-update.tsx', - 'modules/administration/user-management/user-management-detail.tsx', - 'modules/administration/user-management/user-management-delete-dialog.tsx', - 'modules/administration/user-management/user-management.reducer.ts', - ], - }, - { - condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, - ...clientApplicationTemplatesBlock(), - templates: ['modules/administration/gateway/gateway.tsx'], - }, - ], - reactShared: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - // layouts - 'shared/layout/footer/footer.tsx', - 'shared/layout/header/header.tsx', - 'shared/layout/header/header-components.tsx', - 'shared/layout/menus/index.ts', - 'shared/layout/menus/admin.tsx', - 'shared/layout/menus/account.tsx', - 'shared/layout/menus/entities.tsx', - 'shared/layout/menus/menu-components.tsx', - 'shared/layout/menus/menu-item.tsx', - 'shared/layout/password/password-strength-bar.tsx', - // util - 'shared/util/date-utils.ts', - 'shared/util/pagination.constants.ts', - 'shared/util/entity-utils.ts', - // components - 'shared/auth/private-route.tsx', - 'shared/error/error-boundary.tsx', - 'shared/error/error-boundary-routes.tsx', - 'shared/error/page-not-found.tsx', - 'shared/DurationFormat.tsx', - // model - 'shared/model/user.model.ts', - ], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['shared/layout/menus/locale.tsx'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['shared/util/url-utils.ts'], - }, - { - condition: generator => generator.authenticationTypeSession && generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: ['shared/util/cookie-utils.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'shared/layout/header/header.scss', - 'shared/layout/footer/footer.scss', - 'shared/layout/password/password-strength-bar.scss', - ], - }, - ], - microfrontend: [ - clientRootTemplatesBlock({ - condition: generator => generator.microfrontend, - templates: ['webpack/webpack.microfrontend.js.jhi.react'], - }), - { - condition: generator => generator.microfrontend, - ...clientApplicationTemplatesBlock(), - templates: ['main.tsx', 'shared/error/error-loading.tsx'], - }, - { - condition: generator => generator.microfrontend && generator.applicationTypeGateway, - ...clientSrcTemplatesBlock(), - templates: ['microfrontends/entities-menu.tsx', 'microfrontends/entities-routes.tsx'], - }, - ], - clientTestFw: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'config/axios-interceptor.spec.ts', - 'config/notification-middleware.spec.ts', - 'shared/reducers/application-profile.spec.ts', - 'shared/reducers/authentication.spec.ts', - 'shared/util/entity-utils.spec.ts', - 'shared/auth/private-route.spec.tsx', - 'shared/error/error-boundary.spec.tsx', - 'shared/error/error-boundary-routes.spec.tsx', - 'shared/layout/header/header.spec.tsx', - 'shared/layout/menus/account.spec.tsx', - 'modules/administration/administration.reducer.spec.ts', - ], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - // 'spec/app/modules/account/register/register.spec.tsx', - 'modules/account/register/register.reducer.spec.ts', - 'modules/account/activate/activate.reducer.spec.ts', - 'modules/account/password/password.reducer.spec.ts', - 'modules/account/settings/settings.reducer.spec.ts', - ], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: ['modules/administration/user-management/user-management.reducer.spec.ts'], - }, - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['shared/reducers/locale.spec.ts'], - }, - { - condition: generator => generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['shared/reducers/user-management.spec.ts'], - }, - ], -}; - -export async function writeFiles({ application }) { - if (!application.clientFrameworkReact) return; - - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/react/generator.js b/generators/react/generator.js new file mode 100644 index 000000000000..81264f96c4f4 --- /dev/null +++ b/generators/react/generator.js @@ -0,0 +1,298 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { isFileStateModified } from 'mem-fs-editor/state'; +import chalk from 'chalk'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_CLIENT, GENERATOR_LANGUAGES, GENERATOR_REACT } from '../generator-list.js'; +import { writeEntitiesFiles, postWriteEntitiesFiles, cleanupEntitiesFiles } from './entity-files-react.js'; +import cleanupOldFilesTask from './cleanup.js'; +import { writeFiles } from './files-react.js'; +import { prepareEntity } from './application/entities/index.js'; +import { fieldTypes, clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { + generateEntityClientEnumImports as getClientEnumImportsFormat, + generateEntityClientFields as getHydratedEntityClientFields, + generateEntityClientImports as formatEntityClientImports, + generateTestEntityId as getTestEntityId, + generateTestEntityPrimaryKey as getTestEntityPrimaryKey, +} from '../client/support/index.js'; +import { isTranslatedReactFile, translateReactFilesTransform } from './support/index.js'; +import { createNeedleCallback, upperFirstCamelCase } from '../base/support/index.js'; + +const { CommonDBTypes } = fieldTypes; +const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN; +const { REACT } = clientFrameworkTypes; +/** + * @class + * @extends {BaseApplicationGenerator} + */ +export default class ReactGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_REACT); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_CLIENT); + await this.dependsOnJHipster(GENERATOR_LANGUAGES); + } + } + + get loading() { + return this.asLoadingTaskGroup({ + loadPackageJson({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster(GENERATOR_REACT, 'resources', 'package.json'), + ); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application, source }) { + application.webappEnumerationsDir = `${application.clientSrcDir}app/shared/model/enumerations/`; + + source.addWebpackConfig = args => { + const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; + const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; + this.editFile( + webpackPath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-webpack-config', + contentToAdd: `,${args.config}`, + }), + ); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); + } + + get preparingEachEntity() { + return this.asPreparingEachEntityTaskGroup({ + react({ application, entity }) { + prepareEntity({ entity, application }); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { + return this.asPreparingEachEntityTaskGroup(this.delegateTasksToBlueprint(() => this.preparingEachEntity)); + } + + get default() { + return this.asDefaultTaskGroup({ + queueTranslateTransform({ control, application }) { + if (!application.enableTranslation) { + this.queueTransformStream( + { + name: 'translating react application', + filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedReactFile(file), + refresh: false, + }, + translateReactFilesTransform(control.getWebappTranslation), + ); + } + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return { + cleanupOldFilesTask, + writeFiles, + }; + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupEntitiesFiles, + writeEntitiesFiles, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWritingEntities() { + return { + postWriteEntitiesFiles, + }; + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.postWritingEntities); + } + + get end() { + return this.asEndTaskGroup({ + end({ application }) { + this.log.ok('React application generated successfully.'); + this.log.log( + chalk.green(` Start your Webpack development server with: + ${chalk.yellow.bold(`${application.nodePackageManager} start`)} +`), + ); + }, + }); + } + + get [BaseApplicationGenerator.END]() { + return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); + } + + /** + * @private + * Add a new entity in the "entities" menu. + * + * @param {string} routerName - The name of the Angular router (which by default is the name of the entity). + * @param {boolean} enableTranslation - If translations are enabled or not + * @param {string} entityTranslationKeyMenu - i18n key for entity entry in menu + * @param {string} entityTranslationValue - i18n value for entity entry in menu + */ + addEntityToMenu( + routerName, + enableTranslation, + entityTranslationKeyMenu = _.camelCase(routerName), + entityTranslationValue = _.startCase(routerName), + ) { + this.needleApi.clientReact.addEntityToMenu(routerName, enableTranslation, entityTranslationKeyMenu, entityTranslationValue); + } + + /** + * @experimental + * Add a new entity in the TS modules file. + * + * @param {string} entityInstance - Entity Instance + * @param {string} entityClass - Entity Class + * @param {string} entityName - Entity Name + * @param {string} entityFolderName - Entity Folder Name + * @param {string} entityFileName - Entity File Name + * @param {string} entityUrl - Entity router URL + * @param {string} clientFramework - The name of the client framework + * @param {string} microserviceName - Microservice Name + * @param {boolean} readOnly - If the entity is read-only or not + * @param {string} pageTitle - The translation key or the text for the page title in the browser + */ + addEntityToModule( + entityInstance = this.entityInstance, + entityClass = this.entityClass, + entityName = this.entityAngularName, + entityFolderName = this.entityFolderName, + entityFileName = this.entityFileName, + { applicationTypeMicroservice, clientSrcDir }, + ) { + this.needleApi.clientReact.addEntityToModule(entityInstance, entityClass, entityName, entityFolderName, entityFileName, { + applicationTypeMicroservice, + clientSrcDir, + }); + } + + /** + * @private + * Generate Entity Client Field Default Values + * + * @param {Array|Object} fields - array of fields + * @returns {Array} defaultVariablesValues + */ + generateEntityClientFieldDefaultValues(fields) { + const defaultVariablesValues = {}; + fields.forEach(field => { + const fieldType = field.fieldType; + const fieldName = field.fieldName; + if (fieldType === TYPE_BOOLEAN) { + defaultVariablesValues[fieldName] = `${fieldName}: false,`; + } + }); + return defaultVariablesValues; + } + + generateEntityClientFields(primaryKey, fields, relationships, dto, customDateType = 'dayjs.Dayjs', embedded = false) { + return getHydratedEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded, REACT); + } + + generateEntityClientImports(relationships, dto) { + return formatEntityClientImports(relationships, dto, REACT); + } + + generateEntityClientEnumImports(fields) { + return getClientEnumImportsFormat(fields, REACT); + } + + generateTestEntityId(primaryKey, index = 0, wrapped = true) { + return getTestEntityId(primaryKey, index, wrapped); + } + + generateTestEntityPrimaryKey(primaryKey, index) { + return getTestEntityPrimaryKey(primaryKey, index); + } + + /** + * @private + * Add new scss style to the react application in "app.scss". + * + * @param {string} style - css to add in the file + * @param {string} comment - comment to add before css code + * + * example: + * + * style = '.jhipster {\n color: #baa186;\n}' + * comment = 'New JHipster color' + * + * * ========================================================================== + * New JHipster color + * ========================================================================== * + * .jhipster { + * color: #baa186; + * } + * + */ + addAppSCSSStyle(style, comment) { + this.needleApi.clientReact.addAppSCSSStyle(style, comment); + } + + /** + * get the an upperFirst camelCase value. + * @param {string} value string to convert + */ + upperFirstCamelCase(value) { + return upperFirstCamelCase(value); + } +} diff --git a/generators/react/generator.mjs b/generators/react/generator.mjs deleted file mode 100644 index 7b4548ac7a9a..000000000000 --- a/generators/react/generator.mjs +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { isFileStateModified } from 'mem-fs-editor/state'; -import chalk from 'chalk'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_CLIENT, GENERATOR_LANGUAGES, GENERATOR_REACT } from '../generator-list.mjs'; -import { writeEntitiesFiles, postWriteEntitiesFiles, cleanupEntitiesFiles } from './entity-files-react.mjs'; -import cleanupOldFilesTask from './cleanup.mjs'; -import { writeFiles } from './files-react.mjs'; -import { prepareEntity } from './application/entities/index.mjs'; -import { fieldTypes, clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { - generateEntityClientEnumImports as getClientEnumImportsFormat, - generateEntityClientFields as getHydratedEntityClientFields, - generateEntityClientImports as formatEntityClientImports, - generateTestEntityId as getTestEntityId, - generateTestEntityPrimaryKey as getTestEntityPrimaryKey, -} from '../client/support/index.mjs'; -import { isTranslatedReactFile, translateReactFilesTransform } from './support/index.mjs'; -import { createNeedleCallback, upperFirstCamelCase } from '../base/support/index.mjs'; - -const { CommonDBTypes } = fieldTypes; -const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN; -const { REACT } = clientFrameworkTypes; -/** - * @class - * @extends {BaseApplicationGenerator} - */ -export default class ReactGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_REACT); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_CLIENT); - await this.dependsOnJHipster(GENERATOR_LANGUAGES); - } - } - - get loading() { - return this.asLoadingTaskGroup({ - loadPackageJson({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_REACT, 'resources', 'package.json'), - ); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application, source }) { - application.webappEnumerationsDir = `${application.clientSrcDir}app/shared/model/enumerations/`; - - source.addWebpackConfig = args => { - const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; - const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; - this.editFile( - webpackPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-webpack-config', - contentToAdd: `,${args.config}`, - }), - ); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); - } - - get preparingEachEntity() { - return this.asPreparingEachEntityTaskGroup({ - react({ application, entity }) { - prepareEntity({ entity, application }); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { - return this.asPreparingEachEntityTaskGroup(this.delegateTasksToBlueprint(() => this.preparingEachEntity)); - } - - get default() { - return this.asDefaultTaskGroup({ - queueTranslateTransform({ control, application }) { - if (!application.enableTranslation) { - this.queueTransformStream( - { - name: 'translating react application', - filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedReactFile(file), - refresh: false, - }, - translateReactFilesTransform(control.getWebappTranslation), - ); - } - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return { - cleanupOldFilesTask, - writeFiles, - }; - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupEntitiesFiles, - writeEntitiesFiles, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWritingEntities() { - return { - postWriteEntitiesFiles, - }; - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.postWritingEntities); - } - - get end() { - return this.asEndTaskGroup({ - end({ application }) { - this.log.ok('React application generated successfully.'); - this.log.log( - chalk.green(` Start your Webpack development server with: - ${chalk.yellow.bold(`${application.nodePackageManager} start`)} -`), - ); - }, - }); - } - - get [BaseApplicationGenerator.END]() { - return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); - } - - /** - * @private - * Add a new entity in the "entities" menu. - * - * @param {string} routerName - The name of the Angular router (which by default is the name of the entity). - * @param {boolean} enableTranslation - If translations are enabled or not - * @param {string} entityTranslationKeyMenu - i18n key for entity entry in menu - * @param {string} entityTranslationValue - i18n value for entity entry in menu - */ - addEntityToMenu( - routerName, - enableTranslation, - entityTranslationKeyMenu = _.camelCase(routerName), - entityTranslationValue = _.startCase(routerName), - ) { - this.needleApi.clientReact.addEntityToMenu(routerName, enableTranslation, entityTranslationKeyMenu, entityTranslationValue); - } - - /** - * @experimental - * Add a new entity in the TS modules file. - * - * @param {string} entityInstance - Entity Instance - * @param {string} entityClass - Entity Class - * @param {string} entityName - Entity Name - * @param {string} entityFolderName - Entity Folder Name - * @param {string} entityFileName - Entity File Name - * @param {string} entityUrl - Entity router URL - * @param {string} clientFramework - The name of the client framework - * @param {string} microserviceName - Microservice Name - * @param {boolean} readOnly - If the entity is read-only or not - * @param {string} pageTitle - The translation key or the text for the page title in the browser - */ - addEntityToModule( - entityInstance = this.entityInstance, - entityClass = this.entityClass, - entityName = this.entityAngularName, - entityFolderName = this.entityFolderName, - entityFileName = this.entityFileName, - { applicationTypeMicroservice, clientSrcDir }, - ) { - this.needleApi.clientReact.addEntityToModule(entityInstance, entityClass, entityName, entityFolderName, entityFileName, { - applicationTypeMicroservice, - clientSrcDir, - }); - } - - /** - * @private - * Generate Entity Client Field Default Values - * - * @param {Array|Object} fields - array of fields - * @returns {Array} defaultVariablesValues - */ - generateEntityClientFieldDefaultValues(fields) { - const defaultVariablesValues = {}; - fields.forEach(field => { - const fieldType = field.fieldType; - const fieldName = field.fieldName; - if (fieldType === TYPE_BOOLEAN) { - defaultVariablesValues[fieldName] = `${fieldName}: false,`; - } - }); - return defaultVariablesValues; - } - - generateEntityClientFields(primaryKey, fields, relationships, dto, customDateType = 'dayjs.Dayjs', embedded = false) { - return getHydratedEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded, REACT); - } - - generateEntityClientImports(relationships, dto) { - return formatEntityClientImports(relationships, dto, REACT); - } - - generateEntityClientEnumImports(fields) { - return getClientEnumImportsFormat(fields, REACT); - } - - generateTestEntityId(primaryKey, index = 0, wrapped = true) { - return getTestEntityId(primaryKey, index, wrapped); - } - - generateTestEntityPrimaryKey(primaryKey, index) { - return getTestEntityPrimaryKey(primaryKey, index); - } - - /** - * @private - * Add new scss style to the react application in "app.scss". - * - * @param {string} style - css to add in the file - * @param {string} comment - comment to add before css code - * - * example: - * - * style = '.jhipster {\n color: #baa186;\n}' - * comment = 'New JHipster color' - * - * * ========================================================================== - * New JHipster color - * ========================================================================== * - * .jhipster { - * color: #baa186; - * } - * - */ - addAppSCSSStyle(style, comment) { - this.needleApi.clientReact.addAppSCSSStyle(style, comment); - } - - /** - * get the an upperFirst camelCase value. - * @param {string} value string to convert - */ - upperFirstCamelCase(value) { - return upperFirstCamelCase(value); - } -} diff --git a/generators/react/generator.spec.mts b/generators/react/generator.spec.mts deleted file mode 100644 index b2eec37ebb35..000000000000 --- a/generators/react/generator.spec.mts +++ /dev/null @@ -1,134 +0,0 @@ -import { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildClientSamples, - entitiesClientSamples as entities, - checkEnforcements, - defaultHelpers as helpers, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_REACT } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const { REACT: clientFramework } = clientFrameworkTypes; -const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const testSamples = buildClientSamples(commonConfig); - -const clientAdminFiles = clientSrcDir => [ - `${clientSrcDir}app/modules/administration/configuration/configuration.tsx`, - `${clientSrcDir}app/modules/administration/health/health.tsx`, - `${clientSrcDir}app/modules/administration/health/health-modal.tsx`, - `${clientSrcDir}app/modules/administration/metrics/metrics.tsx`, - `${clientSrcDir}app/modules/administration/logs/logs.tsx`, -]; - -class MockedLanguagesGenerator extends BaseApplicationGenerator { - get [BaseApplicationGenerator.PREPARING]() { - return { - mockTranslations({ control }) { - control.getWebappTranslation = () => 'translations'; - }, - }; - } -} - -describe(`generator - ${clientFramework}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({ client: true }, GENERATOR_REACT); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { clientRootDir = '' } = sampleConfig; - - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(sampleConfig, entities) - .withGenerators([[MockedLanguagesGenerator, { namespace: 'jhipster:languages' }]]) - .withMockedGenerators(['jhipster:common']); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct clientFramework', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); - }); - it('should not contain version placeholders at package.json', () => { - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); - }); - - describe('withAdminUi', () => { - const { applicationType, withAdminUi } = sampleConfig; - const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; - const generateAdminUi = applicationType !== 'microservice' && withAdminUi; - const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; - - it(adminUiComponents, () => { - const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); - assertion(clientAdminFiles(clientSrcDir)); - }); - - if (applicationType !== 'microservice') { - const adminUiRoutingTitle = generateAdminUi ? 'should generate admin related code' : 'should not generate admin related code'; - const assertion = (...args) => (generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args)); - - it(adminUiRoutingTitle, () => { - assertion( - `${clientSrcDir}app/modules/administration/administration.reducer.ts`, - 'logs: {\n' + - ' loggers: [] as any[]\n' + - ' },\n' + - ' health: {} as any,\n' + - ' metrics: {} as any,\n' + - ' threadDump: [],\n' + - ' configuration: {\n' + - ' configProps: {} as any,\n' + - ' env: {} as any\n' + - ' },', - ); - - assertion( - `${clientSrcDir}app/shared/layout/menus/admin.tsx`, - ' Metrics\n' + - ' Health\n' + - ' Configuration\n' + - ' Logs', - ); - }); - } - }); - }); - }); -}); diff --git a/generators/react/generator.spec.ts b/generators/react/generator.spec.ts new file mode 100644 index 000000000000..668b606ab756 --- /dev/null +++ b/generators/react/generator.spec.ts @@ -0,0 +1,134 @@ +import { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildClientSamples, + entitiesClientSamples as entities, + checkEnforcements, + defaultHelpers as helpers, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_REACT } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const { REACT: clientFramework } = clientFrameworkTypes; +const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const testSamples = buildClientSamples(commonConfig); + +const clientAdminFiles = clientSrcDir => [ + `${clientSrcDir}app/modules/administration/configuration/configuration.tsx`, + `${clientSrcDir}app/modules/administration/health/health.tsx`, + `${clientSrcDir}app/modules/administration/health/health-modal.tsx`, + `${clientSrcDir}app/modules/administration/metrics/metrics.tsx`, + `${clientSrcDir}app/modules/administration/logs/logs.tsx`, +]; + +class MockedLanguagesGenerator extends BaseApplicationGenerator { + get [BaseApplicationGenerator.PREPARING]() { + return { + mockTranslations({ control }) { + control.getWebappTranslation = () => 'translations'; + }, + }; + } +} + +describe(`generator - ${clientFramework}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({ client: true }, GENERATOR_REACT); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { clientRootDir = '' } = sampleConfig; + + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(sampleConfig, entities) + .withGenerators([[MockedLanguagesGenerator, { namespace: 'jhipster:languages' }]]) + .withMockedGenerators(['jhipster:common']); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct clientFramework', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); + }); + it('should not contain version placeholders at package.json', () => { + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); + }); + + describe('withAdminUi', () => { + const { applicationType, withAdminUi } = sampleConfig; + const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; + const generateAdminUi = applicationType !== 'microservice' && withAdminUi; + const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; + + it(adminUiComponents, () => { + const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); + assertion(clientAdminFiles(clientSrcDir)); + }); + + if (applicationType !== 'microservice') { + const adminUiRoutingTitle = generateAdminUi ? 'should generate admin related code' : 'should not generate admin related code'; + const assertion = (...args) => (generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args)); + + it(adminUiRoutingTitle, () => { + assertion( + `${clientSrcDir}app/modules/administration/administration.reducer.ts`, + 'logs: {\n' + + ' loggers: [] as any[]\n' + + ' },\n' + + ' health: {} as any,\n' + + ' metrics: {} as any,\n' + + ' threadDump: [],\n' + + ' configuration: {\n' + + ' configProps: {} as any,\n' + + ' env: {} as any\n' + + ' },', + ); + + assertion( + `${clientSrcDir}app/shared/layout/menus/admin.tsx`, + ' Metrics\n' + + ' Health\n' + + ' Configuration\n' + + ' Logs', + ); + }); + } + }); + }); + }); +}); diff --git a/generators/react/index.mts b/generators/react/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/react/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/react/index.ts b/generators/react/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/react/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/react/needle-api/needle-client-react.mts b/generators/react/needle-api/needle-client-react.mts deleted file mode 100644 index bcbf847aca8d..000000000000 --- a/generators/react/needle-api/needle-client-react.mts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import needleClientBase from '../../client/needle-api/needle-client.mjs'; -import { stripMargin } from '../../base/support/index.mjs'; - -export default class extends needleClientBase { - addAppSCSSStyle(style: string, comment: string) { - const filePath = `${this.clientSrcDir}app/app.scss`; - this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main'); - } - - addEntityToMenu( - routerName: string, - enableTranslation: boolean, - entityTranslationKeyMenu: string, - entityTranslationValue: string = _.startCase(routerName), - ) { - const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; - const entityMenuPath = `${this.clientSrcDir}app/entities/menu.tsx`; - const entityEntry = - // prettier-ignore - stripMargin(`| - | ${enableTranslation ? `` : `${entityTranslationValue}`} - | `); - const rewriteFileModel = this.generateFileModel(entityMenuPath, 'jhipster-needle-add-entity-to-menu', entityEntry); - - this.addBlockContentToFile(rewriteFileModel, errorMessage); - } - - addEntityToModule( - entityInstance: string, - entityClass: string, - entityName: string, - entityFolderName: string, - entityFileName: string, - { applicationTypeMicroservice, clientSrcDir }: { applicationTypeMicroservice: boolean; clientSrcDir: string }, - ) { - const indexModulePath = `${clientSrcDir}app/entities/routes.tsx`; - const indexReducerPath = `${clientSrcDir}app/entities/reducers.ts`; - - const errorMessage = path => - `${chalk.yellow('Reference to ') + entityInstance + entityClass + entityFolderName + entityFileName} ${chalk.yellow( - `not added to ${path}.\n`, - )}`; - - const indexAddRouteImportRewriteFileModel = this.generateFileModel( - indexModulePath, - 'jhipster-needle-add-route-import', - stripMargin(`|import ${entityName} from './${entityFolderName}';`), - ); - this.addBlockContentToFile(indexAddRouteImportRewriteFileModel, errorMessage(indexModulePath)); - - const indexAddRoutePathRewriteFileModel = this.generateFileModel( - indexModulePath, - 'jhipster-needle-add-route-path', - stripMargin(`|} />`), - ); - this.addBlockContentToFile(indexAddRoutePathRewriteFileModel, errorMessage(indexModulePath)); - - const reducerAddImportRewriteFileModel = this.generateFileModel( - indexReducerPath, - 'jhipster-needle-add-reducer-import', // prettier-ignore - stripMargin(`import ${entityInstance} from 'app/entities/${entityFolderName}/${entityFileName}.reducer';`), - ); - this.addBlockContentToFile(reducerAddImportRewriteFileModel, errorMessage(indexReducerPath)); - - const reducerAddCombineRewriteFileModel = this.generateFileModel( - indexReducerPath, - 'jhipster-needle-add-reducer-combine', - stripMargin(`| ${entityInstance},`), - ); - this.addBlockContentToFile(reducerAddCombineRewriteFileModel, errorMessage(indexReducerPath)); - } -} diff --git a/generators/react/needle-api/needle-client-react.ts b/generators/react/needle-api/needle-client-react.ts new file mode 100644 index 000000000000..53bd7045f01d --- /dev/null +++ b/generators/react/needle-api/needle-client-react.ts @@ -0,0 +1,93 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import needleClientBase from '../../client/needle-api/needle-client.js'; +import { stripMargin } from '../../base/support/index.js'; + +export default class extends needleClientBase { + addAppSCSSStyle(style: string, comment: string) { + const filePath = `${this.clientSrcDir}app/app.scss`; + this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main'); + } + + addEntityToMenu( + routerName: string, + enableTranslation: boolean, + entityTranslationKeyMenu: string, + entityTranslationValue: string = _.startCase(routerName), + ) { + const errorMessage = `${chalk.yellow('Reference to ') + routerName} ${chalk.yellow('not added to menu.\n')}`; + const entityMenuPath = `${this.clientSrcDir}app/entities/menu.tsx`; + const entityEntry = + // prettier-ignore + stripMargin(`| + | ${enableTranslation ? `` : `${entityTranslationValue}`} + | `); + const rewriteFileModel = this.generateFileModel(entityMenuPath, 'jhipster-needle-add-entity-to-menu', entityEntry); + + this.addBlockContentToFile(rewriteFileModel, errorMessage); + } + + addEntityToModule( + entityInstance: string, + entityClass: string, + entityName: string, + entityFolderName: string, + entityFileName: string, + { applicationTypeMicroservice, clientSrcDir }: { applicationTypeMicroservice: boolean; clientSrcDir: string }, + ) { + const indexModulePath = `${clientSrcDir}app/entities/routes.tsx`; + const indexReducerPath = `${clientSrcDir}app/entities/reducers.ts`; + + const errorMessage = path => + `${chalk.yellow('Reference to ') + entityInstance + entityClass + entityFolderName + entityFileName} ${chalk.yellow( + `not added to ${path}.\n`, + )}`; + + const indexAddRouteImportRewriteFileModel = this.generateFileModel( + indexModulePath, + 'jhipster-needle-add-route-import', + stripMargin(`|import ${entityName} from './${entityFolderName}';`), + ); + this.addBlockContentToFile(indexAddRouteImportRewriteFileModel, errorMessage(indexModulePath)); + + const indexAddRoutePathRewriteFileModel = this.generateFileModel( + indexModulePath, + 'jhipster-needle-add-route-path', + stripMargin(`|} />`), + ); + this.addBlockContentToFile(indexAddRoutePathRewriteFileModel, errorMessage(indexModulePath)); + + const reducerAddImportRewriteFileModel = this.generateFileModel( + indexReducerPath, + 'jhipster-needle-add-reducer-import', // prettier-ignore + stripMargin(`import ${entityInstance} from 'app/entities/${entityFolderName}/${entityFileName}.reducer';`), + ); + this.addBlockContentToFile(reducerAddImportRewriteFileModel, errorMessage(indexReducerPath)); + + const reducerAddCombineRewriteFileModel = this.generateFileModel( + indexReducerPath, + 'jhipster-needle-add-reducer-combine', + stripMargin(`| ${entityInstance},`), + ); + this.addBlockContentToFile(reducerAddCombineRewriteFileModel, errorMessage(indexReducerPath)); + } +} diff --git a/generators/react/support/index.mts b/generators/react/support/index.mts deleted file mode 100644 index 4f324a52d3df..000000000000 --- a/generators/react/support/index.mts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2023 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 './needles.mjs'; -export * from './translate-react.mjs'; -export { default as translateReactFilesTransform } from './translate-react.mjs'; -export { default as updateLanguagesTask } from './update-languages.mjs'; diff --git a/generators/react/support/index.ts b/generators/react/support/index.ts new file mode 100644 index 000000000000..1fd20edf18ec --- /dev/null +++ b/generators/react/support/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2013-2023 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 './needles.js'; +export * from './translate-react.js'; +export { default as translateReactFilesTransform } from './translate-react.js'; +export { default as updateLanguagesTask } from './update-languages.js'; diff --git a/generators/react/support/needles.mjs b/generators/react/support/needles.js similarity index 100% rename from generators/react/support/needles.mjs rename to generators/react/support/needles.js diff --git a/generators/react/support/translate-react.js b/generators/react/support/translate-react.js new file mode 100644 index 000000000000..b9c716745ca0 --- /dev/null +++ b/generators/react/support/translate-react.js @@ -0,0 +1,136 @@ +/** + * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; +import { Minimatch } from 'minimatch'; + +const TRANSLATE_IMPORT_1 = /import { ?[T|t]ranslate(?:, ?[T|t]ranslate)? ?} from 'react-jhipster';?/.source; // Translate imports +const TRANSLATE_IMPORT_2 = / *[T|t]ranslate,|, ?[T|t]ranslate/.source; // Translate import +const TRANSLATE_IMPORT = [TRANSLATE_IMPORT_1, TRANSLATE_IMPORT_2].join('|'); + +const TRANSLATE_FUNCTION = /translate\(\s*'(?[^']+)'(?:,\s*(?\{[^}]*\}))?\s*\)/g.source; + +const CONTENT_TYPE_ATTRIBUTE = 'contentKey=(?:"(?[^"]+)"|\\{[^\\}]+\\})\\s*'; +const INTERPOLATE_ATTRIBUTE = 'interpolate=\\{(?\\{[^\\}]+\\})\\}\\s*'; +const COMPONENT_ATTRIBUTE = 'component="(?[^"]+)"\\s*'; +const TRANSLATE_TAG = `(?[\\s\\S]*?)<\\/Translate>`; + +function getTranslationValue(getWebappTranslation, key, data) { + return getWebappTranslation(key, data) || undefined; +} + +const replaceTranslationKeysWithText = ( + getWebappTranslation, + body, + regexp, + { keyPattern, interpolatePattern, wrapTranslation, escapeHtml } = {}, +) => { + const matches = body.matchAll(new RegExp(regexp, 'g')); + if (typeof wrapTranslation === 'string') { + wrapTranslation = [wrapTranslation, wrapTranslation]; + } + for (const match of matches) { + const target = match[0]; + + let key = match.groups && match.groups.key; + if (!key && keyPattern) { + const keyMatch = target.match(new RegExp(keyPattern)); + key = keyMatch && keyMatch.groups && keyMatch.groups.key; + } + if (!key) { + throw new Error(`Translation key not found for ${target}`); + } + + let interpolate = match.groups && match.groups.interpolate; + if (!interpolate && interpolatePattern) { + const interpolateMatch = target.match(new RegExp(interpolatePattern)); + interpolate = interpolateMatch && interpolateMatch.groups && interpolateMatch.groups.interpolate; + } + + let data; + if (interpolate) { + const interpolateMatches = interpolate.matchAll(/(?[^{\s:,}]+)(?::\s*(?[^,}]+))?/g); + data = {}; + for (const interpolateMatch of interpolateMatches) { + const field = interpolateMatch.groups.field; + let value = interpolateMatch.groups.value; + if (value === undefined) { + value = key; + } + value = value.trim(); + if (/^\d+$/.test(value)) { + // convert integer + value = parseInt(value, 10); + } else if (/^'.*'$/.test(value) || /^".*"$/.test(value)) { + // extract string value + value = value.substring(1, value.length - 2); + } else { + // wrap expression + value = `{${value}}`; + } + data[field] = value; + } + } + + const translation = getTranslationValue(getWebappTranslation, key, data); + + let replacement = translation; + if (!replacement) { + replacement = wrapTranslation ? `${wrapTranslation[0]}${wrapTranslation[1]}` : ''; + } else if (wrapTranslation) { + replacement = `${wrapTranslation[0]}${translation}${wrapTranslation[1]}`; + } else if (escapeHtml) { + // Escape specific chars + replacement = replacement.replace(/'/g, ''').replace(/"/g, '"'); + } + body = body.replace(target, replacement); + } + return body; +}; + +/** + * Replace and cleanup translations. + * + * @return {import('../../base/api.js').EditFileCallback} + */ +export const createTranslationReplacer = getWebappTranslation => + function replaceReactTranslations(body, filePath) { + if (/\.tsx$/.test(filePath)) { + body = body.replace(new RegExp(TRANSLATE_IMPORT, 'g'), ''); + body = replaceTranslationKeysWithText(getWebappTranslation, body, `\\{\\s*${TRANSLATE_FUNCTION}\\s*\\}`, { wrapTranslation: '"' }); + body = replaceTranslationKeysWithText(getWebappTranslation, body, TRANSLATE_FUNCTION, { wrapTranslation: '"' }); + body = replaceTranslationKeysWithText(getWebappTranslation, body, TRANSLATE_TAG, { + keyPattern: CONTENT_TYPE_ATTRIBUTE, + interpolatePattern: INTERPOLATE_ATTRIBUTE, + escapeHtml: true, + }); + } + return body; + }; + +const minimatch = new Minimatch('**/*.tsx'); +export const isTranslatedReactFile = file => minimatch.match(file.path); + +const translateReactFilesTransform = getWebappTranslation => { + const translate = createTranslationReplacer(getWebappTranslation); + return passthrough(file => { + file.contents = Buffer.from(translate(file.contents.toString(), file.path)); + }); +}; + +export default translateReactFilesTransform; diff --git a/generators/react/support/translate-react.mjs b/generators/react/support/translate-react.mjs deleted file mode 100644 index 9a93baf80422..000000000000 --- a/generators/react/support/translate-react.mjs +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; -import { Minimatch } from 'minimatch'; - -const TRANSLATE_IMPORT_1 = /import { ?[T|t]ranslate(?:, ?[T|t]ranslate)? ?} from 'react-jhipster';?/.source; // Translate imports -const TRANSLATE_IMPORT_2 = / *[T|t]ranslate,|, ?[T|t]ranslate/.source; // Translate import -const TRANSLATE_IMPORT = [TRANSLATE_IMPORT_1, TRANSLATE_IMPORT_2].join('|'); - -const TRANSLATE_FUNCTION = /translate\(\s*'(?[^']+)'(?:,\s*(?\{[^}]*\}))?\s*\)/g.source; - -const CONTENT_TYPE_ATTRIBUTE = 'contentKey=(?:"(?[^"]+)"|\\{[^\\}]+\\})\\s*'; -const INTERPOLATE_ATTRIBUTE = 'interpolate=\\{(?\\{[^\\}]+\\})\\}\\s*'; -const COMPONENT_ATTRIBUTE = 'component="(?[^"]+)"\\s*'; -const TRANSLATE_TAG = `(?[\\s\\S]*?)<\\/Translate>`; - -function getTranslationValue(getWebappTranslation, key, data) { - return getWebappTranslation(key, data) || undefined; -} - -const replaceTranslationKeysWithText = ( - getWebappTranslation, - body, - regexp, - { keyPattern, interpolatePattern, wrapTranslation, escapeHtml } = {}, -) => { - const matches = body.matchAll(new RegExp(regexp, 'g')); - if (typeof wrapTranslation === 'string') { - wrapTranslation = [wrapTranslation, wrapTranslation]; - } - for (const match of matches) { - const target = match[0]; - - let key = match.groups && match.groups.key; - if (!key && keyPattern) { - const keyMatch = target.match(new RegExp(keyPattern)); - key = keyMatch && keyMatch.groups && keyMatch.groups.key; - } - if (!key) { - throw new Error(`Translation key not found for ${target}`); - } - - let interpolate = match.groups && match.groups.interpolate; - if (!interpolate && interpolatePattern) { - const interpolateMatch = target.match(new RegExp(interpolatePattern)); - interpolate = interpolateMatch && interpolateMatch.groups && interpolateMatch.groups.interpolate; - } - - let data; - if (interpolate) { - const interpolateMatches = interpolate.matchAll(/(?[^{\s:,}]+)(?::\s*(?[^,}]+))?/g); - data = {}; - for (const interpolateMatch of interpolateMatches) { - const field = interpolateMatch.groups.field; - let value = interpolateMatch.groups.value; - if (value === undefined) { - value = key; - } - value = value.trim(); - if (/^\d+$/.test(value)) { - // convert integer - value = parseInt(value, 10); - } else if (/^'.*'$/.test(value) || /^".*"$/.test(value)) { - // extract string value - value = value.substring(1, value.length - 2); - } else { - // wrap expression - value = `{${value}}`; - } - data[field] = value; - } - } - - const translation = getTranslationValue(getWebappTranslation, key, data); - - let replacement = translation; - if (!replacement) { - replacement = wrapTranslation ? `${wrapTranslation[0]}${wrapTranslation[1]}` : ''; - } else if (wrapTranslation) { - replacement = `${wrapTranslation[0]}${translation}${wrapTranslation[1]}`; - } else if (escapeHtml) { - // Escape specific chars - replacement = replacement.replace(/'/g, ''').replace(/"/g, '"'); - } - body = body.replace(target, replacement); - } - return body; -}; - -/** - * Replace and cleanup translations. - * - * @return {import('../../base/api.mjs').EditFileCallback} - */ -export const createTranslationReplacer = getWebappTranslation => - function replaceReactTranslations(body, filePath) { - if (/\.tsx$/.test(filePath)) { - body = body.replace(new RegExp(TRANSLATE_IMPORT, 'g'), ''); - body = replaceTranslationKeysWithText(getWebappTranslation, body, `\\{\\s*${TRANSLATE_FUNCTION}\\s*\\}`, { wrapTranslation: '"' }); - body = replaceTranslationKeysWithText(getWebappTranslation, body, TRANSLATE_FUNCTION, { wrapTranslation: '"' }); - body = replaceTranslationKeysWithText(getWebappTranslation, body, TRANSLATE_TAG, { - keyPattern: CONTENT_TYPE_ATTRIBUTE, - interpolatePattern: INTERPOLATE_ATTRIBUTE, - escapeHtml: true, - }); - } - return body; - }; - -const minimatch = new Minimatch('**/*.tsx'); -export const isTranslatedReactFile = file => minimatch.match(file.path); - -const translateReactFilesTransform = getWebappTranslation => { - const translate = createTranslationReplacer(getWebappTranslation); - return passthrough(file => { - file.contents = Buffer.from(translate(file.contents.toString(), file.path)); - }); -}; - -export default translateReactFilesTransform; diff --git a/generators/react/support/translate-react.spec.js b/generators/react/support/translate-react.spec.js new file mode 100644 index 000000000000..e9f5550ffb64 --- /dev/null +++ b/generators/react/support/translate-react.spec.js @@ -0,0 +1,304 @@ +/** + * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; + +import { createTranslationReplacer } from './translate-react.js'; + +describe('generator - react - transform', () => { + describe('replaceReactTranslations', () => { + let replaceReactTranslations; + beforeEach(() => { + let value = 0; + replaceReactTranslations = createTranslationReplacer( + esmocha.fn().mockImplementation((key, interpolation = '') => { + if (interpolation) { + interpolation = `-${JSON.stringify(interpolation)}`; + } + return `${key}${interpolation}-translated-value-${value++}`; + }), + ); + }); + + describe('with translation disabled', () => { + describe('.tsx files', () => { + const extension = '.tsx'; + + it('should replace Translate tag', () => { + const body = ` +This is your homepage +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +home.subtitle-translated-value-0 +" +`); + }); + + it('should replace multine Translate tag with contentKey coming first', () => { + const body = ` + +Active sessions for [{account.login}] + +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +sessions.title-{"username":"{account.login}"}-translated-value-0 +" +`); + }); + + it('should replace multine Translate tag with interpolate comming first', () => { + const body = ` + +Active sessions for [{account.login}] + +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +sessions.title-{"username":"{account.login}"}-translated-value-0 +" +`); + }); + + it('should replace translate function', () => { + const body = ` +translate('global') +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +"global-translated-value-0" +" +`); + }); + + it('should replace translate function with interpolation', () => { + const body = ` +translate('global', { min:20, max: 50, pattern: '^[a-zA-Z0-9]*$', + anotherPattern: "^[a-zA-Z0-9]*$", + dynamic: exec(), +}) +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*","anotherPattern":"^[a-zA-Z0-9]*","dynamic":"{exec()}"}-translated-value-0" +" +`); + }); + + it('should replace wrapped translate function with interpolation', () => { + const body = ` +{translate('global', { min:20, max: 50, pattern: '^[a-zA-Z0-9]*$', + anotherPattern: "^[a-zA-Z0-9]*$", + dynamic: exec(), +})} +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*","anotherPattern":"^[a-zA-Z0-9]*","dynamic":"{exec()}"}-translated-value-0" +" +`); + }); + + it('should translate tsx file', () => { + const body = ` +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Translate } from 'react-jhipster'; +import { Row, Col, Alert } from 'reactstrap'; + +import { useAppSelector } from 'app/config/store'; + +export const Home = () => { + const account = useAppSelector(state => state.authentication.account); + + return ( + + + + + +

    Welcome, Hipster!

    +

    + This is your homepage +

    + { + (account?.login) ? ( +
    + + You are logged in as user {account.login}. + +
    + ) : ( +
    + + If you want to + <% if (!enableTranslation) { %> <% } %> + sign in + , you can try the default accounts: +
    - Administrator (login="admin" and password="admin") +
    - User (login="user" and password="user"). +
    +
    + + + You do not have an account yet?  + Register a new account + +
    + ) + } +

    + If you have any question on JHipster: +

    + + + +

    + If you like JHipster, do not forget to give us a star on + {' '} + GitHub! +

    + +
    + ); +}; + +export default Home; +`; + expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` +" +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { Row, Col, Alert } from 'reactstrap'; + +import { useAppSelector } from 'app/config/store'; + +export const Home = () => { + const account = useAppSelector(state => state.authentication.account); + + return ( + + + + + +

    home.title-translated-value-0

    +

    + home.subtitle-translated-value-1 +

    + { + (account?.login) ? ( +
    + + home.logged.message-{"username":"{account.login}"}-translated-value-2 + +
    + ) : ( +
    + + global.messages.info.authenticated.prefix-translated-value-3 + <% if (!enableTranslation) { %> <% } %> + global.messages.info.authenticated.link-translated-value-4 + global.messages.info.authenticated.suffix-translated-value-5 + + + + global.messages.info.register.noaccount-translated-value-6  + global.messages.info.register.link-translated-value-7 + +
    + ) + } +

    + home.question-translated-value-8 +

    + + + +

    + home.like-translated-value-14 + {' '} + GitHub! +

    + +
    + ); +}; + +export default Home; +" +`); + }); + }); + }); + }); +}); diff --git a/generators/react/support/translate-react.spec.mjs b/generators/react/support/translate-react.spec.mjs deleted file mode 100644 index 061f59b04aca..000000000000 --- a/generators/react/support/translate-react.spec.mjs +++ /dev/null @@ -1,304 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect, esmocha } from 'esmocha'; - -import { createTranslationReplacer } from './translate-react.mjs'; - -describe('generator - react - transform', () => { - describe('replaceReactTranslations', () => { - let replaceReactTranslations; - beforeEach(() => { - let value = 0; - replaceReactTranslations = createTranslationReplacer( - esmocha.fn().mockImplementation((key, interpolation = '') => { - if (interpolation) { - interpolation = `-${JSON.stringify(interpolation)}`; - } - return `${key}${interpolation}-translated-value-${value++}`; - }), - ); - }); - - describe('with translation disabled', () => { - describe('.tsx files', () => { - const extension = '.tsx'; - - it('should replace Translate tag', () => { - const body = ` -This is your homepage -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -home.subtitle-translated-value-0 -" -`); - }); - - it('should replace multine Translate tag with contentKey coming first', () => { - const body = ` - -Active sessions for [{account.login}] - -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -sessions.title-{"username":"{account.login}"}-translated-value-0 -" -`); - }); - - it('should replace multine Translate tag with interpolate comming first', () => { - const body = ` - -Active sessions for [{account.login}] - -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -sessions.title-{"username":"{account.login}"}-translated-value-0 -" -`); - }); - - it('should replace translate function', () => { - const body = ` -translate('global') -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -"global-translated-value-0" -" -`); - }); - - it('should replace translate function with interpolation', () => { - const body = ` -translate('global', { min:20, max: 50, pattern: '^[a-zA-Z0-9]*$', - anotherPattern: "^[a-zA-Z0-9]*$", - dynamic: exec(), -}) -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*","anotherPattern":"^[a-zA-Z0-9]*","dynamic":"{exec()}"}-translated-value-0" -" -`); - }); - - it('should replace wrapped translate function with interpolation', () => { - const body = ` -{translate('global', { min:20, max: 50, pattern: '^[a-zA-Z0-9]*$', - anotherPattern: "^[a-zA-Z0-9]*$", - dynamic: exec(), -})} -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -"global-{"min":20,"max":50,"pattern":"^[a-zA-Z0-9]*","anotherPattern":"^[a-zA-Z0-9]*","dynamic":"{exec()}"}-translated-value-0" -" -`); - }); - - it('should translate tsx file', () => { - const body = ` -import React from 'react'; -import { Link } from 'react-router-dom'; -import { Translate } from 'react-jhipster'; -import { Row, Col, Alert } from 'reactstrap'; - -import { useAppSelector } from 'app/config/store'; - -export const Home = () => { - const account = useAppSelector(state => state.authentication.account); - - return ( - - - - - -

    Welcome, Hipster!

    -

    - This is your homepage -

    - { - (account?.login) ? ( -
    - - You are logged in as user {account.login}. - -
    - ) : ( -
    - - If you want to - <% if (!enableTranslation) { %> <% } %> - sign in - , you can try the default accounts: -
    - Administrator (login="admin" and password="admin") -
    - User (login="user" and password="user"). -
    -
    - - - You do not have an account yet?  - Register a new account - -
    - ) - } -

    - If you have any question on JHipster: -

    - - - -

    - If you like JHipster, do not forget to give us a star on - {' '} - GitHub! -

    - -
    - ); -}; - -export default Home; -`; - expect(replaceReactTranslations(body, extension)).toMatchInlineSnapshot(` -" -import React from 'react'; -import { Link } from 'react-router-dom'; - -import { Row, Col, Alert } from 'reactstrap'; - -import { useAppSelector } from 'app/config/store'; - -export const Home = () => { - const account = useAppSelector(state => state.authentication.account); - - return ( - - - - - -

    home.title-translated-value-0

    -

    - home.subtitle-translated-value-1 -

    - { - (account?.login) ? ( -
    - - home.logged.message-{"username":"{account.login}"}-translated-value-2 - -
    - ) : ( -
    - - global.messages.info.authenticated.prefix-translated-value-3 - <% if (!enableTranslation) { %> <% } %> - global.messages.info.authenticated.link-translated-value-4 - global.messages.info.authenticated.suffix-translated-value-5 - - - - global.messages.info.register.noaccount-translated-value-6  - global.messages.info.register.link-translated-value-7 - -
    - ) - } -

    - home.question-translated-value-8 -

    - - - -

    - home.like-translated-value-14 - {' '} - GitHub! -

    - -
    - ); -}; - -export default Home; -" -`); - }); - }); - }); - }); -}); diff --git a/generators/react/support/update-languages.mts b/generators/react/support/update-languages.mts deleted file mode 100644 index 063765400a2c..000000000000 --- a/generators/react/support/update-languages.mts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../../base/index.mjs'; -import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/index.mjs'; -import { generateLanguagesWebappOptions } from '../../languages/support/index.mjs'; - -function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languagesDefinition = [] } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - const newContent = `{ - ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')} - // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object - }; -`; - this.editFile(`${clientSrcDir}/app/config/translation.ts`, { ignoreNonExisting }, content => - content.replace(/{\s*('[a-z-]*':)?([^=]*jhipster-needle-i18n-language-key-pipe[^;]*)\};/g, newContent), - ); -} - -function updateLanguagesInWebpackReactTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientRootDir, clientSrcDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'groupBy: [\n'; - // prettier-ignore - languages?.forEach((language) => { - newContent += ` { pattern: "./${this.relativeDir(clientRootDir, clientSrcDir)}i18n/${language}/*.json", fileName: "./i18n/${language}.json" },\n`; - }); - newContent += - ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n' + - ' ]'; - - this.editFile(`${application.clientRootDir}webpack/webpack.common.js`, { ignoreNonExisting }, content => - content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), - ); -} - -export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateClientLanguagesTaskParam) { - updateLanguagesInPipeTask.call(this, taskParam); - updateLanguagesInWebpackReactTask.call(this, taskParam); - updateLanguagesInDayjsConfigurationTask.call(this, taskParam, { - configurationFile: `${taskParam.application.clientSrcDir}app/config/dayjs.ts`, - commonjs: true, - }); -} diff --git a/generators/react/support/update-languages.ts b/generators/react/support/update-languages.ts new file mode 100644 index 000000000000..78de4e3cc87b --- /dev/null +++ b/generators/react/support/update-languages.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../../base/index.js'; +import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/index.js'; +import { generateLanguagesWebappOptions } from '../../languages/support/index.js'; + +function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languagesDefinition = [] } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + const newContent = `{ + ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')} + // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object + }; +`; + this.editFile(`${clientSrcDir}/app/config/translation.ts`, { ignoreNonExisting }, content => + content.replace(/{\s*('[a-z-]*':)?([^=]*jhipster-needle-i18n-language-key-pipe[^;]*)\};/g, newContent), + ); +} + +function updateLanguagesInWebpackReactTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientRootDir, clientSrcDir, languages } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + let newContent = 'groupBy: [\n'; + // prettier-ignore + languages?.forEach((language) => { + newContent += ` { pattern: "./${this.relativeDir(clientRootDir, clientSrcDir)}i18n/${language}/*.json", fileName: "./i18n/${language}.json" },\n`; + }); + newContent += + ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n' + + ' ]'; + + this.editFile(`${application.clientRootDir}webpack/webpack.common.js`, { ignoreNonExisting }, content => + content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), + ); +} + +export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateClientLanguagesTaskParam) { + updateLanguagesInPipeTask.call(this, taskParam); + updateLanguagesInWebpackReactTask.call(this, taskParam); + updateLanguagesInDayjsConfigurationTask.call(this, taskParam, { + configurationFile: `${taskParam.application.clientSrcDir}app/config/dayjs.ts`, + commonjs: true, + }); +} diff --git a/generators/server/__snapshots__/generator.spec.mjs.snap b/generators/server/__snapshots__/generator.spec.js.snap similarity index 100% rename from generators/server/__snapshots__/generator.spec.mjs.snap rename to generators/server/__snapshots__/generator.spec.js.snap diff --git a/generators/server/__test-support/index.mts b/generators/server/__test-support/index.mts deleted file mode 100644 index bd38689fcd0b..000000000000 --- a/generators/server/__test-support/index.mts +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import assert from 'assert'; - -import { messageBrokerTypes, databaseTypes } from '../../../jdl/jhipster/index.mjs'; -import { - GENERATOR_CUCUMBER, - GENERATOR_GATLING, - GENERATOR_SPRING_CACHE, - GENERATOR_SPRING_WEBSOCKET, - GENERATOR_SPRING_DATA_RELATIONAL, - GENERATOR_SPRING_DATA_CASSANDRA, - GENERATOR_SPRING_DATA_COUCHBASE, - GENERATOR_SPRING_DATA_MONGODB, - GENERATOR_MAVEN, - GENERATOR_LIQUIBASE, - GENERATOR_LANGUAGES, - GENERATOR_SPRING_CLOUD_STREAM, - GENERATOR_GRADLE, - GENERATOR_DOCKER, - GENERATOR_COMMON, -} from '../../generator-list.mjs'; - -const { KAFKA, PULSAR } = messageBrokerTypes; -const { SQL, COUCHBASE } = databaseTypes; - -export const mockedGenerators = [ - `jhipster:${GENERATOR_COMMON}`, - `jhipster:${GENERATOR_SPRING_DATA_COUCHBASE}`, - `jhipster:${GENERATOR_CUCUMBER}`, - `jhipster:${GENERATOR_DOCKER}`, - `jhipster:${GENERATOR_GATLING}`, - `jhipster:${GENERATOR_GRADLE}`, - `jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`, - `jhipster:${GENERATOR_LANGUAGES}`, - `jhipster:${GENERATOR_LIQUIBASE}`, - `jhipster:${GENERATOR_MAVEN}`, - `jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`, - `jhipster:${GENERATOR_SPRING_DATA_MONGODB}`, - `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`, - `jhipster:${GENERATOR_SPRING_CACHE}`, - `jhipster:${GENERATOR_SPRING_WEBSOCKET}`, -]; - -export const shouldComposeWithLiquibase = (testSample, runResultSupplier) => { - const liquibaseEnabled = typeof testSample === 'boolean' ? testSample : testSample?.databaseType === SQL; - if (liquibaseEnabled) { - it('should compose with liquibase generator', () => { - assert(runResultSupplier().mockedGenerators['jhipster:liquibase'].calledOnce); - }); - } else { - it('should not compose with liquibase generator', () => { - assert(runResultSupplier().mockedGenerators['jhipster:liquibase'].notCalled); - }); - } -}; - -export const shouldComposeWithSpringCloudStream = (sampleConfig, runResultSupplier) => { - const pulsarEnabled = typeof sampleConfig === 'boolean' ? sampleConfig : sampleConfig?.messageBroker === PULSAR; - const kafkaEnabled = typeof sampleConfig === 'boolean' ? sampleConfig : sampleConfig?.messageBroker === KAFKA; - if (pulsarEnabled || kafkaEnabled) { - it(`should compose with ${GENERATOR_SPRING_CLOUD_STREAM} generator`, () => { - assert(runResultSupplier().mockedGenerators[`jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`].calledOnce); - }); - } else { - it(`should not compose with ${GENERATOR_SPRING_CLOUD_STREAM} generator`, () => { - assert(runResultSupplier().mockedGenerators[`jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`].notCalled); - }); - } -}; - -const shouldComposeWithDatabasetype = (databaseType: string, shouldCompose: boolean, runResultSupplier) => { - const generator = databaseType; - if (shouldCompose) { - it(`should compose with ${generator} generator`, () => { - assert(runResultSupplier().mockedGenerators[`jhipster:spring-data-${generator}`].calledOnce); - }); - } else { - it(`should not compose with ${generator} generator`, () => { - assert(runResultSupplier().mockedGenerators[`jhipster:spring-data-${generator}`].notCalled); - }); - } -}; - -export const shouldComposeWithCouchbase = (shouldCompose: boolean, runResultSupplier) => - shouldComposeWithDatabasetype(COUCHBASE, shouldCompose, runResultSupplier); diff --git a/generators/server/__test-support/index.ts b/generators/server/__test-support/index.ts new file mode 100644 index 000000000000..a29f9c67a748 --- /dev/null +++ b/generators/server/__test-support/index.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import assert from 'assert'; + +import { messageBrokerTypes, databaseTypes } from '../../../jdl/jhipster/index.js'; +import { + GENERATOR_CUCUMBER, + GENERATOR_GATLING, + GENERATOR_SPRING_CACHE, + GENERATOR_SPRING_WEBSOCKET, + GENERATOR_SPRING_DATA_RELATIONAL, + GENERATOR_SPRING_DATA_CASSANDRA, + GENERATOR_SPRING_DATA_COUCHBASE, + GENERATOR_SPRING_DATA_MONGODB, + GENERATOR_MAVEN, + GENERATOR_LIQUIBASE, + GENERATOR_LANGUAGES, + GENERATOR_SPRING_CLOUD_STREAM, + GENERATOR_GRADLE, + GENERATOR_DOCKER, + GENERATOR_COMMON, +} from '../../generator-list.js'; + +const { KAFKA, PULSAR } = messageBrokerTypes; +const { SQL, COUCHBASE } = databaseTypes; + +export const mockedGenerators = [ + `jhipster:${GENERATOR_COMMON}`, + `jhipster:${GENERATOR_SPRING_DATA_COUCHBASE}`, + `jhipster:${GENERATOR_CUCUMBER}`, + `jhipster:${GENERATOR_DOCKER}`, + `jhipster:${GENERATOR_GATLING}`, + `jhipster:${GENERATOR_GRADLE}`, + `jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`, + `jhipster:${GENERATOR_LANGUAGES}`, + `jhipster:${GENERATOR_LIQUIBASE}`, + `jhipster:${GENERATOR_MAVEN}`, + `jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`, + `jhipster:${GENERATOR_SPRING_DATA_MONGODB}`, + `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`, + `jhipster:${GENERATOR_SPRING_CACHE}`, + `jhipster:${GENERATOR_SPRING_WEBSOCKET}`, +]; + +export const shouldComposeWithLiquibase = (testSample, runResultSupplier) => { + const liquibaseEnabled = typeof testSample === 'boolean' ? testSample : testSample?.databaseType === SQL; + if (liquibaseEnabled) { + it('should compose with liquibase generator', () => { + assert(runResultSupplier().mockedGenerators['jhipster:liquibase'].calledOnce); + }); + } else { + it('should not compose with liquibase generator', () => { + assert(runResultSupplier().mockedGenerators['jhipster:liquibase'].notCalled); + }); + } +}; + +export const shouldComposeWithSpringCloudStream = (sampleConfig, runResultSupplier) => { + const pulsarEnabled = typeof sampleConfig === 'boolean' ? sampleConfig : sampleConfig?.messageBroker === PULSAR; + const kafkaEnabled = typeof sampleConfig === 'boolean' ? sampleConfig : sampleConfig?.messageBroker === KAFKA; + if (pulsarEnabled || kafkaEnabled) { + it(`should compose with ${GENERATOR_SPRING_CLOUD_STREAM} generator`, () => { + assert(runResultSupplier().mockedGenerators[`jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`].calledOnce); + }); + } else { + it(`should not compose with ${GENERATOR_SPRING_CLOUD_STREAM} generator`, () => { + assert(runResultSupplier().mockedGenerators[`jhipster:${GENERATOR_SPRING_CLOUD_STREAM}`].notCalled); + }); + } +}; + +const shouldComposeWithDatabasetype = (databaseType: string, shouldCompose: boolean, runResultSupplier) => { + const generator = databaseType; + if (shouldCompose) { + it(`should compose with ${generator} generator`, () => { + assert(runResultSupplier().mockedGenerators[`jhipster:spring-data-${generator}`].calledOnce); + }); + } else { + it(`should not compose with ${generator} generator`, () => { + assert(runResultSupplier().mockedGenerators[`jhipster:spring-data-${generator}`].notCalled); + }); + } +}; + +export const shouldComposeWithCouchbase = (shouldCompose: boolean, runResultSupplier) => + shouldComposeWithDatabasetype(COUCHBASE, shouldCompose, runResultSupplier); diff --git a/generators/server/cleanup-oauth2.mts b/generators/server/cleanup-oauth2.mts deleted file mode 100644 index ac7bcb880d35..000000000000 --- a/generators/server/cleanup-oauth2.mts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../base/index.mjs'; -import { type GeneratorDefinition as ServerGeneratorDefinition } from '../base-application/generator.mjs'; - -type WritingTaskParam = ServerGeneratorDefinition['writingTaskParam']; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupOldServerFilesTask(this: BaseGenerator, { application }: WritingTaskParam) { - if (this.isJhipsterVersionLessThan('6.0.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/OAuth2Configuration.java`); - this.removeFile(`${application.javaPackageSrcDir}security/OAuth2AuthenticationSuccessHandler.java`); - } - if (this.isJhipsterVersionLessThan('7.6.1')) { - if (!application.databaseTypeNo) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/UserResource.java`); - } - } -} diff --git a/generators/server/cleanup-oauth2.ts b/generators/server/cleanup-oauth2.ts new file mode 100644 index 000000000000..6ff71649cf71 --- /dev/null +++ b/generators/server/cleanup-oauth2.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../base/index.js'; +import { type GeneratorDefinition as ServerGeneratorDefinition } from '../base-application/generator.js'; + +type WritingTaskParam = ServerGeneratorDefinition['writingTaskParam']; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupOldServerFilesTask(this: BaseGenerator, { application }: WritingTaskParam) { + if (this.isJhipsterVersionLessThan('6.0.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/OAuth2Configuration.java`); + this.removeFile(`${application.javaPackageSrcDir}security/OAuth2AuthenticationSuccessHandler.java`); + } + if (this.isJhipsterVersionLessThan('7.6.1')) { + if (!application.databaseTypeNo) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/UserResource.java`); + } + } +} diff --git a/generators/server/cleanup.mts b/generators/server/cleanup.mts deleted file mode 100644 index bb4f51008844..000000000000 --- a/generators/server/cleanup.mts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright 2013-2023 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 cleanupOauth2 from './cleanup-oauth2.mjs'; -import { JAVA_DOCKER_DIR } from '../generator-constants.mjs'; - -import type BaseGenerator from '../base/index.mjs'; -import { GeneratorDefinition } from '../base-application/generator.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupOldServerFilesTask(this: BaseGenerator, taskParam: GeneratorDefinition['writingTaskParam']) { - const { application } = taskParam; - if (application.authenticationTypeOauth2) { - cleanupOauth2.call(this, taskParam); - } - - if (this.isJhipsterVersionLessThan('3.5.0')) { - this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310DateTimeSerializer.java`); - this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310LocalDateDeserializer.java`); - } - if (this.isJhipsterVersionLessThan('3.6.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/HerokuDatabaseConfiguration.java`); - } - if (this.isJhipsterVersionLessThan('3.10.0')) { - this.removeFile(`${application.javaPackageSrcDir}security/CustomAccessDeniedHandler.java`); - this.removeFile(`${application.javaPackageSrcDir}web/filter/CsrfCookieGeneratorFilter.java`); - } - if (this.isJhipsterVersionLessThan('4.0.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/locale/AngularCookieLocaleResolver.java`); - } - if (this.isJhipsterVersionLessThan('4.0.0')) { - this.removeFile(`${application.javaPackageSrcDir}async/ExceptionHandlingAsyncTaskExecutor.java`); - this.removeFile(`${application.javaPackageSrcDir}async/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}config/jHipsterProperties.java`); - this.removeFile(`${application.javaPackageSrcDir}config/LoadBalancedResourceDetails.java`); - this.removeFile(`${application.javaPackageSrcDir}config/apidoc/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}config/apidoc/PageableParameterBuilderPlugin.java`); - this.removeFile(`${application.javaPackageSrcDir}config/apidoc/SwaggerConfiguration.java`); - this.removeFile(`${application.javaPackageSrcDir}config/jcache/SpringCacheRegionFactory.java`); - this.removeFile(`${application.javaPackageSrcDir}config/liquibase/AsyncSpringLiquibase.java`); - this.removeFile(`${application.javaPackageSrcDir}config/liquibase/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}config/locale/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310DateConverters.java`); - this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310PersistenceConverters.java`); - this.removeFile(`${application.javaPackageSrcDir}security/AjaxAuthenticationFailureHandler.java`); - this.removeFile(`${application.javaPackageSrcDir}security/AjaxAuthenticationSuccessHandler.java`); - this.removeFile(`${application.javaPackageSrcDir}security/AjaxLogoutSuccessHandler.java`); - this.removeFile(`${application.javaPackageSrcDir}security/CustomPersistentRememberMeServices.java`); - this.removeFile(`${application.javaPackageSrcDir}security/Http401UnauthorizedEntryPoint.java`); - this.removeFile(`${application.javaPackageSrcDir}security/UserDetailsService.java`); - this.removeFile(`${application.javaPackageSrcDir}web/filter/CachingHttpHeadersFilter.java`); - this.removeFile(`${application.javaPackageSrcDir}web/filter/package-info.java`); - } - if (this.isJhipsterVersionLessThan('4.3.0')) { - this.removeFile(`${application.javaPackageSrcDir}gateway/ratelimiting/RateLimitingRepository.java`); - } - if (this.isJhipsterVersionLessThan('4.7.1')) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/ErrorVM.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/ParameterizedErrorVM.java`); - } - if (this.isJhipsterVersionLessThan('4.13.1')) { - this.config.delete('hibernateCache'); - } - if (this.isJhipsterVersionLessThan('5.0.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/ThymeleafConfiguration.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/ProfileInfoResource.java`); - this.removeFile(`${application.srcMainResources}mails/activationEmail.html`); - this.removeFile(`${application.srcMainResources}mails/creationEmail.html`); - this.removeFile(`${application.srcMainResources}mails/passwordResetEmail.html`); - this.removeFile(`${application.srcMainResources}mails/socialRegistrationValidationEmail.html`); - this.removeFile(`${application.srcTestResources}mail/testEmail.html`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/ProfileInfoResourceIT.java`); - } - if (this.isJhipsterVersionLessThan('5.2.2')) { - if (application.authenticationTypeOauth2 && application.applicationTypeMicroservice) { - this.removeFolder(`${JAVA_DOCKER_DIR}realm-config`); - this.removeFile(`${JAVA_DOCKER_DIR}keycloak.yml`); - } - } - if (this.isJhipsterVersionLessThan('5.8.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/MetricsConfiguration.java`); - } - if (this.isJhipsterVersionLessThan('6.0.0')) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/CustomParameterizedException.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/InternalServerErrorException.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/util/PaginationUtil.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/util/HeaderUtil.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/util/PaginationUtilUnitTest.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/vm/LoggerVM.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/LogsResource.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/LogsResourceIT.java`); - } - if (this.isJhipsterVersionLessThan('6.5.2')) { - this.removeFile(`${application.javaPackageSrcDir}service/mapper/UserMapperIT.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/ClientForwardControllerIT.java`); - } - if (this.isJhipsterVersionLessThan('6.6.1')) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/EmailNotFoundException.java`); - this.removeFile(`${application.javaPackageSrcDir}config/DefaultProfileUtil.java`); - this.removeFolder(`${application.javaPackageSrcDir}service/util`); - } - if (this.isJhipsterVersionLessThan('6.8.0')) { - this.removeFile(`${application.javaPackageSrcDir}security/oauth2/JwtAuthorityExtractor.java`); - } - if (this.isJhipsterVersionLessThan('6.8.1')) { - if (application.reactive) { - this.removeFile(`${application.javaPackageSrcDir}config/ReactivePageableHandlerMethodArgumentResolver.java`); - this.removeFile(`${application.javaPackageSrcDir}config/ReactiveSortHandlerMethodArgumentResolver.java`); - } - } - if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/apidoc/SwaggerConfiguration.java`); - this.removeFile(`${application.javaPackageSrcDir}config/metrics/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}config/metrics/JHipsterHealthIndicatorConfiguration.java`); - this.removeFile(`${application.javaPackageSrcDir}config/audit/package-info.java`); - this.removeFile(`${application.javaPackageSrcDir}config/audit/AuditEventConverter.java`); - this.removeFile(`${application.javaPackageSrcDir}domain/PersistentAuditEvent.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/PersistenceAuditEventRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/CustomAuditEventRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}service/AuditEventService.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/AuditResource.java`); - this.removeFile(`${application.javaPackageSrcDir}service/AuditEventServiceIT.java`); - this.removeFile(`${application.javaPackageSrcDir}web/rest/AuditResourceIT.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/CustomAuditEventRepositoryIT.java`); - } - if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { - this.removeFile(`${application.javaPackageSrcDir}config/CloudDatabaseConfiguration.java`); - } - if (this.isJhipsterVersionLessThan('7.4.2')) { - this.removeFile(`${application.javaPackageSrcDir}config/apidocs/GatewaySwaggerResourcesProvider.java`); - this.removeFile(`${application.javaPackageSrcDir}config/apidocs/GatewaySwaggerResourcesProviderTest.java`); - } - if (this.isJhipsterVersionLessThan('7.5.1')) { - if (application.reactive && application.databaseTypeSql) { - this.removeFile(`${application.javaPackageSrcDir}service/ColumnConverter.java`); - this.removeFile(`${application.javaPackageSrcDir}service/EntityManager.java`); - this.removeFile(`${application.javaPackageSrcDir}ArchTest.java`); - } - } - if (this.isJhipsterVersionLessThan('7.7.1')) { - this.removeFile(`${application.javaPackageSrcDir}TestContainersSpringContextCustomizerFactory.java`); - } - if (this.isJhipsterVersionLessThan('7.8.2')) { - this.removeFile(`${JAVA_DOCKER_DIR}realm-config/jhipster-users-0.json`); - this.removeFile(`${application.javaPackageSrcDir}NoOpMailConfiguration.java`); - } - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile(`${application.srcTestResources}testcontainers.properties`); - if (application.authenticationTypeJwt) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/UserJWTController.java`); - this.removeFile(`${application.javaPackageSrcDir}security/jwt/JWTConfigurer.java`); - this.removeFile(`${application.javaPackageSrcDir}security/jwt/JWTFilter.java`); - this.removeFile(`${application.javaPackageSrcDir}security/jwt/TokenProvider.java`); - this.removeFile(`${application.javaPackageTestDir}web/rest/UserJWTControllerIT.java`); - this.removeFile(`${application.javaPackageTestDir}security/jwt/JWTFilterTest.java`); - this.removeFile(`${application.javaPackageTestDir}security/jwt/TokenProviderSecurityMetersTests.java`); - this.removeFile(`${application.javaPackageTestDir}security/jwt/TokenProviderTest.java`); - } - if (!application.skipClient && !application.reactive) { - this.removeFile(`${application.javaPackageSrcDir}web/rest/ClientForwardController.java`); - this.removeFile(`${application.javaPackageTestDir}web/rest/ClientForwardControllerTest.java`); - } - if ( - application.databaseTypeSql || - (application as any).cacheProviderRedis || - application.databaseTypeMongodb || - application.databaseTypeCassandra || - (application as any).searchEngineElasticsearch || - application.databaseTypeCouchbase || - (application as any).searchEngineCouchbase || - application.databaseTypeNeo4j - ) { - // The condition is too complated, delete and recreate. - this.removeFile(`${application.srcTestResources}META-INF/spring.factories`); - this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); - } - } - - if (this.isJhipsterVersionLessThan('8.0.1')) { - if (application.authenticationTypeOauth2) { - this.removeFile(`${application.javaPackageSrcDir}security/oauth2/OAuthIdpTokenResponseDTO.java`); - } - } -} diff --git a/generators/server/cleanup.ts b/generators/server/cleanup.ts new file mode 100644 index 000000000000..76959f3aa4eb --- /dev/null +++ b/generators/server/cleanup.ts @@ -0,0 +1,202 @@ +/** + * Copyright 2013-2023 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 cleanupOauth2 from './cleanup-oauth2.js'; +import { JAVA_DOCKER_DIR } from '../generator-constants.js'; + +import type BaseGenerator from '../base/index.js'; +import { GeneratorDefinition } from '../base-application/generator.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupOldServerFilesTask(this: BaseGenerator, taskParam: GeneratorDefinition['writingTaskParam']) { + const { application } = taskParam; + if (application.authenticationTypeOauth2) { + cleanupOauth2.call(this, taskParam); + } + + if (this.isJhipsterVersionLessThan('3.5.0')) { + this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310DateTimeSerializer.java`); + this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310LocalDateDeserializer.java`); + } + if (this.isJhipsterVersionLessThan('3.6.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/HerokuDatabaseConfiguration.java`); + } + if (this.isJhipsterVersionLessThan('3.10.0')) { + this.removeFile(`${application.javaPackageSrcDir}security/CustomAccessDeniedHandler.java`); + this.removeFile(`${application.javaPackageSrcDir}web/filter/CsrfCookieGeneratorFilter.java`); + } + if (this.isJhipsterVersionLessThan('4.0.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/locale/AngularCookieLocaleResolver.java`); + } + if (this.isJhipsterVersionLessThan('4.0.0')) { + this.removeFile(`${application.javaPackageSrcDir}async/ExceptionHandlingAsyncTaskExecutor.java`); + this.removeFile(`${application.javaPackageSrcDir}async/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}config/jHipsterProperties.java`); + this.removeFile(`${application.javaPackageSrcDir}config/LoadBalancedResourceDetails.java`); + this.removeFile(`${application.javaPackageSrcDir}config/apidoc/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}config/apidoc/PageableParameterBuilderPlugin.java`); + this.removeFile(`${application.javaPackageSrcDir}config/apidoc/SwaggerConfiguration.java`); + this.removeFile(`${application.javaPackageSrcDir}config/jcache/SpringCacheRegionFactory.java`); + this.removeFile(`${application.javaPackageSrcDir}config/liquibase/AsyncSpringLiquibase.java`); + this.removeFile(`${application.javaPackageSrcDir}config/liquibase/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}config/locale/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310DateConverters.java`); + this.removeFile(`${application.javaPackageSrcDir}domain/util/JSR310PersistenceConverters.java`); + this.removeFile(`${application.javaPackageSrcDir}security/AjaxAuthenticationFailureHandler.java`); + this.removeFile(`${application.javaPackageSrcDir}security/AjaxAuthenticationSuccessHandler.java`); + this.removeFile(`${application.javaPackageSrcDir}security/AjaxLogoutSuccessHandler.java`); + this.removeFile(`${application.javaPackageSrcDir}security/CustomPersistentRememberMeServices.java`); + this.removeFile(`${application.javaPackageSrcDir}security/Http401UnauthorizedEntryPoint.java`); + this.removeFile(`${application.javaPackageSrcDir}security/UserDetailsService.java`); + this.removeFile(`${application.javaPackageSrcDir}web/filter/CachingHttpHeadersFilter.java`); + this.removeFile(`${application.javaPackageSrcDir}web/filter/package-info.java`); + } + if (this.isJhipsterVersionLessThan('4.3.0')) { + this.removeFile(`${application.javaPackageSrcDir}gateway/ratelimiting/RateLimitingRepository.java`); + } + if (this.isJhipsterVersionLessThan('4.7.1')) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/ErrorVM.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/ParameterizedErrorVM.java`); + } + if (this.isJhipsterVersionLessThan('4.13.1')) { + this.config.delete('hibernateCache'); + } + if (this.isJhipsterVersionLessThan('5.0.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/ThymeleafConfiguration.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/ProfileInfoResource.java`); + this.removeFile(`${application.srcMainResources}mails/activationEmail.html`); + this.removeFile(`${application.srcMainResources}mails/creationEmail.html`); + this.removeFile(`${application.srcMainResources}mails/passwordResetEmail.html`); + this.removeFile(`${application.srcMainResources}mails/socialRegistrationValidationEmail.html`); + this.removeFile(`${application.srcTestResources}mail/testEmail.html`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/ProfileInfoResourceIT.java`); + } + if (this.isJhipsterVersionLessThan('5.2.2')) { + if (application.authenticationTypeOauth2 && application.applicationTypeMicroservice) { + this.removeFolder(`${JAVA_DOCKER_DIR}realm-config`); + this.removeFile(`${JAVA_DOCKER_DIR}keycloak.yml`); + } + } + if (this.isJhipsterVersionLessThan('5.8.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/MetricsConfiguration.java`); + } + if (this.isJhipsterVersionLessThan('6.0.0')) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/CustomParameterizedException.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/InternalServerErrorException.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/util/PaginationUtil.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/util/HeaderUtil.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/util/PaginationUtilUnitTest.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/vm/LoggerVM.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/LogsResource.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/LogsResourceIT.java`); + } + if (this.isJhipsterVersionLessThan('6.5.2')) { + this.removeFile(`${application.javaPackageSrcDir}service/mapper/UserMapperIT.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/ClientForwardControllerIT.java`); + } + if (this.isJhipsterVersionLessThan('6.6.1')) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/errors/EmailNotFoundException.java`); + this.removeFile(`${application.javaPackageSrcDir}config/DefaultProfileUtil.java`); + this.removeFolder(`${application.javaPackageSrcDir}service/util`); + } + if (this.isJhipsterVersionLessThan('6.8.0')) { + this.removeFile(`${application.javaPackageSrcDir}security/oauth2/JwtAuthorityExtractor.java`); + } + if (this.isJhipsterVersionLessThan('6.8.1')) { + if (application.reactive) { + this.removeFile(`${application.javaPackageSrcDir}config/ReactivePageableHandlerMethodArgumentResolver.java`); + this.removeFile(`${application.javaPackageSrcDir}config/ReactiveSortHandlerMethodArgumentResolver.java`); + } + } + if (this.isJhipsterVersionLessThan('7.0.0-beta.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/apidoc/SwaggerConfiguration.java`); + this.removeFile(`${application.javaPackageSrcDir}config/metrics/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}config/metrics/JHipsterHealthIndicatorConfiguration.java`); + this.removeFile(`${application.javaPackageSrcDir}config/audit/package-info.java`); + this.removeFile(`${application.javaPackageSrcDir}config/audit/AuditEventConverter.java`); + this.removeFile(`${application.javaPackageSrcDir}domain/PersistentAuditEvent.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/PersistenceAuditEventRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/CustomAuditEventRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}service/AuditEventService.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/AuditResource.java`); + this.removeFile(`${application.javaPackageSrcDir}service/AuditEventServiceIT.java`); + this.removeFile(`${application.javaPackageSrcDir}web/rest/AuditResourceIT.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/CustomAuditEventRepositoryIT.java`); + } + if (this.isJhipsterVersionLessThan('7.0.0-beta.1')) { + this.removeFile(`${application.javaPackageSrcDir}config/CloudDatabaseConfiguration.java`); + } + if (this.isJhipsterVersionLessThan('7.4.2')) { + this.removeFile(`${application.javaPackageSrcDir}config/apidocs/GatewaySwaggerResourcesProvider.java`); + this.removeFile(`${application.javaPackageSrcDir}config/apidocs/GatewaySwaggerResourcesProviderTest.java`); + } + if (this.isJhipsterVersionLessThan('7.5.1')) { + if (application.reactive && application.databaseTypeSql) { + this.removeFile(`${application.javaPackageSrcDir}service/ColumnConverter.java`); + this.removeFile(`${application.javaPackageSrcDir}service/EntityManager.java`); + this.removeFile(`${application.javaPackageSrcDir}ArchTest.java`); + } + } + if (this.isJhipsterVersionLessThan('7.7.1')) { + this.removeFile(`${application.javaPackageSrcDir}TestContainersSpringContextCustomizerFactory.java`); + } + if (this.isJhipsterVersionLessThan('7.8.2')) { + this.removeFile(`${JAVA_DOCKER_DIR}realm-config/jhipster-users-0.json`); + this.removeFile(`${application.javaPackageSrcDir}NoOpMailConfiguration.java`); + } + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile(`${application.srcTestResources}testcontainers.properties`); + if (application.authenticationTypeJwt) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/UserJWTController.java`); + this.removeFile(`${application.javaPackageSrcDir}security/jwt/JWTConfigurer.java`); + this.removeFile(`${application.javaPackageSrcDir}security/jwt/JWTFilter.java`); + this.removeFile(`${application.javaPackageSrcDir}security/jwt/TokenProvider.java`); + this.removeFile(`${application.javaPackageTestDir}web/rest/UserJWTControllerIT.java`); + this.removeFile(`${application.javaPackageTestDir}security/jwt/JWTFilterTest.java`); + this.removeFile(`${application.javaPackageTestDir}security/jwt/TokenProviderSecurityMetersTests.java`); + this.removeFile(`${application.javaPackageTestDir}security/jwt/TokenProviderTest.java`); + } + if (!application.skipClient && !application.reactive) { + this.removeFile(`${application.javaPackageSrcDir}web/rest/ClientForwardController.java`); + this.removeFile(`${application.javaPackageTestDir}web/rest/ClientForwardControllerTest.java`); + } + if ( + application.databaseTypeSql || + (application as any).cacheProviderRedis || + application.databaseTypeMongodb || + application.databaseTypeCassandra || + (application as any).searchEngineElasticsearch || + application.databaseTypeCouchbase || + (application as any).searchEngineCouchbase || + application.databaseTypeNeo4j + ) { + // The condition is too complated, delete and recreate. + this.removeFile(`${application.srcTestResources}META-INF/spring.factories`); + this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); + } + } + + if (this.isJhipsterVersionLessThan('8.0.1')) { + if (application.authenticationTypeOauth2) { + this.removeFile(`${application.javaPackageSrcDir}security/oauth2/OAuthIdpTokenResponseDTO.java`); + } + } +} diff --git a/generators/server/command.mts b/generators/server/command.mts deleted file mode 100644 index b7ccaadc84fc..000000000000 --- a/generators/server/command.mts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_COMMON, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.mjs'; -import { APPLICATION_TYPE_GATEWAY, APPLICATION_TYPE_MICROSERVICE, APPLICATION_TYPE_MONOLITH } from '../../jdl/index.js'; - -const command: JHipsterCommandDefinition = { - options: { - authenticationType: { - name: 'auth', - description: 'Provide authentication type for the application when skipping server side generation', - type: String, - scope: 'storage', - }, - db: { - description: 'Provide DB name for the application when skipping server side generation', - type: String, - }, - incrementalChangelog: { - description: 'Creates incremental database changelogs', - type: Boolean, - scope: 'storage', - }, - skipUserManagement: { - description: 'Skip the user management module during app generation', - type: Boolean, - scope: 'storage', - }, - recreateInitialChangelog: { - description: 'Recreate the initial database changelog based on the current config', - type: Boolean, - }, - buildTool: { - name: 'build', - description: 'Provide build tool for the application when skipping server side generation', - type: String, - scope: 'storage', - }, - cacheProvider: { - description: 'Cache provider', - type: String, - scope: 'storage', - }, - enableSwaggerCodegen: { - description: 'API first development using OpenAPI-generator', - type: Boolean, - scope: 'storage', - }, - enableHibernateCache: { - description: 'Enable hibernate cache', - type: Boolean, - scope: 'storage', - }, - messageBroker: { - description: 'message broker', - type: String, - scope: 'storage', - }, - reactive: { - description: 'Generate a reactive backend', - type: Boolean, - scope: 'storage', - }, - searchEngine: { - description: 'Provide search engine for the application when skipping server side generation', - type: String, - scope: 'storage', - }, - skipCheckLengthOfIdentifier: { - description: 'Skip check the length of the identifier, only for recent Oracle databases that support 30+ characters metadata', - type: Boolean, - scope: 'storage', - }, - skipDbChangelog: { - description: 'Skip the generation of database migrations', - type: Boolean, - }, - skipFakeData: { - description: 'Skip generation of fake data for development', - type: Boolean, - scope: 'storage', - }, - websocket: { - description: 'Provide websocket option for the application when skipping server side generation', - type: String, - scope: 'storage', - }, - projectVersion: { - description: 'project version to use, this option is not persisted', - type: String, - env: 'JHI_PROJECT_VERSION', - scope: 'generator', - }, - jhipsterDependenciesVersion: { - description: 'jhipster-dependencies version to use, this option is not persisted', - type: String, - env: 'JHIPSTER_DEPENDENCIES_VERSION', - scope: 'generator', - }, - fakeKeytool: { - description: 'Add a fake certificate store file for test purposes', - type: Boolean, - env: 'FAKE_KEYTOOL', - scope: 'generator', - hide: true, - }, - }, - configs: { - applicationType: { - description: 'Application type to generate', - cli: { - type: String, - }, - prompt: { - type: 'list', - message: `Which ${chalk.yellow('*type*')} of application would you like to create?`, - }, - choices: [ - { - value: APPLICATION_TYPE_MONOLITH, - name: 'Monolithic application (recommended for simple projects)', - }, - { - value: APPLICATION_TYPE_GATEWAY, - name: 'Gateway application', - }, - { - value: APPLICATION_TYPE_MICROSERVICE, - name: 'Microservice application', - }, - ], - }, - feignClient: { - description: 'Generate a feign client', - cli: { - type: Boolean, - }, - prompt: { - type: 'confirm', - message: 'Do you want to generate a feign client?', - }, - default: false, - }, - }, - import: [GENERATOR_COMMON, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL], -}; - -export default command; diff --git a/generators/server/command.ts b/generators/server/command.ts new file mode 100644 index 000000000000..027b63ae8287 --- /dev/null +++ b/generators/server/command.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_COMMON, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.js'; +import { APPLICATION_TYPE_GATEWAY, APPLICATION_TYPE_MICROSERVICE, APPLICATION_TYPE_MONOLITH } from '../../jdl/index.js'; + +const command: JHipsterCommandDefinition = { + options: { + authenticationType: { + name: 'auth', + description: 'Provide authentication type for the application when skipping server side generation', + type: String, + scope: 'storage', + }, + db: { + description: 'Provide DB name for the application when skipping server side generation', + type: String, + }, + incrementalChangelog: { + description: 'Creates incremental database changelogs', + type: Boolean, + scope: 'storage', + }, + skipUserManagement: { + description: 'Skip the user management module during app generation', + type: Boolean, + scope: 'storage', + }, + recreateInitialChangelog: { + description: 'Recreate the initial database changelog based on the current config', + type: Boolean, + }, + buildTool: { + name: 'build', + description: 'Provide build tool for the application when skipping server side generation', + type: String, + scope: 'storage', + }, + cacheProvider: { + description: 'Cache provider', + type: String, + scope: 'storage', + }, + enableSwaggerCodegen: { + description: 'API first development using OpenAPI-generator', + type: Boolean, + scope: 'storage', + }, + enableHibernateCache: { + description: 'Enable hibernate cache', + type: Boolean, + scope: 'storage', + }, + messageBroker: { + description: 'message broker', + type: String, + scope: 'storage', + }, + reactive: { + description: 'Generate a reactive backend', + type: Boolean, + scope: 'storage', + }, + searchEngine: { + description: 'Provide search engine for the application when skipping server side generation', + type: String, + scope: 'storage', + }, + skipCheckLengthOfIdentifier: { + description: 'Skip check the length of the identifier, only for recent Oracle databases that support 30+ characters metadata', + type: Boolean, + scope: 'storage', + }, + skipDbChangelog: { + description: 'Skip the generation of database migrations', + type: Boolean, + }, + skipFakeData: { + description: 'Skip generation of fake data for development', + type: Boolean, + scope: 'storage', + }, + websocket: { + description: 'Provide websocket option for the application when skipping server side generation', + type: String, + scope: 'storage', + }, + projectVersion: { + description: 'project version to use, this option is not persisted', + type: String, + env: 'JHI_PROJECT_VERSION', + scope: 'generator', + }, + jhipsterDependenciesVersion: { + description: 'jhipster-dependencies version to use, this option is not persisted', + type: String, + env: 'JHIPSTER_DEPENDENCIES_VERSION', + scope: 'generator', + }, + fakeKeytool: { + description: 'Add a fake certificate store file for test purposes', + type: Boolean, + env: 'FAKE_KEYTOOL', + scope: 'generator', + hide: true, + }, + }, + configs: { + applicationType: { + description: 'Application type to generate', + cli: { + type: String, + }, + prompt: { + type: 'list', + message: `Which ${chalk.yellow('*type*')} of application would you like to create?`, + }, + choices: [ + { + value: APPLICATION_TYPE_MONOLITH, + name: 'Monolithic application (recommended for simple projects)', + }, + { + value: APPLICATION_TYPE_GATEWAY, + name: 'Gateway application', + }, + { + value: APPLICATION_TYPE_MICROSERVICE, + name: 'Microservice application', + }, + ], + }, + feignClient: { + description: 'Generate a feign client', + cli: { + type: Boolean, + }, + prompt: { + type: 'confirm', + message: 'Do you want to generate a feign client?', + }, + default: false, + }, + }, + import: [GENERATOR_COMMON, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL], +}; + +export default command; diff --git a/generators/server/entity-cleanup.mjs b/generators/server/entity-cleanup.js similarity index 100% rename from generators/server/entity-cleanup.mjs rename to generators/server/entity-cleanup.js diff --git a/generators/server/entity-files.js b/generators/server/entity-files.js new file mode 100644 index 000000000000..61b10ad0f49e --- /dev/null +++ b/generators/server/entity-files.js @@ -0,0 +1,217 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import chalk from 'chalk'; +import { cleanupOldFiles } from './entity-cleanup.js'; +import { moveToJavaPackageSrcDir, javaMainPackageTemplatesBlock, javaTestPackageTemplatesBlock } from './support/index.js'; +import { SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { databaseTypes, entityOptions } from '../../jdl/jhipster/index.js'; + +const { COUCHBASE, MONGODB, NEO4J, SQL } = databaseTypes; +const { MapperTypes, ServiceTypes } = entityOptions; +const { MAPSTRUCT } = MapperTypes; +const { SERVICE_CLASS, SERVICE_IMPL } = ServiceTypes; + +export const restFiles = { + restFiles: [ + { + condition: generator => !generator.embedded, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['web/rest/_entityClass_Resource.java'], + }, + ], + restTestFiles: [ + { + condition: generator => !generator.embedded, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: '_package_/_entityPackage_/web/rest/_entityClass_ResourceIT.java', + options: { + context: { + _, + chalkRed: chalk.red, + fs, + SERVER_TEST_SRC_DIR, + }, + }, + renameTo: generator => `${generator.entityAbsoluteFolder}/web/rest/${generator.entityClass}ResourceIT.java`, + }, + ], + }, + ], +}; + +export const filteringFiles = { + filteringFiles: [ + { + condition: generator => generator.jpaMetamodelFiltering && !generator.reactive, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/criteria/_entityClass_Criteria.java', 'service/_entityClass_QueryService.java'], + }, + ], +}; + +const filteringReactiveFiles = { + filteringReactiveFiles: [ + { + condition: generator => generator.jpaMetamodelFiltering && generator.reactive, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('service/', 'domain/'), + templates: ['service/criteria/_entityClass_Criteria.java'], + }, + ], +}; + +export const repositoryFiles = { + repositoryFiles: [ + { + condition: generator => !generator.reactive && !generator.embedded && generator.databaseType !== COUCHBASE, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['repository/_entityClass_Repository.java'], + }, + { + condition: generator => generator.reactive && !generator.embedded && generator.databaseType !== COUCHBASE, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['repository/_entityClass_Repository_reactive.java'], + }, + ], +}; + +export const serviceFiles = { + serviceFiles: [ + { + condition: generator => generator.service === SERVICE_IMPL && !generator.embedded, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/_entityClass_Service.java', 'service/impl/_entityClass_ServiceImpl.java'], + }, + javaMainPackageTemplatesBlock({ + condition: generator => generator.service === SERVICE_CLASS && !generator.embedded, + relativePath: '_entityPackage_/', + renameTo: (_data, file) => file.replace('service/impl', 'service').replace('Impl.java', '.java'), + templates: ['service/impl/_entityClass_ServiceImpl.java'], + }), + ], +}; + +export const dtoFiles = { + baseDtoFiles: [ + { + condition: generator => generator.dto === MAPSTRUCT, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/mapper/EntityMapper.java'], + }, + ], + dtoFiles: [ + { + condition: generator => generator.dto === MAPSTRUCT, + ...javaMainPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/dto/_dtoClass_.java', 'service/mapper/_entityClass_Mapper.java'], + }, + ], + dtoTestFiles: [ + { + condition: generator => generator.dto === MAPSTRUCT, + ...javaTestPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/dto/_dtoClass_Test.java'], + }, + { + condition: generator => generator.dto === MAPSTRUCT && [SQL, MONGODB, COUCHBASE, NEO4J].includes(generator.databaseType), + ...javaTestPackageTemplatesBlock('_entityPackage_/'), + templates: ['service/mapper/_entityClass_MapperTest.java'], + }, + ], +}; + +const userFiles = { + userFiles: [ + { + ...javaMainPackageTemplatesBlock(), + renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/User.java', `/${data.user.persistClass}.java`), + templates: ['domain/User.java'], + }, + { + ...javaMainPackageTemplatesBlock(), + renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/UserDTO.java', `/${data.user.dtoClass}.java`), + templates: ['service/dto/UserDTO.java'], + }, + { + ...javaMainPackageTemplatesBlock(), + renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/AdminUserDTO.java', `/${data.user.adminUserDto}.java`), + templates: ['service/dto/AdminUserDTO.java'], + }, + { + condition: data => data.generateBuiltInUserEntity, + ...javaMainPackageTemplatesBlock(), + templates: [ + 'service/UserService.java', + 'service/mapper/UserMapper.java', + 'repository/UserRepository.java', + 'web/rest/PublicUserResource.java', + ], + }, + { + condition: data => data.generateBuiltInUserEntity, + ...javaTestPackageTemplatesBlock(), + templates: [ + 'service/UserServiceIT.java', + 'service/mapper/UserMapperTest.java', + 'web/rest/UserResourceIT.java', + 'web/rest/PublicUserResourceIT.java', + ], + }, + ], +}; + +export const serverFiles = { + ...restFiles, + ...filteringFiles, + ...filteringReactiveFiles, + ...repositoryFiles, + ...serviceFiles, + ...dtoFiles, +}; + +export function writeFiles() { + return { + cleanupOldServerFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipServer)) { + cleanupOldFiles.call(this, { application, entity }); + } + }, + + async writeServerFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipServer)) { + if (entity.builtInUser) { + await this.writeFiles({ + sections: userFiles, + context: { ...application, ...entity }, + }); + } else if (!entity.builtIn) { + await this.writeFiles({ + sections: serverFiles, + rootTemplatesPath: application.reactive ? ['reactive', '', '../../java/templates/'] : ['', '../../java/templates/'], + context: { ...application, ...entity }, + }); + } + } + }, + }; +} diff --git a/generators/server/entity-files.mjs b/generators/server/entity-files.mjs deleted file mode 100644 index b529e658bdb0..000000000000 --- a/generators/server/entity-files.mjs +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import chalk from 'chalk'; -import { cleanupOldFiles } from './entity-cleanup.mjs'; -import { moveToJavaPackageSrcDir, javaMainPackageTemplatesBlock, javaTestPackageTemplatesBlock } from './support/index.mjs'; -import { SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { databaseTypes, entityOptions } from '../../jdl/jhipster/index.mjs'; - -const { COUCHBASE, MONGODB, NEO4J, SQL } = databaseTypes; -const { MapperTypes, ServiceTypes } = entityOptions; -const { MAPSTRUCT } = MapperTypes; -const { SERVICE_CLASS, SERVICE_IMPL } = ServiceTypes; - -export const restFiles = { - restFiles: [ - { - condition: generator => !generator.embedded, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['web/rest/_entityClass_Resource.java'], - }, - ], - restTestFiles: [ - { - condition: generator => !generator.embedded, - path: SERVER_TEST_SRC_DIR, - templates: [ - { - file: '_package_/_entityPackage_/web/rest/_entityClass_ResourceIT.java', - options: { - context: { - _, - chalkRed: chalk.red, - fs, - SERVER_TEST_SRC_DIR, - }, - }, - renameTo: generator => `${generator.entityAbsoluteFolder}/web/rest/${generator.entityClass}ResourceIT.java`, - }, - ], - }, - ], -}; - -export const filteringFiles = { - filteringFiles: [ - { - condition: generator => generator.jpaMetamodelFiltering && !generator.reactive, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/criteria/_entityClass_Criteria.java', 'service/_entityClass_QueryService.java'], - }, - ], -}; - -const filteringReactiveFiles = { - filteringReactiveFiles: [ - { - condition: generator => generator.jpaMetamodelFiltering && generator.reactive, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('service/', 'domain/'), - templates: ['service/criteria/_entityClass_Criteria.java'], - }, - ], -}; - -export const repositoryFiles = { - repositoryFiles: [ - { - condition: generator => !generator.reactive && !generator.embedded && generator.databaseType !== COUCHBASE, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['repository/_entityClass_Repository.java'], - }, - { - condition: generator => generator.reactive && !generator.embedded && generator.databaseType !== COUCHBASE, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['repository/_entityClass_Repository_reactive.java'], - }, - ], -}; - -export const serviceFiles = { - serviceFiles: [ - { - condition: generator => generator.service === SERVICE_IMPL && !generator.embedded, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/_entityClass_Service.java', 'service/impl/_entityClass_ServiceImpl.java'], - }, - javaMainPackageTemplatesBlock({ - condition: generator => generator.service === SERVICE_CLASS && !generator.embedded, - relativePath: '_entityPackage_/', - renameTo: (_data, file) => file.replace('service/impl', 'service').replace('Impl.java', '.java'), - templates: ['service/impl/_entityClass_ServiceImpl.java'], - }), - ], -}; - -export const dtoFiles = { - baseDtoFiles: [ - { - condition: generator => generator.dto === MAPSTRUCT, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/mapper/EntityMapper.java'], - }, - ], - dtoFiles: [ - { - condition: generator => generator.dto === MAPSTRUCT, - ...javaMainPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/dto/_dtoClass_.java', 'service/mapper/_entityClass_Mapper.java'], - }, - ], - dtoTestFiles: [ - { - condition: generator => generator.dto === MAPSTRUCT, - ...javaTestPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/dto/_dtoClass_Test.java'], - }, - { - condition: generator => generator.dto === MAPSTRUCT && [SQL, MONGODB, COUCHBASE, NEO4J].includes(generator.databaseType), - ...javaTestPackageTemplatesBlock('_entityPackage_/'), - templates: ['service/mapper/_entityClass_MapperTest.java'], - }, - ], -}; - -const userFiles = { - userFiles: [ - { - ...javaMainPackageTemplatesBlock(), - renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/User.java', `/${data.user.persistClass}.java`), - templates: ['domain/User.java'], - }, - { - ...javaMainPackageTemplatesBlock(), - renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/UserDTO.java', `/${data.user.dtoClass}.java`), - templates: ['service/dto/UserDTO.java'], - }, - { - ...javaMainPackageTemplatesBlock(), - renameTo: (data, file) => moveToJavaPackageSrcDir(data, file).replace('/AdminUserDTO.java', `/${data.user.adminUserDto}.java`), - templates: ['service/dto/AdminUserDTO.java'], - }, - { - condition: data => data.generateBuiltInUserEntity, - ...javaMainPackageTemplatesBlock(), - templates: [ - 'service/UserService.java', - 'service/mapper/UserMapper.java', - 'repository/UserRepository.java', - 'web/rest/PublicUserResource.java', - ], - }, - { - condition: data => data.generateBuiltInUserEntity, - ...javaTestPackageTemplatesBlock(), - templates: [ - 'service/UserServiceIT.java', - 'service/mapper/UserMapperTest.java', - 'web/rest/UserResourceIT.java', - 'web/rest/PublicUserResourceIT.java', - ], - }, - ], -}; - -export const serverFiles = { - ...restFiles, - ...filteringFiles, - ...filteringReactiveFiles, - ...repositoryFiles, - ...serviceFiles, - ...dtoFiles, -}; - -export function writeFiles() { - return { - cleanupOldServerFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipServer)) { - cleanupOldFiles.call(this, { application, entity }); - } - }, - - async writeServerFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipServer)) { - if (entity.builtInUser) { - await this.writeFiles({ - sections: userFiles, - context: { ...application, ...entity }, - }); - } else if (!entity.builtIn) { - await this.writeFiles({ - sections: serverFiles, - rootTemplatesPath: application.reactive ? ['reactive', '', '../../java/templates/'] : ['', '../../java/templates/'], - context: { ...application, ...entity }, - }); - } - } - }, - }; -} diff --git a/generators/server/files.js b/generators/server/files.js new file mode 100644 index 000000000000..2605f466f4f4 --- /dev/null +++ b/generators/server/files.js @@ -0,0 +1,586 @@ +/** + * Copyright 2013-2023 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 cleanupOldServerFiles from './cleanup.js'; +import { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.js'; +import { addSectionsCondition, mergeSections } from '../base/support/index.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir, moveToSrcMainResourcesDir } from './support/index.js'; + +const imperativeConfigFiles = { + imperativeFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['ApplicationWebXml.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/WebConfigurerTest.java', 'config/WebConfigurerTestController.java'], + }, + ], +}; + +const reactiveConfigFiles = { + reactiveFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/ReactorConfiguration.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/JHipsterBlockHoundIntegration.java'], + }, + { + path: SERVER_TEST_RES_DIR, + templates: ['META-INF/services/reactor.blockhound.integration.BlockHoundIntegration'], + }, + ], +}; + +const oauth2Files = { + oauth2Files: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/oauth2/AudienceValidator.java', 'security/oauth2/JwtGrantedAuthorityConverter.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['security/oauth2/AudienceValidatorTest.java', 'config/TestSecurityConfiguration.java'], + }, + { + condition: generator => generator.applicationTypeMonolith, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/OAuth2Configuration.java'], + }, + { + condition: generator => generator.generateAuthenticationApi, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['web/rest/AuthInfoResource.java', data => `web/rest/LogoutResource_${data.imperativeOrReactive}.java`], + }, + { + condition: generator => generator.generateAuthenticationApi, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: generator => `_package_/web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, + renameTo: generator => + `${generator.packageFolder}web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, + }, + ], + }, + { + condition: generator => generator.generateAuthenticationApi, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['test/util/OAuth2TestUtil.java', 'web/rest/LogoutResourceIT.java'], + }, + { + condition: generator => !generator.reactive && generator.generateAuthenticationApi, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/oauth2/CustomClaimConverter.java'], + }, + { + condition: generator => !generator.reactive && generator.generateAuthenticationApi, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['security/oauth2/CustomClaimConverterIT.java'], + }, + ], +}; + +const accountFiles = { + accountResource: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + data => { + if (data.authenticationTypeOauth2 && data.generateBuiltInUserEntity) return 'web/rest/AccountResource_oauth2.java'; + if (data.generateUserManagement) return 'web/rest/AccountResource.java'; + return 'web/rest/AccountResource_skipUserManagement.java'; + }, + ], + }, + { + condition: data => data.generateUserManagement, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['web/rest/vm/ManagedUserVM.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + data => { + if (data.authenticationTypeOauth2) return 'web/rest/AccountResourceIT_oauth2.java'; + if (data.generateUserManagement) return 'web/rest/AccountResourceIT.java'; + return 'web/rest/AccountResourceIT_skipUserManagement.java'; + }, + ], + }, + ], +}; + +const userManagementFiles = { + userManagementFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'security/DomainUserDetailsService.java', + 'security/UserNotActivatedException.java', + 'service/MailService.java', + 'service/dto/PasswordChangeDTO.java', + 'service/EmailAlreadyUsedException.java', + 'service/InvalidPasswordException.java', + 'service/UsernameAlreadyUsedException.java', + 'web/rest/UserResource.java', + 'web/rest/vm/KeyAndPasswordVM.java', + 'web/rest/errors/EmailAlreadyUsedException.java', + 'web/rest/errors/InvalidPasswordException.java', + 'web/rest/errors/LoginAlreadyUsedException.java', + ], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['service/MailServiceIT.java', 'security/DomainUserDetailsServiceIT.java'], + }, + { + path: SERVER_MAIN_RES_DIR, + templates: ['templates/mail/activationEmail.html', 'templates/mail/creationEmail.html', 'templates/mail/passwordResetEmail.html'], + }, + { + path: SERVER_TEST_RES_DIR, + templates: [ + 'templates/mail/activationEmail.html', + 'templates/mail/creationEmail.html', + 'templates/mail/passwordResetEmail.html', + 'templates/mail/testEmail.html', + ], + }, + { + condition: generator => !generator.enableTranslation, + path: SERVER_TEST_RES_DIR, + templates: ['i18n/messages_en.properties'], + }, + ], +}; + +const jwtFiles = { + jwtBaseFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/SecurityJwtConfiguration.java', 'management/SecurityMetersService.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'management/SecurityMetersServiceTests.java', + 'security/jwt/AuthenticationIntegrationTest.java', + 'security/jwt/JwtAuthenticationTestUtils.java', + 'security/jwt/AuthenticationIntegrationTest.java', + 'security/jwt/TokenAuthenticationSecurityMetersIT.java', + 'security/jwt/TokenAuthenticationIT.java', + ], + }, + ], + entrypointFiles: [ + { + condition: data => !data.generateAuthenticationApi, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['security/jwt/TestAuthenticationResource.java'], + }, + { + condition: generator => generator.generateAuthenticationApi, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['web/rest/vm/LoginVM.java', 'web/rest/AuthenticateController.java'], + }, + { + condition: generator => generator.generateAuthenticationApi, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['web/rest/AuthenticateControllerIT.java'], + }, + ], +}; + +const gatewayFiles = { + gatewayFiles: [ + { + condition: generator => generator.authenticationTypeJwt, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/jwt/JWTRelayGatewayFilterFactory.java'], + }, + { + condition: generator => generator.serviceDiscoveryAny, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['web/rest/vm/RouteVM.java', 'web/rest/GatewayResource.java', 'web/filter/ModifyServersOpenApiFilter.java'], + }, + { + condition: generator => generator.serviceDiscoveryAny, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['web/filter/ModifyServersOpenApiFilterTest.java'], + }, + ], +}; + +const swaggerFiles = { + swagger: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/OpenApiConfiguration.java'], + }, + { + condition: generator => generator.buildToolGradle, + templates: ['gradle/swagger.gradle'], + }, + { + path: SERVER_MAIN_RES_DIR, + templates: ['swagger/api.yml'], + }, + ], +}; + +/** + * The default is to use a file path string. It implies use of the template method. + * For any other config an object { file:.., method:.., template:.. } can be used + */ +export const baseServerFiles = { + jib: [ + { + path: 'src/main/docker/jib/', + templates: ['entrypoint.sh'], + }, + ], + readme: [ + { + templates: ['README.md.jhi.spring-boot'], + }, + ], + packageJson: [ + { + condition: generator => generator.clientFrameworkNo, + templates: ['package.json'], + }, + ], + serverBuild: [ + { + templates: ['checkstyle.xml', '.devcontainer/devcontainer.json', '.devcontainer/Dockerfile'], + }, + { + condition: generator => generator.buildToolGradle, + templates: [ + 'build.gradle', + 'settings.gradle', + 'gradle.properties', + 'gradle/sonar.gradle', + 'gradle/docker.gradle', + 'gradle/profile_dev.gradle', + 'gradle/profile_prod.gradle', + 'gradle/war.gradle', + 'gradle/zipkin.gradle', + ], + }, + { + condition: generator => generator.buildToolMaven, + templates: ['pom.xml'], + }, + { + condition: generator => generator.useNpmWrapper, + transform: false, + templates: ['npmw', 'npmw.cmd'], + }, + ], + serverResource: [ + { + path: SERVER_MAIN_RES_DIR, + renameTo: moveToSrcMainResourcesDir, + transform: false, + templates: [data => (data.clientFrameworkReact || data.clientFrameworkVue ? `banner_${data.clientFramework}.txt` : 'banner.txt')], + }, + { + path: SERVER_MAIN_RES_DIR, + templates: [ + // Thymeleaf templates + 'templates/error.html', + 'logback-spring.xml', + 'config/application.yml', + 'config/application-dev.yml', + 'config/application-tls.yml', + 'config/application-prod.yml', + 'i18n/messages.properties', + ], + }, + ], + serverJavaAuthConfig: [ + { + condition: generator => + !generator.reactive && (generator.databaseTypeSql || generator.databaseTypeMongodb || generator.databaseTypeCouchbase), + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/SpringSecurityAuditorAware.java'], + }, + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/SecurityUtils.java', 'security/AuthoritiesConstants.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [data => `security/SecurityUtilsUnitTest_${data.imperativeOrReactive}.java`], + }, + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [data => `config/SecurityConfiguration_${data.imperativeOrReactive}.java`], + }, + { + condition: data => data.generateInMemoryUserCredentials && !data.reactive && data.authenticationTypeJwt, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/SecurityInMemoryConfiguration.java'], + }, + { + condition: generator => generator.generateUserManagement && generator.authenticationTypeSession && !generator.reactive, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['security/PersistentTokenRememberMeServices.java', 'domain/PersistentToken.java'], + }, + { + condition: generator => + generator.generateUserManagement && generator.authenticationTypeSession && !generator.reactive && !generator.databaseTypeCouchbase, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['repository/PersistentTokenRepository.java'], + }, + ], + serverMicroservice: [ + { + condition: generator => generator.applicationTypeMicroservice, + path: SERVER_MAIN_RES_DIR, + templates: [{ file: 'static/index_microservices.html', renameTo: () => 'static/index.html' }], + }, + ], + serviceDiscovery: [ + { + condition: generator => generator.serviceDiscoveryAny, + path: SERVER_MAIN_RES_DIR, + templates: ['config/bootstrap.yml', 'config/bootstrap-prod.yml'], + }, + { + condition: generator => generator.serviceDiscoveryAny, + path: SERVER_TEST_RES_DIR, + templates: ['config/bootstrap.yml'], + }, + { + condition: generator => generator.serviceDiscoveryAny && generator.serviceDiscoveryEureka, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/EurekaWorkaroundConfiguration.java'], + }, + ], + serverJavaApp: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: (data, filename) => moveToJavaPackageSrcDir(data, filename.replace('Application.java', `${data.mainClass}.java`)), + templates: ['Application.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'TechnicalStructureTest.java', + 'config/AsyncSyncConfiguration.java', + 'IntegrationTest.java', + 'config/SpringBootTestClassOrderer.java', + ], + }, + ], + serverJavaConfig: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'aop/logging/LoggingAspect.java', + 'config/AsyncConfiguration.java', + 'config/CRLFLogConverter.java', + 'config/DateTimeFormatConfiguration.java', + 'config/LoggingConfiguration.java', + 'config/ApplicationProperties.java', + 'config/JacksonConfiguration.java', + 'config/LoggingAspectConfiguration.java', + 'config/WebConfigurer.java', + ], + }, + { + condition: generator => + generator.generateUserManagement || + generator.authenticationTypeOauth2 || + generator.databaseTypeSql || + generator.databaseTypeMongodb || + generator.databaseTypeCouchbase, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/Constants.java'], + }, + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [data => `config/LocaleConfiguration_${data.imperativeOrReactive}.java`], + }, + ], + serverJavaDomain: [ + { + condition: generator => + generator.databaseTypeSql || generator.databaseTypeMongodb || generator.databaseTypeNeo4j || generator.databaseTypeCouchbase, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['domain/AbstractAuditingEntity.java'], + }, + ], + serverJavaWebError: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'web/rest/errors/BadRequestAlertException.java', + 'web/rest/errors/ErrorConstants.java', + 'web/rest/errors/ExceptionTranslator.java', + 'web/rest/errors/FieldErrorVM.java', + ], + }, + ], + serverJavaWeb: [ + { + condition: generator => generator.clientFrameworkAny && !generator.reactive, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/StaticResourcesWebConfiguration.java'], + }, + { + // TODO : add these tests to reactive + condition: generator => generator.clientFrameworkAny && !generator.reactive, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/StaticResourcesWebConfigurerTest.java'], + }, + { + condition: generator => generator.clientFrameworkAny, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [data => `web/filter/SpaWebFilter_${data.imperativeOrReactive}.java`], + }, + { + condition: generator => generator.clientFrameworkAny, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [data => `web/filter/SpaWebFilterIT_${data.imperativeOrReactive}.java`], + }, + { + condition: generator => generator.clientFrameworkAny && generator.reactive, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['web/filter/SpaWebFilterTestController_reactive.java'], + }, + ], + serverTestFw: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['web/rest/TestUtil.java', 'web/rest/errors/ExceptionTranslatorTestController.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [data => `web/rest/errors/ExceptionTranslatorIT_${data.imperativeOrReactive}.java`], + }, + { + path: SERVER_TEST_RES_DIR, + templates: ['config/application.yml', 'logback.xml', 'junit-platform.properties'], + }, + ], + serverJavaUserManagement: [ + { + condition: generator => generator.generateBuiltInAuthorityEntity, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['domain/Authority.java', 'repository/AuthorityRepository.java'], + }, + { + condition: generator => + generator.databaseTypeMongodb || + generator.searchEngineElasticsearch || + generator.databaseTypeCouchbase || + generator.searchEngineCouchbase, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/TestContainersSpringContextCustomizerFactory.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['web/rest/WithUnauthenticatedMockUser.java'], + }, + ], +}; + +export const serverFiles = mergeSections( + baseServerFiles, + addSectionsCondition(jwtFiles, context => context.authenticationTypeJwt), + addSectionsCondition(oauth2Files, context => context.authenticationTypeOauth2), + addSectionsCondition(gatewayFiles, context => context.applicationTypeGateway), + addSectionsCondition(accountFiles, context => context.generateAuthenticationApi), + addSectionsCondition(userManagementFiles, context => context.generateUserManagement), + addSectionsCondition(imperativeConfigFiles, context => !context.reactive), + addSectionsCondition(reactiveConfigFiles, context => context.reactive), + addSectionsCondition(swaggerFiles, context => context.enableSwaggerCodegen), +); + +/** + * @this {import('./index.js')} + */ +export function writeFiles() { + return this.asWritingTaskGroup({ + cleanupOldServerFiles, + + async writeFiles({ application }) { + return this.writeFiles({ + sections: serverFiles, + context: application, + }); + }, + }); +} diff --git a/generators/server/files.mjs b/generators/server/files.mjs deleted file mode 100644 index 02a8c122321a..000000000000 --- a/generators/server/files.mjs +++ /dev/null @@ -1,586 +0,0 @@ -/** - * Copyright 2013-2023 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 cleanupOldServerFiles from './cleanup.mjs'; -import { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR, SERVER_TEST_RES_DIR } from '../generator-constants.mjs'; -import { addSectionsCondition, mergeSections } from '../base/support/index.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir, moveToSrcMainResourcesDir } from './support/index.mjs'; - -const imperativeConfigFiles = { - imperativeFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['ApplicationWebXml.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/WebConfigurerTest.java', 'config/WebConfigurerTestController.java'], - }, - ], -}; - -const reactiveConfigFiles = { - reactiveFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/ReactorConfiguration.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/JHipsterBlockHoundIntegration.java'], - }, - { - path: SERVER_TEST_RES_DIR, - templates: ['META-INF/services/reactor.blockhound.integration.BlockHoundIntegration'], - }, - ], -}; - -const oauth2Files = { - oauth2Files: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/oauth2/AudienceValidator.java', 'security/oauth2/JwtGrantedAuthorityConverter.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['security/oauth2/AudienceValidatorTest.java', 'config/TestSecurityConfiguration.java'], - }, - { - condition: generator => generator.applicationTypeMonolith, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/OAuth2Configuration.java'], - }, - { - condition: generator => generator.generateAuthenticationApi, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['web/rest/AuthInfoResource.java', data => `web/rest/LogoutResource_${data.imperativeOrReactive}.java`], - }, - { - condition: generator => generator.generateAuthenticationApi, - path: SERVER_MAIN_SRC_DIR, - templates: [ - { - file: generator => `_package_/web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, - renameTo: generator => - `${generator.packageFolder}web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, - }, - ], - }, - { - condition: generator => generator.generateAuthenticationApi, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['test/util/OAuth2TestUtil.java', 'web/rest/LogoutResourceIT.java'], - }, - { - condition: generator => !generator.reactive && generator.generateAuthenticationApi, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/oauth2/CustomClaimConverter.java'], - }, - { - condition: generator => !generator.reactive && generator.generateAuthenticationApi, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['security/oauth2/CustomClaimConverterIT.java'], - }, - ], -}; - -const accountFiles = { - accountResource: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - data => { - if (data.authenticationTypeOauth2 && data.generateBuiltInUserEntity) return 'web/rest/AccountResource_oauth2.java'; - if (data.generateUserManagement) return 'web/rest/AccountResource.java'; - return 'web/rest/AccountResource_skipUserManagement.java'; - }, - ], - }, - { - condition: data => data.generateUserManagement, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['web/rest/vm/ManagedUserVM.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - data => { - if (data.authenticationTypeOauth2) return 'web/rest/AccountResourceIT_oauth2.java'; - if (data.generateUserManagement) return 'web/rest/AccountResourceIT.java'; - return 'web/rest/AccountResourceIT_skipUserManagement.java'; - }, - ], - }, - ], -}; - -const userManagementFiles = { - userManagementFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'security/DomainUserDetailsService.java', - 'security/UserNotActivatedException.java', - 'service/MailService.java', - 'service/dto/PasswordChangeDTO.java', - 'service/EmailAlreadyUsedException.java', - 'service/InvalidPasswordException.java', - 'service/UsernameAlreadyUsedException.java', - 'web/rest/UserResource.java', - 'web/rest/vm/KeyAndPasswordVM.java', - 'web/rest/errors/EmailAlreadyUsedException.java', - 'web/rest/errors/InvalidPasswordException.java', - 'web/rest/errors/LoginAlreadyUsedException.java', - ], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['service/MailServiceIT.java', 'security/DomainUserDetailsServiceIT.java'], - }, - { - path: SERVER_MAIN_RES_DIR, - templates: ['templates/mail/activationEmail.html', 'templates/mail/creationEmail.html', 'templates/mail/passwordResetEmail.html'], - }, - { - path: SERVER_TEST_RES_DIR, - templates: [ - 'templates/mail/activationEmail.html', - 'templates/mail/creationEmail.html', - 'templates/mail/passwordResetEmail.html', - 'templates/mail/testEmail.html', - ], - }, - { - condition: generator => !generator.enableTranslation, - path: SERVER_TEST_RES_DIR, - templates: ['i18n/messages_en.properties'], - }, - ], -}; - -const jwtFiles = { - jwtBaseFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/SecurityJwtConfiguration.java', 'management/SecurityMetersService.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'management/SecurityMetersServiceTests.java', - 'security/jwt/AuthenticationIntegrationTest.java', - 'security/jwt/JwtAuthenticationTestUtils.java', - 'security/jwt/AuthenticationIntegrationTest.java', - 'security/jwt/TokenAuthenticationSecurityMetersIT.java', - 'security/jwt/TokenAuthenticationIT.java', - ], - }, - ], - entrypointFiles: [ - { - condition: data => !data.generateAuthenticationApi, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['security/jwt/TestAuthenticationResource.java'], - }, - { - condition: generator => generator.generateAuthenticationApi, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['web/rest/vm/LoginVM.java', 'web/rest/AuthenticateController.java'], - }, - { - condition: generator => generator.generateAuthenticationApi, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['web/rest/AuthenticateControllerIT.java'], - }, - ], -}; - -const gatewayFiles = { - gatewayFiles: [ - { - condition: generator => generator.authenticationTypeJwt, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/jwt/JWTRelayGatewayFilterFactory.java'], - }, - { - condition: generator => generator.serviceDiscoveryAny, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['web/rest/vm/RouteVM.java', 'web/rest/GatewayResource.java', 'web/filter/ModifyServersOpenApiFilter.java'], - }, - { - condition: generator => generator.serviceDiscoveryAny, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['web/filter/ModifyServersOpenApiFilterTest.java'], - }, - ], -}; - -const swaggerFiles = { - swagger: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/OpenApiConfiguration.java'], - }, - { - condition: generator => generator.buildToolGradle, - templates: ['gradle/swagger.gradle'], - }, - { - path: SERVER_MAIN_RES_DIR, - templates: ['swagger/api.yml'], - }, - ], -}; - -/** - * The default is to use a file path string. It implies use of the template method. - * For any other config an object { file:.., method:.., template:.. } can be used - */ -export const baseServerFiles = { - jib: [ - { - path: 'src/main/docker/jib/', - templates: ['entrypoint.sh'], - }, - ], - readme: [ - { - templates: ['README.md.jhi.spring-boot'], - }, - ], - packageJson: [ - { - condition: generator => generator.clientFrameworkNo, - templates: ['package.json'], - }, - ], - serverBuild: [ - { - templates: ['checkstyle.xml', '.devcontainer/devcontainer.json', '.devcontainer/Dockerfile'], - }, - { - condition: generator => generator.buildToolGradle, - templates: [ - 'build.gradle', - 'settings.gradle', - 'gradle.properties', - 'gradle/sonar.gradle', - 'gradle/docker.gradle', - 'gradle/profile_dev.gradle', - 'gradle/profile_prod.gradle', - 'gradle/war.gradle', - 'gradle/zipkin.gradle', - ], - }, - { - condition: generator => generator.buildToolMaven, - templates: ['pom.xml'], - }, - { - condition: generator => generator.useNpmWrapper, - transform: false, - templates: ['npmw', 'npmw.cmd'], - }, - ], - serverResource: [ - { - path: SERVER_MAIN_RES_DIR, - renameTo: moveToSrcMainResourcesDir, - transform: false, - templates: [data => (data.clientFrameworkReact || data.clientFrameworkVue ? `banner_${data.clientFramework}.txt` : 'banner.txt')], - }, - { - path: SERVER_MAIN_RES_DIR, - templates: [ - // Thymeleaf templates - 'templates/error.html', - 'logback-spring.xml', - 'config/application.yml', - 'config/application-dev.yml', - 'config/application-tls.yml', - 'config/application-prod.yml', - 'i18n/messages.properties', - ], - }, - ], - serverJavaAuthConfig: [ - { - condition: generator => - !generator.reactive && (generator.databaseTypeSql || generator.databaseTypeMongodb || generator.databaseTypeCouchbase), - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/SpringSecurityAuditorAware.java'], - }, - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/SecurityUtils.java', 'security/AuthoritiesConstants.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [data => `security/SecurityUtilsUnitTest_${data.imperativeOrReactive}.java`], - }, - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [data => `config/SecurityConfiguration_${data.imperativeOrReactive}.java`], - }, - { - condition: data => data.generateInMemoryUserCredentials && !data.reactive && data.authenticationTypeJwt, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/SecurityInMemoryConfiguration.java'], - }, - { - condition: generator => generator.generateUserManagement && generator.authenticationTypeSession && !generator.reactive, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['security/PersistentTokenRememberMeServices.java', 'domain/PersistentToken.java'], - }, - { - condition: generator => - generator.generateUserManagement && generator.authenticationTypeSession && !generator.reactive && !generator.databaseTypeCouchbase, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['repository/PersistentTokenRepository.java'], - }, - ], - serverMicroservice: [ - { - condition: generator => generator.applicationTypeMicroservice, - path: SERVER_MAIN_RES_DIR, - templates: [{ file: 'static/index_microservices.html', renameTo: () => 'static/index.html' }], - }, - ], - serviceDiscovery: [ - { - condition: generator => generator.serviceDiscoveryAny, - path: SERVER_MAIN_RES_DIR, - templates: ['config/bootstrap.yml', 'config/bootstrap-prod.yml'], - }, - { - condition: generator => generator.serviceDiscoveryAny, - path: SERVER_TEST_RES_DIR, - templates: ['config/bootstrap.yml'], - }, - { - condition: generator => generator.serviceDiscoveryAny && generator.serviceDiscoveryEureka, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/EurekaWorkaroundConfiguration.java'], - }, - ], - serverJavaApp: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: (data, filename) => moveToJavaPackageSrcDir(data, filename.replace('Application.java', `${data.mainClass}.java`)), - templates: ['Application.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'TechnicalStructureTest.java', - 'config/AsyncSyncConfiguration.java', - 'IntegrationTest.java', - 'config/SpringBootTestClassOrderer.java', - ], - }, - ], - serverJavaConfig: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'aop/logging/LoggingAspect.java', - 'config/AsyncConfiguration.java', - 'config/CRLFLogConverter.java', - 'config/DateTimeFormatConfiguration.java', - 'config/LoggingConfiguration.java', - 'config/ApplicationProperties.java', - 'config/JacksonConfiguration.java', - 'config/LoggingAspectConfiguration.java', - 'config/WebConfigurer.java', - ], - }, - { - condition: generator => - generator.generateUserManagement || - generator.authenticationTypeOauth2 || - generator.databaseTypeSql || - generator.databaseTypeMongodb || - generator.databaseTypeCouchbase, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/Constants.java'], - }, - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [data => `config/LocaleConfiguration_${data.imperativeOrReactive}.java`], - }, - ], - serverJavaDomain: [ - { - condition: generator => - generator.databaseTypeSql || generator.databaseTypeMongodb || generator.databaseTypeNeo4j || generator.databaseTypeCouchbase, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['domain/AbstractAuditingEntity.java'], - }, - ], - serverJavaWebError: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'web/rest/errors/BadRequestAlertException.java', - 'web/rest/errors/ErrorConstants.java', - 'web/rest/errors/ExceptionTranslator.java', - 'web/rest/errors/FieldErrorVM.java', - ], - }, - ], - serverJavaWeb: [ - { - condition: generator => generator.clientFrameworkAny && !generator.reactive, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/StaticResourcesWebConfiguration.java'], - }, - { - // TODO : add these tests to reactive - condition: generator => generator.clientFrameworkAny && !generator.reactive, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/StaticResourcesWebConfigurerTest.java'], - }, - { - condition: generator => generator.clientFrameworkAny, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [data => `web/filter/SpaWebFilter_${data.imperativeOrReactive}.java`], - }, - { - condition: generator => generator.clientFrameworkAny, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [data => `web/filter/SpaWebFilterIT_${data.imperativeOrReactive}.java`], - }, - { - condition: generator => generator.clientFrameworkAny && generator.reactive, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['web/filter/SpaWebFilterTestController_reactive.java'], - }, - ], - serverTestFw: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['web/rest/TestUtil.java', 'web/rest/errors/ExceptionTranslatorTestController.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [data => `web/rest/errors/ExceptionTranslatorIT_${data.imperativeOrReactive}.java`], - }, - { - path: SERVER_TEST_RES_DIR, - templates: ['config/application.yml', 'logback.xml', 'junit-platform.properties'], - }, - ], - serverJavaUserManagement: [ - { - condition: generator => generator.generateBuiltInAuthorityEntity, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['domain/Authority.java', 'repository/AuthorityRepository.java'], - }, - { - condition: generator => - generator.databaseTypeMongodb || - generator.searchEngineElasticsearch || - generator.databaseTypeCouchbase || - generator.searchEngineCouchbase, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/TestContainersSpringContextCustomizerFactory.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['web/rest/WithUnauthenticatedMockUser.java'], - }, - ], -}; - -export const serverFiles = mergeSections( - baseServerFiles, - addSectionsCondition(jwtFiles, context => context.authenticationTypeJwt), - addSectionsCondition(oauth2Files, context => context.authenticationTypeOauth2), - addSectionsCondition(gatewayFiles, context => context.applicationTypeGateway), - addSectionsCondition(accountFiles, context => context.generateAuthenticationApi), - addSectionsCondition(userManagementFiles, context => context.generateUserManagement), - addSectionsCondition(imperativeConfigFiles, context => !context.reactive), - addSectionsCondition(reactiveConfigFiles, context => context.reactive), - addSectionsCondition(swaggerFiles, context => context.enableSwaggerCodegen), -); - -/** - * @this {import('./index.mjs')} - */ -export function writeFiles() { - return this.asWritingTaskGroup({ - cleanupOldServerFiles, - - async writeFiles({ application }) { - return this.writeFiles({ - sections: serverFiles, - context: application, - }); - }, - }); -} diff --git a/generators/server/generator.js b/generators/server/generator.js new file mode 100644 index 000000000000..acb46ee41e06 --- /dev/null +++ b/generators/server/generator.js @@ -0,0 +1,1111 @@ +/* eslint-disable camelcase */ +/** + * Copyright 2013-2023 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. + */ + +/* eslint-disable consistent-return */ +import { existsSync } from 'fs'; +import os from 'os'; +import chalk from 'chalk'; + +import { + getDBTypeFromDBValue, + buildJavaGet as javaGetCall, + javaBeanCase as javaBeanClassNameFormat, + buildJavaGetter as javaGetter, + buildJavaSetter as javaSetter, + getJavaValueGeneratorForType as getJavaValueForType, + getPrimaryKeyValue as getPKValue, + generateKeyStore, + addSpringFactory, + hibernateSnakeCase, +} from './support/index.js'; +import { askForOptionalItems, askForServerSideOpts, askForServerTestOpts } from './prompts.js'; + +import { + GENERATOR_BOOTSTRAP_APPLICATION, + GENERATOR_SPRING_DATA_CASSANDRA, + GENERATOR_COMMON, + GENERATOR_SPRING_DATA_COUCHBASE, + GENERATOR_CUCUMBER, + GENERATOR_DOCKER, + GENERATOR_SPRING_DATA_ELASTICSEARCH, + GENERATOR_GATLING, + GENERATOR_GRADLE, + GENERATOR_JAVA, + GENERATOR_SPRING_CLOUD_STREAM, + GENERATOR_LANGUAGES, + GENERATOR_MAVEN, + GENERATOR_SPRING_DATA_MONGODB, + GENERATOR_SPRING_DATA_NEO4J, + GENERATOR_SERVER, + GENERATOR_SPRING_CACHE, + GENERATOR_SPRING_WEBSOCKET, + GENERATOR_SPRING_DATA_RELATIONAL, + GENERATOR_FEIGN_CLIENT, +} from '../generator-list.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { writeFiles } from './files.js'; +import { writeFiles as writeEntityFiles } from './entity-files.js'; +import { packageJson } from '../../lib/index.js'; +import { + SERVER_MAIN_SRC_DIR, + SERVER_MAIN_RES_DIR, + SERVER_TEST_SRC_DIR, + SERVER_TEST_RES_DIR, + CLIENT_WEBPACK_DIR, + MAIN_DIR, + LOGIN_REGEX, + TEST_DIR, + JAVA_VERSION, + JAVA_COMPATIBLE_VERSIONS, + ADD_SPRING_MILESTONE_REPOSITORY, + JHIPSTER_DEPENDENCIES_VERSION, +} from '../generator-constants.js'; +import statistics from '../statistics.js'; + +import { + applicationTypes, + authenticationTypes, + buildToolTypes, + databaseTypes, + cacheTypes, + serviceDiscoveryTypes, + websocketTypes, + fieldTypes, + entityOptions, + validations, + reservedKeywords, + searchEngineTypes, + messageBrokerTypes, + clientFrameworkTypes, + testFrameworkTypes, + APPLICATION_TYPE_MICROSERVICE, +} from '../../jdl/jhipster/index.js'; +import { stringifyApplicationData } from '../base-application/support/index.js'; +import { createBase64Secret, createSecret, createNeedleCallback } from '../base/support/index.js'; +import command from './command.js'; +import { addJavaAnnotation } from '../java/support/index.js'; +import { isReservedPaginationWords } from '../../jdl/jhipster/reserved-keywords.js'; +import { loadStoredAppOptions } from '../app/support/index.js'; + +const dbTypes = fieldTypes; +const { + STRING: TYPE_STRING, + INTEGER: TYPE_INTEGER, + LONG: TYPE_LONG, + BIG_DECIMAL: TYPE_BIG_DECIMAL, + FLOAT: TYPE_FLOAT, + DOUBLE: TYPE_DOUBLE, + LOCAL_DATE: TYPE_LOCAL_DATE, + ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, + INSTANT: TYPE_INSTANT, + DURATION: TYPE_DURATION, +} = dbTypes.CommonDBTypes; +const { CUCUMBER, GATLING } = testFrameworkTypes; + +const { SUPPORTED_VALIDATION_RULES } = validations; +const { isReservedTableName } = reservedKeywords; +const { ANGULAR, REACT, VUE } = clientFrameworkTypes; +const { JWT, OAUTH2, SESSION } = authenticationTypes; +const { GRADLE, MAVEN } = buildToolTypes; +const { EUREKA } = serviceDiscoveryTypes; +const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS, NO: NO_CACHE } = cacheTypes; +const { NO: NO_WEBSOCKET, SPRING_WEBSOCKET } = websocketTypes; +const { CASSANDRA, COUCHBASE, MONGODB, NEO4J, SQL, NO: NO_DATABASE } = databaseTypes; +const { MICROSERVICE, GATEWAY } = applicationTypes; +const { KAFKA, PULSAR } = messageBrokerTypes; + +const { NO: NO_SEARCH_ENGINE, ELASTICSEARCH } = searchEngineTypes; +const { CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; +const { INSTANT } = CommonDBTypes; +const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; +const { PaginationTypes, ServiceTypes } = entityOptions; +const { + Validations: { MAX, MIN, MAXLENGTH, MINLENGTH, MAXBYTES, MINBYTES, PATTERN }, +} = validations; + +const WAIT_TIMEOUT = 3 * 60000; +const { NO: NO_PAGINATION } = PaginationTypes; +const { NO: NO_SERVICE } = ServiceTypes; + +export default class JHipsterServerGenerator extends BaseApplicationGenerator { + /** @type {string} */ + jhipsterDependenciesVersion; + /** @type {string} */ + projectVersion; + fakeKeytool; + command = command; + + async beforeQueue() { + if (!this.fromBlueprint) { + loadStoredAppOptions.call(this); + await this.composeWithBlueprints(GENERATOR_SERVER); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + await this.dependsOnJHipster(GENERATOR_COMMON); + await this.dependsOnJHipster(GENERATOR_JAVA); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadConfig() { + this.parseJHipsterCommand(this.command); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); + } + + get prompting() { + return this.asPromptingTaskGroup({ + async prompting({ control }) { + if (control.existingProject && this.options.askAnswered !== true) return; + await this.prompt(this.prepareQuestions(this.command.configs)); + }, + askForServerTestOpts, + askForServerSideOpts, + askForOptionalItems, + }); + } + + get [BaseApplicationGenerator.PROMPTING]() { + return this.asPromptingTaskGroup(this.delegateTasksToBlueprint(() => this.prompting)); + } + + get configuring() { + return this.asConfiguringTaskGroup({ + configServerPort() { + if (!this.jhipsterConfig.serverPort && this.jhipsterConfig.applicationIndex) { + this.jhipsterConfig.serverPort = 8080 + this.jhipsterConfig.applicationIndex; + } + }, + forceReactiveGateway() { + if (this.jhipsterConfig.applicationType === GATEWAY) { + if (this.jhipsterConfig.reactive !== undefined && !this.jhipsterConfig.reactive) { + this.log.warn('Non reactive gateway is not supported. Switching to reactive.'); + } + this.jhipsterConfig.reactive = true; + } + }, + configure() { + this._configureServer(); + }, + feignMigration() { + const { reactive, applicationType, feignClient } = this.jhipsterConfigWithDefaults; + if (feignClient) { + if (reactive) { + this.handleCheckFailure('Feign client is not supported by reactive applications.'); + } + if (applicationType !== APPLICATION_TYPE_MICROSERVICE) { + this.handleCheckFailure('Feign client is only supported by microservice applications.'); + } + } + if ( + feignClient === undefined && + this.isJhipsterVersionLessThan('8.0.1') && + reactive && + applicationType === APPLICATION_TYPE_MICROSERVICE + ) { + this.jhipsterConfig.feignClient = true; + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.asConfiguringTaskGroup(this.delegateTasksToBlueprint(() => this.configuring)); + } + + get composing() { + return this.asComposingTaskGroup({ + async composing() { + const { + buildTool, + enableTranslation, + databaseType, + messageBroker, + searchEngine, + testFrameworks, + websocket, + cacheProvider, + feignClient, + } = this.jhipsterConfigWithDefaults; + + if (buildTool === GRADLE) { + await this.composeWithJHipster(GENERATOR_GRADLE); + } else if (buildTool === MAVEN) { + await this.composeWithJHipster(GENERATOR_MAVEN); + } else { + throw new Error(`Build tool ${buildTool} is not supported`); + } + + await this.composeWithJHipster(GENERATOR_DOCKER); + + if (enableTranslation) { + await this.composeWithJHipster(GENERATOR_LANGUAGES); + } + if (databaseType === SQL) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_RELATIONAL); + } else if (databaseType === CASSANDRA) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_CASSANDRA); + } else if (databaseType === COUCHBASE) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_COUCHBASE); + } else if (databaseType === MONGODB) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_MONGODB); + } else if (databaseType === NEO4J) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_NEO4J); + } + if (messageBroker === KAFKA || messageBroker === PULSAR) { + await this.composeWithJHipster(GENERATOR_SPRING_CLOUD_STREAM); + } + if (searchEngine === ELASTICSEARCH) { + await this.composeWithJHipster(GENERATOR_SPRING_DATA_ELASTICSEARCH); + } + if (testFrameworks?.includes(CUCUMBER)) { + await this.composeWithJHipster(GENERATOR_CUCUMBER); + } + if (testFrameworks?.includes(GATLING)) { + await this.composeWithJHipster(GENERATOR_GATLING); + } + if (websocket === SPRING_WEBSOCKET) { + await this.composeWithJHipster(GENERATOR_SPRING_WEBSOCKET); + } + if ([EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS].includes(cacheProvider)) { + await this.composeWithJHipster(GENERATOR_SPRING_CACHE); + } + if (feignClient) { + await this.composeWithJHipster(GENERATOR_FEIGN_CLIENT); + } + }, + }); + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.asComposingTaskGroup(this.delegateTasksToBlueprint(() => this.composing)); + } + + get loading() { + return this.asLoadingTaskGroup({ + loadEnvironmentVariables({ application }) { + application.packageInfoJavadocs?.push( + { packageName: `${application.packageName}.aop.logging`, documentation: 'Logging aspect.' }, + { packageName: `${application.packageName}.management`, documentation: 'Application management.' }, + { packageName: `${application.packageName}.repository.rowmapper`, documentation: 'Webflux database column mapper.' }, + { packageName: `${application.packageName}.security`, documentation: 'Application security utilities.' }, + { packageName: `${application.packageName}.service.dto`, documentation: 'Data transfer objects for rest mapping.' }, + { packageName: `${application.packageName}.service.mapper`, documentation: 'Data transfer objects mappers.' }, + { packageName: `${application.packageName}.web.filter`, documentation: 'Request chain filters.' }, + { packageName: `${application.packageName}.web.rest.errors`, documentation: 'Rest layer error handling.' }, + { packageName: `${application.packageName}.web.rest.vm`, documentation: 'Rest layer visual models.' }, + ); + application.defaultPackaging = process.env.JHI_WAR === '1' ? 'war' : 'jar'; + if (application.defaultPackaging === 'war') { + this.log.info(`Using ${application.defaultPackaging} as default packaging`); + } + + const JHI_PROFILE = process.env.JHI_PROFILE; + application.defaultEnvironment = (JHI_PROFILE || '').includes('dev') ? 'dev' : 'prod'; + if (JHI_PROFILE) { + this.log.info(`Using ${application.defaultEnvironment} as default profile`); + } + }, + + setupServerconsts({ application }) { + // Make constants available in templates + application.MAIN_DIR = MAIN_DIR; + application.TEST_DIR = TEST_DIR; + application.LOGIN_REGEX = LOGIN_REGEX; + application.CLIENT_WEBPACK_DIR = CLIENT_WEBPACK_DIR; + application.SERVER_MAIN_SRC_DIR = SERVER_MAIN_SRC_DIR; + application.SERVER_MAIN_RES_DIR = SERVER_MAIN_RES_DIR; + application.SERVER_TEST_SRC_DIR = SERVER_TEST_SRC_DIR; + application.SERVER_TEST_RES_DIR = SERVER_TEST_RES_DIR; + + application.JAVA_VERSION = this.useVersionPlaceholders ? 'JAVA_VERSION' : JAVA_VERSION; + application.JAVA_COMPATIBLE_VERSIONS = JAVA_COMPATIBLE_VERSIONS; + application.javaCompatibleVersions = JAVA_COMPATIBLE_VERSIONS; + + if (this.projectVersion) { + application.projectVersion = this.projectVersion; + this.log.info(`Using projectVersion: ${application.projectVersion}`); + } else { + application.projectVersion = '0.0.1-SNAPSHOT'; + } + + if (this.useVersionPlaceholders) { + application.jhipsterDependenciesVersion = 'JHIPSTER_DEPENDENCIES_VERSION'; + } else if (this.jhipsterDependenciesVersion) { + application.jhipsterDependenciesVersion = this.jhipsterDependenciesVersion; + this.log.info(`Using jhipsterDependenciesVersion: ${application.jhipsterDependenciesVersion}`); + } else { + application.jhipsterDependenciesVersion = JHIPSTER_DEPENDENCIES_VERSION; + } + + application.ANGULAR = ANGULAR; + application.VUE = VUE; + application.REACT = REACT; + + this.packagejs = packageJson; + application.jhipsterPackageJson = packageJson; + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application }) { + const SPRING_BOOT_VERSION = application.javaDependencies['spring-boot']; + application.addSpringMilestoneRepository = + (application.backendType ?? 'Java') === 'Java' && + (ADD_SPRING_MILESTONE_REPOSITORY || SPRING_BOOT_VERSION.includes('M') || SPRING_BOOT_VERSION.includes('RC')); + }, + registerSpringFactory({ source, application }) { + source.addTestSpringFactory = ({ key, value }) => { + const springFactoriesFile = `${application.srcTestResources}META-INF/spring.factories`; + this.editFile(springFactoriesFile, { create: true }, addSpringFactory({ key, value })); + }; + }, + addLogNeedles({ source, application }) { + source.addIntegrationTestAnnotation = ({ package: packageName, annotation }) => + this.editFile(this.destinationPath(`${application.javaPackageTestDir}IntegrationTest.java`), content => + addJavaAnnotation(content, { package: packageName, annotation }), + ); + source.addLogbackMainLog = ({ name, level }) => + this.editFile( + this.destinationPath('src/main/resources/logback-spring.xml'), + createNeedleCallback({ + needle: 'logback-add-log', + contentToAdd: ``, + }), + ); + source.addLogbackTestLog = ({ name, level }) => + this.editFile( + this.destinationPath('src/test/resources/logback.xml'), + createNeedleCallback({ + needle: 'logback-add-log', + contentToAdd: ``, + }), + ); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); + } + + get postPreparing() { + return this.asPostPreparingTaskGroup({ + useNpmWrapper({ application }) { + if (application.useNpmWrapper) { + this.useNpmWrapperInstallTask(); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_PREPARING]() { + return this.delegateTasksToBlueprint(() => this.postPreparing); + } + + get configuringEachEntity() { + return this.asConfiguringEachEntityTaskGroup({ + configureMicroservice({ application, entityConfig }) { + if (application.applicationTypeMicroservice) { + if (entityConfig.microserviceName === undefined) { + entityConfig.microserviceName = application.baseName; + } + if (entityConfig.clientRootFolder === undefined) { + entityConfig.clientRootFolder = entityConfig.microserviceName; + } + if (entityConfig.databaseType === undefined) { + entityConfig.databaseType = application.databaseType; + } + } + }, + configureGateway({ application, entityConfig }) { + if (application.applicationTypeGateway) { + if (entityConfig.databaseType === undefined) { + entityConfig.databaseType = application.databaseType; + } + if (entityConfig.clientRootFolder === undefined) { + entityConfig.clientRootFolder = entityConfig.clientRootFolder = entityConfig.skipUiGrouping + ? '' + : entityConfig.microserviceName; + } + } + }, + configureEntitySearchEngine({ application, entityConfig }) { + const { applicationTypeMicroservice, applicationTypeGateway, clientFrameworkAny } = application; + if (entityConfig.microserviceName && !(applicationTypeMicroservice && clientFrameworkAny)) { + if (!entityConfig.searchEngine) { + // If a non-microfrontend microservice entity, should be disabled by default. + entityConfig.searchEngine = NO_SEARCH_ENGINE; + } + } + if ( + // Don't touch the configuration for microservice entities published at gateways + !(applicationTypeGateway && entityConfig.microserviceName) && + !application.searchEngineAny && + !entityConfig.searchEngine + ) { + // Search engine can only be enabled at entity level and disabled at application level for gateways publishing a microservice entity + entityConfig.searchEngine = NO_SEARCH_ENGINE; + this.log.warn('Search engine is enabled at entity level, but disabled at application level. Search engine will be disabled'); + } + }, + configureModelFiltering({ application, entityConfig }) { + const { databaseTypeSql, applicationTypeGateway } = application; + if ( + // Don't touch the configuration for microservice entities published at gateways + !(applicationTypeGateway && entityConfig.microserviceName) && + entityConfig.jpaMetamodelFiltering && + (!databaseTypeSql || entityConfig.service === NO_SERVICE) + ) { + this.log.warn('Not compatible with jpaMetamodelFiltering, disabling'); + entityConfig.jpaMetamodelFiltering = false; + } + }, + configureEntityTable({ application, entityName, entityConfig }) { + if ((application.applicationTypeGateway && entityConfig.microserviceName) || entityConfig.skipServer) return; + + entityConfig.entityTableName = entityConfig.entityTableName || hibernateSnakeCase(entityName); + + const databaseType = + entityConfig.prodDatabaseType ?? application.prodDatabaseType ?? entityConfig.databaseType ?? application.databaseType; + const fixedEntityTableName = this._fixEntityTableName(entityConfig.entityTableName, databaseType, application.jhiTablePrefix); + if (fixedEntityTableName !== entityConfig.entityTableName) { + entityConfig.entityTableName = fixedEntityTableName; + } + const validation = this._validateTableName(entityConfig.entityTableName, databaseType, entityConfig); + if (validation !== true) { + throw new Error(validation); + } + + // disable pagination if there is no database, unless it’s a microservice entity published by a gateway + if ( + ![SQL, MONGODB, COUCHBASE, NEO4J].includes(entityConfig.databaseType ?? application.databaseType) && + (application.applicationType !== GATEWAY || !entityConfig.microserviceName) + ) { + entityConfig.pagination = NO_PAGINATION; + } + + if (entityConfig.incrementalChangelog === undefined) { + // Keep entity's original incrementalChangelog option. + entityConfig.incrementalChangelog = + application.incrementalChangelog && + !existsSync( + this.destinationPath( + `src/main/resources/config/liquibase/changelog/${entityConfig.annotations?.changelogDate}_added_entity_${entityConfig.name}.xml`, + ), + ); + } + }, + + configureFields({ application, entityConfig, entityName }) { + const databaseType = entityConfig.databaseType ?? application.databaseType; + // Validate entity json field content + const fields = entityConfig.fields; + fields.forEach(field => { + // Migration from JodaTime to Java Time + if (field.fieldType === 'DateTime' || field.fieldType === 'Date') { + field.fieldType = INSTANT; + } + if (field.fieldType === BYTES && databaseType === CASSANDRA) { + field.fieldType = BYTE_BUFFER; + } + + this._validateField(entityName, field); + + if (field.fieldType === BYTE_BUFFER) { + this.log.warn( + `Cannot use validation in .jhipster/${entityName}.json for field ${stringifyApplicationData( + field, + )} \nHibernate JPA 2 Metamodel does not work with Bean Validation 2 for LOB fields, so LOB validation is disabled`, + ); + field.fieldValidate = false; + field.fieldValidateRules = []; + } + if (entityConfig.pagination && entityConfig.pagination !== NO_PAGINATION && isReservedPaginationWords(field.fieldName)) { + throw new Error( + `Field name '${field.fieldName}' found in ${entityConfig.name} is a reserved keyword, as it is used by Spring for pagination in the URL.`, + ); + } + // Field type check should be ignored for entities of others microservices. + if (!field.fieldValues && (!entityConfig.microserviceName || entityConfig.microserviceName === application.baseName)) { + if ( + !Object.values(CommonDBTypes).includes(field.fieldType) && + (application.databaseType !== SQL || !Object.values(RelationalOnlyDBTypes).includes(field.fieldType)) + ) { + throw new Error( + `The type '${field.fieldType}' is an unknown field type for field '${field.fieldName}' of entity '${entityConfig.name}' using '${application.databaseType}' database.`, + ); + } + } + }); + entityConfig.fields = fields; + }, + + configureRelationships({ entityConfig, entityName }) { + // Validate entity json relationship content + const relationships = entityConfig.relationships; + relationships.forEach(relationship => { + this._validateRelationship(entityName, relationship); + + if (relationship.relationshipName === undefined) { + relationship.relationshipName = relationship.otherEntityName; + this.log.warn( + `relationshipName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData( + relationship, + )}, using ${relationship.otherEntityName} as fallback`, + ); + } + if (relationship.useJPADerivedIdentifier) { + this.log.verboseInfo('Option useJPADerivedIdentifier is deprecated, use id instead'); + relationship.id = true; + } + }); + entityConfig.relationships = relationships; + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { + return this.asConfiguringEachEntityTaskGroup(this.delegateTasksToBlueprint(() => this.configuringEachEntity)); + } + + /** @inheritdoc */ + get default() { + return this.asDefaultTaskGroup({ + loadDomains({ application, entities }) { + application.domains = [ + ...new Set([application.packageName, ...entities.map(entity => entity.entityAbsolutePackage).filter(Boolean)]), + ]; + }, + + insight({ application }) { + statistics.sendSubGenEvent('generator', GENERATOR_SERVER, { + app: { + authenticationType: application.authenticationType, + cacheProvider: application.cacheProvider, + enableHibernateCache: application.enableHibernateCache, + websocket: application.websocket, + databaseType: application.databaseType, + devDatabaseType: application.devDatabaseType, + prodDatabaseType: application.prodDatabaseType, + searchEngine: application.searchEngine, + messageBroker: application.messageBroker, + serviceDiscoveryType: application.serviceDiscoveryType, + buildTool: application.buildTool, + enableSwaggerCodegen: application.enableSwaggerCodegen, + enableGradleEnterprise: application.enableGradleEnterprise, + }, + }); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); + } + + /** @inheritdoc */ + get writing() { + return this.asWritingTaskGroup({ + resetFakeDataSeed() { + this.resetEntitiesFakeData('server'); + }, + ...writeFiles.call(this), + async generateKeyStore({ application }) { + const keyStoreFile = this.destinationPath(`${application.srcMainResources}config/tls/keystore.p12`); + if (this.fakeKeytool) { + this.writeDestination(keyStoreFile, 'fake key-tool'); + } else { + this.validateResult(await generateKeyStore(keyStoreFile, { packageName: application.packageName })); + } + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.asWritingTaskGroup(this.delegateTasksToBlueprint(() => this.writing)); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + ...writeEntityFiles(), + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.asWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.writingEntities)); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addTestSpringFactory({ source, application }) { + if ( + application.databaseTypeMongodb || + application.searchEngineElasticsearch || + application.databaseTypeCouchbase || + application.searchEngineCouchbase + ) { + source.addTestSpringFactory({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.TestContainersSpringContextCustomizerFactory`, + }); + } + }, + customizeMaven({ application, source }) { + if (!application.buildToolMaven) return; + if (application.addSpringMilestoneRepository) { + const springRepository = { + id: 'spring-milestone', + name: 'Spring Milestones', + url: 'https://repo.spring.io/milestone', + }; + source.addMavenPluginRepository?.(springRepository); + source.addMavenRepository?.(springRepository); + source.addMavenDependency?.({ + groupId: 'org.springframework.boot', + artifactId: 'spring-boot-properties-migrator', + scope: 'runtime', + }); + } + if (application.jhipsterDependenciesVersion.endsWith('-SNAPSHOT')) { + source.addMavenRepository?.({ + id: 'ossrh-snapshots', + url: 'https://oss.sonatype.org/content/repositories/snapshots/', + releasesEnabled: false, + }); + } + }, + packageJsonScripts({ application }) { + const packageJsonConfigStorage = this.packageJson.createStorage('config').createProxy(); + packageJsonConfigStorage.backend_port = application.gatewayServerPort || application.serverPort; + packageJsonConfigStorage.packaging = application.defaultPackaging; + packageJsonConfigStorage.default_environment = application.defaultEnvironment; + }, + packageJsonBackendScripts({ application }) { + const scriptsStorage = this.packageJson.createStorage('scripts'); + const javaCommonLog = `-Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.${application.packageName}=OFF`; + const javaTestLog = + '-Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF'; + + const buildTool = application.buildTool; + let e2ePackage = 'target/e2e'; + if (buildTool === MAVEN) { + const excludeWebapp = application.skipClient ? '' : ' -Dskip.installnodenpm -Dskip.npm'; + scriptsStorage.set({ + 'app:start': './mvnw', + 'backend:info': './mvnw -ntp enforcer:display-info --batch-mode', + 'backend:doc:test': './mvnw -ntp javadoc:javadoc --batch-mode', + 'backend:nohttp:test': './mvnw -ntp checkstyle:check --batch-mode', + 'backend:start': `./mvnw${excludeWebapp}`, + 'java:jar': './mvnw -ntp verify -DskipTests --batch-mode', + 'java:war': './mvnw -ntp verify -DskipTests --batch-mode -Pwar', + 'java:docker': './mvnw -ntp verify -DskipTests -Pprod jib:dockerBuild', + 'java:docker:arm64': 'npm run java:docker -- -Djib-maven-plugin.architecture=arm64', + 'backend:unit:test': `./mvnw -ntp${excludeWebapp} verify --batch-mode ${javaCommonLog} ${javaTestLog}`, + 'backend:build-cache': './mvnw dependency:go-offline -ntp', + 'backend:debug': './mvnw -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"', + }); + } else if (buildTool === GRADLE) { + const excludeWebapp = application.skipClient ? '' : '-x webapp -x webapp_test'; + e2ePackage = 'e2e'; + scriptsStorage.set({ + 'app:start': './gradlew', + 'backend:info': './gradlew -v', + 'backend:doc:test': `./gradlew javadoc ${excludeWebapp}`, + 'backend:nohttp:test': `./gradlew checkstyleNohttp ${excludeWebapp}`, + 'backend:start': `./gradlew ${excludeWebapp}`, + 'java:jar': './gradlew bootJar -x test -x integrationTest', + 'java:war': './gradlew bootWar -Pwar -x test -x integrationTest', + 'java:docker': './gradlew bootJar -Pprod jibDockerBuild', + 'java:docker:arm64': 'npm run java:docker -- -PjibArchitecture=arm64', + 'backend:unit:test': `./gradlew test integrationTest ${excludeWebapp} ${javaCommonLog} ${javaTestLog}`, + 'postci:e2e:package': 'cp build/libs/*.$npm_package_config_packaging e2e.$npm_package_config_packaging', + 'backend:build-cache': + 'npm run backend:info && npm run backend:nohttp:test && npm run ci:e2e:package -- -x webapp -x webapp_test', + }); + } + + scriptsStorage.set({ + 'java:jar:dev': 'npm run java:jar -- -Pdev,webapp', + 'java:jar:prod': 'npm run java:jar -- -Pprod', + 'java:war:dev': 'npm run java:war -- -Pdev,webapp', + 'java:war:prod': 'npm run java:war -- -Pprod', + 'java:docker:dev': 'npm run java:docker -- -Pdev,webapp', + 'java:docker:prod': 'npm run java:docker -- -Pprod', + 'ci:backend:test': + 'npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment', + 'ci:e2e:package': + 'npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true', + 'preci:e2e:server:start': 'npm run services:db:await --if-present && npm run services:others:await --if-present', + 'ci:e2e:server:start': `java -jar ${e2ePackage}.$npm_package_config_packaging --spring.profiles.active=e2e,$npm_package_config_default_environment ${javaCommonLog} ${javaTestLog} --logging.level.org.springframework.web=ERROR`, + }); + }, + packageJsonE2eScripts({ application }) { + const scriptsStorage = this.packageJson.createStorage('scripts'); + const buildCmd = application.buildToolGradle ? 'gradlew' : 'mvnw'; + // TODO add e2eTests property to application. + if (this.jhipsterConfig.testFrameworks?.includes('cypress')) { + const applicationWaitTimeout = WAIT_TIMEOUT * (application.applicationTypeGateway ? 2 : 1); + const applicationEndpoint = application.applicationTypeMicroservice + ? `http-get://127.0.0.1:${application.gatewayServerPort}/${application.endpointPrefix}/management/health/readiness` + : 'http-get://127.0.0.1:$npm_package_config_backend_port/management/health'; + + scriptsStorage.set({ + 'ci:server:await': `echo "Waiting for server at port $npm_package_config_backend_port to start" && wait-on -t ${applicationWaitTimeout} ${applicationEndpoint} && echo "Server at port $npm_package_config_backend_port started"`, + 'pree2e:headless': 'npm run ci:server:await', + 'ci:e2e:run': 'concurrently -k -s first "npm run ci:e2e:server:start" "npm run e2e:headless"', + 'e2e:dev': `concurrently -k -s first "./${buildCmd}" "npm run e2e"`, + 'e2e:devserver': `concurrently -k -s first "npm run backend:start" "npm start" "wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:9000 && npm run e2e:headless -- -c baseUrl=http://localhost:9000"`, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + packageJsonE2eScripts({ application, entities }) { + if (application.applicationTypeGateway) { + const { serverPort, lowercaseBaseName } = application; + const microservices = [...new Set(entities.map(entity => entity.microserviceName))].filter(Boolean).map(ms => ms.toLowerCase()); + const scriptsStorage = this.packageJson.createStorage('scripts'); + const waitServices = microservices + .concat(lowercaseBaseName) + .map(ms => `npm run ci:server:await:${ms}`) + .join(' && '); + + scriptsStorage.set({ + [`ci:server:await:${lowercaseBaseName}`]: `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:$npm_package_config_backend_port/management/health`, + ...Object.fromEntries( + microservices.map(ms => [ + `ci:server:await:${ms}`, + `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${serverPort}/services/${ms}/management/health/readiness`, + ]), + ), + 'ci:server:await': `echo "Waiting for services to start" && ${waitServices} && echo "Services started"`, + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.asPostWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.postWritingEntities)); + } + + get end() { + return this.asEndTaskGroup({ + end({ application }) { + this.log.ok('Spring Boot application generated successfully.'); + + let executable = 'mvnw'; + if (application.buildTool === GRADLE) { + executable = 'gradlew'; + } + let logMsgComment = ''; + if (os.platform() === 'win32') { + logMsgComment = ` (${chalk.yellow.bold(executable)} if using Windows Command Prompt)`; + } + this.log.log(chalk.green(` Run your Spring Boot application:\n ${chalk.yellow.bold(`./${executable}`)}${logMsgComment}`)); + }, + }); + } + + get [BaseApplicationGenerator.END]() { + return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); + } + + _configureServer(config = this.jhipsterConfigWithDefaults, dest = this.jhipsterConfig) { + // JWT authentication is mandatory with Eureka, so the JHipster Registry + // can control the applications + if (config.serviceDiscoveryType === EUREKA && config.authenticationType !== OAUTH2) { + dest.authenticationType = JWT; + } + + // Generate JWT secret key if key does not already exist in config + if ( + (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) && + config.jwtSecretKey === undefined + ) { + dest.jwtSecretKey = createBase64Secret(64, this.options.reproducibleTests); + } + // Generate remember me key if key does not already exist in config + if (config.authenticationType === SESSION && !dest.rememberMeKey) { + dest.rememberMeKey = createSecret(); + } + + if (config.authenticationType === OAUTH2) { + dest.skipUserManagement = true; + } + + if (config.enableHibernateCache && [NO_CACHE, MEMCACHED].includes(config.cacheProvider)) { + this.log.verboseInfo(`Disabling hibernate cache for cache provider ${config.cacheProvider}`); + dest.enableHibernateCache = false; + } + + if (!config.databaseType && config.prodDatabaseType) { + dest.databaseType = getDBTypeFromDBValue(config.prodDatabaseType); + } + if (!config.devDatabaseType && config.prodDatabaseType) { + dest.devDatabaseType = config.prodDatabaseType; + } + + if (config.websocket && config.websocket !== NO_WEBSOCKET) { + if (config.reactive) { + throw new Error('Spring Websocket is not supported with reactive applications.'); + } + if (config.applicationType === MICROSERVICE) { + throw new Error('Spring Websocket is not supported with microservice applications.'); + } + } + + const databaseType = config.databaseType; + if (databaseType === NO_DATABASE) { + dest.devDatabaseType = NO_DATABASE; + dest.prodDatabaseType = NO_DATABASE; + dest.enableHibernateCache = false; + dest.skipUserManagement = true; + } else if ([MONGODB, NEO4J, COUCHBASE, CASSANDRA].includes(databaseType)) { + dest.devDatabaseType = databaseType; + dest.prodDatabaseType = databaseType; + dest.enableHibernateCache = false; + } + } + + /** + * Validate the entityTableName + * @return {true|string} true for a valid value or error message. + */ + _validateTableName(entityTableName, prodDatabaseType, entity) { + const jhiTablePrefix = entity.jhiTablePrefix; + const instructions = `You can specify a different table name in your JDL file or change it in .jhipster/${entity.name}.json file and then run again 'jhipster entity ${entity.name}.'`; + + if (!/^([a-zA-Z0-9_]*)$/.test(entityTableName)) { + return `The table name cannot contain special characters.\n${instructions}`; + } + if (!entityTableName) { + return 'The table name cannot be empty'; + } + if (isReservedTableName(entityTableName, prodDatabaseType)) { + if (jhiTablePrefix) { + this.log.warn( + `The table name cannot contain the '${entityTableName.toUpperCase()}' reserved keyword, so it will be prefixed with '${jhiTablePrefix}_'.\n${instructions}`, + ); + entity.entityTableName = `${jhiTablePrefix}_${entityTableName}`; + } else { + this.log.warn( + `The table name contain the '${entityTableName.toUpperCase()}' reserved keyword but you have defined an empty jhiPrefix so it won't be prefixed and thus the generated application might not work'.\n${instructions}`, + ); + } + } + return true; + } + + _validateField(entityName, field) { + if (field.fieldName === undefined) { + throw new Error(`fieldName is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); + } + + if (field.fieldType === undefined) { + throw new Error(`fieldType is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); + } + + if (field.fieldValidateRules !== undefined) { + if (!Array.isArray(field.fieldValidateRules)) { + throw new Error(`fieldValidateRules is not an array in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); + } + field.fieldValidateRules.forEach(fieldValidateRule => { + if (!SUPPORTED_VALIDATION_RULES.includes(fieldValidateRule)) { + throw new Error( + `fieldValidateRules contains unknown validation rule ${fieldValidateRule} in .jhipster/${entityName}.json for field ${stringifyApplicationData( + field, + )} [supported validation rules ${SUPPORTED_VALIDATION_RULES}]`, + ); + } + }); + if (field.fieldValidateRules.includes(MAX) && field.fieldValidateRulesMax === undefined) { + throw new Error(`fieldValidateRulesMax is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); + } + if (field.fieldValidateRules.includes(MIN) && field.fieldValidateRulesMin === undefined) { + throw new Error(`fieldValidateRulesMin is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); + } + if (field.fieldValidateRules.includes(MAXLENGTH) && field.fieldValidateRulesMaxlength === undefined) { + throw new Error( + `fieldValidateRulesMaxlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, + ); + } + if (field.fieldValidateRules.includes(MINLENGTH) && field.fieldValidateRulesMinlength === undefined) { + throw new Error( + `fieldValidateRulesMinlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, + ); + } + if (field.fieldValidateRules.includes(MAXBYTES) && field.fieldValidateRulesMaxbytes === undefined) { + throw new Error( + `fieldValidateRulesMaxbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, + ); + } + if (field.fieldValidateRules.includes(MINBYTES) && field.fieldValidateRulesMinbytes === undefined) { + throw new Error( + `fieldValidateRulesMinbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, + ); + } + if (field.fieldValidateRules.includes(PATTERN) && field.fieldValidateRulesPattern === undefined) { + throw new Error( + `fieldValidateRulesPattern is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, + ); + } + } + } + + useNpmWrapperInstallTask() { + this.setFeatures({ + customInstallTask: async function customInstallTask(preferredPm, defaultInstallTask) { + const buildTool = this.jhipsterConfig.buildTool; + if ((preferredPm && preferredPm !== 'npm') || this.jhipsterConfig.skipClient || (buildTool !== GRADLE && buildTool !== MAVEN)) { + return defaultInstallTask(); + } + + const npmCommand = process.platform === 'win32' ? 'npmw' : './npmw'; + try { + await this.spawnCommand(npmCommand, ['install'], { preferLocal: true }); + } catch (error) { + this.log.error(chalk.red(`Error executing '${npmCommand} install', please execute it yourself. (${error.shortMessage})`)); + } + return true; + }.bind(this), + }); + } + + _validateRelationship(entityName, relationship) { + if (relationship.otherEntityName === undefined) { + throw new Error( + `otherEntityName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, + ); + } + if (relationship.relationshipType === undefined) { + throw new Error( + `relationshipType is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, + ); + } + + if ( + relationship.relationshipSide === undefined && + (relationship.relationshipType === 'one-to-one' || relationship.relationshipType === 'many-to-many') + ) { + throw new Error( + `relationshipSide is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, + ); + } + } + + _fixEntityTableName(entityTableName, prodDatabaseType, jhiTablePrefix) { + if (isReservedTableName(entityTableName, prodDatabaseType) && jhiTablePrefix) { + entityTableName = `${jhiTablePrefix}_${entityTableName}`; + } + return entityTableName; + } + + /** + * @private + * Return the method name which converts the filter to specification + * @param {string} fieldType + */ + getSpecificationBuilder(fieldType) { + if ( + [ + TYPE_INTEGER, + TYPE_LONG, + TYPE_FLOAT, + TYPE_DOUBLE, + TYPE_BIG_DECIMAL, + TYPE_LOCAL_DATE, + TYPE_ZONED_DATE_TIME, + TYPE_INSTANT, + TYPE_DURATION, + ].includes(fieldType) + ) { + return 'buildRangeSpecification'; + } + if (fieldType === TYPE_STRING) { + return 'buildStringSpecification'; + } + return 'buildSpecification'; + } + + getJavaValueGeneratorForType(type) { + return getJavaValueForType(type); + } + + /** + * @private + * Returns the primary key value based on the primary key type, DB and default value + * + * @param {string} primaryKey - the primary key type + * @param {string} databaseType - the database type + * @param {string} defaultValue - default value + * @returns {string} java primary key value + */ + getPrimaryKeyValue(primaryKey, databaseType = this.jhipsterConfig.databaseType, defaultValue = 1) { + return getPKValue(primaryKey, databaseType, defaultValue); + } + + /** + * @private + * Convert to Java bean name case + * + * Handle the specific case when the second letter is capitalized + * See http://stackoverflow.com/questions/2948083/naming-convention-for-getters-setters-in-java + * + * @param {string} beanName name of the class to check + * @return {string} + */ + javaBeanCase(beanName) { + return javaBeanClassNameFormat(beanName); + } + + buildJavaGet(reference) { + return javaGetCall(reference); + } + + buildJavaGetter(reference, type = reference.type) { + return javaGetter(reference, type); + } + + buildJavaSetter(reference, valueDefinition = `${reference.type} ${reference.name}`) { + return javaSetter(reference, valueDefinition); + } +} diff --git a/generators/server/generator.mjs b/generators/server/generator.mjs deleted file mode 100644 index cb75d7ff21c6..000000000000 --- a/generators/server/generator.mjs +++ /dev/null @@ -1,1111 +0,0 @@ -/* eslint-disable camelcase */ -/** - * Copyright 2013-2023 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. - */ - -/* eslint-disable consistent-return */ -import { existsSync } from 'fs'; -import os from 'os'; -import chalk from 'chalk'; - -import { - getDBTypeFromDBValue, - buildJavaGet as javaGetCall, - javaBeanCase as javaBeanClassNameFormat, - buildJavaGetter as javaGetter, - buildJavaSetter as javaSetter, - getJavaValueGeneratorForType as getJavaValueForType, - getPrimaryKeyValue as getPKValue, - generateKeyStore, - addSpringFactory, - hibernateSnakeCase, -} from './support/index.mjs'; -import { askForOptionalItems, askForServerSideOpts, askForServerTestOpts } from './prompts.mjs'; - -import { - GENERATOR_BOOTSTRAP_APPLICATION, - GENERATOR_SPRING_DATA_CASSANDRA, - GENERATOR_COMMON, - GENERATOR_SPRING_DATA_COUCHBASE, - GENERATOR_CUCUMBER, - GENERATOR_DOCKER, - GENERATOR_SPRING_DATA_ELASTICSEARCH, - GENERATOR_GATLING, - GENERATOR_GRADLE, - GENERATOR_JAVA, - GENERATOR_SPRING_CLOUD_STREAM, - GENERATOR_LANGUAGES, - GENERATOR_MAVEN, - GENERATOR_SPRING_DATA_MONGODB, - GENERATOR_SPRING_DATA_NEO4J, - GENERATOR_SERVER, - GENERATOR_SPRING_CACHE, - GENERATOR_SPRING_WEBSOCKET, - GENERATOR_SPRING_DATA_RELATIONAL, - GENERATOR_FEIGN_CLIENT, -} from '../generator-list.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { writeFiles } from './files.mjs'; -import { writeFiles as writeEntityFiles } from './entity-files.mjs'; -import { packageJson } from '../../lib/index.mjs'; -import { - SERVER_MAIN_SRC_DIR, - SERVER_MAIN_RES_DIR, - SERVER_TEST_SRC_DIR, - SERVER_TEST_RES_DIR, - CLIENT_WEBPACK_DIR, - MAIN_DIR, - LOGIN_REGEX, - TEST_DIR, - JAVA_VERSION, - JAVA_COMPATIBLE_VERSIONS, - ADD_SPRING_MILESTONE_REPOSITORY, - JHIPSTER_DEPENDENCIES_VERSION, -} from '../generator-constants.mjs'; -import statistics from '../statistics.mjs'; - -import { - applicationTypes, - authenticationTypes, - buildToolTypes, - databaseTypes, - cacheTypes, - serviceDiscoveryTypes, - websocketTypes, - fieldTypes, - entityOptions, - validations, - reservedKeywords, - searchEngineTypes, - messageBrokerTypes, - clientFrameworkTypes, - testFrameworkTypes, - APPLICATION_TYPE_MICROSERVICE, -} from '../../jdl/jhipster/index.mjs'; -import { stringifyApplicationData } from '../base-application/support/index.mjs'; -import { createBase64Secret, createSecret, createNeedleCallback } from '../base/support/index.mjs'; -import command from './command.mjs'; -import { addJavaAnnotation } from '../java/support/index.mjs'; -import { isReservedPaginationWords } from '../../jdl/jhipster/reserved-keywords.js'; -import { loadStoredAppOptions } from '../app/support/index.mjs'; - -const dbTypes = fieldTypes; -const { - STRING: TYPE_STRING, - INTEGER: TYPE_INTEGER, - LONG: TYPE_LONG, - BIG_DECIMAL: TYPE_BIG_DECIMAL, - FLOAT: TYPE_FLOAT, - DOUBLE: TYPE_DOUBLE, - LOCAL_DATE: TYPE_LOCAL_DATE, - ZONED_DATE_TIME: TYPE_ZONED_DATE_TIME, - INSTANT: TYPE_INSTANT, - DURATION: TYPE_DURATION, -} = dbTypes.CommonDBTypes; -const { CUCUMBER, GATLING } = testFrameworkTypes; - -const { SUPPORTED_VALIDATION_RULES } = validations; -const { isReservedTableName } = reservedKeywords; -const { ANGULAR, REACT, VUE } = clientFrameworkTypes; -const { JWT, OAUTH2, SESSION } = authenticationTypes; -const { GRADLE, MAVEN } = buildToolTypes; -const { EUREKA } = serviceDiscoveryTypes; -const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS, NO: NO_CACHE } = cacheTypes; -const { NO: NO_WEBSOCKET, SPRING_WEBSOCKET } = websocketTypes; -const { CASSANDRA, COUCHBASE, MONGODB, NEO4J, SQL, NO: NO_DATABASE } = databaseTypes; -const { MICROSERVICE, GATEWAY } = applicationTypes; -const { KAFKA, PULSAR } = messageBrokerTypes; - -const { NO: NO_SEARCH_ENGINE, ELASTICSEARCH } = searchEngineTypes; -const { CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes; -const { INSTANT } = CommonDBTypes; -const { BYTES, BYTE_BUFFER } = RelationalOnlyDBTypes; -const { PaginationTypes, ServiceTypes } = entityOptions; -const { - Validations: { MAX, MIN, MAXLENGTH, MINLENGTH, MAXBYTES, MINBYTES, PATTERN }, -} = validations; - -const WAIT_TIMEOUT = 3 * 60000; -const { NO: NO_PAGINATION } = PaginationTypes; -const { NO: NO_SERVICE } = ServiceTypes; - -export default class JHipsterServerGenerator extends BaseApplicationGenerator { - /** @type {string} */ - jhipsterDependenciesVersion; - /** @type {string} */ - projectVersion; - fakeKeytool; - command = command; - - async beforeQueue() { - if (!this.fromBlueprint) { - loadStoredAppOptions.call(this); - await this.composeWithBlueprints(GENERATOR_SERVER); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - await this.dependsOnJHipster(GENERATOR_COMMON); - await this.dependsOnJHipster(GENERATOR_JAVA); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadConfig() { - this.parseJHipsterCommand(this.command); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.asInitializingTaskGroup(this.delegateTasksToBlueprint(() => this.initializing)); - } - - get prompting() { - return this.asPromptingTaskGroup({ - async prompting({ control }) { - if (control.existingProject && this.options.askAnswered !== true) return; - await this.prompt(this.prepareQuestions(this.command.configs)); - }, - askForServerTestOpts, - askForServerSideOpts, - askForOptionalItems, - }); - } - - get [BaseApplicationGenerator.PROMPTING]() { - return this.asPromptingTaskGroup(this.delegateTasksToBlueprint(() => this.prompting)); - } - - get configuring() { - return this.asConfiguringTaskGroup({ - configServerPort() { - if (!this.jhipsterConfig.serverPort && this.jhipsterConfig.applicationIndex) { - this.jhipsterConfig.serverPort = 8080 + this.jhipsterConfig.applicationIndex; - } - }, - forceReactiveGateway() { - if (this.jhipsterConfig.applicationType === GATEWAY) { - if (this.jhipsterConfig.reactive !== undefined && !this.jhipsterConfig.reactive) { - this.log.warn('Non reactive gateway is not supported. Switching to reactive.'); - } - this.jhipsterConfig.reactive = true; - } - }, - configure() { - this._configureServer(); - }, - feignMigration() { - const { reactive, applicationType, feignClient } = this.jhipsterConfigWithDefaults; - if (feignClient) { - if (reactive) { - this.handleCheckFailure('Feign client is not supported by reactive applications.'); - } - if (applicationType !== APPLICATION_TYPE_MICROSERVICE) { - this.handleCheckFailure('Feign client is only supported by microservice applications.'); - } - } - if ( - feignClient === undefined && - this.isJhipsterVersionLessThan('8.0.1') && - reactive && - applicationType === APPLICATION_TYPE_MICROSERVICE - ) { - this.jhipsterConfig.feignClient = true; - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING]() { - return this.asConfiguringTaskGroup(this.delegateTasksToBlueprint(() => this.configuring)); - } - - get composing() { - return this.asComposingTaskGroup({ - async composing() { - const { - buildTool, - enableTranslation, - databaseType, - messageBroker, - searchEngine, - testFrameworks, - websocket, - cacheProvider, - feignClient, - } = this.jhipsterConfigWithDefaults; - - if (buildTool === GRADLE) { - await this.composeWithJHipster(GENERATOR_GRADLE); - } else if (buildTool === MAVEN) { - await this.composeWithJHipster(GENERATOR_MAVEN); - } else { - throw new Error(`Build tool ${buildTool} is not supported`); - } - - await this.composeWithJHipster(GENERATOR_DOCKER); - - if (enableTranslation) { - await this.composeWithJHipster(GENERATOR_LANGUAGES); - } - if (databaseType === SQL) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_RELATIONAL); - } else if (databaseType === CASSANDRA) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_CASSANDRA); - } else if (databaseType === COUCHBASE) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_COUCHBASE); - } else if (databaseType === MONGODB) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_MONGODB); - } else if (databaseType === NEO4J) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_NEO4J); - } - if (messageBroker === KAFKA || messageBroker === PULSAR) { - await this.composeWithJHipster(GENERATOR_SPRING_CLOUD_STREAM); - } - if (searchEngine === ELASTICSEARCH) { - await this.composeWithJHipster(GENERATOR_SPRING_DATA_ELASTICSEARCH); - } - if (testFrameworks?.includes(CUCUMBER)) { - await this.composeWithJHipster(GENERATOR_CUCUMBER); - } - if (testFrameworks?.includes(GATLING)) { - await this.composeWithJHipster(GENERATOR_GATLING); - } - if (websocket === SPRING_WEBSOCKET) { - await this.composeWithJHipster(GENERATOR_SPRING_WEBSOCKET); - } - if ([EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS].includes(cacheProvider)) { - await this.composeWithJHipster(GENERATOR_SPRING_CACHE); - } - if (feignClient) { - await this.composeWithJHipster(GENERATOR_FEIGN_CLIENT); - } - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.asComposingTaskGroup(this.delegateTasksToBlueprint(() => this.composing)); - } - - get loading() { - return this.asLoadingTaskGroup({ - loadEnvironmentVariables({ application }) { - application.packageInfoJavadocs?.push( - { packageName: `${application.packageName}.aop.logging`, documentation: 'Logging aspect.' }, - { packageName: `${application.packageName}.management`, documentation: 'Application management.' }, - { packageName: `${application.packageName}.repository.rowmapper`, documentation: 'Webflux database column mapper.' }, - { packageName: `${application.packageName}.security`, documentation: 'Application security utilities.' }, - { packageName: `${application.packageName}.service.dto`, documentation: 'Data transfer objects for rest mapping.' }, - { packageName: `${application.packageName}.service.mapper`, documentation: 'Data transfer objects mappers.' }, - { packageName: `${application.packageName}.web.filter`, documentation: 'Request chain filters.' }, - { packageName: `${application.packageName}.web.rest.errors`, documentation: 'Rest layer error handling.' }, - { packageName: `${application.packageName}.web.rest.vm`, documentation: 'Rest layer visual models.' }, - ); - application.defaultPackaging = process.env.JHI_WAR === '1' ? 'war' : 'jar'; - if (application.defaultPackaging === 'war') { - this.log.info(`Using ${application.defaultPackaging} as default packaging`); - } - - const JHI_PROFILE = process.env.JHI_PROFILE; - application.defaultEnvironment = (JHI_PROFILE || '').includes('dev') ? 'dev' : 'prod'; - if (JHI_PROFILE) { - this.log.info(`Using ${application.defaultEnvironment} as default profile`); - } - }, - - setupServerconsts({ application }) { - // Make constants available in templates - application.MAIN_DIR = MAIN_DIR; - application.TEST_DIR = TEST_DIR; - application.LOGIN_REGEX = LOGIN_REGEX; - application.CLIENT_WEBPACK_DIR = CLIENT_WEBPACK_DIR; - application.SERVER_MAIN_SRC_DIR = SERVER_MAIN_SRC_DIR; - application.SERVER_MAIN_RES_DIR = SERVER_MAIN_RES_DIR; - application.SERVER_TEST_SRC_DIR = SERVER_TEST_SRC_DIR; - application.SERVER_TEST_RES_DIR = SERVER_TEST_RES_DIR; - - application.JAVA_VERSION = this.useVersionPlaceholders ? 'JAVA_VERSION' : JAVA_VERSION; - application.JAVA_COMPATIBLE_VERSIONS = JAVA_COMPATIBLE_VERSIONS; - application.javaCompatibleVersions = JAVA_COMPATIBLE_VERSIONS; - - if (this.projectVersion) { - application.projectVersion = this.projectVersion; - this.log.info(`Using projectVersion: ${application.projectVersion}`); - } else { - application.projectVersion = '0.0.1-SNAPSHOT'; - } - - if (this.useVersionPlaceholders) { - application.jhipsterDependenciesVersion = 'JHIPSTER_DEPENDENCIES_VERSION'; - } else if (this.jhipsterDependenciesVersion) { - application.jhipsterDependenciesVersion = this.jhipsterDependenciesVersion; - this.log.info(`Using jhipsterDependenciesVersion: ${application.jhipsterDependenciesVersion}`); - } else { - application.jhipsterDependenciesVersion = JHIPSTER_DEPENDENCIES_VERSION; - } - - application.ANGULAR = ANGULAR; - application.VUE = VUE; - application.REACT = REACT; - - this.packagejs = packageJson; - application.jhipsterPackageJson = packageJson; - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application }) { - const SPRING_BOOT_VERSION = application.javaDependencies['spring-boot']; - application.addSpringMilestoneRepository = - (application.backendType ?? 'Java') === 'Java' && - (ADD_SPRING_MILESTONE_REPOSITORY || SPRING_BOOT_VERSION.includes('M') || SPRING_BOOT_VERSION.includes('RC')); - }, - registerSpringFactory({ source, application }) { - source.addTestSpringFactory = ({ key, value }) => { - const springFactoriesFile = `${application.srcTestResources}META-INF/spring.factories`; - this.editFile(springFactoriesFile, { create: true }, addSpringFactory({ key, value })); - }; - }, - addLogNeedles({ source, application }) { - source.addIntegrationTestAnnotation = ({ package: packageName, annotation }) => - this.editFile(this.destinationPath(`${application.javaPackageTestDir}IntegrationTest.java`), content => - addJavaAnnotation(content, { package: packageName, annotation }), - ); - source.addLogbackMainLog = ({ name, level }) => - this.editFile( - this.destinationPath('src/main/resources/logback-spring.xml'), - createNeedleCallback({ - needle: 'logback-add-log', - contentToAdd: ``, - }), - ); - source.addLogbackTestLog = ({ name, level }) => - this.editFile( - this.destinationPath('src/test/resources/logback.xml'), - createNeedleCallback({ - needle: 'logback-add-log', - contentToAdd: ``, - }), - ); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.asPreparingTaskGroup(this.delegateTasksToBlueprint(() => this.preparing)); - } - - get postPreparing() { - return this.asPostPreparingTaskGroup({ - useNpmWrapper({ application }) { - if (application.useNpmWrapper) { - this.useNpmWrapperInstallTask(); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_PREPARING]() { - return this.delegateTasksToBlueprint(() => this.postPreparing); - } - - get configuringEachEntity() { - return this.asConfiguringEachEntityTaskGroup({ - configureMicroservice({ application, entityConfig }) { - if (application.applicationTypeMicroservice) { - if (entityConfig.microserviceName === undefined) { - entityConfig.microserviceName = application.baseName; - } - if (entityConfig.clientRootFolder === undefined) { - entityConfig.clientRootFolder = entityConfig.microserviceName; - } - if (entityConfig.databaseType === undefined) { - entityConfig.databaseType = application.databaseType; - } - } - }, - configureGateway({ application, entityConfig }) { - if (application.applicationTypeGateway) { - if (entityConfig.databaseType === undefined) { - entityConfig.databaseType = application.databaseType; - } - if (entityConfig.clientRootFolder === undefined) { - entityConfig.clientRootFolder = entityConfig.clientRootFolder = entityConfig.skipUiGrouping - ? '' - : entityConfig.microserviceName; - } - } - }, - configureEntitySearchEngine({ application, entityConfig }) { - const { applicationTypeMicroservice, applicationTypeGateway, clientFrameworkAny } = application; - if (entityConfig.microserviceName && !(applicationTypeMicroservice && clientFrameworkAny)) { - if (!entityConfig.searchEngine) { - // If a non-microfrontend microservice entity, should be disabled by default. - entityConfig.searchEngine = NO_SEARCH_ENGINE; - } - } - if ( - // Don't touch the configuration for microservice entities published at gateways - !(applicationTypeGateway && entityConfig.microserviceName) && - !application.searchEngineAny && - !entityConfig.searchEngine - ) { - // Search engine can only be enabled at entity level and disabled at application level for gateways publishing a microservice entity - entityConfig.searchEngine = NO_SEARCH_ENGINE; - this.log.warn('Search engine is enabled at entity level, but disabled at application level. Search engine will be disabled'); - } - }, - configureModelFiltering({ application, entityConfig }) { - const { databaseTypeSql, applicationTypeGateway } = application; - if ( - // Don't touch the configuration for microservice entities published at gateways - !(applicationTypeGateway && entityConfig.microserviceName) && - entityConfig.jpaMetamodelFiltering && - (!databaseTypeSql || entityConfig.service === NO_SERVICE) - ) { - this.log.warn('Not compatible with jpaMetamodelFiltering, disabling'); - entityConfig.jpaMetamodelFiltering = false; - } - }, - configureEntityTable({ application, entityName, entityConfig }) { - if ((application.applicationTypeGateway && entityConfig.microserviceName) || entityConfig.skipServer) return; - - entityConfig.entityTableName = entityConfig.entityTableName || hibernateSnakeCase(entityName); - - const databaseType = - entityConfig.prodDatabaseType ?? application.prodDatabaseType ?? entityConfig.databaseType ?? application.databaseType; - const fixedEntityTableName = this._fixEntityTableName(entityConfig.entityTableName, databaseType, application.jhiTablePrefix); - if (fixedEntityTableName !== entityConfig.entityTableName) { - entityConfig.entityTableName = fixedEntityTableName; - } - const validation = this._validateTableName(entityConfig.entityTableName, databaseType, entityConfig); - if (validation !== true) { - throw new Error(validation); - } - - // disable pagination if there is no database, unless it’s a microservice entity published by a gateway - if ( - ![SQL, MONGODB, COUCHBASE, NEO4J].includes(entityConfig.databaseType ?? application.databaseType) && - (application.applicationType !== GATEWAY || !entityConfig.microserviceName) - ) { - entityConfig.pagination = NO_PAGINATION; - } - - if (entityConfig.incrementalChangelog === undefined) { - // Keep entity's original incrementalChangelog option. - entityConfig.incrementalChangelog = - application.incrementalChangelog && - !existsSync( - this.destinationPath( - `src/main/resources/config/liquibase/changelog/${entityConfig.annotations?.changelogDate}_added_entity_${entityConfig.name}.xml`, - ), - ); - } - }, - - configureFields({ application, entityConfig, entityName }) { - const databaseType = entityConfig.databaseType ?? application.databaseType; - // Validate entity json field content - const fields = entityConfig.fields; - fields.forEach(field => { - // Migration from JodaTime to Java Time - if (field.fieldType === 'DateTime' || field.fieldType === 'Date') { - field.fieldType = INSTANT; - } - if (field.fieldType === BYTES && databaseType === CASSANDRA) { - field.fieldType = BYTE_BUFFER; - } - - this._validateField(entityName, field); - - if (field.fieldType === BYTE_BUFFER) { - this.log.warn( - `Cannot use validation in .jhipster/${entityName}.json for field ${stringifyApplicationData( - field, - )} \nHibernate JPA 2 Metamodel does not work with Bean Validation 2 for LOB fields, so LOB validation is disabled`, - ); - field.fieldValidate = false; - field.fieldValidateRules = []; - } - if (entityConfig.pagination && entityConfig.pagination !== NO_PAGINATION && isReservedPaginationWords(field.fieldName)) { - throw new Error( - `Field name '${field.fieldName}' found in ${entityConfig.name} is a reserved keyword, as it is used by Spring for pagination in the URL.`, - ); - } - // Field type check should be ignored for entities of others microservices. - if (!field.fieldValues && (!entityConfig.microserviceName || entityConfig.microserviceName === application.baseName)) { - if ( - !Object.values(CommonDBTypes).includes(field.fieldType) && - (application.databaseType !== SQL || !Object.values(RelationalOnlyDBTypes).includes(field.fieldType)) - ) { - throw new Error( - `The type '${field.fieldType}' is an unknown field type for field '${field.fieldName}' of entity '${entityConfig.name}' using '${application.databaseType}' database.`, - ); - } - } - }); - entityConfig.fields = fields; - }, - - configureRelationships({ entityConfig, entityName }) { - // Validate entity json relationship content - const relationships = entityConfig.relationships; - relationships.forEach(relationship => { - this._validateRelationship(entityName, relationship); - - if (relationship.relationshipName === undefined) { - relationship.relationshipName = relationship.otherEntityName; - this.log.warn( - `relationshipName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData( - relationship, - )}, using ${relationship.otherEntityName} as fallback`, - ); - } - if (relationship.useJPADerivedIdentifier) { - this.log.verboseInfo('Option useJPADerivedIdentifier is deprecated, use id instead'); - relationship.id = true; - } - }); - entityConfig.relationships = relationships; - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { - return this.asConfiguringEachEntityTaskGroup(this.delegateTasksToBlueprint(() => this.configuringEachEntity)); - } - - /** @inheritdoc */ - get default() { - return this.asDefaultTaskGroup({ - loadDomains({ application, entities }) { - application.domains = [ - ...new Set([application.packageName, ...entities.map(entity => entity.entityAbsolutePackage).filter(Boolean)]), - ]; - }, - - insight({ application }) { - statistics.sendSubGenEvent('generator', GENERATOR_SERVER, { - app: { - authenticationType: application.authenticationType, - cacheProvider: application.cacheProvider, - enableHibernateCache: application.enableHibernateCache, - websocket: application.websocket, - databaseType: application.databaseType, - devDatabaseType: application.devDatabaseType, - prodDatabaseType: application.prodDatabaseType, - searchEngine: application.searchEngine, - messageBroker: application.messageBroker, - serviceDiscoveryType: application.serviceDiscoveryType, - buildTool: application.buildTool, - enableSwaggerCodegen: application.enableSwaggerCodegen, - enableGradleEnterprise: application.enableGradleEnterprise, - }, - }); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); - } - - /** @inheritdoc */ - get writing() { - return this.asWritingTaskGroup({ - resetFakeDataSeed() { - this.resetEntitiesFakeData('server'); - }, - ...writeFiles.call(this), - async generateKeyStore({ application }) { - const keyStoreFile = this.destinationPath(`${application.srcMainResources}config/tls/keystore.p12`); - if (this.fakeKeytool) { - this.writeDestination(keyStoreFile, 'fake key-tool'); - } else { - this.validateResult(await generateKeyStore(keyStoreFile, { packageName: application.packageName })); - } - }, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.asWritingTaskGroup(this.delegateTasksToBlueprint(() => this.writing)); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - ...writeEntityFiles(), - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.asWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.writingEntities)); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addTestSpringFactory({ source, application }) { - if ( - application.databaseTypeMongodb || - application.searchEngineElasticsearch || - application.databaseTypeCouchbase || - application.searchEngineCouchbase - ) { - source.addTestSpringFactory({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.TestContainersSpringContextCustomizerFactory`, - }); - } - }, - customizeMaven({ application, source }) { - if (!application.buildToolMaven) return; - if (application.addSpringMilestoneRepository) { - const springRepository = { - id: 'spring-milestone', - name: 'Spring Milestones', - url: 'https://repo.spring.io/milestone', - }; - source.addMavenPluginRepository?.(springRepository); - source.addMavenRepository?.(springRepository); - source.addMavenDependency?.({ - groupId: 'org.springframework.boot', - artifactId: 'spring-boot-properties-migrator', - scope: 'runtime', - }); - } - if (application.jhipsterDependenciesVersion.endsWith('-SNAPSHOT')) { - source.addMavenRepository?.({ - id: 'ossrh-snapshots', - url: 'https://oss.sonatype.org/content/repositories/snapshots/', - releasesEnabled: false, - }); - } - }, - packageJsonScripts({ application }) { - const packageJsonConfigStorage = this.packageJson.createStorage('config').createProxy(); - packageJsonConfigStorage.backend_port = application.gatewayServerPort || application.serverPort; - packageJsonConfigStorage.packaging = application.defaultPackaging; - packageJsonConfigStorage.default_environment = application.defaultEnvironment; - }, - packageJsonBackendScripts({ application }) { - const scriptsStorage = this.packageJson.createStorage('scripts'); - const javaCommonLog = `-Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.${application.packageName}=OFF`; - const javaTestLog = - '-Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF'; - - const buildTool = application.buildTool; - let e2ePackage = 'target/e2e'; - if (buildTool === MAVEN) { - const excludeWebapp = application.skipClient ? '' : ' -Dskip.installnodenpm -Dskip.npm'; - scriptsStorage.set({ - 'app:start': './mvnw', - 'backend:info': './mvnw -ntp enforcer:display-info --batch-mode', - 'backend:doc:test': './mvnw -ntp javadoc:javadoc --batch-mode', - 'backend:nohttp:test': './mvnw -ntp checkstyle:check --batch-mode', - 'backend:start': `./mvnw${excludeWebapp}`, - 'java:jar': './mvnw -ntp verify -DskipTests --batch-mode', - 'java:war': './mvnw -ntp verify -DskipTests --batch-mode -Pwar', - 'java:docker': './mvnw -ntp verify -DskipTests -Pprod jib:dockerBuild', - 'java:docker:arm64': 'npm run java:docker -- -Djib-maven-plugin.architecture=arm64', - 'backend:unit:test': `./mvnw -ntp${excludeWebapp} verify --batch-mode ${javaCommonLog} ${javaTestLog}`, - 'backend:build-cache': './mvnw dependency:go-offline -ntp', - 'backend:debug': './mvnw -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"', - }); - } else if (buildTool === GRADLE) { - const excludeWebapp = application.skipClient ? '' : '-x webapp -x webapp_test'; - e2ePackage = 'e2e'; - scriptsStorage.set({ - 'app:start': './gradlew', - 'backend:info': './gradlew -v', - 'backend:doc:test': `./gradlew javadoc ${excludeWebapp}`, - 'backend:nohttp:test': `./gradlew checkstyleNohttp ${excludeWebapp}`, - 'backend:start': `./gradlew ${excludeWebapp}`, - 'java:jar': './gradlew bootJar -x test -x integrationTest', - 'java:war': './gradlew bootWar -Pwar -x test -x integrationTest', - 'java:docker': './gradlew bootJar -Pprod jibDockerBuild', - 'java:docker:arm64': 'npm run java:docker -- -PjibArchitecture=arm64', - 'backend:unit:test': `./gradlew test integrationTest ${excludeWebapp} ${javaCommonLog} ${javaTestLog}`, - 'postci:e2e:package': 'cp build/libs/*.$npm_package_config_packaging e2e.$npm_package_config_packaging', - 'backend:build-cache': - 'npm run backend:info && npm run backend:nohttp:test && npm run ci:e2e:package -- -x webapp -x webapp_test', - }); - } - - scriptsStorage.set({ - 'java:jar:dev': 'npm run java:jar -- -Pdev,webapp', - 'java:jar:prod': 'npm run java:jar -- -Pprod', - 'java:war:dev': 'npm run java:war -- -Pdev,webapp', - 'java:war:prod': 'npm run java:war -- -Pprod', - 'java:docker:dev': 'npm run java:docker -- -Pdev,webapp', - 'java:docker:prod': 'npm run java:docker -- -Pprod', - 'ci:backend:test': - 'npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment', - 'ci:e2e:package': - 'npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true', - 'preci:e2e:server:start': 'npm run services:db:await --if-present && npm run services:others:await --if-present', - 'ci:e2e:server:start': `java -jar ${e2ePackage}.$npm_package_config_packaging --spring.profiles.active=e2e,$npm_package_config_default_environment ${javaCommonLog} ${javaTestLog} --logging.level.org.springframework.web=ERROR`, - }); - }, - packageJsonE2eScripts({ application }) { - const scriptsStorage = this.packageJson.createStorage('scripts'); - const buildCmd = application.buildToolGradle ? 'gradlew' : 'mvnw'; - // TODO add e2eTests property to application. - if (this.jhipsterConfig.testFrameworks?.includes('cypress')) { - const applicationWaitTimeout = WAIT_TIMEOUT * (application.applicationTypeGateway ? 2 : 1); - const applicationEndpoint = application.applicationTypeMicroservice - ? `http-get://127.0.0.1:${application.gatewayServerPort}/${application.endpointPrefix}/management/health/readiness` - : 'http-get://127.0.0.1:$npm_package_config_backend_port/management/health'; - - scriptsStorage.set({ - 'ci:server:await': `echo "Waiting for server at port $npm_package_config_backend_port to start" && wait-on -t ${applicationWaitTimeout} ${applicationEndpoint} && echo "Server at port $npm_package_config_backend_port started"`, - 'pree2e:headless': 'npm run ci:server:await', - 'ci:e2e:run': 'concurrently -k -s first "npm run ci:e2e:server:start" "npm run e2e:headless"', - 'e2e:dev': `concurrently -k -s first "./${buildCmd}" "npm run e2e"`, - 'e2e:devserver': `concurrently -k -s first "npm run backend:start" "npm start" "wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:9000 && npm run e2e:headless -- -c baseUrl=http://localhost:9000"`, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - packageJsonE2eScripts({ application, entities }) { - if (application.applicationTypeGateway) { - const { serverPort, lowercaseBaseName } = application; - const microservices = [...new Set(entities.map(entity => entity.microserviceName))].filter(Boolean).map(ms => ms.toLowerCase()); - const scriptsStorage = this.packageJson.createStorage('scripts'); - const waitServices = microservices - .concat(lowercaseBaseName) - .map(ms => `npm run ci:server:await:${ms}`) - .join(' && '); - - scriptsStorage.set({ - [`ci:server:await:${lowercaseBaseName}`]: `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:$npm_package_config_backend_port/management/health`, - ...Object.fromEntries( - microservices.map(ms => [ - `ci:server:await:${ms}`, - `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${serverPort}/services/${ms}/management/health/readiness`, - ]), - ), - 'ci:server:await': `echo "Waiting for services to start" && ${waitServices} && echo "Services started"`, - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.asPostWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.postWritingEntities)); - } - - get end() { - return this.asEndTaskGroup({ - end({ application }) { - this.log.ok('Spring Boot application generated successfully.'); - - let executable = 'mvnw'; - if (application.buildTool === GRADLE) { - executable = 'gradlew'; - } - let logMsgComment = ''; - if (os.platform() === 'win32') { - logMsgComment = ` (${chalk.yellow.bold(executable)} if using Windows Command Prompt)`; - } - this.log.log(chalk.green(` Run your Spring Boot application:\n ${chalk.yellow.bold(`./${executable}`)}${logMsgComment}`)); - }, - }); - } - - get [BaseApplicationGenerator.END]() { - return this.asEndTaskGroup(this.delegateTasksToBlueprint(() => this.end)); - } - - _configureServer(config = this.jhipsterConfigWithDefaults, dest = this.jhipsterConfig) { - // JWT authentication is mandatory with Eureka, so the JHipster Registry - // can control the applications - if (config.serviceDiscoveryType === EUREKA && config.authenticationType !== OAUTH2) { - dest.authenticationType = JWT; - } - - // Generate JWT secret key if key does not already exist in config - if ( - (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) && - config.jwtSecretKey === undefined - ) { - dest.jwtSecretKey = createBase64Secret(64, this.options.reproducibleTests); - } - // Generate remember me key if key does not already exist in config - if (config.authenticationType === SESSION && !dest.rememberMeKey) { - dest.rememberMeKey = createSecret(); - } - - if (config.authenticationType === OAUTH2) { - dest.skipUserManagement = true; - } - - if (config.enableHibernateCache && [NO_CACHE, MEMCACHED].includes(config.cacheProvider)) { - this.log.verboseInfo(`Disabling hibernate cache for cache provider ${config.cacheProvider}`); - dest.enableHibernateCache = false; - } - - if (!config.databaseType && config.prodDatabaseType) { - dest.databaseType = getDBTypeFromDBValue(config.prodDatabaseType); - } - if (!config.devDatabaseType && config.prodDatabaseType) { - dest.devDatabaseType = config.prodDatabaseType; - } - - if (config.websocket && config.websocket !== NO_WEBSOCKET) { - if (config.reactive) { - throw new Error('Spring Websocket is not supported with reactive applications.'); - } - if (config.applicationType === MICROSERVICE) { - throw new Error('Spring Websocket is not supported with microservice applications.'); - } - } - - const databaseType = config.databaseType; - if (databaseType === NO_DATABASE) { - dest.devDatabaseType = NO_DATABASE; - dest.prodDatabaseType = NO_DATABASE; - dest.enableHibernateCache = false; - dest.skipUserManagement = true; - } else if ([MONGODB, NEO4J, COUCHBASE, CASSANDRA].includes(databaseType)) { - dest.devDatabaseType = databaseType; - dest.prodDatabaseType = databaseType; - dest.enableHibernateCache = false; - } - } - - /** - * Validate the entityTableName - * @return {true|string} true for a valid value or error message. - */ - _validateTableName(entityTableName, prodDatabaseType, entity) { - const jhiTablePrefix = entity.jhiTablePrefix; - const instructions = `You can specify a different table name in your JDL file or change it in .jhipster/${entity.name}.json file and then run again 'jhipster entity ${entity.name}.'`; - - if (!/^([a-zA-Z0-9_]*)$/.test(entityTableName)) { - return `The table name cannot contain special characters.\n${instructions}`; - } - if (!entityTableName) { - return 'The table name cannot be empty'; - } - if (isReservedTableName(entityTableName, prodDatabaseType)) { - if (jhiTablePrefix) { - this.log.warn( - `The table name cannot contain the '${entityTableName.toUpperCase()}' reserved keyword, so it will be prefixed with '${jhiTablePrefix}_'.\n${instructions}`, - ); - entity.entityTableName = `${jhiTablePrefix}_${entityTableName}`; - } else { - this.log.warn( - `The table name contain the '${entityTableName.toUpperCase()}' reserved keyword but you have defined an empty jhiPrefix so it won't be prefixed and thus the generated application might not work'.\n${instructions}`, - ); - } - } - return true; - } - - _validateField(entityName, field) { - if (field.fieldName === undefined) { - throw new Error(`fieldName is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); - } - - if (field.fieldType === undefined) { - throw new Error(`fieldType is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); - } - - if (field.fieldValidateRules !== undefined) { - if (!Array.isArray(field.fieldValidateRules)) { - throw new Error(`fieldValidateRules is not an array in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); - } - field.fieldValidateRules.forEach(fieldValidateRule => { - if (!SUPPORTED_VALIDATION_RULES.includes(fieldValidateRule)) { - throw new Error( - `fieldValidateRules contains unknown validation rule ${fieldValidateRule} in .jhipster/${entityName}.json for field ${stringifyApplicationData( - field, - )} [supported validation rules ${SUPPORTED_VALIDATION_RULES}]`, - ); - } - }); - if (field.fieldValidateRules.includes(MAX) && field.fieldValidateRulesMax === undefined) { - throw new Error(`fieldValidateRulesMax is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); - } - if (field.fieldValidateRules.includes(MIN) && field.fieldValidateRulesMin === undefined) { - throw new Error(`fieldValidateRulesMin is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`); - } - if (field.fieldValidateRules.includes(MAXLENGTH) && field.fieldValidateRulesMaxlength === undefined) { - throw new Error( - `fieldValidateRulesMaxlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, - ); - } - if (field.fieldValidateRules.includes(MINLENGTH) && field.fieldValidateRulesMinlength === undefined) { - throw new Error( - `fieldValidateRulesMinlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, - ); - } - if (field.fieldValidateRules.includes(MAXBYTES) && field.fieldValidateRulesMaxbytes === undefined) { - throw new Error( - `fieldValidateRulesMaxbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, - ); - } - if (field.fieldValidateRules.includes(MINBYTES) && field.fieldValidateRulesMinbytes === undefined) { - throw new Error( - `fieldValidateRulesMinbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, - ); - } - if (field.fieldValidateRules.includes(PATTERN) && field.fieldValidateRulesPattern === undefined) { - throw new Error( - `fieldValidateRulesPattern is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`, - ); - } - } - } - - useNpmWrapperInstallTask() { - this.setFeatures({ - customInstallTask: async function customInstallTask(preferredPm, defaultInstallTask) { - const buildTool = this.jhipsterConfig.buildTool; - if ((preferredPm && preferredPm !== 'npm') || this.jhipsterConfig.skipClient || (buildTool !== GRADLE && buildTool !== MAVEN)) { - return defaultInstallTask(); - } - - const npmCommand = process.platform === 'win32' ? 'npmw' : './npmw'; - try { - await this.spawnCommand(npmCommand, ['install'], { preferLocal: true }); - } catch (error) { - this.log.error(chalk.red(`Error executing '${npmCommand} install', please execute it yourself. (${error.shortMessage})`)); - } - return true; - }.bind(this), - }); - } - - _validateRelationship(entityName, relationship) { - if (relationship.otherEntityName === undefined) { - throw new Error( - `otherEntityName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, - ); - } - if (relationship.relationshipType === undefined) { - throw new Error( - `relationshipType is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, - ); - } - - if ( - relationship.relationshipSide === undefined && - (relationship.relationshipType === 'one-to-one' || relationship.relationshipType === 'many-to-many') - ) { - throw new Error( - `relationshipSide is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`, - ); - } - } - - _fixEntityTableName(entityTableName, prodDatabaseType, jhiTablePrefix) { - if (isReservedTableName(entityTableName, prodDatabaseType) && jhiTablePrefix) { - entityTableName = `${jhiTablePrefix}_${entityTableName}`; - } - return entityTableName; - } - - /** - * @private - * Return the method name which converts the filter to specification - * @param {string} fieldType - */ - getSpecificationBuilder(fieldType) { - if ( - [ - TYPE_INTEGER, - TYPE_LONG, - TYPE_FLOAT, - TYPE_DOUBLE, - TYPE_BIG_DECIMAL, - TYPE_LOCAL_DATE, - TYPE_ZONED_DATE_TIME, - TYPE_INSTANT, - TYPE_DURATION, - ].includes(fieldType) - ) { - return 'buildRangeSpecification'; - } - if (fieldType === TYPE_STRING) { - return 'buildStringSpecification'; - } - return 'buildSpecification'; - } - - getJavaValueGeneratorForType(type) { - return getJavaValueForType(type); - } - - /** - * @private - * Returns the primary key value based on the primary key type, DB and default value - * - * @param {string} primaryKey - the primary key type - * @param {string} databaseType - the database type - * @param {string} defaultValue - default value - * @returns {string} java primary key value - */ - getPrimaryKeyValue(primaryKey, databaseType = this.jhipsterConfig.databaseType, defaultValue = 1) { - return getPKValue(primaryKey, databaseType, defaultValue); - } - - /** - * @private - * Convert to Java bean name case - * - * Handle the specific case when the second letter is capitalized - * See http://stackoverflow.com/questions/2948083/naming-convention-for-getters-setters-in-java - * - * @param {string} beanName name of the class to check - * @return {string} - */ - javaBeanCase(beanName) { - return javaBeanClassNameFormat(beanName); - } - - buildJavaGet(reference) { - return javaGetCall(reference); - } - - buildJavaGetter(reference, type = reference.type) { - return javaGetter(reference, type); - } - - buildJavaSetter(reference, valueDefinition = `${reference.type} ${reference.name}`) { - return javaSetter(reference, valueDefinition); - } -} diff --git a/generators/server/generator.spec.js b/generators/server/generator.spec.js new file mode 100644 index 000000000000..37ea67bc858a --- /dev/null +++ b/generators/server/generator.spec.js @@ -0,0 +1,217 @@ +/** + * Copyright 2013-2023 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 assert from 'assert/strict'; +import { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import { defaultHelpers as helpers, checkEnforcements, result as runResult } from '../../test/support/index.js'; +import Generator from './index.js'; +import { mockedGenerators, shouldComposeWithCouchbase, shouldComposeWithSpringCloudStream } from './__test-support/index.js'; +import { GENERATOR_SERVER } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorPath = join(__dirname, 'index.js'); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({}, GENERATOR_SERVER); + + describe('composing', () => { + describe('buildTool option', () => { + describe('maven', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + buildTool: 'maven', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + + it('should compose with maven generator', () => { + assert(runResult.mockedGenerators['jhipster:maven'].calledOnce); + }); + it('should not compose with others buildTool generators', () => { + assert(runResult.mockedGenerators['jhipster:gradle'].notCalled); + }); + }); + describe('gradle', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + buildTool: 'gradle', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + + it('should compose with gradle generator', () => { + assert(runResult.mockedGenerators['jhipster:gradle'].calledOnce); + }); + it('should not compose with others buildTool generators', () => { + assert(runResult.mockedGenerators['jhipster:maven'].notCalled); + }); + }); + }); + + describe('messageBroker option', () => { + describe('no', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + messageBroker: 'no', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + + shouldComposeWithSpringCloudStream(false, () => runResult); + }); + describe('kafka', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + messageBroker: 'kafka', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + shouldComposeWithSpringCloudStream(true, () => runResult); + }); + describe('pulsar', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + messageBroker: 'pulsar', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + shouldComposeWithSpringCloudStream(true, () => runResult); + }); + }); + + describe('databaseType option', () => { + describe('no with jwt', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({ + databaseType: 'no', + authenticationType: 'jwt', + }) + .withMockedGenerators(mockedGenerators); + }); + + it('should match generated files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + shouldComposeWithCouchbase(false, () => runResult); + }); + describe('no with session', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({ + databaseType: 'no', + authenticationType: 'session', + }) + .withMockedGenerators(mockedGenerators); + }); + + it('should match generated files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + shouldComposeWithCouchbase(false, () => runResult); + }); + describe('no with oauth2', () => { + before(async () => { + await helpers + .run(generatorPath) + .withJHipsterConfig({ + databaseType: 'no', + authenticationType: 'oauth2', + }) + .withMockedGenerators(mockedGenerators); + }); + + it('should match generated files', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + shouldComposeWithCouchbase(false, () => runResult); + }); + describe('couchbase', () => { + let runResult; + before(async () => { + runResult = await helpers + .run(generatorPath) + .withJHipsterConfig({ + databaseType: 'couchbase', + }) + .withSkipWritingPriorities() + .withMockedGenerators(mockedGenerators); + }); + shouldComposeWithCouchbase(true, () => runResult); + }); + }); + }); + + describe('with entities', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig({ skipClient: true }, [ + { name: 'Foo', changelogDate: '20160926101210', fields: [{ fieldName: 'name', fieldType: 'String' }] }, + { + name: 'Bar', + changelogDate: '20160926101211', + dto: 'mapstruct', + fields: [{ fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }], + }, + ]); + }); + + it('should match files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/server/generator.spec.mjs b/generators/server/generator.spec.mjs deleted file mode 100644 index 21e06c328fe5..000000000000 --- a/generators/server/generator.spec.mjs +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright 2013-2023 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 assert from 'assert/strict'; -import { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { defaultHelpers as helpers, checkEnforcements, result as runResult } from '../../test/support/index.mjs'; -import Generator from './index.mjs'; -import { mockedGenerators, shouldComposeWithCouchbase, shouldComposeWithSpringCloudStream } from './__test-support/index.mjs'; -import { GENERATOR_SERVER } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorPath = join(__dirname, 'index.mjs'); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({}, GENERATOR_SERVER); - - describe('composing', () => { - describe('buildTool option', () => { - describe('maven', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - buildTool: 'maven', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - - it('should compose with maven generator', () => { - assert(runResult.mockedGenerators['jhipster:maven'].calledOnce); - }); - it('should not compose with others buildTool generators', () => { - assert(runResult.mockedGenerators['jhipster:gradle'].notCalled); - }); - }); - describe('gradle', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - buildTool: 'gradle', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - - it('should compose with gradle generator', () => { - assert(runResult.mockedGenerators['jhipster:gradle'].calledOnce); - }); - it('should not compose with others buildTool generators', () => { - assert(runResult.mockedGenerators['jhipster:maven'].notCalled); - }); - }); - }); - - describe('messageBroker option', () => { - describe('no', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - messageBroker: 'no', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - - shouldComposeWithSpringCloudStream(false, () => runResult); - }); - describe('kafka', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - messageBroker: 'kafka', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - shouldComposeWithSpringCloudStream(true, () => runResult); - }); - describe('pulsar', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - messageBroker: 'pulsar', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - shouldComposeWithSpringCloudStream(true, () => runResult); - }); - }); - - describe('databaseType option', () => { - describe('no with jwt', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({ - databaseType: 'no', - authenticationType: 'jwt', - }) - .withMockedGenerators(mockedGenerators); - }); - - it('should match generated files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - shouldComposeWithCouchbase(false, () => runResult); - }); - describe('no with session', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({ - databaseType: 'no', - authenticationType: 'session', - }) - .withMockedGenerators(mockedGenerators); - }); - - it('should match generated files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - shouldComposeWithCouchbase(false, () => runResult); - }); - describe('no with oauth2', () => { - before(async () => { - await helpers - .run(generatorPath) - .withJHipsterConfig({ - databaseType: 'no', - authenticationType: 'oauth2', - }) - .withMockedGenerators(mockedGenerators); - }); - - it('should match generated files', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - shouldComposeWithCouchbase(false, () => runResult); - }); - describe('couchbase', () => { - let runResult; - before(async () => { - runResult = await helpers - .run(generatorPath) - .withJHipsterConfig({ - databaseType: 'couchbase', - }) - .withSkipWritingPriorities() - .withMockedGenerators(mockedGenerators); - }); - shouldComposeWithCouchbase(true, () => runResult); - }); - }); - }); - - describe('with entities', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig({ skipClient: true }, [ - { name: 'Foo', changelogDate: '20160926101210', fields: [{ fieldName: 'name', fieldType: 'String' }] }, - { - name: 'Bar', - changelogDate: '20160926101211', - dto: 'mapstruct', - fields: [{ fieldName: 'name', fieldType: 'String', fieldValidateRules: ['required'] }], - }, - ]); - }); - - it('should match files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/server/index.mts b/generators/server/index.mts deleted file mode 100644 index af09206f5b13..000000000000 --- a/generators/server/index.mts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013-2023 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 { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from '../base-application/tasks.mjs'; -import { GenericSourceTypeDefinition } from '../base/tasks.mjs'; -import { SpringBootApplication, SpringBootSourceType } from './types.mjs'; - -export { default } from './generator.mjs'; -export { default as command } from './command.mjs'; -export { serverFiles as files } from './files.mjs'; - -// TODO move to ./generator.mts -export type ApplicationDefinition = GenericApplicationDefinition; - -// TODO move to ./generator.mts -export type GeneratorDefinition = BaseApplicationGeneratorDefinition< - ApplicationDefinition & GenericSourceTypeDefinition ->; diff --git a/generators/server/index.ts b/generators/server/index.ts new file mode 100644 index 000000000000..fdd352bdd865 --- /dev/null +++ b/generators/server/index.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2013-2023 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 { BaseApplicationGeneratorDefinition, GenericApplicationDefinition } from '../base-application/tasks.js'; +import { GenericSourceTypeDefinition } from '../base/tasks.js'; +import { SpringBootApplication, SpringBootSourceType } from './types.js'; + +export { default } from './generator.js'; +export { default as command } from './command.js'; +export { serverFiles as files } from './files.js'; + +// TODO move to ./generator.mts +export type ApplicationDefinition = GenericApplicationDefinition; + +// TODO move to ./generator.mts +export type GeneratorDefinition = BaseApplicationGeneratorDefinition< + ApplicationDefinition & GenericSourceTypeDefinition +>; diff --git a/generators/server/jdl/application-definition.mts b/generators/server/jdl/application-definition.mts deleted file mode 100644 index 2f19d97c477c..000000000000 --- a/generators/server/jdl/application-definition.mts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2013-2023 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 * as _ from 'lodash-es'; -import { JDLApplicationConfig, JHipsterOptionDefinition } from '../../../jdl/types/types.mjs'; -import databaseMigrationOption from '../options/database-migration.mjs'; -import messageBrokerOption from '../options/message-broker.mjs'; -import { feignClientDefinition } from '../options/index.mjs'; - -const { upperCase, snakeCase } = _; - -const jdlOptions: JHipsterOptionDefinition[] = [databaseMigrationOption, messageBrokerOption, feignClientDefinition]; - -const applicationConfig: JDLApplicationConfig = { - 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: messageBrokerOption.type, - }, - ]), - ), -}; - -export default applicationConfig; diff --git a/generators/server/jdl/application-definition.ts b/generators/server/jdl/application-definition.ts new file mode 100644 index 000000000000..91c2fc90f9cd --- /dev/null +++ b/generators/server/jdl/application-definition.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2013-2023 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 * as _ from 'lodash-es'; +import { JDLApplicationConfig, JHipsterOptionDefinition } from '../../../jdl/types/types.js'; +import databaseMigrationOption from '../options/database-migration.js'; +import messageBrokerOption from '../options/message-broker.js'; +import { feignClientDefinition } from '../options/index.js'; + +const { upperCase, snakeCase } = _; + +const jdlOptions: JHipsterOptionDefinition[] = [databaseMigrationOption, messageBrokerOption, feignClientDefinition]; + +const applicationConfig: JDLApplicationConfig = { + 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: messageBrokerOption.type, + }, + ]), + ), +}; + +export default applicationConfig; diff --git a/generators/server/jdl/index.mts b/generators/server/jdl/index.mts deleted file mode 100644 index a63d24a4cd2b..000000000000 --- a/generators/server/jdl/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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.mjs'; diff --git a/generators/server/jdl/index.ts b/generators/server/jdl/index.ts new file mode 100644 index 000000000000..70388c95bbe6 --- /dev/null +++ b/generators/server/jdl/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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/needle-logback.spec.mts b/generators/server/needle-logback.spec.mts deleted file mode 100644 index 14410bd92dc0..000000000000 --- a/generators/server/needle-logback.spec.mts +++ /dev/null @@ -1,38 +0,0 @@ -import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { GENERATOR_SERVER } from '../generator-list.mjs'; - -const filePath = `${SERVER_MAIN_RES_DIR}logback-spring.xml`; - -class mockBlueprintSubGen extends BaseApplicationGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - addlogStep({ source }) { - source.addLogbackMainLog?.({ name: 'org.test.logTest', level: 'OFF' }); - }, - }); - } -} - -describe('generators - server - needle - logback', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_SERVER) - .withJHipsterConfig({ - blueprint: 'myblueprint', - clientFramework: 'no', - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:server' }]]); - }); - - it('Assert log is added to logback-spring.xml', () => { - runResult.assertFileContent(filePath, ''); - }); -}); diff --git a/generators/server/needle-logback.spec.ts b/generators/server/needle-logback.spec.ts new file mode 100644 index 000000000000..8060199be6d7 --- /dev/null +++ b/generators/server/needle-logback.spec.ts @@ -0,0 +1,38 @@ +import { dryRunHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { GENERATOR_SERVER } from '../generator-list.js'; + +const filePath = `${SERVER_MAIN_RES_DIR}logback-spring.xml`; + +class mockBlueprintSubGen extends BaseApplicationGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + addlogStep({ source }) { + source.addLogbackMainLog?.({ name: 'org.test.logTest', level: 'OFF' }); + }, + }); + } +} + +describe('generators - server - needle - logback', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_SERVER) + .withJHipsterConfig({ + blueprint: 'myblueprint', + clientFramework: 'no', + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:server' }]]); + }); + + it('Assert log is added to logback-spring.xml', () => { + runResult.assertFileContent(filePath, ''); + }); +}); diff --git a/generators/server/options/database-migration.mts b/generators/server/options/database-migration.mts deleted file mode 100644 index 2d774093e8f8..000000000000 --- a/generators/server/options/database-migration.mts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.mjs'; - -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/database-migration.spec.ts b/generators/server/options/database-migration.spec.ts index df0f769e0a0f..deb30ff76901 100644 --- a/generators/server/options/database-migration.spec.ts +++ b/generators/server/options/database-migration.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'esmocha'; import { createImporterFromContent, ImportState } from '../../../jdl/jdl-importer.js'; -import { DATABASE_MIGRATION as optionName } from './index.mjs'; -import optionDefinition from './database-migration.mjs'; +import { DATABASE_MIGRATION as optionName } from './index.js'; +import optionDefinition from './database-migration.js'; describe(`generators - server - jdl - ${optionName}`, () => { optionDefinition.knownChoices.forEach(optionValue => { diff --git a/generators/server/options/database-migration.ts b/generators/server/options/database-migration.ts new file mode 100644 index 000000000000..c85b8769f5b3 --- /dev/null +++ b/generators/server/options/database-migration.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.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/feign-client.mts b/generators/server/options/feign-client.mts deleted file mode 100644 index f3e034d6e632..000000000000 --- a/generators/server/options/feign-client.mts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.mjs'; - -export const FEIGN_CLIENT = 'feignClient'; - -export const feignClientDefinition: JHipsterOptionDefinition = { - name: FEIGN_CLIENT, - type: 'boolean', - tokenType: 'BOOLEAN', -}; diff --git a/generators/server/options/feign-client.spec.ts b/generators/server/options/feign-client.spec.ts index 36a6174fcaa5..cca446e26ff1 100644 --- a/generators/server/options/feign-client.spec.ts +++ b/generators/server/options/feign-client.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'esmocha'; import { createImporterFromContent, ImportState } from '../../../jdl/jdl-importer.js'; -import { FEIGN_CLIENT as optionName } from './index.mjs'; +import { FEIGN_CLIENT as optionName } from './index.js'; describe(`generators - server - jdl - ${optionName}`, () => { [true, false].forEach(optionValue => { diff --git a/generators/server/options/feign-client.ts b/generators/server/options/feign-client.ts new file mode 100644 index 000000000000..e761235c6da1 --- /dev/null +++ b/generators/server/options/feign-client.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.js'; + +export const FEIGN_CLIENT = 'feignClient'; + +export const feignClientDefinition: JHipsterOptionDefinition = { + name: FEIGN_CLIENT, + type: 'boolean', + tokenType: 'BOOLEAN', +}; diff --git a/generators/server/options/index.mts b/generators/server/options/index.mts deleted file mode 100644 index feb7b6b47e4f..000000000000 --- a/generators/server/options/index.mts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2023 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.mjs'; -export * from './message-broker.mjs'; -export * from './feign-client.mjs'; diff --git a/generators/server/options/index.ts b/generators/server/options/index.ts new file mode 100644 index 000000000000..da775ab8ca89 --- /dev/null +++ b/generators/server/options/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2013-2023 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 './feign-client.js'; diff --git a/generators/server/options/message-broker.mts b/generators/server/options/message-broker.mts deleted file mode 100644 index 330913f718a8..000000000000 --- a/generators/server/options/message-broker.mts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.mjs'; -import { OptionWithDerivedProperties } from '../../base-application/application-options.mjs'; - -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/message-broker.spec.ts b/generators/server/options/message-broker.spec.ts index 9b4517a1b889..4f0566d646ce 100644 --- a/generators/server/options/message-broker.spec.ts +++ b/generators/server/options/message-broker.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'esmocha'; import { createImporterFromContent, ImportState } from '../../../jdl/jdl-importer.js'; -import { MESSAGE_BROKER } from './index.mjs'; -import optionDefinition from './message-broker.mjs'; +import { MESSAGE_BROKER } from './index.js'; +import optionDefinition from './message-broker.js'; describe('generators - server - jdl - messageBroker', () => { optionDefinition.knownChoices.forEach(optionValue => { diff --git a/generators/server/options/message-broker.ts b/generators/server/options/message-broker.ts new file mode 100644 index 000000000000..4974240da0a3 --- /dev/null +++ b/generators/server/options/message-broker.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2013-2023 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 { JHipsterOptionDefinition } from '../../../jdl/types/types.js'; +import { 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/prompts.js b/generators/server/prompts.js new file mode 100644 index 000000000000..34fe9631903e --- /dev/null +++ b/generators/server/prompts.js @@ -0,0 +1,392 @@ +/** + * Copyright 2013-2023 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 chalk from 'chalk'; +import * as _ from 'lodash-es'; + +import { + applicationOptions, + applicationTypes, + authenticationTypes, + buildToolTypes, + databaseTypes, + cacheTypes, + serviceDiscoveryTypes, + testFrameworkTypes, +} from '../../jdl/jhipster/index.js'; +import { MESSAGE_BROKER } from './options/index.js'; +import { R2DBC_DB_OPTIONS, SQL_DB_OPTIONS } from './support/database.js'; + +const { OptionNames } = applicationOptions; +const { GATEWAY, MICROSERVICE, MONOLITH } = applicationTypes; +const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; +const { JWT, OAUTH2, SESSION } = authenticationTypes; +const { GRADLE, MAVEN } = buildToolTypes; +const { CASSANDRA, H2_DISK, H2_MEMORY, MONGODB, NEO4J, SQL, COUCHBASE } = databaseTypes; +const { CONSUL, EUREKA } = serviceDiscoveryTypes; +const { + AUTHENTICATION_TYPE, + BUILD_TOOL, + CACHE_PROVIDER, + DATABASE_TYPE, + PACKAGE_NAME, + DEV_DATABASE_TYPE, + PROD_DATABASE_TYPE, + REACTIVE, + SERVER_PORT, + SERVICE_DISCOVERY_TYPE, + WEBSOCKET, + SEARCH_ENGINE, + ENABLE_SWAGGER_CODEGEN, +} = OptionNames; +const NO_SERVICE_DISCOVERY = serviceDiscoveryTypes.NO; +const NO_DATABASE = databaseTypes.NO; +const NO_CACHE_PROVIDER = cacheTypes.NO; +const { GATLING, CUCUMBER } = testFrameworkTypes; +const { intersection } = _; + +/** + * Get Option From Array + * + * @param {Array} array - array + * @param {any} option - options + * @returns {boolean} true if option is in array and is set to 'true' + */ +const getOptionFromArray = (array, option) => { + let optionValue = false; + array.forEach(value => { + if (_.includes(value, option)) { + optionValue = value.split(':')[1]; + } + }); + optionValue = optionValue === 'true' ? true : optionValue; + return optionValue; +}; + +export async function askForServerSideOpts({ control }) { + if (control.existingProject && !this.options.askAnswered) return; + + const { applicationType, serverPort: defaultServerPort, reactive } = this.jhipsterConfigWithDefaults; + const prompts = [ + { + when: () => [MONOLITH, MICROSERVICE].includes(applicationType), + type: 'confirm', + name: REACTIVE, + message: 'Do you want to make it reactive with Spring WebFlux?', + default: reactive, + }, + { + when: () => applicationType === GATEWAY || applicationType === MICROSERVICE, + type: 'input', + name: SERVER_PORT, + validate: input => (/^([0-9]*)$/.test(input) ? true : 'This is not a valid port number.'), + message: + 'As you are running in a microservice architecture, on which port would like your server to run? It should be unique to avoid port conflicts.', + default: defaultServerPort, + }, + { + type: 'input', + name: PACKAGE_NAME, + validate: input => + /^([a-z_]{1}[a-z0-9_]*(\.[a-z_]{1}[a-z0-9_]*)*)$/.test(input) + ? true + : 'The package name you have provided is not a valid Java package name.', + message: 'What is your default Java package name?', + default: this.jhipsterConfigWithDefaults.packageName, + store: true, + }, + { + when: () => applicationType === 'gateway' || applicationType === 'microservice', + type: 'list', + name: SERVICE_DISCOVERY_TYPE, + message: 'Which service discovery server do you want to use?', + choices: [ + { + value: CONSUL, + name: 'Consul (recommended)', + }, + { + value: EUREKA, + name: 'JHipster Registry (legacy, uses Eureka, provides Spring Cloud Config support)', + }, + { + value: NO_SERVICE_DISCOVERY, + name: 'No service discovery', + }, + ], + default: CONSUL, + }, + { + when: answers => + (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) || [GATEWAY, MICROSERVICE].includes(applicationType), + type: 'list', + name: AUTHENTICATION_TYPE, + message: `Which ${chalk.yellow('*type*')} of authentication would you like to use?`, + choices: answers => { + const opts = [ + { + value: JWT, + name: 'JWT authentication (stateless, with a token)', + }, + ]; + opts.push({ + value: OAUTH2, + name: 'OAuth 2.0 / OIDC Authentication (stateful, works with Keycloak and Okta)', + }); + if (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) { + opts.push({ + value: SESSION, + name: 'HTTP Session Authentication (stateful, default Spring Security mechanism)', + }); + } + return opts; + }, + default: this.jhipsterConfigWithDefaults.authenticationType, + }, + { + type: 'list', + name: DATABASE_TYPE, + message: `Which ${chalk.yellow('*type*')} of database would you like to use?`, + choices: answers => { + const opts = []; + if (!answers.reactive) { + opts.push({ + value: SQL, + name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, Oracle, MSSQL)', + }); + } else { + opts.push({ + value: SQL, + name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, MSSQL)', + }); + } + opts.push({ + value: MONGODB, + name: 'MongoDB', + }); + if (answers.authenticationType !== OAUTH2) { + opts.push({ + value: CASSANDRA, + name: 'Cassandra', + }); + } + opts.push({ + value: 'couchbase', + name: '[BETA] Couchbase', + }); + opts.push({ + value: NEO4J, + name: '[BETA] Neo4j', + }); + opts.push({ + value: NO_DATABASE, + name: 'No database', + }); + return opts; + }, + default: this.jhipsterConfigWithDefaults.databaseType, + }, + { + when: response => response.databaseType === SQL, + type: 'list', + name: PROD_DATABASE_TYPE, + message: `Which ${chalk.yellow('*production*')} database would you like to use?`, + choices: answers => (answers.reactive ? R2DBC_DB_OPTIONS : SQL_DB_OPTIONS), + default: this.jhipsterConfigWithDefaults.prodDatabaseType, + }, + { + when: response => response.databaseType === SQL, + type: 'list', + name: DEV_DATABASE_TYPE, + message: `Which ${chalk.yellow('*development*')} database would you like to use?`, + choices: response => + [SQL_DB_OPTIONS.find(it => it.value === response.prodDatabaseType)].concat([ + { + value: H2_DISK, + name: 'H2 with disk-based persistence', + }, + { + value: H2_MEMORY, + name: 'H2 with in-memory persistence', + }, + ]), + default: this.jhipsterConfigWithDefaults.devDatabaseType, + }, + { + when: answers => !answers.reactive, + type: 'list', + name: CACHE_PROVIDER, + message: 'Which cache do you want to use? (Spring cache abstraction)', + choices: [ + { + value: EHCACHE, + name: 'Ehcache (local cache, for a single node)', + }, + { + value: CAFFEINE, + name: 'Caffeine (local cache, for a single node)', + }, + { + value: HAZELCAST, + name: 'Hazelcast (distributed cache, for multiple nodes, supports rate-limiting for gateway applications)', + }, + { + value: INFINISPAN, + name: 'Infinispan (hybrid cache, for multiple nodes)', + }, + { + value: MEMCACHED, + name: 'Memcached (distributed cache) - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', + }, + { + value: REDIS, + name: 'Redis (distributed cache)', + }, + { + value: NO_CACHE_PROVIDER, + name: 'No cache - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', + }, + ], + default: this.jhipsterConfigWithDefaults.cacheProvider, + }, + { + when: answers => + ((answers.cacheProvider !== NO_CACHE_PROVIDER && answers.cacheProvider !== MEMCACHED) || applicationType === GATEWAY) && + answers.databaseType === SQL && + !answers.reactive, + type: 'confirm', + name: 'enableHibernateCache', + message: 'Do you want to use Hibernate 2nd level cache?', + default: this.jhipsterConfigWithDefaults.enableHibernateCache, + }, + { + type: 'list', + name: BUILD_TOOL, + message: 'Would you like to use Maven or Gradle for building the backend?', + choices: [ + { + value: MAVEN, + name: 'Maven', + }, + { + value: GRADLE, + name: 'Gradle', + }, + ], + default: this.jhipsterConfigWithDefaults.buildTool, + }, + { + when: answers => answers.buildTool === GRADLE && this.experimental, + type: 'confirm', + name: 'enableGradleEnterprise', + message: 'Do you want to enable Gradle Enterprise integration?', + default: this.jhipsterConfigWithDefaults.enableGradleEnterprise, + }, + { + when: answers => answers.enableGradleEnterprise, + type: 'input', + name: 'gradleEnterpriseHost', + message: 'Enter your Gradle Enterprise host', + validate: input => (input.length === 0 ? 'Please enter your Gradle Enterprise host' : true), + }, + ]; + + await this.prompt(prompts, this.config); +} + +export async function askForOptionalItems({ control }) { + if (control.existingProject && !this.options.askAnswered) return; + + const { applicationType, reactive, databaseType } = this.jhipsterConfigWithDefaults; + + const choices = []; + const defaultChoice = []; + if ([SQL, MONGODB, NEO4J].includes(databaseType)) { + choices.push({ + name: 'Elasticsearch as search engine', + value: 'searchEngine:elasticsearch', + }); + } + if (databaseType === COUCHBASE) { + choices.push({ + name: 'Couchbase FTS as search engine', + value: 'searchEngine:couchbase', + }); + } + if (!reactive) { + if (applicationType === MONOLITH || applicationType === GATEWAY) { + choices.push({ + name: 'WebSockets using Spring Websocket', + value: 'websocket:spring-websocket', + }); + } + } + choices.push({ + name: 'Apache Kafka as asynchronous messages broker', + value: 'messageBroker:kafka', + }); + choices.push({ + name: 'Apache Pulsar as asynchronous messages broker', + value: 'messageBroker:pulsar', + }); + choices.push({ + name: 'API first development using OpenAPI-generator', + value: 'enableSwaggerCodegen:true', + }); + + const PROMPTS = { + type: 'checkbox', + name: 'serverSideOptions', + message: 'Which other technologies would you like to use?', + choices, + default: defaultChoice, + }; + + if (choices.length > 0) { + await this.prompt(PROMPTS).then(answers => { + 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.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) { + this.jhipsterConfig.serviceDiscoveryType = getOptionFromArray(answers.serverSideOptions, SERVICE_DISCOVERY_TYPE); + } + }); + } +} + +export async function askForServerTestOpts({ control }) { + if (control.existingProject && this.options.askAnswered !== true) return; + + const answers = await this.prompt([ + { + type: 'checkbox', + name: 'serverTestFrameworks', + message: 'Besides Junit, which testing frameworks would you like to use?', + choices: [ + { name: 'Gatling', value: GATLING }, + { name: 'Cucumber', value: CUCUMBER }, + ], + default: intersection([GATLING, CUCUMBER], this.jhipsterConfigWithDefaults.testFrameworks), + }, + ]); + this.jhipsterConfig.testFrameworks = [...new Set([...(this.jhipsterConfig.testFrameworks ?? []), ...answers.serverTestFrameworks])]; +} diff --git a/generators/server/prompts.mjs b/generators/server/prompts.mjs deleted file mode 100644 index 04ad88fb9fbe..000000000000 --- a/generators/server/prompts.mjs +++ /dev/null @@ -1,392 +0,0 @@ -/** - * Copyright 2013-2023 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 chalk from 'chalk'; -import * as _ from 'lodash-es'; - -import { - applicationOptions, - applicationTypes, - authenticationTypes, - buildToolTypes, - databaseTypes, - cacheTypes, - serviceDiscoveryTypes, - testFrameworkTypes, -} from '../../jdl/jhipster/index.mjs'; -import { MESSAGE_BROKER } from './options/index.mjs'; -import { R2DBC_DB_OPTIONS, SQL_DB_OPTIONS } from './support/database.mjs'; - -const { OptionNames } = applicationOptions; -const { GATEWAY, MICROSERVICE, MONOLITH } = applicationTypes; -const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; -const { JWT, OAUTH2, SESSION } = authenticationTypes; -const { GRADLE, MAVEN } = buildToolTypes; -const { CASSANDRA, H2_DISK, H2_MEMORY, MONGODB, NEO4J, SQL, COUCHBASE } = databaseTypes; -const { CONSUL, EUREKA } = serviceDiscoveryTypes; -const { - AUTHENTICATION_TYPE, - BUILD_TOOL, - CACHE_PROVIDER, - DATABASE_TYPE, - PACKAGE_NAME, - DEV_DATABASE_TYPE, - PROD_DATABASE_TYPE, - REACTIVE, - SERVER_PORT, - SERVICE_DISCOVERY_TYPE, - WEBSOCKET, - SEARCH_ENGINE, - ENABLE_SWAGGER_CODEGEN, -} = OptionNames; -const NO_SERVICE_DISCOVERY = serviceDiscoveryTypes.NO; -const NO_DATABASE = databaseTypes.NO; -const NO_CACHE_PROVIDER = cacheTypes.NO; -const { GATLING, CUCUMBER } = testFrameworkTypes; -const { intersection } = _; - -/** - * Get Option From Array - * - * @param {Array} array - array - * @param {any} option - options - * @returns {boolean} true if option is in array and is set to 'true' - */ -const getOptionFromArray = (array, option) => { - let optionValue = false; - array.forEach(value => { - if (_.includes(value, option)) { - optionValue = value.split(':')[1]; - } - }); - optionValue = optionValue === 'true' ? true : optionValue; - return optionValue; -}; - -export async function askForServerSideOpts({ control }) { - if (control.existingProject && !this.options.askAnswered) return; - - const { applicationType, serverPort: defaultServerPort, reactive } = this.jhipsterConfigWithDefaults; - const prompts = [ - { - when: () => [MONOLITH, MICROSERVICE].includes(applicationType), - type: 'confirm', - name: REACTIVE, - message: 'Do you want to make it reactive with Spring WebFlux?', - default: reactive, - }, - { - when: () => applicationType === GATEWAY || applicationType === MICROSERVICE, - type: 'input', - name: SERVER_PORT, - validate: input => (/^([0-9]*)$/.test(input) ? true : 'This is not a valid port number.'), - message: - 'As you are running in a microservice architecture, on which port would like your server to run? It should be unique to avoid port conflicts.', - default: defaultServerPort, - }, - { - type: 'input', - name: PACKAGE_NAME, - validate: input => - /^([a-z_]{1}[a-z0-9_]*(\.[a-z_]{1}[a-z0-9_]*)*)$/.test(input) - ? true - : 'The package name you have provided is not a valid Java package name.', - message: 'What is your default Java package name?', - default: this.jhipsterConfigWithDefaults.packageName, - store: true, - }, - { - when: () => applicationType === 'gateway' || applicationType === 'microservice', - type: 'list', - name: SERVICE_DISCOVERY_TYPE, - message: 'Which service discovery server do you want to use?', - choices: [ - { - value: CONSUL, - name: 'Consul (recommended)', - }, - { - value: EUREKA, - name: 'JHipster Registry (legacy, uses Eureka, provides Spring Cloud Config support)', - }, - { - value: NO_SERVICE_DISCOVERY, - name: 'No service discovery', - }, - ], - default: CONSUL, - }, - { - when: answers => - (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) || [GATEWAY, MICROSERVICE].includes(applicationType), - type: 'list', - name: AUTHENTICATION_TYPE, - message: `Which ${chalk.yellow('*type*')} of authentication would you like to use?`, - choices: answers => { - const opts = [ - { - value: JWT, - name: 'JWT authentication (stateless, with a token)', - }, - ]; - opts.push({ - value: OAUTH2, - name: 'OAuth 2.0 / OIDC Authentication (stateful, works with Keycloak and Okta)', - }); - if (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) { - opts.push({ - value: SESSION, - name: 'HTTP Session Authentication (stateful, default Spring Security mechanism)', - }); - } - return opts; - }, - default: this.jhipsterConfigWithDefaults.authenticationType, - }, - { - type: 'list', - name: DATABASE_TYPE, - message: `Which ${chalk.yellow('*type*')} of database would you like to use?`, - choices: answers => { - const opts = []; - if (!answers.reactive) { - opts.push({ - value: SQL, - name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, Oracle, MSSQL)', - }); - } else { - opts.push({ - value: SQL, - name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, MSSQL)', - }); - } - opts.push({ - value: MONGODB, - name: 'MongoDB', - }); - if (answers.authenticationType !== OAUTH2) { - opts.push({ - value: CASSANDRA, - name: 'Cassandra', - }); - } - opts.push({ - value: 'couchbase', - name: '[BETA] Couchbase', - }); - opts.push({ - value: NEO4J, - name: '[BETA] Neo4j', - }); - opts.push({ - value: NO_DATABASE, - name: 'No database', - }); - return opts; - }, - default: this.jhipsterConfigWithDefaults.databaseType, - }, - { - when: response => response.databaseType === SQL, - type: 'list', - name: PROD_DATABASE_TYPE, - message: `Which ${chalk.yellow('*production*')} database would you like to use?`, - choices: answers => (answers.reactive ? R2DBC_DB_OPTIONS : SQL_DB_OPTIONS), - default: this.jhipsterConfigWithDefaults.prodDatabaseType, - }, - { - when: response => response.databaseType === SQL, - type: 'list', - name: DEV_DATABASE_TYPE, - message: `Which ${chalk.yellow('*development*')} database would you like to use?`, - choices: response => - [SQL_DB_OPTIONS.find(it => it.value === response.prodDatabaseType)].concat([ - { - value: H2_DISK, - name: 'H2 with disk-based persistence', - }, - { - value: H2_MEMORY, - name: 'H2 with in-memory persistence', - }, - ]), - default: this.jhipsterConfigWithDefaults.devDatabaseType, - }, - { - when: answers => !answers.reactive, - type: 'list', - name: CACHE_PROVIDER, - message: 'Which cache do you want to use? (Spring cache abstraction)', - choices: [ - { - value: EHCACHE, - name: 'Ehcache (local cache, for a single node)', - }, - { - value: CAFFEINE, - name: 'Caffeine (local cache, for a single node)', - }, - { - value: HAZELCAST, - name: 'Hazelcast (distributed cache, for multiple nodes, supports rate-limiting for gateway applications)', - }, - { - value: INFINISPAN, - name: 'Infinispan (hybrid cache, for multiple nodes)', - }, - { - value: MEMCACHED, - name: 'Memcached (distributed cache) - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', - }, - { - value: REDIS, - name: 'Redis (distributed cache)', - }, - { - value: NO_CACHE_PROVIDER, - name: 'No cache - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', - }, - ], - default: this.jhipsterConfigWithDefaults.cacheProvider, - }, - { - when: answers => - ((answers.cacheProvider !== NO_CACHE_PROVIDER && answers.cacheProvider !== MEMCACHED) || applicationType === GATEWAY) && - answers.databaseType === SQL && - !answers.reactive, - type: 'confirm', - name: 'enableHibernateCache', - message: 'Do you want to use Hibernate 2nd level cache?', - default: this.jhipsterConfigWithDefaults.enableHibernateCache, - }, - { - type: 'list', - name: BUILD_TOOL, - message: 'Would you like to use Maven or Gradle for building the backend?', - choices: [ - { - value: MAVEN, - name: 'Maven', - }, - { - value: GRADLE, - name: 'Gradle', - }, - ], - default: this.jhipsterConfigWithDefaults.buildTool, - }, - { - when: answers => answers.buildTool === GRADLE && this.experimental, - type: 'confirm', - name: 'enableGradleEnterprise', - message: 'Do you want to enable Gradle Enterprise integration?', - default: this.jhipsterConfigWithDefaults.enableGradleEnterprise, - }, - { - when: answers => answers.enableGradleEnterprise, - type: 'input', - name: 'gradleEnterpriseHost', - message: 'Enter your Gradle Enterprise host', - validate: input => (input.length === 0 ? 'Please enter your Gradle Enterprise host' : true), - }, - ]; - - await this.prompt(prompts, this.config); -} - -export async function askForOptionalItems({ control }) { - if (control.existingProject && !this.options.askAnswered) return; - - const { applicationType, reactive, databaseType } = this.jhipsterConfigWithDefaults; - - const choices = []; - const defaultChoice = []; - if ([SQL, MONGODB, NEO4J].includes(databaseType)) { - choices.push({ - name: 'Elasticsearch as search engine', - value: 'searchEngine:elasticsearch', - }); - } - if (databaseType === COUCHBASE) { - choices.push({ - name: 'Couchbase FTS as search engine', - value: 'searchEngine:couchbase', - }); - } - if (!reactive) { - if (applicationType === MONOLITH || applicationType === GATEWAY) { - choices.push({ - name: 'WebSockets using Spring Websocket', - value: 'websocket:spring-websocket', - }); - } - } - choices.push({ - name: 'Apache Kafka as asynchronous messages broker', - value: 'messageBroker:kafka', - }); - choices.push({ - name: 'Apache Pulsar as asynchronous messages broker', - value: 'messageBroker:pulsar', - }); - choices.push({ - name: 'API first development using OpenAPI-generator', - value: 'enableSwaggerCodegen:true', - }); - - const PROMPTS = { - type: 'checkbox', - name: 'serverSideOptions', - message: 'Which other technologies would you like to use?', - choices, - default: defaultChoice, - }; - - if (choices.length > 0) { - await this.prompt(PROMPTS).then(answers => { - 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.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) { - this.jhipsterConfig.serviceDiscoveryType = getOptionFromArray(answers.serverSideOptions, SERVICE_DISCOVERY_TYPE); - } - }); - } -} - -export async function askForServerTestOpts({ control }) { - if (control.existingProject && this.options.askAnswered !== true) return; - - const answers = await this.prompt([ - { - type: 'checkbox', - name: 'serverTestFrameworks', - message: 'Besides Junit, which testing frameworks would you like to use?', - choices: [ - { name: 'Gatling', value: GATLING }, - { name: 'Cucumber', value: CUCUMBER }, - ], - default: intersection([GATLING, CUCUMBER], this.jhipsterConfigWithDefaults.testFrameworks), - }, - ]); - this.jhipsterConfig.testFrameworks = [...new Set([...(this.jhipsterConfig.testFrameworks ?? []), ...answers.serverTestFrameworks])]; -} diff --git a/generators/server/support/__snapshots__/needles.spec.mts.snap b/generators/server/support/__snapshots__/needles.spec.ts.snap similarity index 100% rename from generators/server/support/__snapshots__/needles.spec.mts.snap rename to generators/server/support/__snapshots__/needles.spec.ts.snap diff --git a/generators/server/support/config.mts b/generators/server/support/config.mts deleted file mode 100644 index 2e66e077b635..000000000000 --- a/generators/server/support/config.mts +++ /dev/null @@ -1,182 +0,0 @@ -import { mutateData, normalizePathEnd, pickFields } from '../../base/support/index.mjs'; - -import { - databaseTypes, - monitoringTypes, - authenticationTypes, - buildToolTypes, - cacheTypes, - websocketTypes, - serviceDiscoveryTypes, - searchEngineTypes, -} from '../../../jdl/jhipster/index.mjs'; -import { prepareSqlApplicationProperties } from '../../spring-data-relational/support/index.mjs'; -import { - CLIENT_DIST_DIR, - CLIENT_MAIN_SRC_DIR, - CLIENT_TEST_SRC_DIR, - SERVER_MAIN_RES_DIR, - SERVER_MAIN_SRC_DIR, - SERVER_TEST_RES_DIR, - SERVER_TEST_SRC_DIR, -} from '../../generator-constants.mjs'; -import { MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_NO, MESSAGE_BROKER_PULSAR } from '../../server/options/index.mjs'; -import { PlatformApplication } from '../../base-application/types.mjs'; - -const { SQL, MONGODB, COUCHBASE, NEO4J, CASSANDRA } = databaseTypes; -const NO_DATABASE = databaseTypes.NO; -const { PROMETHEUS, ELK } = monitoringTypes; -const { OAUTH2, SESSION } = authenticationTypes; -const { CAFFEINE, EHCACHE, REDIS, HAZELCAST, INFINISPAN, MEMCACHED } = cacheTypes; -const { GRADLE, MAVEN } = buildToolTypes; -const { SPRING_WEBSOCKET } = websocketTypes; -const { CONSUL, EUREKA } = serviceDiscoveryTypes; -const { ELASTICSEARCH } = searchEngineTypes; -const NO_CACHE = cacheTypes.NO; -const NO_SERVICE_DISCOVERY = serviceDiscoveryTypes.NO; -const NO_SEARCH_ENGINE = searchEngineTypes.NO; - -/** - * Load server configs into application. - * all variables should be set to dest, - * all variables should be referred from config, - */ -export const loadServerConfig = ({ config, application }: { config: any; application: any }) => { - mutateData( - application, - { - srcMainJava: SERVER_MAIN_SRC_DIR, - srcMainResources: SERVER_MAIN_RES_DIR, - srcMainWebapp: CLIENT_MAIN_SRC_DIR, - srcTestJava: SERVER_TEST_SRC_DIR, - srcTestResources: SERVER_TEST_RES_DIR, - srcTestJavascript: CLIENT_TEST_SRC_DIR, - }, - pickFields(config, [ - 'packageName', - 'packageFolder', - 'serverPort', - 'buildTool', - 'databaseType', - 'databaseMigration', - 'devDatabaseType', - 'prodDatabaseType', - 'incrementalChangelog', - 'reactive', - 'searchEngine', - 'cacheProvider', - 'enableHibernateCache', - 'serviceDiscoveryType', - 'enableSwaggerCodegen', - 'messageBroker', - 'websocket', - 'embeddableLaunchScript', - 'enableGradleEnterprise', - 'gradleEnterpriseHost', - 'feignClient', - ]), - { - packageFolder: ({ packageFolder }) => (packageFolder ? normalizePathEnd(packageFolder) : packageFolder), - gradleEnterpriseHost: ({ gradleEnterpriseHost }) => - !gradleEnterpriseHost || gradleEnterpriseHost.startsWith('https://') ? gradleEnterpriseHost : `https://${gradleEnterpriseHost}`, - }, - ); -}; - -/** - * @param {Object} config - config to load config from - * @param {import('./base-application/types.js').PlatformApplication} dest - destination context to use default is context - */ -export const loadPlatformConfig = ({ config, application }: { config: any; application: PlatformApplication }) => { - mutateData(application, pickFields(config, ['serviceDiscoveryType', 'monitoring'])); -}; - -export const loadDerivedServerAndPlatformProperties = ({ application }: { application: any }) => { - if (!application.serviceDiscoveryType) { - application.serviceDiscoveryType = NO_SERVICE_DISCOVERY; - } - application.serviceDiscoveryAny = application.serviceDiscoveryType !== NO_SERVICE_DISCOVERY; - application.serviceDiscoveryConsul = application.serviceDiscoveryType === CONSUL; - application.serviceDiscoveryEureka = application.serviceDiscoveryType === EUREKA; -}; - -/** - * @param {import('./bootstrap-application-server/types').SpringBootApplication} dest - destination context to use default is context - * @param {import('./base-application/types.js').PlatformApplication} dest - destination context to use default is context - */ -export const loadDerivedPlatformConfig = ({ application }: { application: PlatformApplication }) => { - application.monitoringElk = application.monitoring === ELK; - application.monitoringPrometheus = application.monitoring === PROMETHEUS; - loadDerivedServerAndPlatformProperties({ application }); -}; - -/** - * @param {import('./bootstrap-application-server/types').SpringBootApplication} dest - destination context to use default is context - */ -export const loadDerivedServerConfig = ({ application }: { application: any }) => { - application.prodDatabaseTypePostgresql = undefined; - application.prodDatabaseTypeMssql = undefined; - application.devDatabaseTypeH2Any = undefined; - application.prodDatabaseTypeMariadb = undefined; - - application.communicationSpringWebsocket = application.websocket === SPRING_WEBSOCKET; - - if (!application.searchEngine) { - application.searchEngine = NO_SEARCH_ENGINE; - } - application.searchEngineNo = application.searchEngine === NO_SEARCH_ENGINE; - application.searchEngineAny = !application.searchEngineNo; - 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; - - application.cacheProviderNo = !application.cacheProvider || application.cacheProvider === NO_CACHE; - application.cacheProviderCaffeine = application.cacheProvider === CAFFEINE; - application.cacheProviderEhcache = application.cacheProvider === EHCACHE; - application.cacheProviderHazelcast = application.cacheProvider === HAZELCAST; - application.cacheProviderInfinispan = application.cacheProvider === INFINISPAN; - application.cacheProviderMemcached = application.cacheProvider === MEMCACHED; - application.cacheProviderRedis = application.cacheProvider === REDIS; - application.cacheProviderAny = application.cacheProvider && application.cacheProvider !== NO_CACHE; - - application.databaseTypeNo = application.databaseType === NO_DATABASE; - application.databaseTypeSql = application.databaseType === SQL; - application.databaseTypeCassandra = application.databaseType === CASSANDRA; - application.databaseTypeCouchbase = application.databaseType === COUCHBASE; - application.databaseTypeMongodb = application.databaseType === MONGODB; - application.databaseTypeNeo4j = application.databaseType === NEO4J; - application.databaseTypeAny = !application.databaseTypeNo; - - application.databaseMigrationLiquibase = application.databaseMigration - ? application.databaseMigration === 'liquibase' - : application.databaseType === SQL; - - mutateData(application, { - packageFolder: ({ packageName }) => `${packageName.replace(/\./g, '/')}/`, - javaPackageSrcDir: ({ srcMainJava, packageFolder }) => normalizePathEnd(`${srcMainJava}${packageFolder}`), - javaPackageTestDir: ({ srcTestJava, packageFolder }) => normalizePathEnd(`${srcTestJava}${packageFolder}`), - - temporaryDir: ({ buildToolGradle }) => (buildToolGradle ? 'build/' : 'target/'), - clientDistDir: ({ temporaryDir, buildToolGradle }) => - `${temporaryDir}${buildToolGradle ? 'resources/main/' : 'classes/'}${CLIENT_DIST_DIR}`, - - authenticationUsesCsrf: ({ authenticationType }) => [OAUTH2, SESSION].includes(authenticationType), - imperativeOrReactive: ({ reactive }) => (reactive ? 'reactive' : 'imperative'), - }); - - if (application.databaseTypeSql) { - prepareSqlApplicationProperties({ application }); - } - - loadDerivedServerAndPlatformProperties({ application }); -}; diff --git a/generators/server/support/config.ts b/generators/server/support/config.ts new file mode 100644 index 000000000000..a79400166e20 --- /dev/null +++ b/generators/server/support/config.ts @@ -0,0 +1,182 @@ +import { mutateData, normalizePathEnd, pickFields } from '../../base/support/index.js'; + +import { + databaseTypes, + monitoringTypes, + authenticationTypes, + buildToolTypes, + cacheTypes, + websocketTypes, + serviceDiscoveryTypes, + searchEngineTypes, +} from '../../../jdl/jhipster/index.js'; +import { prepareSqlApplicationProperties } from '../../spring-data-relational/support/index.js'; +import { + CLIENT_DIST_DIR, + CLIENT_MAIN_SRC_DIR, + CLIENT_TEST_SRC_DIR, + SERVER_MAIN_RES_DIR, + SERVER_MAIN_SRC_DIR, + 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 { PlatformApplication } from '../../base-application/types.js'; + +const { SQL, MONGODB, COUCHBASE, NEO4J, CASSANDRA } = databaseTypes; +const NO_DATABASE = databaseTypes.NO; +const { PROMETHEUS, ELK } = monitoringTypes; +const { OAUTH2, SESSION } = authenticationTypes; +const { CAFFEINE, EHCACHE, REDIS, HAZELCAST, INFINISPAN, MEMCACHED } = cacheTypes; +const { GRADLE, MAVEN } = buildToolTypes; +const { SPRING_WEBSOCKET } = websocketTypes; +const { CONSUL, EUREKA } = serviceDiscoveryTypes; +const { ELASTICSEARCH } = searchEngineTypes; +const NO_CACHE = cacheTypes.NO; +const NO_SERVICE_DISCOVERY = serviceDiscoveryTypes.NO; +const NO_SEARCH_ENGINE = searchEngineTypes.NO; + +/** + * Load server configs into application. + * all variables should be set to dest, + * all variables should be referred from config, + */ +export const loadServerConfig = ({ config, application }: { config: any; application: any }) => { + mutateData( + application, + { + srcMainJava: SERVER_MAIN_SRC_DIR, + srcMainResources: SERVER_MAIN_RES_DIR, + srcMainWebapp: CLIENT_MAIN_SRC_DIR, + srcTestJava: SERVER_TEST_SRC_DIR, + srcTestResources: SERVER_TEST_RES_DIR, + srcTestJavascript: CLIENT_TEST_SRC_DIR, + }, + pickFields(config, [ + 'packageName', + 'packageFolder', + 'serverPort', + 'buildTool', + 'databaseType', + 'databaseMigration', + 'devDatabaseType', + 'prodDatabaseType', + 'incrementalChangelog', + 'reactive', + 'searchEngine', + 'cacheProvider', + 'enableHibernateCache', + 'serviceDiscoveryType', + 'enableSwaggerCodegen', + 'messageBroker', + 'websocket', + 'embeddableLaunchScript', + 'enableGradleEnterprise', + 'gradleEnterpriseHost', + 'feignClient', + ]), + { + packageFolder: ({ packageFolder }) => (packageFolder ? normalizePathEnd(packageFolder) : packageFolder), + gradleEnterpriseHost: ({ gradleEnterpriseHost }) => + !gradleEnterpriseHost || gradleEnterpriseHost.startsWith('https://') ? gradleEnterpriseHost : `https://${gradleEnterpriseHost}`, + }, + ); +}; + +/** + * @param {Object} config - config to load config from + * @param {import('./base-application/types.js').PlatformApplication} dest - destination context to use default is context + */ +export const loadPlatformConfig = ({ config, application }: { config: any; application: PlatformApplication }) => { + mutateData(application, pickFields(config, ['serviceDiscoveryType', 'monitoring'])); +}; + +export const loadDerivedServerAndPlatformProperties = ({ application }: { application: any }) => { + if (!application.serviceDiscoveryType) { + application.serviceDiscoveryType = NO_SERVICE_DISCOVERY; + } + application.serviceDiscoveryAny = application.serviceDiscoveryType !== NO_SERVICE_DISCOVERY; + application.serviceDiscoveryConsul = application.serviceDiscoveryType === CONSUL; + application.serviceDiscoveryEureka = application.serviceDiscoveryType === EUREKA; +}; + +/** + * @param {import('./bootstrap-application-server/types').SpringBootApplication} dest - destination context to use default is context + * @param {import('./base-application/types.js').PlatformApplication} dest - destination context to use default is context + */ +export const loadDerivedPlatformConfig = ({ application }: { application: PlatformApplication }) => { + application.monitoringElk = application.monitoring === ELK; + application.monitoringPrometheus = application.monitoring === PROMETHEUS; + loadDerivedServerAndPlatformProperties({ application }); +}; + +/** + * @param {import('./bootstrap-application-server/types').SpringBootApplication} dest - destination context to use default is context + */ +export const loadDerivedServerConfig = ({ application }: { application: any }) => { + application.prodDatabaseTypePostgresql = undefined; + application.prodDatabaseTypeMssql = undefined; + application.devDatabaseTypeH2Any = undefined; + application.prodDatabaseTypeMariadb = undefined; + + application.communicationSpringWebsocket = application.websocket === SPRING_WEBSOCKET; + + if (!application.searchEngine) { + application.searchEngine = NO_SEARCH_ENGINE; + } + application.searchEngineNo = application.searchEngine === NO_SEARCH_ENGINE; + application.searchEngineAny = !application.searchEngineNo; + 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; + + application.cacheProviderNo = !application.cacheProvider || application.cacheProvider === NO_CACHE; + application.cacheProviderCaffeine = application.cacheProvider === CAFFEINE; + application.cacheProviderEhcache = application.cacheProvider === EHCACHE; + application.cacheProviderHazelcast = application.cacheProvider === HAZELCAST; + application.cacheProviderInfinispan = application.cacheProvider === INFINISPAN; + application.cacheProviderMemcached = application.cacheProvider === MEMCACHED; + application.cacheProviderRedis = application.cacheProvider === REDIS; + application.cacheProviderAny = application.cacheProvider && application.cacheProvider !== NO_CACHE; + + application.databaseTypeNo = application.databaseType === NO_DATABASE; + application.databaseTypeSql = application.databaseType === SQL; + application.databaseTypeCassandra = application.databaseType === CASSANDRA; + application.databaseTypeCouchbase = application.databaseType === COUCHBASE; + application.databaseTypeMongodb = application.databaseType === MONGODB; + application.databaseTypeNeo4j = application.databaseType === NEO4J; + application.databaseTypeAny = !application.databaseTypeNo; + + application.databaseMigrationLiquibase = application.databaseMigration + ? application.databaseMigration === 'liquibase' + : application.databaseType === SQL; + + mutateData(application, { + packageFolder: ({ packageName }) => `${packageName.replace(/\./g, '/')}/`, + javaPackageSrcDir: ({ srcMainJava, packageFolder }) => normalizePathEnd(`${srcMainJava}${packageFolder}`), + javaPackageTestDir: ({ srcTestJava, packageFolder }) => normalizePathEnd(`${srcTestJava}${packageFolder}`), + + temporaryDir: ({ buildToolGradle }) => (buildToolGradle ? 'build/' : 'target/'), + clientDistDir: ({ temporaryDir, buildToolGradle }) => + `${temporaryDir}${buildToolGradle ? 'resources/main/' : 'classes/'}${CLIENT_DIST_DIR}`, + + authenticationUsesCsrf: ({ authenticationType }) => [OAUTH2, SESSION].includes(authenticationType), + imperativeOrReactive: ({ reactive }) => (reactive ? 'reactive' : 'imperative'), + }); + + if (application.databaseTypeSql) { + prepareSqlApplicationProperties({ application }); + } + + loadDerivedServerAndPlatformProperties({ application }); +}; diff --git a/generators/server/support/database.mts b/generators/server/support/database.mts deleted file mode 100644 index 2f5b0eb0bc51..000000000000 --- a/generators/server/support/database.mts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Copyright 2013-2023 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 crypto from 'crypto'; - -import { databaseTypes, fieldTypes } from '../../../jdl/jhipster/index.mjs'; -import { hibernateSnakeCase } from './string.mjs'; -import { databaseData } from '../../spring-data-relational/support/index.mjs'; -import { ValidationResult } from '../../base/api.mjs'; - -const dbTypes = fieldTypes; -const { STRING: TYPE_STRING, LONG: TYPE_LONG, UUID: TYPE_UUID } = dbTypes.CommonDBTypes; -const { MONGODB, NEO4J, COUCHBASE, CASSANDRA, SQL } = databaseTypes; - -type DatabaseTypeData = { - name: string; - defaultPrimaryKeyType: string; -}; - -const databaseTypeDataFallback: DatabaseTypeData = { - name: 'Unknown', - defaultPrimaryKeyType: TYPE_LONG, -}; - -export const databaseTypeData: Record = { - [CASSANDRA]: { - name: 'Cassandra', - defaultPrimaryKeyType: TYPE_UUID, - }, - [COUCHBASE]: { - name: 'Couchbase', - defaultPrimaryKeyType: TYPE_STRING, - }, - [MONGODB]: { - name: 'MongoDB', - defaultPrimaryKeyType: TYPE_STRING, - }, - [NEO4J]: { - name: 'Neo4j', - defaultPrimaryKeyType: TYPE_STRING, - }, - [SQL]: { - name: 'SQL', - defaultPrimaryKeyType: TYPE_LONG, - }, -}; - -export const getDatabaseTypeData = (databaseType: string): DatabaseTypeData => { - return databaseTypeData[databaseType] ?? databaseTypeDataFallback; -}; - -export const R2DBC_DB_OPTIONS = [ - { - value: databaseTypes.POSTGRESQL, - name: 'PostgreSQL', - }, - { - value: databaseTypes.MYSQL, - name: 'MySQL', - }, - { - value: databaseTypes.MARIADB, - name: 'MariaDB', - }, - { - value: databaseTypes.MSSQL, - name: 'Microsoft SQL Server', - }, -]; - -export const SQL_DB_OPTIONS = [ - { - value: databaseTypes.POSTGRESQL, - name: 'PostgreSQL', - }, - { - value: databaseTypes.MYSQL, - name: 'MySQL', - }, - { - value: databaseTypes.MARIADB, - name: 'MariaDB', - }, - { - value: databaseTypes.ORACLE, - name: 'Oracle', - }, - { - value: databaseTypes.MSSQL, - name: 'Microsoft SQL Server', - }, -]; - -/** - * Get DB type from DB value - * @param {string} db - db - */ -export function getDBTypeFromDBValue(db) { - if (SQL_DB_OPTIONS.map(db => db.value).includes(db)) { - return SQL; - } - return db; -} - -/** - * get for tables/constraints in JHipster preferred style after applying any length limits required. - * - * @param {string} tableOrEntityName - name of the table or entity - * @param {string} columnOrRelationshipName - name of the column or relationship - * @param {number} limit - max length of the returned db reference name - * @param {object} [options] - * @param {boolean} [options.noSnakeCase = false] - do not convert names to snakecase - * @param {string} [options.prefix = ''] - * @param {string} [options.separator = '__'] - * @return {string} db referente name - */ -export function calculateDbNameWithLimit( - tableOrEntityName: string, - columnOrRelationshipName: string, - limit: number, - { noSnakeCase = false, prefix = '', separator = '__' }: { noSnakeCase?: boolean; prefix?: string; separator?: string } = {}, -): string { - const halfLimit = Math.floor(limit / 2); - const suffix = `_${crypto - .createHash('shake256', { outputLength: 1 }) - .update(`${tableOrEntityName}.${columnOrRelationshipName}`, 'utf8') - .digest('hex')}`; - - let formattedName = noSnakeCase ? tableOrEntityName : hibernateSnakeCase(tableOrEntityName); - formattedName = formattedName.substring(0, halfLimit - separator.length); - - let otherFormattedName = noSnakeCase ? columnOrRelationshipName : hibernateSnakeCase(columnOrRelationshipName); - otherFormattedName = otherFormattedName.substring(0, limit - formattedName.length - separator.length - prefix.length - suffix.length); - - return `${prefix}${formattedName}${separator}${otherFormattedName}${suffix}`; -} - -type ConstraintName = { - prodDatabaseType?: string; - noSnakeCase?: boolean; - prefix?: string; - suffix?: string; - skipCheckLengthOfIdentifier?: boolean; -}; - -/** - * get a constraint name for tables in JHipster preferred style - */ -export function calculateDbName( - tableOrEntityName: string, - columnOrRelationshipName: string, - { prodDatabaseType, noSnakeCase = false, prefix = '', suffix = '', skipCheckLengthOfIdentifier = false }: ConstraintName = {}, -): ValidationResult & { value: string } { - const separator = '__'; - const convertCase = noSnakeCase ? str => str : hibernateSnakeCase; - const constraintName = `${prefix}${convertCase(tableOrEntityName)}${separator}${convertCase(columnOrRelationshipName)}${suffix}`; - const { name, constraintNameMaxLength } = (prodDatabaseType && databaseData[prodDatabaseType]) || {}; - if (constraintNameMaxLength && constraintName.length > constraintNameMaxLength && !skipCheckLengthOfIdentifier) { - return { - warning: `The generated constraint name "${constraintName}" is too long for ${name} (which has a ${constraintNameMaxLength} character limit). It will be truncated!`, - value: `${calculateDbNameWithLimit(tableOrEntityName, columnOrRelationshipName, constraintNameMaxLength - suffix.length, { - separator, - noSnakeCase, - prefix, - })}${suffix}`, - }; - } - return { value: constraintName }; -} - -type FKConstraintName = { - prodDatabaseType?: string; - noSnakeCase?: boolean; - skipCheckLengthOfIdentifier?: boolean; -}; - -/** - * get a foreign key constraint name for tables in JHipster preferred style. - */ -export function getFKConstraintName( - tableOrEntityName: string, - columnOrRelationshipName: string, - { prodDatabaseType, noSnakeCase, skipCheckLengthOfIdentifier }: FKConstraintName = {}, -) { - return calculateDbName(tableOrEntityName, columnOrRelationshipName, { - prodDatabaseType, - noSnakeCase, - prefix: 'fk_', - suffix: '_id', - skipCheckLengthOfIdentifier, - }); -} - -type JoinTableName = { - prodDatabaseType?: string; - skipCheckLengthOfIdentifier?: boolean; -}; - -/** - * get a table name for joined tables in JHipster preferred style. - */ -export function getJoinTableName( - entityName, - relationshipName, - { prodDatabaseType, skipCheckLengthOfIdentifier }: JoinTableName = {}, -): ValidationResult & { value: string } { - const separator = '__'; - const prefix = 'rel_'; - const joinTableName = `${prefix}${hibernateSnakeCase(entityName)}${separator}${hibernateSnakeCase(relationshipName)}`; - const { name, tableNameMaxLength } = (prodDatabaseType && databaseData[prodDatabaseType]) || {}; - if (tableNameMaxLength && joinTableName.length > tableNameMaxLength && !skipCheckLengthOfIdentifier) { - return { - warning: `The generated join table "${joinTableName}" is too long for ${name} (which has a ${tableNameMaxLength} character limit). It will be truncated!`, - value: calculateDbNameWithLimit(entityName, relationshipName, tableNameMaxLength, { prefix, separator }), - }; - } - return { value: joinTableName }; -} - -type UXConstraintName = { - prodDatabaseType?: string; - noSnakeCase?: boolean; -}; - -/** - * get a unique constraint name for tables in JHipster preferred style. - */ -export function getUXConstraintName(entityName, columnName, { prodDatabaseType, noSnakeCase }: UXConstraintName = {}) { - return calculateDbName(entityName, columnName, { prodDatabaseType, noSnakeCase, prefix: 'ux_' }); -} diff --git a/generators/server/support/database.spec.mts b/generators/server/support/database.spec.mts deleted file mode 100644 index 88523244e174..000000000000 --- a/generators/server/support/database.spec.mts +++ /dev/null @@ -1,168 +0,0 @@ -import { expect } from 'esmocha'; - -import { databaseTypes } from '../../../jdl/jhipster/index.mjs'; -import { getDBTypeFromDBValue, getFKConstraintName, getJoinTableName, getUXConstraintName } from './database.mjs'; -import { hibernateSnakeCase } from './string.mjs'; - -const { CASSANDRA, MONGODB, MYSQL, SQL, POSTGRESQL } = databaseTypes; - -describe('generator - server - support - database', () => { - describe('getDBTypeFromDBValue', () => { - describe('when called with sql DB name', () => { - it('return SQL', () => { - expect(getDBTypeFromDBValue(MYSQL)).toEqual(SQL); - }); - }); - describe('when called with mongo DB', () => { - it('return mongodb', () => { - expect(getDBTypeFromDBValue(MONGODB)).toEqual(MONGODB); - }); - }); - describe('when called with cassandra', () => { - it('return cassandra', () => { - expect(getDBTypeFromDBValue(CASSANDRA)).toEqual(CASSANDRA); - }); - }); - }); - - describe('hibernateSnakeCase', () => { - describe('when called with a value', () => { - it('returns a column name', () => { - expect(hibernateSnakeCase('colName')).toBe('col_name'); - expect(hibernateSnakeCase('colNName')).toBe('colnname'); - }); - }); - }); - describe('getJoinTableName', () => { - describe('when called with a value', () => { - it('returns a join table name', () => { - expect(getJoinTableName('entityName', 'relationshipName', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'rel_entity_name__relationship_name', - ); - }); - }); - describe('when called with a long name', () => { - it('returns a proper join table name', () => { - expect( - getJoinTableName('entityNameLongerForPostgresql', 'relationshipNameForPostgresql', { prodDatabaseType: POSTGRESQL }).value, - ).toBe('rel_entity_name_longer_for_postgr__relationship_name_for_pos_24'); - expect( - getJoinTableName('entityNameLongerForPostgresql', 'relationshipNameForPostgresql', { prodDatabaseType: POSTGRESQL }).value, - ).toHaveLength(63); - }); - }); - }); - describe('getFKConstraintName', () => { - describe('when called with a value', () => { - it('returns a constraint name', () => { - expect(getFKConstraintName('entityName', 'relationshipName', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'fk_entity_name__relationship_name_id', - ); - }); - }); - describe('when called with a long name and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getFKConstraintName('entityLongerNameWithPaginationAndDTO', 'relationshipLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - }).value.length, - ).toBe(63); - expect( - getFKConstraintName('entityLongerNameWithPaginationAndDTO', 'relationshipLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - }).value, - ).toBe('fk_entity_longer_name_with_pagi__relationship_longer_name_b6_id'); - }); - }); - describe('when called with a long name that is near limit and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToMany', { prodDatabaseType: POSTGRESQL }).value.length, - ).toBeLessThan(64); - expect(getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToMany', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'fk_test_custom_table_name__user_many_to_many_user_many_to_8c_id', - ); - expect( - getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value.length, - ).toBeLessThan(64); - expect(getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'fk_test_custom_table_name__user_many_to_many_user_many_to_72_id', - ); - }); - }); - describe('when called with a long name that is equal to limit and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getFKConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value, - ).toHaveLength(63); - expect(getFKConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'fk_test_custom_table_names__user_many_to_many_user_many_t_50_id', - ); - }); - }); - }); - describe('getUXConstraintName', () => { - describe('when called with a value', () => { - it('returns a constraint name', () => { - expect(getUXConstraintName('entityName', 'columnName', { prodDatabaseType: POSTGRESQL }).value).toBe('ux_entity_name__column_name'); - }); - }); - describe('when called with a value and no snake case', () => { - it('returns a constraint name', () => { - expect(getUXConstraintName('entityName', 'columnName', { prodDatabaseType: POSTGRESQL, noSnakeCase: true }).value).toBe( - 'ux_entityName__columnName', - ); - }); - }); - describe('when called with a long name and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - }).value, - ).toHaveLength(63); - expect( - getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - }).value, - ).toBe('ux_entity_longer_name_with_pagin__column_longer_name_with_pa_8b'); - }); - }); - describe('when called with a long name that is near limit and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getUXConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value.length, - ).toBeLessThan(64); - expect(getUXConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'ux_test_custom_table_name__user_many_to_many_user_many_to_ma_72', - ); - }); - }); - describe('when called with a long name that is equal to limit and postgresql', () => { - it('returns a proper constraint name', () => { - expect( - getUXConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value, - ).toHaveLength(63); - expect(getUXConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( - 'ux_test_custom_table_names__user_many_to_many_user_many_to_m_50', - ); - }); - }); - describe('when called with a long name and postgresql and no snake case', () => { - it('returns a proper constraint name', () => { - expect( - getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - noSnakeCase: true, - }).value, - ).toHaveLength(63); - expect( - getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { - prodDatabaseType: POSTGRESQL, - noSnakeCase: true, - }).value, - ).toBe('ux_entityLongerNameWithPaginatio__columnLongerNameWithPagina_8b'); - }); - }); - }); -}); diff --git a/generators/server/support/database.spec.ts b/generators/server/support/database.spec.ts new file mode 100644 index 000000000000..4b70416b5677 --- /dev/null +++ b/generators/server/support/database.spec.ts @@ -0,0 +1,168 @@ +import { expect } from 'esmocha'; + +import { databaseTypes } from '../../../jdl/jhipster/index.js'; +import { getDBTypeFromDBValue, getFKConstraintName, getJoinTableName, getUXConstraintName } from './database.js'; +import { hibernateSnakeCase } from './string.js'; + +const { CASSANDRA, MONGODB, MYSQL, SQL, POSTGRESQL } = databaseTypes; + +describe('generator - server - support - database', () => { + describe('getDBTypeFromDBValue', () => { + describe('when called with sql DB name', () => { + it('return SQL', () => { + expect(getDBTypeFromDBValue(MYSQL)).toEqual(SQL); + }); + }); + describe('when called with mongo DB', () => { + it('return mongodb', () => { + expect(getDBTypeFromDBValue(MONGODB)).toEqual(MONGODB); + }); + }); + describe('when called with cassandra', () => { + it('return cassandra', () => { + expect(getDBTypeFromDBValue(CASSANDRA)).toEqual(CASSANDRA); + }); + }); + }); + + describe('hibernateSnakeCase', () => { + describe('when called with a value', () => { + it('returns a column name', () => { + expect(hibernateSnakeCase('colName')).toBe('col_name'); + expect(hibernateSnakeCase('colNName')).toBe('colnname'); + }); + }); + }); + describe('getJoinTableName', () => { + describe('when called with a value', () => { + it('returns a join table name', () => { + expect(getJoinTableName('entityName', 'relationshipName', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'rel_entity_name__relationship_name', + ); + }); + }); + describe('when called with a long name', () => { + it('returns a proper join table name', () => { + expect( + getJoinTableName('entityNameLongerForPostgresql', 'relationshipNameForPostgresql', { prodDatabaseType: POSTGRESQL }).value, + ).toBe('rel_entity_name_longer_for_postgr__relationship_name_for_pos_24'); + expect( + getJoinTableName('entityNameLongerForPostgresql', 'relationshipNameForPostgresql', { prodDatabaseType: POSTGRESQL }).value, + ).toHaveLength(63); + }); + }); + }); + describe('getFKConstraintName', () => { + describe('when called with a value', () => { + it('returns a constraint name', () => { + expect(getFKConstraintName('entityName', 'relationshipName', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'fk_entity_name__relationship_name_id', + ); + }); + }); + describe('when called with a long name and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getFKConstraintName('entityLongerNameWithPaginationAndDTO', 'relationshipLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + }).value.length, + ).toBe(63); + expect( + getFKConstraintName('entityLongerNameWithPaginationAndDTO', 'relationshipLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + }).value, + ).toBe('fk_entity_longer_name_with_pagi__relationship_longer_name_b6_id'); + }); + }); + describe('when called with a long name that is near limit and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToMany', { prodDatabaseType: POSTGRESQL }).value.length, + ).toBeLessThan(64); + expect(getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToMany', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'fk_test_custom_table_name__user_many_to_many_user_many_to_8c_id', + ); + expect( + getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value.length, + ).toBeLessThan(64); + expect(getFKConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'fk_test_custom_table_name__user_many_to_many_user_many_to_72_id', + ); + }); + }); + describe('when called with a long name that is equal to limit and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getFKConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value, + ).toHaveLength(63); + expect(getFKConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'fk_test_custom_table_names__user_many_to_many_user_many_t_50_id', + ); + }); + }); + }); + describe('getUXConstraintName', () => { + describe('when called with a value', () => { + it('returns a constraint name', () => { + expect(getUXConstraintName('entityName', 'columnName', { prodDatabaseType: POSTGRESQL }).value).toBe('ux_entity_name__column_name'); + }); + }); + describe('when called with a value and no snake case', () => { + it('returns a constraint name', () => { + expect(getUXConstraintName('entityName', 'columnName', { prodDatabaseType: POSTGRESQL, noSnakeCase: true }).value).toBe( + 'ux_entityName__columnName', + ); + }); + }); + describe('when called with a long name and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + }).value, + ).toHaveLength(63); + expect( + getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + }).value, + ).toBe('ux_entity_longer_name_with_pagin__column_longer_name_with_pa_8b'); + }); + }); + describe('when called with a long name that is near limit and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getUXConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value.length, + ).toBeLessThan(64); + expect(getUXConstraintName('testCustomTableName', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'ux_test_custom_table_name__user_many_to_many_user_many_to_ma_72', + ); + }); + }); + describe('when called with a long name that is equal to limit and postgresql', () => { + it('returns a proper constraint name', () => { + expect( + getUXConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value, + ).toHaveLength(63); + expect(getUXConstraintName('testCustomTableNames', 'userManyToManyUserManyToManies', { prodDatabaseType: POSTGRESQL }).value).toBe( + 'ux_test_custom_table_names__user_many_to_many_user_many_to_m_50', + ); + }); + }); + describe('when called with a long name and postgresql and no snake case', () => { + it('returns a proper constraint name', () => { + expect( + getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + noSnakeCase: true, + }).value, + ).toHaveLength(63); + expect( + getUXConstraintName('entityLongerNameWithPaginationAndDTO', 'columnLongerNameWithPaginationAndDTO', { + prodDatabaseType: POSTGRESQL, + noSnakeCase: true, + }).value, + ).toBe('ux_entityLongerNameWithPaginatio__columnLongerNameWithPagina_8b'); + }); + }); + }); +}); diff --git a/generators/server/support/database.ts b/generators/server/support/database.ts new file mode 100644 index 000000000000..3c594dc316bc --- /dev/null +++ b/generators/server/support/database.ts @@ -0,0 +1,245 @@ +/** + * Copyright 2013-2023 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 crypto from 'crypto'; + +import { databaseTypes, fieldTypes } from '../../../jdl/jhipster/index.js'; +import { hibernateSnakeCase } from './string.js'; +import { databaseData } from '../../spring-data-relational/support/index.js'; +import { ValidationResult } from '../../base/api.js'; + +const dbTypes = fieldTypes; +const { STRING: TYPE_STRING, LONG: TYPE_LONG, UUID: TYPE_UUID } = dbTypes.CommonDBTypes; +const { MONGODB, NEO4J, COUCHBASE, CASSANDRA, SQL } = databaseTypes; + +type DatabaseTypeData = { + name: string; + defaultPrimaryKeyType: string; +}; + +const databaseTypeDataFallback: DatabaseTypeData = { + name: 'Unknown', + defaultPrimaryKeyType: TYPE_LONG, +}; + +export const databaseTypeData: Record = { + [CASSANDRA]: { + name: 'Cassandra', + defaultPrimaryKeyType: TYPE_UUID, + }, + [COUCHBASE]: { + name: 'Couchbase', + defaultPrimaryKeyType: TYPE_STRING, + }, + [MONGODB]: { + name: 'MongoDB', + defaultPrimaryKeyType: TYPE_STRING, + }, + [NEO4J]: { + name: 'Neo4j', + defaultPrimaryKeyType: TYPE_STRING, + }, + [SQL]: { + name: 'SQL', + defaultPrimaryKeyType: TYPE_LONG, + }, +}; + +export const getDatabaseTypeData = (databaseType: string): DatabaseTypeData => { + return databaseTypeData[databaseType] ?? databaseTypeDataFallback; +}; + +export const R2DBC_DB_OPTIONS = [ + { + value: databaseTypes.POSTGRESQL, + name: 'PostgreSQL', + }, + { + value: databaseTypes.MYSQL, + name: 'MySQL', + }, + { + value: databaseTypes.MARIADB, + name: 'MariaDB', + }, + { + value: databaseTypes.MSSQL, + name: 'Microsoft SQL Server', + }, +]; + +export const SQL_DB_OPTIONS = [ + { + value: databaseTypes.POSTGRESQL, + name: 'PostgreSQL', + }, + { + value: databaseTypes.MYSQL, + name: 'MySQL', + }, + { + value: databaseTypes.MARIADB, + name: 'MariaDB', + }, + { + value: databaseTypes.ORACLE, + name: 'Oracle', + }, + { + value: databaseTypes.MSSQL, + name: 'Microsoft SQL Server', + }, +]; + +/** + * Get DB type from DB value + * @param {string} db - db + */ +export function getDBTypeFromDBValue(db) { + if (SQL_DB_OPTIONS.map(db => db.value).includes(db)) { + return SQL; + } + return db; +} + +/** + * get for tables/constraints in JHipster preferred style after applying any length limits required. + * + * @param {string} tableOrEntityName - name of the table or entity + * @param {string} columnOrRelationshipName - name of the column or relationship + * @param {number} limit - max length of the returned db reference name + * @param {object} [options] + * @param {boolean} [options.noSnakeCase = false] - do not convert names to snakecase + * @param {string} [options.prefix = ''] + * @param {string} [options.separator = '__'] + * @return {string} db referente name + */ +export function calculateDbNameWithLimit( + tableOrEntityName: string, + columnOrRelationshipName: string, + limit: number, + { noSnakeCase = false, prefix = '', separator = '__' }: { noSnakeCase?: boolean; prefix?: string; separator?: string } = {}, +): string { + const halfLimit = Math.floor(limit / 2); + const suffix = `_${crypto + .createHash('shake256', { outputLength: 1 }) + .update(`${tableOrEntityName}.${columnOrRelationshipName}`, 'utf8') + .digest('hex')}`; + + let formattedName = noSnakeCase ? tableOrEntityName : hibernateSnakeCase(tableOrEntityName); + formattedName = formattedName.substring(0, halfLimit - separator.length); + + let otherFormattedName = noSnakeCase ? columnOrRelationshipName : hibernateSnakeCase(columnOrRelationshipName); + otherFormattedName = otherFormattedName.substring(0, limit - formattedName.length - separator.length - prefix.length - suffix.length); + + return `${prefix}${formattedName}${separator}${otherFormattedName}${suffix}`; +} + +type ConstraintName = { + prodDatabaseType?: string; + noSnakeCase?: boolean; + prefix?: string; + suffix?: string; + skipCheckLengthOfIdentifier?: boolean; +}; + +/** + * get a constraint name for tables in JHipster preferred style + */ +export function calculateDbName( + tableOrEntityName: string, + columnOrRelationshipName: string, + { prodDatabaseType, noSnakeCase = false, prefix = '', suffix = '', skipCheckLengthOfIdentifier = false }: ConstraintName = {}, +): ValidationResult & { value: string } { + const separator = '__'; + const convertCase = noSnakeCase ? str => str : hibernateSnakeCase; + const constraintName = `${prefix}${convertCase(tableOrEntityName)}${separator}${convertCase(columnOrRelationshipName)}${suffix}`; + const { name, constraintNameMaxLength } = (prodDatabaseType && databaseData[prodDatabaseType]) || {}; + if (constraintNameMaxLength && constraintName.length > constraintNameMaxLength && !skipCheckLengthOfIdentifier) { + return { + warning: `The generated constraint name "${constraintName}" is too long for ${name} (which has a ${constraintNameMaxLength} character limit). It will be truncated!`, + value: `${calculateDbNameWithLimit(tableOrEntityName, columnOrRelationshipName, constraintNameMaxLength - suffix.length, { + separator, + noSnakeCase, + prefix, + })}${suffix}`, + }; + } + return { value: constraintName }; +} + +type FKConstraintName = { + prodDatabaseType?: string; + noSnakeCase?: boolean; + skipCheckLengthOfIdentifier?: boolean; +}; + +/** + * get a foreign key constraint name for tables in JHipster preferred style. + */ +export function getFKConstraintName( + tableOrEntityName: string, + columnOrRelationshipName: string, + { prodDatabaseType, noSnakeCase, skipCheckLengthOfIdentifier }: FKConstraintName = {}, +) { + return calculateDbName(tableOrEntityName, columnOrRelationshipName, { + prodDatabaseType, + noSnakeCase, + prefix: 'fk_', + suffix: '_id', + skipCheckLengthOfIdentifier, + }); +} + +type JoinTableName = { + prodDatabaseType?: string; + skipCheckLengthOfIdentifier?: boolean; +}; + +/** + * get a table name for joined tables in JHipster preferred style. + */ +export function getJoinTableName( + entityName, + relationshipName, + { prodDatabaseType, skipCheckLengthOfIdentifier }: JoinTableName = {}, +): ValidationResult & { value: string } { + const separator = '__'; + const prefix = 'rel_'; + const joinTableName = `${prefix}${hibernateSnakeCase(entityName)}${separator}${hibernateSnakeCase(relationshipName)}`; + const { name, tableNameMaxLength } = (prodDatabaseType && databaseData[prodDatabaseType]) || {}; + if (tableNameMaxLength && joinTableName.length > tableNameMaxLength && !skipCheckLengthOfIdentifier) { + return { + warning: `The generated join table "${joinTableName}" is too long for ${name} (which has a ${tableNameMaxLength} character limit). It will be truncated!`, + value: calculateDbNameWithLimit(entityName, relationshipName, tableNameMaxLength, { prefix, separator }), + }; + } + return { value: joinTableName }; +} + +type UXConstraintName = { + prodDatabaseType?: string; + noSnakeCase?: boolean; +}; + +/** + * get a unique constraint name for tables in JHipster preferred style. + */ +export function getUXConstraintName(entityName, columnName, { prodDatabaseType, noSnakeCase }: UXConstraintName = {}) { + return calculateDbName(entityName, columnName, { prodDatabaseType, noSnakeCase, prefix: 'ux_' }); +} diff --git a/generators/server/support/dependabot-gradle.mts b/generators/server/support/dependabot-gradle.ts similarity index 100% rename from generators/server/support/dependabot-gradle.mts rename to generators/server/support/dependabot-gradle.ts diff --git a/generators/server/support/dependabot-maven.mts b/generators/server/support/dependabot-maven.ts similarity index 100% rename from generators/server/support/dependabot-maven.mts rename to generators/server/support/dependabot-maven.ts diff --git a/generators/server/support/doc.js b/generators/server/support/doc.js new file mode 100644 index 000000000000..55e9a1b3f735 --- /dev/null +++ b/generators/server/support/doc.js @@ -0,0 +1,54 @@ +/** + * Copyright 2013-2023 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 { formatDocAsSingleLine } from '../../base-application/support/index.js'; + +const escapeDoubleQuotes = text => { + if (text.includes('"')) { + return text.replace(/"/g, '\\"'); + } + return text; +}; + +/** + * Convert passed block of string to javadoc formatted string. + * + * @param {string} text text to convert to javadoc format + * @param {number} indentSize indent size (default 0) + * @returns javadoc formatted string + */ +export const formatDocAsJavaDoc = (text, indentSize = 0) => { + if (indentSize < 0) { + indentSize = 0; + } + if (!text) { + text = ''; + } + text = escapeDoubleQuotes(text); + const indent = ' '.repeat(indentSize); + const rows = ['/**', ...text.split('\\n').map(row => ` * ${row}`), ' */'].map(row => `${indent}${row}`); + return rows.join('\n'); +}; + +export const formatDocAsApiDescription = text => { + if (!text) { + return text; + } + + return escapeDoubleQuotes(formatDocAsSingleLine(text)); +}; diff --git a/generators/server/support/doc.mjs b/generators/server/support/doc.mjs deleted file mode 100644 index 442f2747bf6a..000000000000 --- a/generators/server/support/doc.mjs +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2013-2023 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 { formatDocAsSingleLine } from '../../base-application/support/index.mjs'; - -const escapeDoubleQuotes = text => { - if (text.includes('"')) { - return text.replace(/"/g, '\\"'); - } - return text; -}; - -/** - * Convert passed block of string to javadoc formatted string. - * - * @param {string} text text to convert to javadoc format - * @param {number} indentSize indent size (default 0) - * @returns javadoc formatted string - */ -export const formatDocAsJavaDoc = (text, indentSize = 0) => { - if (indentSize < 0) { - indentSize = 0; - } - if (!text) { - text = ''; - } - text = escapeDoubleQuotes(text); - const indent = ' '.repeat(indentSize); - const rows = ['/**', ...text.split('\\n').map(row => ` * ${row}`), ' */'].map(row => `${indent}${row}`); - return rows.join('\n'); -}; - -export const formatDocAsApiDescription = text => { - if (!text) { - return text; - } - - return escapeDoubleQuotes(formatDocAsSingleLine(text)); -}; diff --git a/generators/server/support/doc.spec.mts b/generators/server/support/doc.spec.mts deleted file mode 100644 index 80343e77f3e4..000000000000 --- a/generators/server/support/doc.spec.mts +++ /dev/null @@ -1,70 +0,0 @@ -import { expect } from 'esmocha'; -import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.mjs'; - -describe('generator - server - support - doc', () => { - describe('formatDocAsJavaDoc', () => { - describe('when passing a negative or nil increment', () => { - it('returns the comment with no increment', () => { - expect(formatDocAsJavaDoc('whatever', -42)).toBe('/**\n * whatever\n */'); - expect(formatDocAsJavaDoc('whatever', 0)).toBe('/**\n * whatever\n */'); - }); - }); - describe('when passing a positive increment', () => { - it('returns the comment with the increment', () => { - expect(formatDocAsJavaDoc('whatever', 1)).toBe(' /**\n * whatever\n */'); - }); - }); - describe('when passing a nil comment', () => { - it('inserts an empty comment instead of failing', () => { - // @ts-expect-error - expect(formatDocAsJavaDoc(null, 1)).toBe(' /**\n * \n */'); - }); - }); - describe('when passing a comment containing double quotes', () => { - it('escapes the quotes', () => { - expect(formatDocAsJavaDoc('Comment="KO"', 1)).toBe(' /**\n * Comment=\\"KO\\"\n */'); - }); - }); - describe('when passing a comment with newlines', () => { - it('formats the comment correctly with line breaks', () => { - const comment = 'This is the first line.\\nAnd this is the second.'; - expect(formatDocAsJavaDoc(comment, 1)).toBe(' /**\n * This is the first line.\n * And this is the second.\n */'); - }); - }); - }); - - describe('formatDocAsApiDescription', () => { - describe('when formatting a nil text', () => { - it('returns it', () => { - expect(formatDocAsApiDescription()).toEqual(undefined); - }); - }); - describe('when formatting an empty text', () => { - it('returns it', () => { - expect(formatDocAsApiDescription('')).toEqual(''); - }); - }); - describe('when formatting normal texts', () => { - describe('when having empty lines', () => { - it('discards them', () => { - expect(formatDocAsApiDescription('First line\n \nSecond line\n\nThird line')).toEqual('First line Second line Third line'); - }); - }); - describe('when having HTML tags', () => { - it('keeps them', () => { - expect(formatDocAsApiDescription('Not boldy\nboldy')).toEqual('Not boldyboldy'); - }); - }); - describe('when having a plain text', () => { - it('puts a space before each line', () => { - expect(formatDocAsApiDescription('JHipster is\na great generator')).toEqual('JHipster is a great generator'); - }); - }); - describe('when having quotes', () => { - it('formats the text to make the string valid', () => { - expect(formatDocAsApiDescription('JHipster is "the" best')).toEqual('JHipster is \\"the\\" best'); - }); - }); - }); - }); -}); diff --git a/generators/server/support/doc.spec.ts b/generators/server/support/doc.spec.ts new file mode 100644 index 000000000000..8b221c6ef635 --- /dev/null +++ b/generators/server/support/doc.spec.ts @@ -0,0 +1,70 @@ +import { expect } from 'esmocha'; +import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.js'; + +describe('generator - server - support - doc', () => { + describe('formatDocAsJavaDoc', () => { + describe('when passing a negative or nil increment', () => { + it('returns the comment with no increment', () => { + expect(formatDocAsJavaDoc('whatever', -42)).toBe('/**\n * whatever\n */'); + expect(formatDocAsJavaDoc('whatever', 0)).toBe('/**\n * whatever\n */'); + }); + }); + describe('when passing a positive increment', () => { + it('returns the comment with the increment', () => { + expect(formatDocAsJavaDoc('whatever', 1)).toBe(' /**\n * whatever\n */'); + }); + }); + describe('when passing a nil comment', () => { + it('inserts an empty comment instead of failing', () => { + // @ts-expect-error + expect(formatDocAsJavaDoc(null, 1)).toBe(' /**\n * \n */'); + }); + }); + describe('when passing a comment containing double quotes', () => { + it('escapes the quotes', () => { + expect(formatDocAsJavaDoc('Comment="KO"', 1)).toBe(' /**\n * Comment=\\"KO\\"\n */'); + }); + }); + describe('when passing a comment with newlines', () => { + it('formats the comment correctly with line breaks', () => { + const comment = 'This is the first line.\\nAnd this is the second.'; + expect(formatDocAsJavaDoc(comment, 1)).toBe(' /**\n * This is the first line.\n * And this is the second.\n */'); + }); + }); + }); + + describe('formatDocAsApiDescription', () => { + describe('when formatting a nil text', () => { + it('returns it', () => { + expect(formatDocAsApiDescription()).toEqual(undefined); + }); + }); + describe('when formatting an empty text', () => { + it('returns it', () => { + expect(formatDocAsApiDescription('')).toEqual(''); + }); + }); + describe('when formatting normal texts', () => { + describe('when having empty lines', () => { + it('discards them', () => { + expect(formatDocAsApiDescription('First line\n \nSecond line\n\nThird line')).toEqual('First line Second line Third line'); + }); + }); + describe('when having HTML tags', () => { + it('keeps them', () => { + expect(formatDocAsApiDescription('Not boldy\nboldy')).toEqual('Not boldyboldy'); + }); + }); + describe('when having a plain text', () => { + it('puts a space before each line', () => { + expect(formatDocAsApiDescription('JHipster is\na great generator')).toEqual('JHipster is a great generator'); + }); + }); + describe('when having quotes', () => { + it('formats the text to make the string valid', () => { + expect(formatDocAsApiDescription('JHipster is "the" best')).toEqual('JHipster is \\"the\\" best'); + }); + }); + }); + }); +}); diff --git a/generators/server/support/index.mts b/generators/server/support/index.mts deleted file mode 100644 index 1e4621a2da87..000000000000 --- a/generators/server/support/index.mts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2013-2023 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 './config.mjs'; -export * from './doc.mjs'; -export * from './database.mjs'; -export * from './dependabot-maven.mjs'; -export * from './dependabot-gradle.mjs'; -export * from '../../java/support/files.mjs'; -export * from './java-formatting.mjs'; -export * from './key-store.mjs'; -export * from './needles.mjs'; -export { default as prepareEntity } from './prepare-entity.mjs'; -export * from './prepare-entity.mjs'; -export { default as prepareField } from './prepare-field.mjs'; -export * from './prepare-relationship.mjs'; -export * from './relationship.mjs'; -export * from './spring-factories.mjs'; -export * from './string.mjs'; -export * from './templates/field-values.mjs'; -export { default as updateLanguagesTask } from './update-languages.mjs'; -export * from './update-languages.mjs'; diff --git a/generators/server/support/index.ts b/generators/server/support/index.ts new file mode 100644 index 000000000000..1398ee5a95a3 --- /dev/null +++ b/generators/server/support/index.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2013-2023 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 './config.js'; +export * from './doc.js'; +export * from './database.js'; +export * from './dependabot-maven.js'; +export * from './dependabot-gradle.js'; +export * from '../../java/support/files.js'; +export * from './java-formatting.js'; +export * from './key-store.js'; +export * from './needles.js'; +export { default as prepareEntity } from './prepare-entity.js'; +export * from './prepare-entity.js'; +export { default as prepareField } from './prepare-field.js'; +export * from './prepare-relationship.js'; +export * from './relationship.js'; +export * from './spring-factories.js'; +export * from './string.js'; +export * from './templates/field-values.js'; +export { default as updateLanguagesTask } from './update-languages.js'; +export * from './update-languages.js'; diff --git a/generators/server/support/java-formatting.mjs b/generators/server/support/java-formatting.js similarity index 100% rename from generators/server/support/java-formatting.mjs rename to generators/server/support/java-formatting.js diff --git a/generators/server/support/key-store.mts b/generators/server/support/key-store.mts deleted file mode 100644 index 4f916aec841b..000000000000 --- a/generators/server/support/key-store.mts +++ /dev/null @@ -1,52 +0,0 @@ -import { mkdir, lstat } from 'fs/promises'; -import { dirname } from 'path'; -import { execa } from 'execa'; -import { ValidationResult } from '../../base/api.mjs'; - -/** - * Generate a KeyStore. - */ -// eslint-disable-next-line import/prefer-default-export -export async function generateKeyStore(keyStoreFile: string, { packageName }: { packageName: string }): Promise { - try { - const stat = await lstat(keyStoreFile); - if (stat.isFile()) { - return { info: `KeyStore '${keyStoreFile}' already exists. Leaving unchanged.` }; - } - throw new Error(`${keyStoreFile} is not a file`); - } catch { - /* File doesn't exist */ - } - - await mkdir(dirname(keyStoreFile), { recursive: true }); - const javaHome = process.env.JAVA_HOME; - const keytoolCmd = javaHome ? `${javaHome}/bin/keytool` : 'keytool'; - try { - // Generate the PKCS#12 keystore - const result = await execa(keytoolCmd, [ - '-genkey', - '-noprompt', - '-storetype', - 'PKCS12', - '-keyalg', - 'RSA', - '-alias', - 'selfsigned', - '-keystore', - keyStoreFile, - '-storepass', - 'password', - '-keypass', - 'password', - '-keysize', - '2048', - '-validity', - '99999', - '-dname', - `CN=Java Hipster, OU=Development, O=${packageName}, L=, ST=, C=`, - ]); - return { info: [...result.stderr.split('\n').filter(line => line), `KeyStore '${keyStoreFile}' generated successfully.`] }; - } catch (error) { - return { debug: error, warning: `Failed to create a KeyStore with 'keytool': ${(error as Error).message}` }; - } -} diff --git a/generators/server/support/key-store.ts b/generators/server/support/key-store.ts new file mode 100644 index 000000000000..71884d274ab4 --- /dev/null +++ b/generators/server/support/key-store.ts @@ -0,0 +1,52 @@ +import { mkdir, lstat } from 'fs/promises'; +import { dirname } from 'path'; +import { execa } from 'execa'; +import { ValidationResult } from '../../base/api.js'; + +/** + * Generate a KeyStore. + */ +// eslint-disable-next-line import/prefer-default-export +export async function generateKeyStore(keyStoreFile: string, { packageName }: { packageName: string }): Promise { + try { + const stat = await lstat(keyStoreFile); + if (stat.isFile()) { + return { info: `KeyStore '${keyStoreFile}' already exists. Leaving unchanged.` }; + } + throw new Error(`${keyStoreFile} is not a file`); + } catch { + /* File doesn't exist */ + } + + await mkdir(dirname(keyStoreFile), { recursive: true }); + const javaHome = process.env.JAVA_HOME; + const keytoolCmd = javaHome ? `${javaHome}/bin/keytool` : 'keytool'; + try { + // Generate the PKCS#12 keystore + const result = await execa(keytoolCmd, [ + '-genkey', + '-noprompt', + '-storetype', + 'PKCS12', + '-keyalg', + 'RSA', + '-alias', + 'selfsigned', + '-keystore', + keyStoreFile, + '-storepass', + 'password', + '-keypass', + 'password', + '-keysize', + '2048', + '-validity', + '99999', + '-dname', + `CN=Java Hipster, OU=Development, O=${packageName}, L=, ST=, C=`, + ]); + return { info: [...result.stderr.split('\n').filter(line => line), `KeyStore '${keyStoreFile}' generated successfully.`] }; + } catch (error) { + return { debug: error, warning: `Failed to create a KeyStore with 'keytool': ${(error as Error).message}` }; + } +} diff --git a/generators/server/support/needles.mts b/generators/server/support/needles.mts deleted file mode 100644 index a326f09b08dd..000000000000 --- a/generators/server/support/needles.mts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2013-2023 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 CoreGenerator from '../../base-core/index.mjs'; -import { createBaseNeedle } from '../../base/support/needles.mjs'; -import { SpringBootApplication } from '../types.mjs'; - -type ApplicationPropertiesNeedles = { - property?: string; - propertyGetter?: string; - propertyClass?: string; -}; - -/** - * Insert content into ApplicationProperties class - * @example - * insertContentIntoApplicationProperties.call(generator, application, { - * property: 'private final bar = new Bar();', - * proppertyGetter: ` - * public getBar() { - * return bar; - * }`, - * propertyClass: ` - * public static class Async { - * private String foo = "default"; - * }`, - * }) - * @example - * generator.editFile( - * 'ApplicationProperties.java', - * insertContentIntoApplicationProperties({ - * property: 'private final bar = new Bar();', - * propertyGetter: ` - * public getBar() { - * return bar; - * }`, - * propertyClass: ` - * public static class Async { - * private String foo = "default"; - * }`, - * }); - * ); - */ -// eslint-disable-next-line import/prefer-default-export -export function insertContentIntoApplicationProperties( - this: CoreGenerator | void, - application: SpringBootApplication, - needles: ApplicationPropertiesNeedles, -) { - if (this) { - return createBaseNeedle.call( - this, - { - filePath: `${application.javaPackageSrcDir}config/ApplicationProperties.java`, - needlesPrefix: 'application-properties', - }, - needles, - ); - } - return createBaseNeedle( - { - needlesPrefix: 'application-properties', - }, - needles, - ); -} diff --git a/generators/server/support/needles.spec.mts b/generators/server/support/needles.spec.mts deleted file mode 100644 index b2b1a23e3884..000000000000 --- a/generators/server/support/needles.spec.mts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2013-2023 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 { expect } from 'esmocha'; - -import { defaultHelpers as helpers } from '../../../test/support/index.mjs'; -import { GENERATOR_SERVER } from '../../generator-list.mjs'; -import { insertContentIntoApplicationProperties } from './needles.mjs'; - -describe('generator - server - support - needles', () => { - describe('generated project', () => { - let runResult; - before(async () => { - runResult = await helpers.runJHipster(GENERATOR_SERVER).withMockedGenerators(['jhipster:common', 'jhipster:languages']); - }); - - it('should match state snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - describe('insertContentIntoApplicationProperties needle', () => { - it('with a non existing needle', () => { - const application = runResult.generator.sharedData.getApplication(); - expect(() => insertContentIntoApplicationProperties.call(runResult.generator, application, { foo: 'foo' })).toThrow( - /Missing required jhipster-needle application-properties-foo not found at/, - ); - }); - - it('without a needle', () => { - const application = runResult.generator.sharedData.getApplication(); - expect(() => insertContentIntoApplicationProperties.call(runResult.generator, application, {})).toThrow( - /At least 1 needle is required/, - ); - }); - - describe('when applied', () => { - const fileRegexp = /config\/ApplicationProperties.java/; - const property = 'private Foo foo;'; - const propertyGetter = ` - - private Foo getFoo() { - return foo; - };`; - const propertyClass = ` - - public static Foo{} { - private String bar; - - public String getBar() { - return bar; - } - };`; - let snapshot; - - before(() => { - const application = runResult.generator.sharedData.getApplication(); - insertContentIntoApplicationProperties.call(runResult.generator, application, { - property, - propertyGetter, - propertyClass, - }); - snapshot = runResult.getSnapshot(file => fileRegexp.test(file.path)); - }); - - it('should match snapshot', () => { - expect(snapshot).toMatchInlineSnapshot(` -{ - "src/main/java/com/mycompany/myapp/config/ApplicationProperties.java": { - "contents": "package com.mycompany.myapp.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Properties specific to JHipster. - *

    - * Properties are configured in the {@code application.yml} file. - * See {@link tech.jhipster.config.JHipsterProperties} for a good example. - */ -@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) -public class ApplicationProperties { - private Foo foo; - // jhipster-needle-application-properties-property - - private Foo getFoo() { - return foo; - }; - // jhipster-needle-application-properties-property-getter - - public static Foo{} { - private String bar; - - public String getBar() { - return bar; - } - }; - // jhipster-needle-application-properties-property-class -} -", - "state": "modified", - "stateCleared": "modified", - }, -} -`); - }); - - it('should not be add the content at second call', () => { - const application = runResult.generator.sharedData.getApplication(); - insertContentIntoApplicationProperties.call(runResult.generator, application, { - property, - propertyGetter, - propertyClass, - }); - expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); - }); - - it('should not be add new content with prettier differences', () => { - const application = runResult.generator.sharedData.getApplication(); - insertContentIntoApplicationProperties.call(runResult.generator, application, { - property: ' private Foo foo;', - }); - expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); - }); - - it('should not be add new content with prettier differences and new lines', () => { - const application = runResult.generator.sharedData.getApplication(); - insertContentIntoApplicationProperties.call(runResult.generator, application, { - property: ` private Foo getFoo() { - - return foo; - - }; -`, - }); - expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); - }); - }); - }); - }); -}); diff --git a/generators/server/support/needles.spec.ts b/generators/server/support/needles.spec.ts new file mode 100644 index 000000000000..5e233c827873 --- /dev/null +++ b/generators/server/support/needles.spec.ts @@ -0,0 +1,154 @@ +/** + * Copyright 2013-2023 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 { expect } from 'esmocha'; + +import { defaultHelpers as helpers } from '../../../test/support/index.js'; +import { GENERATOR_SERVER } from '../../generator-list.js'; +import { insertContentIntoApplicationProperties } from './needles.js'; + +describe('generator - server - support - needles', () => { + describe('generated project', () => { + let runResult; + before(async () => { + runResult = await helpers.runJHipster(GENERATOR_SERVER).withMockedGenerators(['jhipster:common', 'jhipster:languages']); + }); + + it('should match state snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + describe('insertContentIntoApplicationProperties needle', () => { + it('with a non existing needle', () => { + const application = runResult.generator.sharedData.getApplication(); + expect(() => insertContentIntoApplicationProperties.call(runResult.generator, application, { foo: 'foo' })).toThrow( + /Missing required jhipster-needle application-properties-foo not found at/, + ); + }); + + it('without a needle', () => { + const application = runResult.generator.sharedData.getApplication(); + expect(() => insertContentIntoApplicationProperties.call(runResult.generator, application, {})).toThrow( + /At least 1 needle is required/, + ); + }); + + describe('when applied', () => { + const fileRegexp = /config\/ApplicationProperties.java/; + const property = 'private Foo foo;'; + const propertyGetter = ` + + private Foo getFoo() { + return foo; + };`; + const propertyClass = ` + + public static Foo{} { + private String bar; + + public String getBar() { + return bar; + } + };`; + let snapshot; + + before(() => { + const application = runResult.generator.sharedData.getApplication(); + insertContentIntoApplicationProperties.call(runResult.generator, application, { + property, + propertyGetter, + propertyClass, + }); + snapshot = runResult.getSnapshot(file => fileRegexp.test(file.path)); + }); + + it('should match snapshot', () => { + expect(snapshot).toMatchInlineSnapshot(` +{ + "src/main/java/com/mycompany/myapp/config/ApplicationProperties.java": { + "contents": "package com.mycompany.myapp.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties specific to JHipster. + *

    + * Properties are configured in the {@code application.yml} file. + * See {@link tech.jhipster.config.JHipsterProperties} for a good example. + */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties { + private Foo foo; + // jhipster-needle-application-properties-property + + private Foo getFoo() { + return foo; + }; + // jhipster-needle-application-properties-property-getter + + public static Foo{} { + private String bar; + + public String getBar() { + return bar; + } + }; + // jhipster-needle-application-properties-property-class +} +", + "state": "modified", + "stateCleared": "modified", + }, +} +`); + }); + + it('should not be add the content at second call', () => { + const application = runResult.generator.sharedData.getApplication(); + insertContentIntoApplicationProperties.call(runResult.generator, application, { + property, + propertyGetter, + propertyClass, + }); + expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); + }); + + it('should not be add new content with prettier differences', () => { + const application = runResult.generator.sharedData.getApplication(); + insertContentIntoApplicationProperties.call(runResult.generator, application, { + property: ' private Foo foo;', + }); + expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); + }); + + it('should not be add new content with prettier differences and new lines', () => { + const application = runResult.generator.sharedData.getApplication(); + insertContentIntoApplicationProperties.call(runResult.generator, application, { + property: ` private Foo getFoo() { + + return foo; + + }; +`, + }); + expect(runResult.getSnapshot(file => fileRegexp.test(file.path))).toEqual(snapshot); + }); + }); + }); + }); +}); diff --git a/generators/server/support/needles.ts b/generators/server/support/needles.ts new file mode 100644 index 000000000000..b2f63338913a --- /dev/null +++ b/generators/server/support/needles.ts @@ -0,0 +1,81 @@ +/** + * Copyright 2013-2023 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 CoreGenerator from '../../base-core/index.js'; +import { createBaseNeedle } from '../../base/support/needles.js'; +import { SpringBootApplication } from '../types.js'; + +type ApplicationPropertiesNeedles = { + property?: string; + propertyGetter?: string; + propertyClass?: string; +}; + +/** + * Insert content into ApplicationProperties class + * @example + * insertContentIntoApplicationProperties.call(generator, application, { + * property: 'private final bar = new Bar();', + * proppertyGetter: ` + * public getBar() { + * return bar; + * }`, + * propertyClass: ` + * public static class Async { + * private String foo = "default"; + * }`, + * }) + * @example + * generator.editFile( + * 'ApplicationProperties.java', + * insertContentIntoApplicationProperties({ + * property: 'private final bar = new Bar();', + * propertyGetter: ` + * public getBar() { + * return bar; + * }`, + * propertyClass: ` + * public static class Async { + * private String foo = "default"; + * }`, + * }); + * ); + */ +// eslint-disable-next-line import/prefer-default-export +export function insertContentIntoApplicationProperties( + this: CoreGenerator | void, + application: SpringBootApplication, + needles: ApplicationPropertiesNeedles, +) { + if (this) { + return createBaseNeedle.call( + this, + { + filePath: `${application.javaPackageSrcDir}config/ApplicationProperties.java`, + needlesPrefix: 'application-properties', + }, + needles, + ); + } + return createBaseNeedle( + { + needlesPrefix: 'application-properties', + }, + needles, + ); +} diff --git a/generators/server/support/prepare-entity.js b/generators/server/support/prepare-entity.js new file mode 100644 index 000000000000..e5145c9ca57a --- /dev/null +++ b/generators/server/support/prepare-entity.js @@ -0,0 +1,133 @@ +/** + * Copyright 2013-2023 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 path from 'path'; + +import { databaseTypes, searchEngineTypes } from '../../../jdl/jhipster/index.js'; + +import { isReservedTableName } from '../../../jdl/jhipster/reserved-keywords.js'; +import { mutateData, normalizePathEnd } from '../../base/support/index.js'; +import { hibernateSnakeCase } from './string.js'; +import { getDatabaseTypeData } from './database.js'; +import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.js'; + +const { ELASTICSEARCH } = searchEngineTypes; +const NO_SEARCH_ENGINE = searchEngineTypes.NO; + +const { POSTGRESQL, MYSQL, MARIADB, COUCHBASE, SQL } = databaseTypes; + +export default function prepareEntity(entity) { + const { entityPackage, packageName, packageFolder, persistClass } = entity; + let { entityAbsolutePackage = packageName, entityAbsoluteFolder = packageFolder, entityJavaPackageFolder } = entity; + if (entityPackage) { + entityJavaPackageFolder = `${entityPackage.replace(/\./g, '/')}/`; + entityAbsolutePackage = [packageName, entityPackage].join('.'); + entityAbsoluteFolder = path.join(packageFolder, entityJavaPackageFolder); + } + entityAbsoluteFolder = normalizePathEnd(entityAbsoluteFolder); + entity.entityJavaPackageFolder = entityJavaPackageFolder ?? ''; + entity.entityAbsolutePackage = entityAbsolutePackage; + entity.entityAbsoluteFolder = entityAbsoluteFolder; + entity.entityAbsoluteClass = `${entityAbsolutePackage}.domain.${persistClass}`; + + mutateData(entity, { + entityJavadoc: ({ documentation }) => (documentation ? formatDocAsJavaDoc(documentation) : documentation), + entityApiDescription: ({ documentation }) => (documentation ? formatDocAsApiDescription(documentation) : documentation), + }); + + if (isReservedTableName(entity.entityInstance, entity.prodDatabaseType ?? entity.databaseType) && entity.jhiPrefix) { + entity.entityInstanceDbSafe = `${entity.jhiPrefix}${entity.entityClass}`; + } else { + entity.entityInstanceDbSafe = entity.entityInstance; + } +} + +export function loadRequiredConfigDerivedProperties(entity) { + entity.jhiTablePrefix = hibernateSnakeCase(entity.jhiPrefix); + entity.searchEngineCouchbase = entity.searchEngine === COUCHBASE; + entity.searchEngineElasticsearch = entity.searchEngine === ELASTICSEARCH; + entity.searchEngineAny = entity.searchEngine && entity.searchEngine !== NO_SEARCH_ENGINE; + entity.searchEngineNo = !entity.searchEngineAny; +} + +export function preparePostEntityServerDerivedProperties(entity) { + const { databaseType, reactive } = entity; + entity.officialDatabaseType = getDatabaseTypeData(databaseType).name; + let springDataDatabase; + if (entity.databaseType !== SQL) { + springDataDatabase = entity.officialDatabaseType; + if (reactive) { + springDataDatabase += ' reactive'; + } + } else { + springDataDatabase = reactive ? 'R2DBC' : 'JPA'; + } + entity.springDataDescription = `Spring Data ${springDataDatabase}`; + + // Blueprints may disable cypress relationships by setting to false. + entity.cypressBootstrapEntities = true; + + // Reactive with some r2dbc databases doesn't allow insertion without data. + entity.workaroundEntityCannotBeEmpty = entity.reactive && [POSTGRESQL, MYSQL, MARIADB].includes(entity.prodDatabaseType); + // Reactive with MariaDB doesn't allow null value at Instant fields. + entity.workaroundInstantReactiveMariaDB = entity.reactive && entity.prodDatabaseType === MARIADB; + + entity.relationships + .filter(relationship => relationship.ignoreOtherSideProperty === undefined) + .forEach(relationship => { + relationship.ignoreOtherSideProperty = + entity.databaseType !== 'neo4j' && + !relationship.embedded && + !!relationship.otherEntity && + relationship.otherEntity.relationships.length > 0; + }); + entity.relationshipsContainOtherSideIgnore = entity.relationships.some(relationship => relationship.ignoreOtherSideProperty); + + entity.importApiModelProperty = + entity.relationships.some(relationship => relationship.documentation) || entity.fields.some(field => field.documentation); + + entity.uniqueEnums = {}; + + entity.fields.forEach(field => { + if ( + field.fieldIsEnum && + (!entity.uniqueEnums[field.fieldType] || (entity.uniqueEnums[field.fieldType] && field.fieldValues.length !== 0)) + ) { + entity.uniqueEnums[field.fieldType] = field.fieldType; + } + }); + if (entity.primaryKey && entity.primaryKey.derived) { + entity.isUsingMapsId = true; + entity.mapsIdAssoc = entity.relationships.find(rel => rel.id); + } else { + entity.isUsingMapsId = false; + entity.mapsIdAssoc = null; + } + entity.reactiveOtherEntities = new Set(entity.reactiveEagerRelations.map(rel => rel.otherEntity)); + entity.reactiveUniqueEntityTypes = new Set(entity.reactiveEagerRelations.map(rel => rel.otherEntityNameCapitalized)); + entity.reactiveUniqueEntityTypes.add(entity.entityClass); + if (entity.databaseType === 'sql') { + for (const relationship of entity.relationships) { + if (!relationship.otherEntity.embedded) { + relationship.joinColumnNames = relationship.otherEntity.primaryKey.fields.map( + otherField => `${relationship.columnNamePrefix}${otherField.columnName}`, + ); + } + } + } +} diff --git a/generators/server/support/prepare-entity.mjs b/generators/server/support/prepare-entity.mjs deleted file mode 100644 index b07cdec31d09..000000000000 --- a/generators/server/support/prepare-entity.mjs +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright 2013-2023 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 path from 'path'; - -import { databaseTypes, searchEngineTypes } from '../../../jdl/jhipster/index.mjs'; - -import { isReservedTableName } from '../../../jdl/jhipster/reserved-keywords.js'; -import { mutateData, normalizePathEnd } from '../../base/support/index.mjs'; -import { hibernateSnakeCase } from './string.mjs'; -import { getDatabaseTypeData } from './database.mjs'; -import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.mjs'; - -const { ELASTICSEARCH } = searchEngineTypes; -const NO_SEARCH_ENGINE = searchEngineTypes.NO; - -const { POSTGRESQL, MYSQL, MARIADB, COUCHBASE, SQL } = databaseTypes; - -export default function prepareEntity(entity) { - const { entityPackage, packageName, packageFolder, persistClass } = entity; - let { entityAbsolutePackage = packageName, entityAbsoluteFolder = packageFolder, entityJavaPackageFolder } = entity; - if (entityPackage) { - entityJavaPackageFolder = `${entityPackage.replace(/\./g, '/')}/`; - entityAbsolutePackage = [packageName, entityPackage].join('.'); - entityAbsoluteFolder = path.join(packageFolder, entityJavaPackageFolder); - } - entityAbsoluteFolder = normalizePathEnd(entityAbsoluteFolder); - entity.entityJavaPackageFolder = entityJavaPackageFolder ?? ''; - entity.entityAbsolutePackage = entityAbsolutePackage; - entity.entityAbsoluteFolder = entityAbsoluteFolder; - entity.entityAbsoluteClass = `${entityAbsolutePackage}.domain.${persistClass}`; - - mutateData(entity, { - entityJavadoc: ({ documentation }) => (documentation ? formatDocAsJavaDoc(documentation) : documentation), - entityApiDescription: ({ documentation }) => (documentation ? formatDocAsApiDescription(documentation) : documentation), - }); - - if (isReservedTableName(entity.entityInstance, entity.prodDatabaseType ?? entity.databaseType) && entity.jhiPrefix) { - entity.entityInstanceDbSafe = `${entity.jhiPrefix}${entity.entityClass}`; - } else { - entity.entityInstanceDbSafe = entity.entityInstance; - } -} - -export function loadRequiredConfigDerivedProperties(entity) { - entity.jhiTablePrefix = hibernateSnakeCase(entity.jhiPrefix); - entity.searchEngineCouchbase = entity.searchEngine === COUCHBASE; - entity.searchEngineElasticsearch = entity.searchEngine === ELASTICSEARCH; - entity.searchEngineAny = entity.searchEngine && entity.searchEngine !== NO_SEARCH_ENGINE; - entity.searchEngineNo = !entity.searchEngineAny; -} - -export function preparePostEntityServerDerivedProperties(entity) { - const { databaseType, reactive } = entity; - entity.officialDatabaseType = getDatabaseTypeData(databaseType).name; - let springDataDatabase; - if (entity.databaseType !== SQL) { - springDataDatabase = entity.officialDatabaseType; - if (reactive) { - springDataDatabase += ' reactive'; - } - } else { - springDataDatabase = reactive ? 'R2DBC' : 'JPA'; - } - entity.springDataDescription = `Spring Data ${springDataDatabase}`; - - // Blueprints may disable cypress relationships by setting to false. - entity.cypressBootstrapEntities = true; - - // Reactive with some r2dbc databases doesn't allow insertion without data. - entity.workaroundEntityCannotBeEmpty = entity.reactive && [POSTGRESQL, MYSQL, MARIADB].includes(entity.prodDatabaseType); - // Reactive with MariaDB doesn't allow null value at Instant fields. - entity.workaroundInstantReactiveMariaDB = entity.reactive && entity.prodDatabaseType === MARIADB; - - entity.relationships - .filter(relationship => relationship.ignoreOtherSideProperty === undefined) - .forEach(relationship => { - relationship.ignoreOtherSideProperty = - entity.databaseType !== 'neo4j' && - !relationship.embedded && - !!relationship.otherEntity && - relationship.otherEntity.relationships.length > 0; - }); - entity.relationshipsContainOtherSideIgnore = entity.relationships.some(relationship => relationship.ignoreOtherSideProperty); - - entity.importApiModelProperty = - entity.relationships.some(relationship => relationship.documentation) || entity.fields.some(field => field.documentation); - - entity.uniqueEnums = {}; - - entity.fields.forEach(field => { - if ( - field.fieldIsEnum && - (!entity.uniqueEnums[field.fieldType] || (entity.uniqueEnums[field.fieldType] && field.fieldValues.length !== 0)) - ) { - entity.uniqueEnums[field.fieldType] = field.fieldType; - } - }); - if (entity.primaryKey && entity.primaryKey.derived) { - entity.isUsingMapsId = true; - entity.mapsIdAssoc = entity.relationships.find(rel => rel.id); - } else { - entity.isUsingMapsId = false; - entity.mapsIdAssoc = null; - } - entity.reactiveOtherEntities = new Set(entity.reactiveEagerRelations.map(rel => rel.otherEntity)); - entity.reactiveUniqueEntityTypes = new Set(entity.reactiveEagerRelations.map(rel => rel.otherEntityNameCapitalized)); - entity.reactiveUniqueEntityTypes.add(entity.entityClass); - if (entity.databaseType === 'sql') { - for (const relationship of entity.relationships) { - if (!relationship.otherEntity.embedded) { - relationship.joinColumnNames = relationship.otherEntity.primaryKey.fields.map( - otherField => `${relationship.columnNamePrefix}${otherField.columnName}`, - ); - } - } - } -} diff --git a/generators/server/support/prepare-field.js b/generators/server/support/prepare-field.js new file mode 100644 index 000000000000..68293ca03636 --- /dev/null +++ b/generators/server/support/prepare-field.js @@ -0,0 +1,173 @@ +/** + * Copyright 2013-2023 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 assert from 'assert'; +import * as _ from 'lodash-es'; + +import { databaseTypes, entityOptions, fieldTypes, reservedKeywords } from '../../../jdl/jhipster/index.js'; +import { getUXConstraintName } from './database.js'; +import { hibernateSnakeCase } from './string.js'; +import { getJavaValueGeneratorForType } from './templates/field-values.js'; +import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.js'; + +const TYPE_BYTES = fieldTypes.RelationalOnlyDBTypes.BYTES; +const TYPE_BYTE_BUFFER = fieldTypes.RelationalOnlyDBTypes.BYTE_BUFFER; + +const { isReservedTableName } = reservedKeywords; +const { CommonDBTypes } = fieldTypes; +const { MYSQL, SQL } = databaseTypes; +const { MapperTypes } = entityOptions; + +const { MAPSTRUCT } = MapperTypes; +const { INTEGER, LONG, UUID } = CommonDBTypes; + +const { snakeCase, upperFirst } = _; + +export default function prepareField(entityWithConfig, field, generator) { + if (field.mapstructExpression) { + assert.equal( + entityWithConfig.dto, + MAPSTRUCT, + `@MapstructExpression requires an Entity with mapstruct dto [${entityWithConfig.name}.${field.fieldName}].`, + ); + // Remove from Entity.java and liquibase. + field.transient = true; + // Disable update form. + field.readonly = true; + } + + if (field.documentation) { + field.fieldJavadoc = formatDocAsJavaDoc(field.documentation, 4); + field.fieldApiDescription = formatDocAsApiDescription(field.documentation); + } + + if (field.id && entityWithConfig.primaryKey) { + if (field.autoGenerate === undefined) { + field.autoGenerate = !entityWithConfig.primaryKey.composite && [INTEGER, LONG, UUID].includes(field.fieldType); + } + + if (!field.autoGenerate) { + field.liquibaseAutoIncrement = false; + field.jpaGeneratedValue = false; + field.autoGenerateByService = false; + field.autoGenerateByRepository = false; + field.requiresPersistableImplementation = true; + } else if (entityWithConfig.databaseType !== SQL) { + field.liquibaseAutoIncrement = false; + field.jpaGeneratedValue = false; + field.autoGenerateByService = field.fieldType === UUID; + field.autoGenerateByRepository = !field.autoGenerateByService; + field.requiresPersistableImplementation = false; + field.readonly = true; + } else if (entityWithConfig.reactive) { + field.liquibaseAutoIncrement = field.fieldType === LONG; + field.jpaGeneratedValue = false; + field.autoGenerateByService = !field.liquibaseAutoIncrement; + field.autoGenerateByRepository = !field.autoGenerateByService; + field.requiresPersistableImplementation = !field.liquibaseAutoIncrement; + field.readonly = true; + } else { + const defaultGenerationType = entityWithConfig.prodDatabaseType === MYSQL ? 'identity' : 'sequence'; + field.jpaGeneratedValue = field.jpaGeneratedValue || [INTEGER, LONG].includes(field.fieldType) ? defaultGenerationType : true; + field.jpaGeneratedValueSequence = field.jpaGeneratedValue === 'sequence'; + field.jpaGeneratedValueIdentity = field.jpaGeneratedValue === 'identity'; + field.autoGenerateByService = false; + field.autoGenerateByRepository = true; + field.requiresPersistableImplementation = false; + field.readonly = true; + if (field.jpaGeneratedValueIdentity) { + field.liquibaseAutoIncrement = true; + } else if (field.jpaGeneratedValueSequence) { + field.jpaSequenceGeneratorName = field.sequenceGeneratorName ?? 'sequenceGenerator'; + field.liquibaseSequenceGeneratorName = snakeCase(field.jpaSequenceGeneratorName); + field.liquibaseCustomSequenceGenerator = field.liquibaseSequenceGeneratorName !== 'sequence_generator'; + } + } + } + + if (field.fieldNameAsDatabaseColumn === undefined) { + const fieldNameUnderscored = snakeCase(field.fieldName); + const jhiFieldNamePrefix = hibernateSnakeCase(entityWithConfig.jhiPrefix); + + if (isReservedTableName(fieldNameUnderscored, entityWithConfig.prodDatabaseType ?? entityWithConfig.databaseType)) { + if (!jhiFieldNamePrefix) { + generator.log.warn( + `The field name '${fieldNameUnderscored}' is regarded as a reserved keyword, but you have defined an empty jhiPrefix. This might lead to a non-working application.`, + ); + field.fieldNameAsDatabaseColumn = fieldNameUnderscored; + } else { + field.fieldNameAsDatabaseColumn = `${jhiFieldNamePrefix}_${fieldNameUnderscored}`; + } + } else { + field.fieldNameAsDatabaseColumn = fieldNameUnderscored; + } + } + + field.columnName = field.fieldNameAsDatabaseColumn; + if (field.unique) { + field.uniqueConstraintName = getUXConstraintName(entityWithConfig.entityTableName, field.columnName, { + prodDatabaseType: entityWithConfig.prodDatabaseType, + }).value; + } + + if (field.fieldInJavaBeanMethod === undefined) { + // Handle the specific case when the second letter is capitalized + // See http://stackoverflow.com/questions/2948083/naming-convention-for-getters-setters-in-java + if (field.fieldName.length > 1) { + const firstLetter = field.fieldName.charAt(0); + const secondLetter = field.fieldName.charAt(1); + if (firstLetter === firstLetter.toLowerCase() && secondLetter === secondLetter.toUpperCase()) { + field.fieldInJavaBeanMethod = firstLetter.toLowerCase() + field.fieldName.slice(1); + } else { + field.fieldInJavaBeanMethod = upperFirst(field.fieldName); + } + } else { + field.fieldInJavaBeanMethod = upperFirst(field.fieldName); + } + } + + if (field.fieldValidateRulesPatternJava === undefined) { + field.fieldValidateRulesPatternJava = field.fieldValidateRulesPattern + ? field.fieldValidateRulesPattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + : field.fieldValidateRulesPattern; + } + + if (field.blobContentTypeText) { + field.javaFieldType = 'String'; + } else { + field.javaFieldType = field.fieldType; + } + + if (field.fieldTypeInteger || field.fieldTypeLong || field.fieldTypeString || field.fieldTypeUUID) { + if (field.fieldTypeInteger) { + field.javaValueSample1 = '1'; + field.javaValueSample2 = '2'; + } else if (field.fieldTypeLong) { + field.javaValueSample1 = '1L'; + field.javaValueSample2 = '2L'; + } else if (field.fieldTypeString) { + field.javaValueSample1 = `"${field.fieldName}1"`; + field.javaValueSample2 = `"${field.fieldName}2"`; + } else if (field.fieldTypeUUID) { + field.javaValueSample1 = 'UUID.fromString("23d8dc04-a48b-45d9-a01d-4b728f0ad4aa")'; + field.javaValueSample2 = 'UUID.fromString("ad79f240-3727-46c3-b89f-2cf6ebd74367")'; + } + field.javaValueGenerator = getJavaValueGeneratorForType(field.javaFieldType); + } + field.filterableField = ![TYPE_BYTES, TYPE_BYTE_BUFFER].includes(field.fieldType); +} diff --git a/generators/server/support/prepare-field.mjs b/generators/server/support/prepare-field.mjs deleted file mode 100644 index 66322d66d7e9..000000000000 --- a/generators/server/support/prepare-field.mjs +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright 2013-2023 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 assert from 'assert'; -import * as _ from 'lodash-es'; - -import { databaseTypes, entityOptions, fieldTypes, reservedKeywords } from '../../../jdl/jhipster/index.mjs'; -import { getUXConstraintName } from './database.mjs'; -import { hibernateSnakeCase } from './string.mjs'; -import { getJavaValueGeneratorForType } from './templates/field-values.mjs'; -import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.mjs'; - -const TYPE_BYTES = fieldTypes.RelationalOnlyDBTypes.BYTES; -const TYPE_BYTE_BUFFER = fieldTypes.RelationalOnlyDBTypes.BYTE_BUFFER; - -const { isReservedTableName } = reservedKeywords; -const { CommonDBTypes } = fieldTypes; -const { MYSQL, SQL } = databaseTypes; -const { MapperTypes } = entityOptions; - -const { MAPSTRUCT } = MapperTypes; -const { INTEGER, LONG, UUID } = CommonDBTypes; - -const { snakeCase, upperFirst } = _; - -export default function prepareField(entityWithConfig, field, generator) { - if (field.mapstructExpression) { - assert.equal( - entityWithConfig.dto, - MAPSTRUCT, - `@MapstructExpression requires an Entity with mapstruct dto [${entityWithConfig.name}.${field.fieldName}].`, - ); - // Remove from Entity.java and liquibase. - field.transient = true; - // Disable update form. - field.readonly = true; - } - - if (field.documentation) { - field.fieldJavadoc = formatDocAsJavaDoc(field.documentation, 4); - field.fieldApiDescription = formatDocAsApiDescription(field.documentation); - } - - if (field.id && entityWithConfig.primaryKey) { - if (field.autoGenerate === undefined) { - field.autoGenerate = !entityWithConfig.primaryKey.composite && [INTEGER, LONG, UUID].includes(field.fieldType); - } - - if (!field.autoGenerate) { - field.liquibaseAutoIncrement = false; - field.jpaGeneratedValue = false; - field.autoGenerateByService = false; - field.autoGenerateByRepository = false; - field.requiresPersistableImplementation = true; - } else if (entityWithConfig.databaseType !== SQL) { - field.liquibaseAutoIncrement = false; - field.jpaGeneratedValue = false; - field.autoGenerateByService = field.fieldType === UUID; - field.autoGenerateByRepository = !field.autoGenerateByService; - field.requiresPersistableImplementation = false; - field.readonly = true; - } else if (entityWithConfig.reactive) { - field.liquibaseAutoIncrement = field.fieldType === LONG; - field.jpaGeneratedValue = false; - field.autoGenerateByService = !field.liquibaseAutoIncrement; - field.autoGenerateByRepository = !field.autoGenerateByService; - field.requiresPersistableImplementation = !field.liquibaseAutoIncrement; - field.readonly = true; - } else { - const defaultGenerationType = entityWithConfig.prodDatabaseType === MYSQL ? 'identity' : 'sequence'; - field.jpaGeneratedValue = field.jpaGeneratedValue || [INTEGER, LONG].includes(field.fieldType) ? defaultGenerationType : true; - field.jpaGeneratedValueSequence = field.jpaGeneratedValue === 'sequence'; - field.jpaGeneratedValueIdentity = field.jpaGeneratedValue === 'identity'; - field.autoGenerateByService = false; - field.autoGenerateByRepository = true; - field.requiresPersistableImplementation = false; - field.readonly = true; - if (field.jpaGeneratedValueIdentity) { - field.liquibaseAutoIncrement = true; - } else if (field.jpaGeneratedValueSequence) { - field.jpaSequenceGeneratorName = field.sequenceGeneratorName ?? 'sequenceGenerator'; - field.liquibaseSequenceGeneratorName = snakeCase(field.jpaSequenceGeneratorName); - field.liquibaseCustomSequenceGenerator = field.liquibaseSequenceGeneratorName !== 'sequence_generator'; - } - } - } - - if (field.fieldNameAsDatabaseColumn === undefined) { - const fieldNameUnderscored = snakeCase(field.fieldName); - const jhiFieldNamePrefix = hibernateSnakeCase(entityWithConfig.jhiPrefix); - - if (isReservedTableName(fieldNameUnderscored, entityWithConfig.prodDatabaseType ?? entityWithConfig.databaseType)) { - if (!jhiFieldNamePrefix) { - generator.log.warn( - `The field name '${fieldNameUnderscored}' is regarded as a reserved keyword, but you have defined an empty jhiPrefix. This might lead to a non-working application.`, - ); - field.fieldNameAsDatabaseColumn = fieldNameUnderscored; - } else { - field.fieldNameAsDatabaseColumn = `${jhiFieldNamePrefix}_${fieldNameUnderscored}`; - } - } else { - field.fieldNameAsDatabaseColumn = fieldNameUnderscored; - } - } - - field.columnName = field.fieldNameAsDatabaseColumn; - if (field.unique) { - field.uniqueConstraintName = getUXConstraintName(entityWithConfig.entityTableName, field.columnName, { - prodDatabaseType: entityWithConfig.prodDatabaseType, - }).value; - } - - if (field.fieldInJavaBeanMethod === undefined) { - // Handle the specific case when the second letter is capitalized - // See http://stackoverflow.com/questions/2948083/naming-convention-for-getters-setters-in-java - if (field.fieldName.length > 1) { - const firstLetter = field.fieldName.charAt(0); - const secondLetter = field.fieldName.charAt(1); - if (firstLetter === firstLetter.toLowerCase() && secondLetter === secondLetter.toUpperCase()) { - field.fieldInJavaBeanMethod = firstLetter.toLowerCase() + field.fieldName.slice(1); - } else { - field.fieldInJavaBeanMethod = upperFirst(field.fieldName); - } - } else { - field.fieldInJavaBeanMethod = upperFirst(field.fieldName); - } - } - - if (field.fieldValidateRulesPatternJava === undefined) { - field.fieldValidateRulesPatternJava = field.fieldValidateRulesPattern - ? field.fieldValidateRulesPattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"') - : field.fieldValidateRulesPattern; - } - - if (field.blobContentTypeText) { - field.javaFieldType = 'String'; - } else { - field.javaFieldType = field.fieldType; - } - - if (field.fieldTypeInteger || field.fieldTypeLong || field.fieldTypeString || field.fieldTypeUUID) { - if (field.fieldTypeInteger) { - field.javaValueSample1 = '1'; - field.javaValueSample2 = '2'; - } else if (field.fieldTypeLong) { - field.javaValueSample1 = '1L'; - field.javaValueSample2 = '2L'; - } else if (field.fieldTypeString) { - field.javaValueSample1 = `"${field.fieldName}1"`; - field.javaValueSample2 = `"${field.fieldName}2"`; - } else if (field.fieldTypeUUID) { - field.javaValueSample1 = 'UUID.fromString("23d8dc04-a48b-45d9-a01d-4b728f0ad4aa")'; - field.javaValueSample2 = 'UUID.fromString("ad79f240-3727-46c3-b89f-2cf6ebd74367")'; - } - field.javaValueGenerator = getJavaValueGeneratorForType(field.javaFieldType); - } - field.filterableField = ![TYPE_BYTES, TYPE_BYTE_BUFFER].includes(field.fieldType); -} diff --git a/generators/server/support/prepare-relationship.mts b/generators/server/support/prepare-relationship.mts deleted file mode 100644 index e1255bb3ae32..000000000000 --- a/generators/server/support/prepare-relationship.mts +++ /dev/null @@ -1,8 +0,0 @@ -import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.mjs'; - -export function prepareRelationship({ relationship }: { relationship: any; entity: any }) { - if (relationship.documentation) { - relationship.relationshipJavadoc = formatDocAsJavaDoc(relationship.documentation, 4); - relationship.relationshipApiDescription = formatDocAsApiDescription(relationship.documentation); - } -} diff --git a/generators/server/support/prepare-relationship.ts b/generators/server/support/prepare-relationship.ts new file mode 100644 index 000000000000..6d193894e40c --- /dev/null +++ b/generators/server/support/prepare-relationship.ts @@ -0,0 +1,8 @@ +import { formatDocAsApiDescription, formatDocAsJavaDoc } from './doc.js'; + +export function prepareRelationship({ relationship }: { relationship: any; entity: any }) { + if (relationship.documentation) { + relationship.relationshipJavadoc = formatDocAsJavaDoc(relationship.documentation, 4); + relationship.relationshipApiDescription = formatDocAsApiDescription(relationship.documentation); + } +} diff --git a/generators/server/support/relationship.mts b/generators/server/support/relationship.mts deleted file mode 100644 index 8613a00f637e..000000000000 --- a/generators/server/support/relationship.mts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2013-2023 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 { Entity } from '../../../jdl/converters/types.js'; -import { addOtherRelationship } from '../../base-application/support/index.mjs'; -import { ValidationResult } from '../../base/api.mjs'; - -// eslint-disable-next-line import/prefer-default-export -export const addEntitiesOtherRelationships = (entities: Entity[]): ValidationResult => { - const result: { warning: string[] } = { warning: [] }; - for (const entity of entities) { - for (const relationship of entity.relationships ?? []) { - if ( - !relationship.otherRelationship && - (relationship.otherEntityRelationshipName || - relationship.relationshipType === 'many-to-many' || - // OneToOne back reference is required due to filtering - (relationship.relationshipType === 'one-to-one' && entity.databaseType === 'sql') || - (relationship.relationshipType === 'one-to-many' && entity.databaseType !== 'neo4j' && entity.databaseType !== 'no')) - ) { - if (relationship.otherEntity.builtIn) { - result.warning.push( - `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); - } - } - } - } - return result; -}; diff --git a/generators/server/support/relationship.ts b/generators/server/support/relationship.ts new file mode 100644 index 000000000000..937acc2f43a4 --- /dev/null +++ b/generators/server/support/relationship.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2013-2023 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 { Entity } from '../../../jdl/converters/types.js'; +import { addOtherRelationship } from '../../base-application/support/index.js'; +import { ValidationResult } from '../../base/api.js'; + +// eslint-disable-next-line import/prefer-default-export +export const addEntitiesOtherRelationships = (entities: Entity[]): ValidationResult => { + const result: { warning: string[] } = { warning: [] }; + for (const entity of entities) { + for (const relationship of entity.relationships ?? []) { + if ( + !relationship.otherRelationship && + (relationship.otherEntityRelationshipName || + relationship.relationshipType === 'many-to-many' || + // OneToOne back reference is required due to filtering + (relationship.relationshipType === 'one-to-one' && entity.databaseType === 'sql') || + (relationship.relationshipType === 'one-to-many' && entity.databaseType !== 'neo4j' && entity.databaseType !== 'no')) + ) { + if (relationship.otherEntity.builtIn) { + result.warning.push( + `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); + } + } + } + } + return result; +}; diff --git a/generators/server/support/spring-factories.spec.mts b/generators/server/support/spring-factories.spec.mts deleted file mode 100644 index 783e1e6ebee6..000000000000 --- a/generators/server/support/spring-factories.spec.mts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect } from 'esmocha'; - -import { addSpringFactory } from './spring-factories.mjs'; - -describe('generator - server - support - spring-factories', () => { - describe('addSpringFactory', () => { - it('should add the first property', () => { - expect(addSpringFactory({ key: 'key.prop', value: 'value' })(null)).toBe('key.prop = value'); - }); - it('should add wrap long value', () => { - expect(addSpringFactory({ key: 'key.prop', value: '12345678901234567890123456789012345678901234567890' })(null)).toBe( - 'key.prop = 12345678901234567890123456789012345678901234567890', - ); - }); - it('should add a new value to a property', () => { - expect( - addSpringFactory({ key: 'key.prop', value: 'new.value' })(`key.prop=\\ -value`), - ).toBe('key.prop = value,new.value'); - }); - it('should add a new property', () => { - expect( - addSpringFactory({ key: 'key.prop2', value: 'new.value' })(`key.prop = value -`), - ).toBe(`key.prop = value -key.prop2 = new.value`); - }); - }); -}); diff --git a/generators/server/support/spring-factories.spec.ts b/generators/server/support/spring-factories.spec.ts new file mode 100644 index 000000000000..d5eb82d4cdf0 --- /dev/null +++ b/generators/server/support/spring-factories.spec.ts @@ -0,0 +1,29 @@ +import { expect } from 'esmocha'; + +import { addSpringFactory } from './spring-factories.js'; + +describe('generator - server - support - spring-factories', () => { + describe('addSpringFactory', () => { + it('should add the first property', () => { + expect(addSpringFactory({ key: 'key.prop', value: 'value' })(null)).toBe('key.prop = value'); + }); + it('should add wrap long value', () => { + expect(addSpringFactory({ key: 'key.prop', value: '12345678901234567890123456789012345678901234567890' })(null)).toBe( + 'key.prop = 12345678901234567890123456789012345678901234567890', + ); + }); + it('should add a new value to a property', () => { + expect( + addSpringFactory({ key: 'key.prop', value: 'new.value' })(`key.prop=\\ +value`), + ).toBe('key.prop = value,new.value'); + }); + it('should add a new property', () => { + expect( + addSpringFactory({ key: 'key.prop2', value: 'new.value' })(`key.prop = value +`), + ).toBe(`key.prop = value +key.prop2 = new.value`); + }); + }); +}); diff --git a/generators/server/support/spring-factories.mts b/generators/server/support/spring-factories.ts similarity index 100% rename from generators/server/support/spring-factories.mts rename to generators/server/support/spring-factories.ts diff --git a/generators/server/support/string.spec.mts b/generators/server/support/string.spec.mts deleted file mode 100644 index 9284277c7ba6..000000000000 --- a/generators/server/support/string.spec.mts +++ /dev/null @@ -1,12 +0,0 @@ -import { expect } from 'esmocha'; -import { hibernateSnakeCase } from './string.mjs'; - -describe('generator - server - support - string', () => { - describe('hibernateSnakeCase', () => { - describe('when called with a value', () => { - it('returns a table name', () => { - expect(hibernateSnakeCase('tableName')).toEqual('table_name'); - }); - }); - }); -}); diff --git a/generators/server/support/string.spec.ts b/generators/server/support/string.spec.ts new file mode 100644 index 000000000000..88a39035de62 --- /dev/null +++ b/generators/server/support/string.spec.ts @@ -0,0 +1,12 @@ +import { expect } from 'esmocha'; +import { hibernateSnakeCase } from './string.js'; + +describe('generator - server - support - string', () => { + describe('hibernateSnakeCase', () => { + describe('when called with a value', () => { + it('returns a table name', () => { + expect(hibernateSnakeCase('tableName')).toEqual('table_name'); + }); + }); + }); +}); diff --git a/generators/server/support/string.mts b/generators/server/support/string.ts similarity index 100% rename from generators/server/support/string.mts rename to generators/server/support/string.ts diff --git a/generators/server/support/templates/field-values.js b/generators/server/support/templates/field-values.js new file mode 100644 index 000000000000..972d82d36dcf --- /dev/null +++ b/generators/server/support/templates/field-values.js @@ -0,0 +1,71 @@ +/** + * Copyright 2013-2023 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 { fieldTypes, databaseTypes } from '../../../../jdl/jhipster/index.js'; + +const dbTypes = fieldTypes; +const { STRING, UUID, LONG, INTEGER } = dbTypes.CommonDBTypes; +const { SQL } = databaseTypes; + +/** + * @private + */ +// eslint-disable-next-line import/prefer-default-export +export const getJavaValueGeneratorForType = (type) => { + if (type === STRING) { + return 'UUID.randomUUID().toString()'; + } + if (type === UUID) { + return 'UUID.randomUUID()'; + } + if (type === INTEGER) { + return 'intCount.incrementAndGet()'; + } + if (type === LONG) { + return 'longCount.incrementAndGet()'; + } + throw new Error(`Java type ${type} does not have a random generator implemented`); +} + +/** + * @private + * Returns the primary key value based on the primary key type, DB and default value + * + * @param {string} primaryKey - the primary key type + * @param {string} databaseType - the database type + * @param {string} defaultValue - default value + * @returns {string} java primary key value + */ +export const getPrimaryKeyValue = (primaryKey, databaseType, defaultValue = 1) => { + if (typeof primaryKey === 'object' && primaryKey.composite) { + return `new ${primaryKey.type}(${primaryKey.references + .map(ref => getPrimaryKeyValue(ref.type, databaseType, defaultValue)) + .join(', ')})`; + } + const primaryKeyType = typeof primaryKey === 'string' ? primaryKey : primaryKey.type; + if (primaryKeyType === STRING) { + if (databaseType === SQL && defaultValue === 0) { + return getJavaValueGeneratorForType(primaryKeyType); + } + return `"id${defaultValue}"`; + } + if (primaryKeyType === UUID) { + return getJavaValueGeneratorForType(primaryKeyType); + } + return `${defaultValue}L`; +} diff --git a/generators/server/support/templates/field-values.mjs b/generators/server/support/templates/field-values.mjs deleted file mode 100644 index 70d0366d09d2..000000000000 --- a/generators/server/support/templates/field-values.mjs +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2013-2023 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 { fieldTypes, databaseTypes } from '../../../../jdl/jhipster/index.mjs'; - -const dbTypes = fieldTypes; -const { STRING, UUID, LONG, INTEGER } = dbTypes.CommonDBTypes; -const { SQL } = databaseTypes; - -/** - * @private - */ -// eslint-disable-next-line import/prefer-default-export -export const getJavaValueGeneratorForType = (type) => { - if (type === STRING) { - return 'UUID.randomUUID().toString()'; - } - if (type === UUID) { - return 'UUID.randomUUID()'; - } - if (type === INTEGER) { - return 'intCount.incrementAndGet()'; - } - if (type === LONG) { - return 'longCount.incrementAndGet()'; - } - throw new Error(`Java type ${type} does not have a random generator implemented`); -} - -/** - * @private - * Returns the primary key value based on the primary key type, DB and default value - * - * @param {string} primaryKey - the primary key type - * @param {string} databaseType - the database type - * @param {string} defaultValue - default value - * @returns {string} java primary key value - */ -export const getPrimaryKeyValue = (primaryKey, databaseType, defaultValue = 1) => { - if (typeof primaryKey === 'object' && primaryKey.composite) { - return `new ${primaryKey.type}(${primaryKey.references - .map(ref => getPrimaryKeyValue(ref.type, databaseType, defaultValue)) - .join(', ')})`; - } - const primaryKeyType = typeof primaryKey === 'string' ? primaryKey : primaryKey.type; - if (primaryKeyType === STRING) { - if (databaseType === SQL && defaultValue === 0) { - return getJavaValueGeneratorForType(primaryKeyType); - } - return `"id${defaultValue}"`; - } - if (primaryKeyType === UUID) { - return getJavaValueGeneratorForType(primaryKeyType); - } - return `${defaultValue}L`; -} diff --git a/generators/server/support/update-languages.mts b/generators/server/support/update-languages.mts deleted file mode 100644 index cf4979ac864e..000000000000 --- a/generators/server/support/update-languages.mts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../../base/index.mjs'; -import { type SpringBootApplication } from '../types.mjs'; - -type UpdateServerLanguagesTaskParam = { application: SpringBootApplication & { enableTranslation: true }; control: any }; - -/** - * Update Languages In MailServiceIT - * - * @param application - */ -export function updateLanguagesInMailServiceITTask(this: BaseGenerator, { application, control }: UpdateServerLanguagesTaskParam) { - const { javaPackageTestDir, languagesDefinition } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'private static final String[] languages = {\n'; - languagesDefinition?.forEach((language, i) => { - newContent += ` "${language.languageTag}"${i !== languagesDefinition.length - 1 ? ',' : ''}\n`; - }); - newContent += ' // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array\n };'; - - this.editFile(`${javaPackageTestDir}/service/MailServiceIT.java`, { ignoreNonExisting }, content => - content.replace(/private.*static.*String.*languages.*\{([^}]*jhipster-needle-i18n-language-constant[^}]*)\};/g, newContent), - ); -} - -export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateServerLanguagesTaskParam) { - updateLanguagesInMailServiceITTask.call(this, taskParam); -} diff --git a/generators/server/support/update-languages.ts b/generators/server/support/update-languages.ts new file mode 100644 index 000000000000..bd4206bd87f7 --- /dev/null +++ b/generators/server/support/update-languages.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../../base/index.js'; +import { type SpringBootApplication } from '../types.js'; + +type UpdateServerLanguagesTaskParam = { application: SpringBootApplication & { enableTranslation: true }; control: any }; + +/** + * Update Languages In MailServiceIT + * + * @param application + */ +export function updateLanguagesInMailServiceITTask(this: BaseGenerator, { application, control }: UpdateServerLanguagesTaskParam) { + const { javaPackageTestDir, languagesDefinition } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + let newContent = 'private static final String[] languages = {\n'; + languagesDefinition?.forEach((language, i) => { + newContent += ` "${language.languageTag}"${i !== languagesDefinition.length - 1 ? ',' : ''}\n`; + }); + newContent += ' // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array\n };'; + + this.editFile(`${javaPackageTestDir}/service/MailServiceIT.java`, { ignoreNonExisting }, content => + content.replace(/private.*static.*String.*languages.*\{([^}]*jhipster-needle-i18n-language-constant[^}]*)\};/g, newContent), + ); +} + +export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateServerLanguagesTaskParam) { + updateLanguagesInMailServiceITTask.call(this, taskParam); +} diff --git a/generators/server/types.d.mts b/generators/server/types.d.mts deleted file mode 100644 index 16faa496f5f3..000000000000 --- a/generators/server/types.d.mts +++ /dev/null @@ -1,89 +0,0 @@ -import { JavaApplication } from '../java/types.mjs'; -import { GradleSourceType } from '../gradle/types.mjs'; -import { MavenSourceType } from '../maven/types.mjs'; -import { LiquibaseSourceType } from '../liquibase/types.mjs'; -import { SpringCacheSourceType } from '../spring-cache/types.mjs'; -import { MessageBrokerApplicationType } from './options/message-broker.mjs'; -import type { DeterministicOptionWithDerivedProperties, OptionWithDerivedProperties } from '../base-application/application-options.mjs'; - -export type SpringBootSourceType = GradleSourceType & - MavenSourceType & - SpringCacheSourceType & - LiquibaseSourceType & { - addTestSpringFactory?({ key, value }: { key: string; value: string }): void; - addLogbackMainLog?({ name, level }: { name: string; level: string }): void; - addLogbackTestLog?({ name, level }: { name: string; level: string }): void; - addIntegrationTestAnnotation?({ package, annotation }: { package?: string; annotation: string }): void; - }; - -type CacheProviderApplication = OptionWithDerivedProperties< - 'cacheProvider', - ['no', 'caffeine', 'ehcache', 'hazelcast', 'infinispan', 'memcached', 'redis'] ->; - -type ImperativeApplication = { - reactive: false; -}; - -type ReactiveApplication = { - reactive: true; -}; - -export type LiquibaseApplication = { - incrementalChangelog: boolean; - liquibaseDefaultSchemaName: string; -}; - -type DatabaseTypeSqlApplication = ( - | ReactiveApplication - | (ImperativeApplication & { - enableHibernateCache: boolean; - }) -) & { - devDatabaseType: string; - prodDatabaseType: string; - devDatabaseTypeMysql: boolean; -} & LiquibaseApplication; - -type DatabaseTypeApplication = DeterministicOptionWithDerivedProperties< - 'databaseType', - ['sql', 'no', 'cassandra', 'couchbase', 'mongodb', 'neo4j'], - [DatabaseTypeSqlApplication] ->; - -type BuildToolApplication = DeterministicOptionWithDerivedProperties< - 'buildTool', - ['maven', 'gradle'], - [ - Record, - { - enableGradleEnterprise: boolean; - }, - ] ->; - -type SearchEngine = { - searchEngine: string; -}; - -type ApplicationNature = (ImperativeApplication & CacheProviderApplication) | ReactiveApplication; - -export type SpringBootApplication = JavaApplication & - ApplicationNature & - BuildToolApplication & - SearchEngine & - DatabaseTypeApplication & - MessageBrokerApplicationType & { - javaVersion: string; - dockerContainers: Record; - - enableSwaggerCodegen: boolean; - embeddableLaunchScript: boolean; - skipFakeData: boolean; - skipCheckLengthOfIdentifier: boolean; - - imperativeOrReactive: string; - - databaseMigration: string; - databaseMigrationLiquibase: boolean; - }; diff --git a/generators/server/types.d.ts b/generators/server/types.d.ts new file mode 100644 index 000000000000..c0575f9b0c0e --- /dev/null +++ b/generators/server/types.d.ts @@ -0,0 +1,89 @@ +import { JavaApplication } from '../java/types.js'; +import { GradleSourceType } from '../gradle/types.js'; +import { MavenSourceType } from '../maven/types.js'; +import { LiquibaseSourceType } from '../liquibase/types.js'; +import { SpringCacheSourceType } from '../spring-cache/types.js'; +import { MessageBrokerApplicationType } from './options/message-broker.js'; +import type { DeterministicOptionWithDerivedProperties, OptionWithDerivedProperties } from '../base-application/application-options.js'; + +export type SpringBootSourceType = GradleSourceType & + MavenSourceType & + SpringCacheSourceType & + LiquibaseSourceType & { + addTestSpringFactory?({ key, value }: { key: string; value: string }): void; + addLogbackMainLog?({ name, level }: { name: string; level: string }): void; + addLogbackTestLog?({ name, level }: { name: string; level: string }): void; + addIntegrationTestAnnotation?({ package, annotation }: { package?: string; annotation: string }): void; + }; + +type CacheProviderApplication = OptionWithDerivedProperties< + 'cacheProvider', + ['no', 'caffeine', 'ehcache', 'hazelcast', 'infinispan', 'memcached', 'redis'] +>; + +type ImperativeApplication = { + reactive: false; +}; + +type ReactiveApplication = { + reactive: true; +}; + +export type LiquibaseApplication = { + incrementalChangelog: boolean; + liquibaseDefaultSchemaName: string; +}; + +type DatabaseTypeSqlApplication = ( + | ReactiveApplication + | (ImperativeApplication & { + enableHibernateCache: boolean; + }) +) & { + devDatabaseType: string; + prodDatabaseType: string; + devDatabaseTypeMysql: boolean; +} & LiquibaseApplication; + +type DatabaseTypeApplication = DeterministicOptionWithDerivedProperties< + 'databaseType', + ['sql', 'no', 'cassandra', 'couchbase', 'mongodb', 'neo4j'], + [DatabaseTypeSqlApplication] +>; + +type BuildToolApplication = DeterministicOptionWithDerivedProperties< + 'buildTool', + ['maven', 'gradle'], + [ + Record, + { + enableGradleEnterprise: boolean; + }, + ] +>; + +type SearchEngine = { + searchEngine: string; +}; + +type ApplicationNature = (ImperativeApplication & CacheProviderApplication) | ReactiveApplication; + +export type SpringBootApplication = JavaApplication & + ApplicationNature & + BuildToolApplication & + SearchEngine & + DatabaseTypeApplication & + MessageBrokerApplicationType & { + javaVersion: string; + dockerContainers: Record; + + enableSwaggerCodegen: boolean; + embeddableLaunchScript: boolean; + skipFakeData: boolean; + skipCheckLengthOfIdentifier: boolean; + + imperativeOrReactive: string; + + databaseMigration: string; + databaseMigrationLiquibase: boolean; + }; diff --git a/generators/spring-cache/__snapshots__/generator.spec.mts.snap b/generators/spring-cache/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-cache/__snapshots__/generator.spec.mts.snap rename to generators/spring-cache/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-cache/cleanup.mts b/generators/spring-cache/cleanup.mts deleted file mode 100644 index 1af334e25309..000000000000 --- a/generators/spring-cache/cleanup.mts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupTask(this: Generator, { application }: any) { - if (application.cacheProviderHazelcast) { - if (this.isJhipsterVersionLessThan('3.12.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/hazelcast/HazelcastCacheRegionFactory.java`); - this.removeFile(`${application.javaPackageSrcDir}config/hazelcast/package-info.java`); - } - } - if (application.cacheProviderRedis) { - if (this.isJhipsterVersionLessThan('7.8.2')) { - this.removeFile(`${application.javaPackageTestDir}RedisTestContainerExtension.java`); - } - } -} diff --git a/generators/spring-cache/cleanup.ts b/generators/spring-cache/cleanup.ts new file mode 100644 index 000000000000..2f1e8add269d --- /dev/null +++ b/generators/spring-cache/cleanup.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupTask(this: Generator, { application }: any) { + if (application.cacheProviderHazelcast) { + if (this.isJhipsterVersionLessThan('3.12.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/hazelcast/HazelcastCacheRegionFactory.java`); + this.removeFile(`${application.javaPackageSrcDir}config/hazelcast/package-info.java`); + } + } + if (application.cacheProviderRedis) { + if (this.isJhipsterVersionLessThan('7.8.2')) { + this.removeFile(`${application.javaPackageTestDir}RedisTestContainerExtension.java`); + } + } +} diff --git a/generators/spring-cache/files.mts b/generators/spring-cache/files.mts deleted file mode 100644 index 55465be7600d..000000000000 --- a/generators/spring-cache/files.mts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../java/support/index.mjs'; -import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { WriteFileSection } from '../base/api.mjs'; - -const files: WriteFileSection = { - cacheFiles: [ - { - condition: data => data.buildToolGradle, - templates: ['gradle/cache.gradle'], - }, - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/CacheConfiguration.java'], - }, - { - condition: data => data.cacheProviderInfinispan, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/CacheFactoryConfiguration.java'], - }, - { - condition: data => data.cacheProviderRedis, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'config/EmbeddedRedis.java', - 'config/RedisTestContainer.java', - 'config/RedisTestContainersSpringContextCustomizerFactory.java', - ], - }, - ], -}; - -export default async function writeTask(this: Generator, { application }) { - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/spring-cache/files.ts b/generators/spring-cache/files.ts new file mode 100644 index 000000000000..8c971a99992f --- /dev/null +++ b/generators/spring-cache/files.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../java/support/index.js'; +import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { WriteFileSection } from '../base/api.js'; + +const files: WriteFileSection = { + cacheFiles: [ + { + condition: data => data.buildToolGradle, + templates: ['gradle/cache.gradle'], + }, + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/CacheConfiguration.java'], + }, + { + condition: data => data.cacheProviderInfinispan, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/CacheFactoryConfiguration.java'], + }, + { + condition: data => data.cacheProviderRedis, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'config/EmbeddedRedis.java', + 'config/RedisTestContainer.java', + 'config/RedisTestContainersSpringContextCustomizerFactory.java', + ], + }, + ], +}; + +export default async function writeTask(this: Generator, { application }) { + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/spring-cache/generator.mts b/generators/spring-cache/generator.mts deleted file mode 100644 index bfe8a0b28790..000000000000 --- a/generators/spring-cache/generator.mts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_CACHE, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; -import { createNeedleCallback } from '../base/support/needles.mjs'; -import { getCacheProviderMavenDefinition } from './internal/dependencies.mjs'; - -export default class SpringCacheGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_CACHE); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get preparing() { - return this.asPreparingTaskGroup({ - addNeedles({ source, application }) { - if ( - (application as any).cacheProviderEhcache || - (application as any).cacheProviderCaffeine || - (application as any).cacheProviderRedis - ) { - const cacheConfigurationFile = `${application.javaPackageSrcDir}config/CacheConfiguration.java`; - const needle = `${(application as any).cacheProvider}-add-entry`; - const useJcacheConfiguration = (application as any).cacheProviderRedis; - const addEntryToCacheCallback = entry => - createNeedleCallback({ - needle, - contentToAdd: `createCache(cm, ${entry}${useJcacheConfiguration ? ', jcacheConfiguration' : ''});`, - }); - - source.addEntryToCache = ({ entry }) => this.editFile(cacheConfigurationFile, addEntryToCacheCallback(entry)); - source.addEntityToCache = ({ entityAbsoluteClass, relationships }) => { - const entry = `${entityAbsoluteClass}.class.getName()`; - this.editFile( - cacheConfigurationFile, - addEntryToCacheCallback(entry), - ...(relationships ?? []) - .filter(rel => rel.collection) - .map(rel => addEntryToCacheCallback(`${entry} + ".${rel.propertyName}"`)), - ); - }; - } else { - // Add noop - source.addEntryToCache = () => {}; - // Add noop - source.addEntityToCache = () => {}; - } - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addTestSpringFactory({ source, application }) { - if ((application as any).cacheProviderRedis) { - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.RedisTestContainersSpringContextCustomizerFactory`, - }); - } - }, - applyGradleScript({ source, application }) { - if (application.buildToolGradle) { - source.applyFromGradle?.({ script: 'gradle/cache.gradle' }); - } - }, - addDependencies({ application, source }) { - if (application.buildToolMaven) { - if (!application.javaDependencies) { - throw new Error('Some application fields are be mandatory'); - } - const applicationAny = application as any; - - source.addMavenDependency?.({ - groupId: 'org.springframework.boot', - artifactId: 'spring-boot-starter-cache', - }); - - const definition = getCacheProviderMavenDefinition(applicationAny.cacheProvider, application.javaDependencies); - source.addMavenDefinition?.(definition.base); - if (applicationAny.enableHibernateCache && definition.hibernateCache) { - source.addMavenDefinition?.(definition.hibernateCache); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - customizeFiles({ application, entities, source }) { - if (application.databaseTypeSql) { - for (const entity of entities.filter(entity => !entity.skipServer && !entity.builtIn)) { - source.addEntityToCache?.({ - entityAbsoluteClass: (entity as any).entityAbsoluteClass, - relationships: entity.relationships, - }); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.asPostWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.postWritingEntities)); - } -} diff --git a/generators/spring-cache/generator.spec.mts b/generators/spring-cache/generator.spec.mts deleted file mode 100644 index 82235aad6051..000000000000 --- a/generators/spring-cache/generator.spec.mts +++ /dev/null @@ -1,50 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { fromMatrix, defaultHelpers as helpers, result } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -import { GENERATOR_SPRING_CACHE } from '../generator-list.mjs'; -import { cacheTypes, buildToolTypes } from '../../jdl/jhipster/index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -const { EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; -const { MAVEN, GRADLE } = buildToolTypes; - -const samples = fromMatrix({ - cacheProvider: [EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], - buildTool: [MAVEN, GRADLE], -}); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - Object.entries(samples).forEach(([name, sample]) => { - describe(name, () => { - before(async () => { - await helpers.runJHipster(GENERATOR_SPRING_CACHE).withJHipsterConfig(sample).withMockedSource(); - }); - - it('should match files snapshot', () => { - expect(result.getStateSnapshot()).toMatchSnapshot(); - }); - - it('should match source calls', () => { - expect(result.sourceCallsArg).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/spring-cache/generator.spec.ts b/generators/spring-cache/generator.spec.ts new file mode 100644 index 000000000000..6dfa4f73293c --- /dev/null +++ b/generators/spring-cache/generator.spec.ts @@ -0,0 +1,50 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { fromMatrix, defaultHelpers as helpers, result } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +import { GENERATOR_SPRING_CACHE } from '../generator-list.js'; +import { cacheTypes, buildToolTypes } from '../../jdl/jhipster/index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +const { EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; +const { MAVEN, GRADLE } = buildToolTypes; + +const samples = fromMatrix({ + cacheProvider: [EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], + buildTool: [MAVEN, GRADLE], +}); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + Object.entries(samples).forEach(([name, sample]) => { + describe(name, () => { + before(async () => { + await helpers.runJHipster(GENERATOR_SPRING_CACHE).withJHipsterConfig(sample).withMockedSource(); + }); + + it('should match files snapshot', () => { + expect(result.getStateSnapshot()).toMatchSnapshot(); + }); + + it('should match source calls', () => { + expect(result.sourceCallsArg).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/spring-cache/generator.ts b/generators/spring-cache/generator.ts new file mode 100644 index 000000000000..465f01438945 --- /dev/null +++ b/generators/spring-cache/generator.ts @@ -0,0 +1,149 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_CACHE, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; +import { createNeedleCallback } from '../base/support/needles.js'; +import { getCacheProviderMavenDefinition } from './internal/dependencies.js'; + +export default class SpringCacheGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_CACHE); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get preparing() { + return this.asPreparingTaskGroup({ + addNeedles({ source, application }) { + if ( + (application as any).cacheProviderEhcache || + (application as any).cacheProviderCaffeine || + (application as any).cacheProviderRedis + ) { + const cacheConfigurationFile = `${application.javaPackageSrcDir}config/CacheConfiguration.java`; + const needle = `${(application as any).cacheProvider}-add-entry`; + const useJcacheConfiguration = (application as any).cacheProviderRedis; + const addEntryToCacheCallback = entry => + createNeedleCallback({ + needle, + contentToAdd: `createCache(cm, ${entry}${useJcacheConfiguration ? ', jcacheConfiguration' : ''});`, + }); + + source.addEntryToCache = ({ entry }) => this.editFile(cacheConfigurationFile, addEntryToCacheCallback(entry)); + source.addEntityToCache = ({ entityAbsoluteClass, relationships }) => { + const entry = `${entityAbsoluteClass}.class.getName()`; + this.editFile( + cacheConfigurationFile, + addEntryToCacheCallback(entry), + ...(relationships ?? []) + .filter(rel => rel.collection) + .map(rel => addEntryToCacheCallback(`${entry} + ".${rel.propertyName}"`)), + ); + }; + } else { + // Add noop + source.addEntryToCache = () => {}; + // Add noop + source.addEntityToCache = () => {}; + } + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addTestSpringFactory({ source, application }) { + if ((application as any).cacheProviderRedis) { + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.RedisTestContainersSpringContextCustomizerFactory`, + }); + } + }, + applyGradleScript({ source, application }) { + if (application.buildToolGradle) { + source.applyFromGradle?.({ script: 'gradle/cache.gradle' }); + } + }, + addDependencies({ application, source }) { + if (application.buildToolMaven) { + if (!application.javaDependencies) { + throw new Error('Some application fields are be mandatory'); + } + const applicationAny = application as any; + + source.addMavenDependency?.({ + groupId: 'org.springframework.boot', + artifactId: 'spring-boot-starter-cache', + }); + + const definition = getCacheProviderMavenDefinition(applicationAny.cacheProvider, application.javaDependencies); + source.addMavenDefinition?.(definition.base); + if (applicationAny.enableHibernateCache && definition.hibernateCache) { + source.addMavenDefinition?.(definition.hibernateCache); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + customizeFiles({ application, entities, source }) { + if (application.databaseTypeSql) { + for (const entity of entities.filter(entity => !entity.skipServer && !entity.builtIn)) { + source.addEntityToCache?.({ + entityAbsoluteClass: (entity as any).entityAbsoluteClass, + relationships: entity.relationships, + }); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.asPostWritingEntitiesTaskGroup(this.delegateTasksToBlueprint(() => this.postWritingEntities)); + } +} diff --git a/generators/spring-cache/index.mts b/generators/spring-cache/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-cache/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-cache/index.ts b/generators/spring-cache/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-cache/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-cache/internal/dependencies.mts b/generators/spring-cache/internal/dependencies.mts deleted file mode 100644 index 4604291152f8..000000000000 --- a/generators/spring-cache/internal/dependencies.mts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright 2013-2023 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 { MavenDefinition } from '../../maven/types.mjs'; - -const javaxCacheApi = { - groupId: 'javax.cache', - artifactId: 'cache-api', -}; -const hibernateJCache = { - groupId: 'org.hibernate.orm', - artifactId: 'hibernate-jcache', - // TODO drop forced version. Refer to https://github.com/jhipster/generator-jhipster/issues/22579 - // eslint-disable-next-line no-template-curly-in-string - version: '${hibernate.version}', -}; - -type CacheProviderDependencies = { - base: MavenDefinition; - hibernateCache?: MavenDefinition; -}; - -// eslint-disable-next-line import/prefer-default-export -export const getCacheProviderMavenDefinition: ( - cacheProvider: string, - javaDependencies: Record, -) => CacheProviderDependencies = (cacheProvider: string, javaDependencies: Record) => { - const dependenciesForCache: Record = { - redis: { - base: { - dependencies: [ - { - groupId: 'org.testcontainers', - artifactId: 'junit-jupiter', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'testcontainers', - scope: 'test', - }, - { - groupId: 'org.redisson', - artifactId: 'redisson', - }, - ], - }, - hibernateCache: { - dependencies: [hibernateJCache], - }, - }, - ehcache: { - base: { - dependencies: [ - javaxCacheApi, - { - groupId: 'org.ehcache', - artifactId: 'ehcache', - classifier: 'jakarta', - }, - ], - }, - hibernateCache: { - dependencies: [hibernateJCache], - }, - }, - caffeine: { - base: { - properties: [ - { - property: 'typesafe.version', - value: javaDependencies.typesafe, - }, - ], - dependencies: [ - javaxCacheApi, - { - groupId: 'com.github.ben-manes.caffeine', - artifactId: 'jcache', - }, - { - groupId: 'com.github.ben-manes.caffeine', - artifactId: 'caffeine', - }, - { - groupId: 'com.typesafe', - artifactId: 'config', - // eslint-disable-next-line no-template-curly-in-string - version: '${typesafe.version}', - }, - ], - }, - hibernateCache: { - dependencies: [hibernateJCache], - }, - }, - hazelcast: { - base: { - properties: [ - { - property: 'hazelcast-spring.version', - value: javaDependencies['hazelcast-spring'], - }, - ], - dependencies: [ - javaxCacheApi, - { - groupId: 'com.hazelcast', - artifactId: 'hazelcast-spring', - // eslint-disable-next-line no-template-curly-in-string - version: '${hazelcast-spring.version}', - }, - ], - }, - hibernateCache: { - properties: [ - { - property: 'hazelcast-hibernate53.version', - value: javaDependencies['hazelcast-hibernate53'], - }, - ], - dependencies: [ - { - groupId: 'com.hazelcast', - artifactId: 'hazelcast-hibernate53', - // eslint-disable-next-line no-template-curly-in-string - version: '${hazelcast-hibernate53.version}', - }, - ], - }, - }, - infinispan: { - base: { - dependencies: [ - javaxCacheApi, - { - groupId: 'org.infinispan', - artifactId: 'infinispan-hibernate-cache-v62', - }, - { - groupId: 'org.infinispan', - artifactId: 'infinispan-spring-boot3-starter-embedded', - }, - { - groupId: 'org.infinispan', - artifactId: 'infinispan-commons-jakarta', - }, - { - groupId: 'org.infinispan', - artifactId: 'infinispan-core-jakarta', - }, - { - groupId: 'org.infinispan', - artifactId: 'infinispan-component-annotations', - scope: 'compile', - }, - ], - }, - }, - memcached: { - base: { - dependencies: [ - javaxCacheApi, - { - groupId: 'com.google.code.simple-spring-memcached', - artifactId: 'spring-cache', - }, - { - groupId: 'com.google.code.simple-spring-memcached', - artifactId: 'xmemcached-provider', - }, - { - groupId: 'com.googlecode.xmemcached', - artifactId: 'xmemcached', - }, - ], - }, - }, - }; - return dependenciesForCache[cacheProvider]; -}; diff --git a/generators/spring-cache/internal/dependencies.ts b/generators/spring-cache/internal/dependencies.ts new file mode 100644 index 000000000000..ed1e464235bb --- /dev/null +++ b/generators/spring-cache/internal/dependencies.ts @@ -0,0 +1,197 @@ +/** + * Copyright 2013-2023 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 { MavenDefinition } from '../../maven/types.js'; + +const javaxCacheApi = { + groupId: 'javax.cache', + artifactId: 'cache-api', +}; +const hibernateJCache = { + groupId: 'org.hibernate.orm', + artifactId: 'hibernate-jcache', + // TODO drop forced version. Refer to https://github.com/jhipster/generator-jhipster/issues/22579 + // eslint-disable-next-line no-template-curly-in-string + version: '${hibernate.version}', +}; + +type CacheProviderDependencies = { + base: MavenDefinition; + hibernateCache?: MavenDefinition; +}; + +// eslint-disable-next-line import/prefer-default-export +export const getCacheProviderMavenDefinition: ( + cacheProvider: string, + javaDependencies: Record, +) => CacheProviderDependencies = (cacheProvider: string, javaDependencies: Record) => { + const dependenciesForCache: Record = { + redis: { + base: { + dependencies: [ + { + groupId: 'org.testcontainers', + artifactId: 'junit-jupiter', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'testcontainers', + scope: 'test', + }, + { + groupId: 'org.redisson', + artifactId: 'redisson', + }, + ], + }, + hibernateCache: { + dependencies: [hibernateJCache], + }, + }, + ehcache: { + base: { + dependencies: [ + javaxCacheApi, + { + groupId: 'org.ehcache', + artifactId: 'ehcache', + classifier: 'jakarta', + }, + ], + }, + hibernateCache: { + dependencies: [hibernateJCache], + }, + }, + caffeine: { + base: { + properties: [ + { + property: 'typesafe.version', + value: javaDependencies.typesafe, + }, + ], + dependencies: [ + javaxCacheApi, + { + groupId: 'com.github.ben-manes.caffeine', + artifactId: 'jcache', + }, + { + groupId: 'com.github.ben-manes.caffeine', + artifactId: 'caffeine', + }, + { + groupId: 'com.typesafe', + artifactId: 'config', + // eslint-disable-next-line no-template-curly-in-string + version: '${typesafe.version}', + }, + ], + }, + hibernateCache: { + dependencies: [hibernateJCache], + }, + }, + hazelcast: { + base: { + properties: [ + { + property: 'hazelcast-spring.version', + value: javaDependencies['hazelcast-spring'], + }, + ], + dependencies: [ + javaxCacheApi, + { + groupId: 'com.hazelcast', + artifactId: 'hazelcast-spring', + // eslint-disable-next-line no-template-curly-in-string + version: '${hazelcast-spring.version}', + }, + ], + }, + hibernateCache: { + properties: [ + { + property: 'hazelcast-hibernate53.version', + value: javaDependencies['hazelcast-hibernate53'], + }, + ], + dependencies: [ + { + groupId: 'com.hazelcast', + artifactId: 'hazelcast-hibernate53', + // eslint-disable-next-line no-template-curly-in-string + version: '${hazelcast-hibernate53.version}', + }, + ], + }, + }, + infinispan: { + base: { + dependencies: [ + javaxCacheApi, + { + groupId: 'org.infinispan', + artifactId: 'infinispan-hibernate-cache-v62', + }, + { + groupId: 'org.infinispan', + artifactId: 'infinispan-spring-boot3-starter-embedded', + }, + { + groupId: 'org.infinispan', + artifactId: 'infinispan-commons-jakarta', + }, + { + groupId: 'org.infinispan', + artifactId: 'infinispan-core-jakarta', + }, + { + groupId: 'org.infinispan', + artifactId: 'infinispan-component-annotations', + scope: 'compile', + }, + ], + }, + }, + memcached: { + base: { + dependencies: [ + javaxCacheApi, + { + groupId: 'com.google.code.simple-spring-memcached', + artifactId: 'spring-cache', + }, + { + groupId: 'com.google.code.simple-spring-memcached', + artifactId: 'xmemcached-provider', + }, + { + groupId: 'com.googlecode.xmemcached', + artifactId: 'xmemcached', + }, + ], + }, + }, + }; + return dependenciesForCache[cacheProvider]; +}; diff --git a/generators/spring-cache/needles.spec.mts b/generators/spring-cache/needles.spec.mts deleted file mode 100644 index 47a9f3559f0c..000000000000 --- a/generators/spring-cache/needles.spec.mts +++ /dev/null @@ -1,138 +0,0 @@ -import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.mjs'; -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { SERVER_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { GENERATOR_SPRING_CACHE } from '../generator-list.mjs'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends BaseApplicationGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup({ - addCacheEntries({ source }) { - source.addEntryToCache?.({ entry: 'entry' }); - source.addEntityToCache?.({ - entityAbsoluteClass: 'com.mycompany.myapp.domain.entityClass', - relationships: [ - { collection: true, propertyName: 'entitiesOneToMany' }, - { collection: true, propertyName: 'entitiesManoToMany' }, - ], - }); - }, - }); - } -}; - -describe('needle API server cache: JHipster server generator with blueprint', () => { - describe('ehcache', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_SPRING_CACHE) - .withOptions({ - blueprint: 'myblueprint', - }) - .withJHipsterConfig({ - cacheProvider: 'ehcache', - clientFramework: 'no', - enableHibernateCache: true, - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); - }); - - it('Assert ehCache configuration has entry added', () => { - runResult.assertFileContent(`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, 'createCache(cm, entry);'); - }); - - it('Assert ehCache configuration has entity added', () => { - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName());', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany");', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany");', - ); - }); - }); - - describe('caffeine', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_SPRING_CACHE) - .withOptions({ - blueprint: 'myblueprint', - }) - .withJHipsterConfig({ - cacheProvider: 'caffeine', - clientFramework: 'no', - enableHibernateCache: true, - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); - }); - - it('Assert caffeine configuration has entry added', () => { - runResult.assertFileContent(`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, 'createCache(cm, entry);'); - }); - - it('Assert caffeine configuration has entity added', () => { - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName());', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany");', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany");', - ); - }); - }); - - describe('redis', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_SPRING_CACHE) - .withOptions({ - blueprint: 'myblueprint', - }) - .withJHipsterConfig({ - cacheProvider: 'redis', - clientFramework: 'no', - enableHibernateCache: true, - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); - }); - - it('Assert redis configuration has entry added', () => { - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, entry, jcacheConfiguration);', - ); - }); - - it('Assert redis configuration has entity added', () => { - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName(), jcacheConfiguration);', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany", jcacheConfiguration);', - ); - runResult.assertFileContent( - `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, - 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany", jcacheConfiguration);', - ); - }); - }); -}); diff --git a/generators/spring-cache/needles.spec.ts b/generators/spring-cache/needles.spec.ts new file mode 100644 index 000000000000..86740ea1d972 --- /dev/null +++ b/generators/spring-cache/needles.spec.ts @@ -0,0 +1,138 @@ +import { defaultHelpers as helpers, result as runResult } from '../../test/support/index.js'; +import BaseApplicationGenerator from '../base-application/index.js'; +import { SERVER_MAIN_SRC_DIR } from '../generator-constants.js'; +import { GENERATOR_SPRING_CACHE } from '../generator-list.js'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends BaseApplicationGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + addCacheEntries({ source }) { + source.addEntryToCache?.({ entry: 'entry' }); + source.addEntityToCache?.({ + entityAbsoluteClass: 'com.mycompany.myapp.domain.entityClass', + relationships: [ + { collection: true, propertyName: 'entitiesOneToMany' }, + { collection: true, propertyName: 'entitiesManoToMany' }, + ], + }); + }, + }); + } +}; + +describe('needle API server cache: JHipster server generator with blueprint', () => { + describe('ehcache', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_SPRING_CACHE) + .withOptions({ + blueprint: 'myblueprint', + }) + .withJHipsterConfig({ + cacheProvider: 'ehcache', + clientFramework: 'no', + enableHibernateCache: true, + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); + }); + + it('Assert ehCache configuration has entry added', () => { + runResult.assertFileContent(`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, 'createCache(cm, entry);'); + }); + + it('Assert ehCache configuration has entity added', () => { + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName());', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany");', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany");', + ); + }); + }); + + describe('caffeine', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_SPRING_CACHE) + .withOptions({ + blueprint: 'myblueprint', + }) + .withJHipsterConfig({ + cacheProvider: 'caffeine', + clientFramework: 'no', + enableHibernateCache: true, + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); + }); + + it('Assert caffeine configuration has entry added', () => { + runResult.assertFileContent(`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, 'createCache(cm, entry);'); + }); + + it('Assert caffeine configuration has entity added', () => { + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName());', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany");', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany");', + ); + }); + }); + + describe('redis', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_SPRING_CACHE) + .withOptions({ + blueprint: 'myblueprint', + }) + .withJHipsterConfig({ + cacheProvider: 'redis', + clientFramework: 'no', + enableHibernateCache: true, + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:spring-cache' }]]); + }); + + it('Assert redis configuration has entry added', () => { + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, entry, jcacheConfiguration);', + ); + }); + + it('Assert redis configuration has entity added', () => { + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName(), jcacheConfiguration);', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesOneToMany", jcacheConfiguration);', + ); + runResult.assertFileContent( + `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, + 'createCache(cm, com.mycompany.myapp.domain.entityClass.class.getName() + ".entitiesManoToMany", jcacheConfiguration);', + ); + }); + }); +}); diff --git a/generators/spring-cache/types.d.mts b/generators/spring-cache/types.d.ts similarity index 100% rename from generators/spring-cache/types.d.mts rename to generators/spring-cache/types.d.ts diff --git a/generators/spring-cloud-stream/__snapshots__/generator-pulsar.spec.mts.snap b/generators/spring-cloud-stream/__snapshots__/generator-pulsar.spec.ts.snap similarity index 100% rename from generators/spring-cloud-stream/__snapshots__/generator-pulsar.spec.mts.snap rename to generators/spring-cloud-stream/__snapshots__/generator-pulsar.spec.ts.snap diff --git a/generators/spring-cloud-stream/__snapshots__/generator.spec.mts.snap b/generators/spring-cloud-stream/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-cloud-stream/__snapshots__/generator.spec.mts.snap rename to generators/spring-cloud-stream/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-cloud-stream/cleanup.mts b/generators/spring-cloud-stream/cleanup.mts deleted file mode 100644 index 02e42410f60e..000000000000 --- a/generators/spring-cloud-stream/cleanup.mts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2013-2023 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 KafkaGenerator from './generator.mjs'; - -export default function cleanupKafkaFilesTask(this: KafkaGenerator, { application }) { - if (this.isJhipsterVersionLessThan('6.5.2')) { - this.removeFile(`${application.javaPackageSrcDir}service/${application.upperFirstCamelCaseBaseName}KafkaConsumer.java`); - this.removeFile(`${application.javaPackageSrcDir}service/${application.upperFirstCamelCaseBaseName}KafkaProducer.java`); - } - if (this.isJhipsterVersionLessThan('7.7.1')) { - this.removeFile(`${application.javaPackageSrcDir}config/KafkaProperties.java`); - } - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile(`${application.javaPackageSrcDir}config/KafkaSseConsumer.java`); - this.removeFile(`${application.javaPackageSrcDir}config/KafkaSseProducer.java`); - - // make sure those files are removed and recreated - this.removeFile(`${application.srcTestResources}META-INF/spring.factories`); - this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); - } -} diff --git a/generators/spring-cloud-stream/cleanup.ts b/generators/spring-cloud-stream/cleanup.ts new file mode 100644 index 000000000000..d6a5a4e15429 --- /dev/null +++ b/generators/spring-cloud-stream/cleanup.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2013-2023 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 KafkaGenerator from './generator.js'; + +export default function cleanupKafkaFilesTask(this: KafkaGenerator, { application }) { + if (this.isJhipsterVersionLessThan('6.5.2')) { + this.removeFile(`${application.javaPackageSrcDir}service/${application.upperFirstCamelCaseBaseName}KafkaConsumer.java`); + this.removeFile(`${application.javaPackageSrcDir}service/${application.upperFirstCamelCaseBaseName}KafkaProducer.java`); + } + if (this.isJhipsterVersionLessThan('7.7.1')) { + this.removeFile(`${application.javaPackageSrcDir}config/KafkaProperties.java`); + } + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile(`${application.javaPackageSrcDir}config/KafkaSseConsumer.java`); + this.removeFile(`${application.javaPackageSrcDir}config/KafkaSseProducer.java`); + + // make sure those files are removed and recreated + this.removeFile(`${application.srcTestResources}META-INF/spring.factories`); + this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); + } +} diff --git a/generators/spring-cloud-stream/files.mts b/generators/spring-cloud-stream/files.mts deleted file mode 100644 index 2d6d5cccf562..000000000000 --- a/generators/spring-cloud-stream/files.mts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.mjs'; -import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; -import KafkaGenerator from './generator.mjs'; - -export const kafkaFiles: WriteFileSection = { - config: [ - { - condition: data => data.buildToolGradle, - templates: ['gradle/kafka.gradle'], - }, - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [data => `broker/KafkaConsumer_${data.imperativeOrReactive}.java`, 'broker/KafkaProducer.java'], - }, - ], - resources: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - to: moveToJavaPackageSrcDir, - templates: [ - { - sourceFile: data => `web/rest/KafkaResource_${data.imperativeOrReactive}.java`, - destinationFile: data => `web/rest/${data.upperFirstCamelCaseBaseName}KafkaResource.java`, - }, - ], - }, - ], - test: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'config/KafkaTestContainer.java', - 'config/EmbeddedKafka.java', - 'config/KafkaTestContainersSpringContextCustomizerFactory.java', - ], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - to: moveToJavaPackageTestDir, - templates: [ - { - sourceFile: data => `web/rest/KafkaResourceIT_${data.imperativeOrReactive}.java`, - destinationFile: data => `web/rest/${data.upperFirstCamelCaseBaseName}KafkaResourceIT.java`, - }, - ], - }, - ], -}; - -export const pulsarFiles: WriteFileSection = { - config: [ - { - condition: data => data.buildToolGradle, - templates: ['gradle/pulsar.gradle'], - }, - ], - test: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'broker/PulsarIT.java', - 'config/BrokerConfiguration.java', - 'config/EmbeddedPulsar.java', - 'config/PulsarTestContainer.java', - 'config/PulsarTestContainersSpringContextCustomizerFactory.java', - ], - }, - ], -}; - -export default async function writeFilesTask(this: KafkaGenerator, { application }) { - if (application.messageBrokerKafka) { - await this.writeFiles({ - sections: kafkaFiles, - context: application, - }); - } - if (application.messageBrokerPulsar) { - await this.writeFiles({ - sections: pulsarFiles, - context: application, - }); - } -} diff --git a/generators/spring-cloud-stream/files.ts b/generators/spring-cloud-stream/files.ts new file mode 100644 index 000000000000..dedc31f90983 --- /dev/null +++ b/generators/spring-cloud-stream/files.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2013-2023 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 { WriteFileSection } from '../base/api.js'; +import { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; +import KafkaGenerator from './generator.js'; + +export const kafkaFiles: WriteFileSection = { + config: [ + { + condition: data => data.buildToolGradle, + templates: ['gradle/kafka.gradle'], + }, + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [data => `broker/KafkaConsumer_${data.imperativeOrReactive}.java`, 'broker/KafkaProducer.java'], + }, + ], + resources: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + to: moveToJavaPackageSrcDir, + templates: [ + { + sourceFile: data => `web/rest/KafkaResource_${data.imperativeOrReactive}.java`, + destinationFile: data => `web/rest/${data.upperFirstCamelCaseBaseName}KafkaResource.java`, + }, + ], + }, + ], + test: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'config/KafkaTestContainer.java', + 'config/EmbeddedKafka.java', + 'config/KafkaTestContainersSpringContextCustomizerFactory.java', + ], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + to: moveToJavaPackageTestDir, + templates: [ + { + sourceFile: data => `web/rest/KafkaResourceIT_${data.imperativeOrReactive}.java`, + destinationFile: data => `web/rest/${data.upperFirstCamelCaseBaseName}KafkaResourceIT.java`, + }, + ], + }, + ], +}; + +export const pulsarFiles: WriteFileSection = { + config: [ + { + condition: data => data.buildToolGradle, + templates: ['gradle/pulsar.gradle'], + }, + ], + test: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'broker/PulsarIT.java', + 'config/BrokerConfiguration.java', + 'config/EmbeddedPulsar.java', + 'config/PulsarTestContainer.java', + 'config/PulsarTestContainersSpringContextCustomizerFactory.java', + ], + }, + ], +}; + +export default async function writeFilesTask(this: KafkaGenerator, { application }) { + if (application.messageBrokerKafka) { + await this.writeFiles({ + sections: kafkaFiles, + context: application, + }); + } + if (application.messageBrokerPulsar) { + await this.writeFiles({ + sections: pulsarFiles, + context: application, + }); + } +} diff --git a/generators/spring-cloud-stream/generator-pulsar.spec.mts b/generators/spring-cloud-stream/generator-pulsar.spec.mts deleted file mode 100644 index 53afe8f23c9c..000000000000 --- a/generators/spring-cloud-stream/generator-pulsar.spec.mts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { buildSamplesFromMatrix, buildServerMatrix, defaultHelpers as helpers } from '../../test/support/index.mjs'; -import Generator from './index.mjs'; - -import { messageBrokerTypes } from '../../jdl/jhipster/index.mjs'; - -const { PULSAR } = messageBrokerTypes; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const commonConfig = { messageBroker: PULSAR }; - -const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - Object.entries(testSamples).forEach(([name, config]) => { - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers.run(generatorFile).withJHipsterConfig(config); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct messageBroker', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"messageBroker": "${PULSAR}"`)); - }); - }); - }); -}); diff --git a/generators/spring-cloud-stream/generator-pulsar.spec.ts b/generators/spring-cloud-stream/generator-pulsar.spec.ts new file mode 100644 index 000000000000..cc7b130d7d24 --- /dev/null +++ b/generators/spring-cloud-stream/generator-pulsar.spec.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import { buildSamplesFromMatrix, buildServerMatrix, defaultHelpers as helpers } from '../../test/support/index.js'; +import Generator from './index.js'; + +import { messageBrokerTypes } from '../../jdl/jhipster/index.js'; + +const { PULSAR } = messageBrokerTypes; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const commonConfig = { messageBroker: PULSAR }; + +const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + Object.entries(testSamples).forEach(([name, config]) => { + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers.run(generatorFile).withJHipsterConfig(config); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct messageBroker', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"messageBroker": "${PULSAR}"`)); + }); + }); + }); +}); diff --git a/generators/spring-cloud-stream/generator.mts b/generators/spring-cloud-stream/generator.mts deleted file mode 100644 index 5d1db434385c..000000000000 --- a/generators/spring-cloud-stream/generator.mts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_CLOUD_STREAM, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.mjs'; -import cleanupFilesTask from './cleanup.mjs'; -import writeFilesTask from './files.mjs'; - -export default class KafkaGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_CLOUD_STREAM); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); - } - } - - get preparing() { - return this.asPreparingTaskGroup({ - preparing({ application }) { - application.packageInfoJavadocs?.push({ - packageName: `${application.packageName}.broker`, - documentation: 'Spring cloud consumers and providers', - }); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupFilesTask, - writeFilesTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - customizeApplicationForKafka({ source, application }) { - if (application.messageBrokerKafka) { - source.addLogbackMainLog?.({ name: 'org.apache.kafka', level: 'INFO' }); - source.addLogbackTestLog?.({ name: 'kafka', level: 'WARN' }); - source.addLogbackTestLog?.({ name: 'org.I0Itec', level: 'WARN' }); - source.addIntegrationTestAnnotation?.({ package: `${application.packageName}.config`, annotation: 'EmbeddedKafka' }); - - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.KafkaTestContainersSpringContextCustomizerFactory`, - }); - } - }, - applyKafkaGradleScript({ source, application }) { - if (application.buildToolGradle && application.messageBrokerKafka) { - if (application.messageBrokerKafka) { - source.applyFromGradle?.({ script: 'gradle/kafka.gradle' }); - } - } - }, - addKafkaMavenDependencies({ application, source }) { - if (application.buildToolMaven && application.messageBrokerKafka) { - source.addMavenDependency?.([ - { - groupId: 'org.springframework.cloud', - artifactId: 'spring-cloud-stream', - }, - { - groupId: 'org.springframework.cloud', - artifactId: 'spring-cloud-starter-stream-kafka', - }, - { - groupId: 'org.springframework.cloud', - artifactId: 'spring-cloud-stream-test-binder', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'junit-jupiter', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'testcontainers', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'kafka', - scope: 'test', - }, - ]); - } - }, - customizeApplicationForPulsar({ source, application }) { - if (application.messageBrokerPulsar) { - source.addLogbackMainLog?.({ name: 'org.apache.pulsar', level: 'INFO' }); - source.addIntegrationTestAnnotation?.({ package: `${application.packageName}.config`, annotation: 'EmbeddedPulsar' }); - - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.PulsarTestContainersSpringContextCustomizerFactory`, - }); - } - }, - applyPulsarGradleScript({ source, application }) { - if (application.buildToolGradle && application.messageBrokerPulsar) { - source.applyFromGradle?.({ script: 'gradle/pulsar.gradle' }); - } - }, - addPulsarMavenDependencies({ application, source }) { - if (application.buildToolMaven && application.messageBrokerPulsar) { - const { javaDependencies } = application; - source.addMavenDefinition?.({ - properties: [{ property: 'spring-pulsar.version', value: javaDependencies?.['spring-pulsar'] }], - dependencies: [ - { groupId: 'org.springframework.cloud', artifactId: 'spring-cloud-stream' }, - { - groupId: 'org.springframework.pulsar', - artifactId: 'spring-pulsar-spring-cloud-stream-binder', - // eslint-disable-next-line no-template-curly-in-string - version: '${spring-pulsar.version}', - }, - { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'pulsar', scope: 'test' }, - ], - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-cloud-stream/generator.spec.mts b/generators/spring-cloud-stream/generator.spec.mts deleted file mode 100644 index fead3b9d1d16..000000000000 --- a/generators/spring-cloud-stream/generator.spec.mts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import { buildSamplesFromMatrix, buildServerMatrix, defaultHelpers as helpers } from '../../test/support/index.mjs'; -import Generator from './index.mjs'; - -import { messageBrokerTypes } from '../../jdl/jhipster/index.mjs'; - -const { KAFKA } = messageBrokerTypes; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const commonConfig = { messageBroker: KAFKA }; - -const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - Object.entries(testSamples).forEach(([name, config]) => { - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers.run(generatorFile).withJHipsterConfig(config); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct messageBroker', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"messageBroker": "${KAFKA}"`)); - }); - }); - }); -}); diff --git a/generators/spring-cloud-stream/generator.spec.ts b/generators/spring-cloud-stream/generator.spec.ts new file mode 100644 index 000000000000..58f971851527 --- /dev/null +++ b/generators/spring-cloud-stream/generator.spec.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import { buildSamplesFromMatrix, buildServerMatrix, defaultHelpers as helpers } from '../../test/support/index.js'; +import Generator from './index.js'; + +import { messageBrokerTypes } from '../../jdl/jhipster/index.js'; + +const { KAFKA } = messageBrokerTypes; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const commonConfig = { messageBroker: KAFKA }; + +const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + Object.entries(testSamples).forEach(([name, config]) => { + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers.run(generatorFile).withJHipsterConfig(config); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct messageBroker', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"messageBroker": "${KAFKA}"`)); + }); + }); + }); +}); diff --git a/generators/spring-cloud-stream/generator.ts b/generators/spring-cloud-stream/generator.ts new file mode 100644 index 000000000000..c79b81b1777c --- /dev/null +++ b/generators/spring-cloud-stream/generator.ts @@ -0,0 +1,160 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_CLOUD_STREAM, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.js'; +import cleanupFilesTask from './cleanup.js'; +import writeFilesTask from './files.js'; + +export default class KafkaGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_CLOUD_STREAM); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER); + } + } + + get preparing() { + return this.asPreparingTaskGroup({ + preparing({ application }) { + application.packageInfoJavadocs?.push({ + packageName: `${application.packageName}.broker`, + documentation: 'Spring cloud consumers and providers', + }); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupFilesTask, + writeFilesTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + customizeApplicationForKafka({ source, application }) { + if (application.messageBrokerKafka) { + source.addLogbackMainLog?.({ name: 'org.apache.kafka', level: 'INFO' }); + source.addLogbackTestLog?.({ name: 'kafka', level: 'WARN' }); + source.addLogbackTestLog?.({ name: 'org.I0Itec', level: 'WARN' }); + source.addIntegrationTestAnnotation?.({ package: `${application.packageName}.config`, annotation: 'EmbeddedKafka' }); + + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.KafkaTestContainersSpringContextCustomizerFactory`, + }); + } + }, + applyKafkaGradleScript({ source, application }) { + if (application.buildToolGradle && application.messageBrokerKafka) { + if (application.messageBrokerKafka) { + source.applyFromGradle?.({ script: 'gradle/kafka.gradle' }); + } + } + }, + addKafkaMavenDependencies({ application, source }) { + if (application.buildToolMaven && application.messageBrokerKafka) { + source.addMavenDependency?.([ + { + groupId: 'org.springframework.cloud', + artifactId: 'spring-cloud-stream', + }, + { + groupId: 'org.springframework.cloud', + artifactId: 'spring-cloud-starter-stream-kafka', + }, + { + groupId: 'org.springframework.cloud', + artifactId: 'spring-cloud-stream-test-binder', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'junit-jupiter', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'testcontainers', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'kafka', + scope: 'test', + }, + ]); + } + }, + customizeApplicationForPulsar({ source, application }) { + if (application.messageBrokerPulsar) { + source.addLogbackMainLog?.({ name: 'org.apache.pulsar', level: 'INFO' }); + source.addIntegrationTestAnnotation?.({ package: `${application.packageName}.config`, annotation: 'EmbeddedPulsar' }); + + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.PulsarTestContainersSpringContextCustomizerFactory`, + }); + } + }, + applyPulsarGradleScript({ source, application }) { + if (application.buildToolGradle && application.messageBrokerPulsar) { + source.applyFromGradle?.({ script: 'gradle/pulsar.gradle' }); + } + }, + addPulsarMavenDependencies({ application, source }) { + if (application.buildToolMaven && application.messageBrokerPulsar) { + const { javaDependencies } = application; + source.addMavenDefinition?.({ + properties: [{ property: 'spring-pulsar.version', value: javaDependencies?.['spring-pulsar'] }], + dependencies: [ + { groupId: 'org.springframework.cloud', artifactId: 'spring-cloud-stream' }, + { + groupId: 'org.springframework.pulsar', + artifactId: 'spring-pulsar-spring-cloud-stream-binder', + // eslint-disable-next-line no-template-curly-in-string + version: '${spring-pulsar.version}', + }, + { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'pulsar', scope: 'test' }, + ], + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-cloud-stream/index.mts b/generators/spring-cloud-stream/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-cloud-stream/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-cloud-stream/index.ts b/generators/spring-cloud-stream/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-cloud-stream/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-cassandra/__snapshots__/generator.spec.mts.snap b/generators/spring-data-cassandra/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-cassandra/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-cassandra/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-cassandra/cleanup.mjs b/generators/spring-data-cassandra/cleanup.js similarity index 100% rename from generators/spring-data-cassandra/cleanup.mjs rename to generators/spring-data-cassandra/cleanup.js diff --git a/generators/spring-data-cassandra/database-changelog.spec.mts b/generators/spring-data-cassandra/database-changelog.spec.mts deleted file mode 100644 index d28a85a59628..000000000000 --- a/generators/spring-data-cassandra/database-changelog.spec.mts +++ /dev/null @@ -1,24 +0,0 @@ -import { dryRunHelpers as helpers } from '../../test/support/index.mjs'; - -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { GENERATOR_SPRING_DATA_CASSANDRA } from '../generator-list.mjs'; - -const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; - -describe('generator - app - database changelogs', () => { - context('when regenerating the application', () => { - describe('with cassandra database', () => { - let runResult; - before(async () => { - runResult = await helpers - .runJHipster(GENERATOR_SPRING_DATA_CASSANDRA) - .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo]) - .withOptions({ force: true, skipClient: true }); - }); - - it('should create database changelog for the entity', () => { - runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`]); - }); - }); - }); -}); diff --git a/generators/spring-data-cassandra/database-changelog.spec.ts b/generators/spring-data-cassandra/database-changelog.spec.ts new file mode 100644 index 000000000000..f96e43c0c7ce --- /dev/null +++ b/generators/spring-data-cassandra/database-changelog.spec.ts @@ -0,0 +1,24 @@ +import { dryRunHelpers as helpers } from '../../test/support/index.js'; + +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { GENERATOR_SPRING_DATA_CASSANDRA } from '../generator-list.js'; + +const entityFoo = { name: 'Foo', changelogDate: '20160926101210' }; + +describe('generator - app - database changelogs', () => { + context('when regenerating the application', () => { + describe('with cassandra database', () => { + let runResult; + before(async () => { + runResult = await helpers + .runJHipster(GENERATOR_SPRING_DATA_CASSANDRA) + .withJHipsterConfig({ databaseType: 'cassandra' }, [entityFoo]) + .withOptions({ force: true, skipClient: true }); + }); + + it('should create database changelog for the entity', () => { + runResult.assertFile([`${SERVER_MAIN_RES_DIR}config/cql/changelog/20160926101210_added_entity_Foo.cql`]); + }); + }); + }); +}); diff --git a/generators/spring-data-cassandra/entity-files.js b/generators/spring-data-cassandra/entity-files.js new file mode 100644 index 000000000000..845f5b121282 --- /dev/null +++ b/generators/spring-data-cassandra/entity-files.js @@ -0,0 +1,52 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { javaMainPackageTemplatesBlock } from '../server/support/index.js'; + +export const entityFiles = { + dbChangelog: [ + { + condition: ctx => !ctx.skipDbChangelog, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/cql/changelog/added_entity.cql', + renameTo: ctx => `config/cql/changelog/${ctx.changelogDate}_added_entity_${ctx.entityClass}.cql`, + }, + ], + }, + ], + server: [ + { + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_cassandra'], + }, + ], +}; + +export function cleanupCassandraEntityFilesTask() {} + +export default async function writeEntityCassandraFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/spring-data-cassandra/entity-files.mjs b/generators/spring-data-cassandra/entity-files.mjs deleted file mode 100644 index 02a59756a0f1..000000000000 --- a/generators/spring-data-cassandra/entity-files.mjs +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { javaMainPackageTemplatesBlock } from '../server/support/index.mjs'; - -export const entityFiles = { - dbChangelog: [ - { - condition: ctx => !ctx.skipDbChangelog, - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/cql/changelog/added_entity.cql', - renameTo: ctx => `config/cql/changelog/${ctx.changelogDate}_added_entity_${ctx.entityClass}.cql`, - }, - ], - }, - ], - server: [ - { - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_cassandra'], - }, - ], -}; - -export function cleanupCassandraEntityFilesTask() {} - -export default async function writeEntityCassandraFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/spring-data-cassandra/files.js b/generators/spring-data-cassandra/files.js new file mode 100644 index 000000000000..700f77281ec2 --- /dev/null +++ b/generators/spring-data-cassandra/files.js @@ -0,0 +1,71 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; + +export const cassandraFiles = { + serverFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/DatabaseConfiguration.java'], + }, + ], + serverResource: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + 'config/cql/create-keyspace-prod.cql', + 'config/cql/create-keyspace.cql', + 'config/cql/drop-keyspace.cql', + 'config/cql/changelog/README.md', + ], + }, + { + condition: ctx => !ctx.applicationTypeMicroservice && ctx.generateBuiltInUserEntity, + path: SERVER_MAIN_RES_DIR, + templates: [ + { file: 'config/cql/changelog/create-tables.cql', renameTo: () => 'config/cql/changelog/00000000000000_create-tables.cql' }, + { + file: 'config/cql/changelog/insert_default_users.cql', + renameTo: () => 'config/cql/changelog/00000000000001_insert_default_users.cql', + }, + ], + }, + ], + serverTestFw: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'CassandraKeyspaceIT.java', + 'config/CassandraTestContainer.java', + 'config/CassandraTestContainersSpringContextCustomizerFactory.java', + 'config/EmbeddedCassandra.java', + ], + }, + ], +}; + +export default async function writeCassandraFilesTask({ application }) { + await this.writeFiles({ + sections: cassandraFiles, + context: application, + }); +} diff --git a/generators/spring-data-cassandra/files.mjs b/generators/spring-data-cassandra/files.mjs deleted file mode 100644 index b66921bbaa42..000000000000 --- a/generators/spring-data-cassandra/files.mjs +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; - -export const cassandraFiles = { - serverFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/DatabaseConfiguration.java'], - }, - ], - serverResource: [ - { - path: SERVER_MAIN_RES_DIR, - templates: [ - 'config/cql/create-keyspace-prod.cql', - 'config/cql/create-keyspace.cql', - 'config/cql/drop-keyspace.cql', - 'config/cql/changelog/README.md', - ], - }, - { - condition: ctx => !ctx.applicationTypeMicroservice && ctx.generateBuiltInUserEntity, - path: SERVER_MAIN_RES_DIR, - templates: [ - { file: 'config/cql/changelog/create-tables.cql', renameTo: () => 'config/cql/changelog/00000000000000_create-tables.cql' }, - { - file: 'config/cql/changelog/insert_default_users.cql', - renameTo: () => 'config/cql/changelog/00000000000001_insert_default_users.cql', - }, - ], - }, - ], - serverTestFw: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'CassandraKeyspaceIT.java', - 'config/CassandraTestContainer.java', - 'config/CassandraTestContainersSpringContextCustomizerFactory.java', - 'config/EmbeddedCassandra.java', - ], - }, - ], -}; - -export default async function writeCassandraFilesTask({ application }) { - await this.writeFiles({ - sections: cassandraFiles, - context: application, - }); -} diff --git a/generators/spring-data-cassandra/generator.js b/generators/spring-data-cassandra/generator.js new file mode 100644 index 000000000000..1dbae3e9e7cb --- /dev/null +++ b/generators/spring-data-cassandra/generator.js @@ -0,0 +1,137 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_DATA_CASSANDRA, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeCassandraFilesTask from './files.js'; +import cleanupCassandraFilesTask from './cleanup.js'; +import writeCassandraEntityFilesTask, { cleanupCassandraEntityFilesTask } from './entity-files.js'; + +export default class CassandraGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_CASSANDRA); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get configuringEachEntity() { + return this.asConfiguringEachEntityTaskGroup({ + checkEntities({ entityConfig }) { + if (entityConfig.pagination && entityConfig.pagination !== 'no') { + throw new Error("Pagination isn't allowed when the app uses Cassandra."); + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.configuringEachEntity); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupCassandraFilesTask, + writeCassandraFilesTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + cleanupCassandraEntityFilesTask, + writeCassandraEntityFilesTask, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addTestSpringFactory({ source, application }) { + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.CassandraTestContainersSpringContextCustomizerFactory`, + }); + }, + addDependencies({ application, source }) { + const { reactive } = application; + if (application.buildToolMaven) { + source.addMavenProperty?.({ + property: 'cassandra-driver.version', + value: application.javaDependencies.cassandra, + }); + + source.addMavenAnnotationProcessor?.({ + groupId: 'com.datastax.oss', + artifactId: 'java-driver-mapper-processor', + // eslint-disable-next-line no-template-curly-in-string + version: '${cassandra-driver.version}', + }); + + source.addMavenDependency?.([ + { + groupId: 'com.datastax.oss', + artifactId: 'java-driver-mapper-runtime', + }, + { + groupId: 'commons-codec', + artifactId: 'commons-codec', + }, + { + groupId: 'org.lz4', + artifactId: 'lz4-java', + }, + { + groupId: 'org.springframework.boot', + artifactId: `spring-boot-starter-data-cassandra${reactive ? '-reactive' : ''}`, + }, + { + groupId: 'org.testcontainers', + artifactId: 'junit-jupiter', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'testcontainers', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'cassandra', + scope: 'test', + }, + ]); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-data-cassandra/generator.mjs b/generators/spring-data-cassandra/generator.mjs deleted file mode 100644 index a92390d493d2..000000000000 --- a/generators/spring-data-cassandra/generator.mjs +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_DATA_CASSANDRA, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeCassandraFilesTask from './files.mjs'; -import cleanupCassandraFilesTask from './cleanup.mjs'; -import writeCassandraEntityFilesTask, { cleanupCassandraEntityFilesTask } from './entity-files.mjs'; - -export default class CassandraGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_CASSANDRA); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get configuringEachEntity() { - return this.asConfiguringEachEntityTaskGroup({ - checkEntities({ entityConfig }) { - if (entityConfig.pagination && entityConfig.pagination !== 'no') { - throw new Error("Pagination isn't allowed when the app uses Cassandra."); - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.configuringEachEntity); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupCassandraFilesTask, - writeCassandraFilesTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - cleanupCassandraEntityFilesTask, - writeCassandraEntityFilesTask, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addTestSpringFactory({ source, application }) { - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.CassandraTestContainersSpringContextCustomizerFactory`, - }); - }, - addDependencies({ application, source }) { - const { reactive } = application; - if (application.buildToolMaven) { - source.addMavenProperty?.({ - property: 'cassandra-driver.version', - value: application.javaDependencies.cassandra, - }); - - source.addMavenAnnotationProcessor?.({ - groupId: 'com.datastax.oss', - artifactId: 'java-driver-mapper-processor', - // eslint-disable-next-line no-template-curly-in-string - version: '${cassandra-driver.version}', - }); - - source.addMavenDependency?.([ - { - groupId: 'com.datastax.oss', - artifactId: 'java-driver-mapper-runtime', - }, - { - groupId: 'commons-codec', - artifactId: 'commons-codec', - }, - { - groupId: 'org.lz4', - artifactId: 'lz4-java', - }, - { - groupId: 'org.springframework.boot', - artifactId: `spring-boot-starter-data-cassandra${reactive ? '-reactive' : ''}`, - }, - { - groupId: 'org.testcontainers', - artifactId: 'junit-jupiter', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'testcontainers', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'cassandra', - scope: 'test', - }, - ]); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-data-cassandra/generator.spec.mts b/generators/spring-data-cassandra/generator.spec.mts deleted file mode 100644 index 6bd1c2c8c38d..000000000000 --- a/generators/spring-data-cassandra/generator.spec.mts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { buildServerSamples, entitiesSimple as entities, defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from '../server/index.mjs'; - -import { databaseTypes } from '../../jdl/jhipster/index.mjs'; -import { - mockedGenerators as serverGenerators, - shouldComposeWithSpringCloudStream, - shouldComposeWithLiquibase, -} from '../server/__test-support/index.mjs'; -import { GENERATOR_SPRING_DATA_CASSANDRA } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -// compose with server generator, many conditionals at server generator -const generatorFile = join(__dirname, '../server/index.mjs'); - -const { CASSANDRA: databaseType } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`); - -const testSamples = buildServerSamples(commonConfig); - -describe(`generator - ${databaseType}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { authenticationType } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct authenticationType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); - }); - it('contains correct databaseType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - shouldComposeWithLiquibase(false, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-cassandra/generator.spec.ts b/generators/spring-data-cassandra/generator.spec.ts new file mode 100644 index 000000000000..21e475602b81 --- /dev/null +++ b/generators/spring-data-cassandra/generator.spec.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { buildServerSamples, entitiesSimple as entities, defaultHelpers as helpers, runResult } from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from '../server/index.js'; + +import { databaseTypes } from '../../jdl/jhipster/index.js'; +import { + mockedGenerators as serverGenerators, + shouldComposeWithSpringCloudStream, + shouldComposeWithLiquibase, +} from '../server/__test-support/index.js'; +import { GENERATOR_SPRING_DATA_CASSANDRA } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +// compose with server generator, many conditionals at server generator +const generatorFile = join(__dirname, '../server/index.js'); + +const { CASSANDRA: databaseType } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`); + +const testSamples = buildServerSamples(commonConfig); + +describe(`generator - ${databaseType}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { authenticationType } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct authenticationType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); + }); + it('contains correct databaseType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + shouldComposeWithLiquibase(false, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-cassandra/index.mts b/generators/spring-data-cassandra/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-data-cassandra/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-data-cassandra/index.ts b/generators/spring-data-cassandra/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-data-cassandra/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-couchbase/__snapshots__/generator.spec.mts.snap b/generators/spring-data-couchbase/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-couchbase/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-couchbase/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-couchbase/entity-files.js b/generators/spring-data-couchbase/entity-files.js new file mode 100644 index 000000000000..669841a1eab9 --- /dev/null +++ b/generators/spring-data-couchbase/entity-files.js @@ -0,0 +1,76 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import { javaMainPackageTemplatesBlock } from '../server/support/index.js'; + +export const entityFiles = { + dbChangelog: [ + { + condition: generator => !generator.skipDbChangelog && !generator.embedded, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/couchmove/changelog/entity.n1ql', + renameTo: generator => `config/couchmove/changelog/V${generator.changelogDate}__${generator.entityInstance.toLowerCase()}.n1ql`, + }, + ], + }, + { + condition: generator => generator.searchEngineCouchbase && !generator.skipDbChangelog && !generator.embedded, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'config/couchmove/changelog/entity.fts', + renameTo: generator => + `config/couchmove/changelog/V${parseInt(generator.changelogDate, 10) + 10}__${generator.entityInstance.toLowerCase()}.fts`, + }, + ], + }, + ], + server: [ + { + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_couchbase'], + }, + { + condition: generator => !generator.embedded, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['repository/_entityClass_Repository.java'], + }, + ], +}; + +export function cleanupCouchbaseEntityFilesTask({ application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + if (this.isJhipsterVersionLessThan('7.6.1')) { + this.removeFile( + `${application.srcMainResources}config/couchmove/changelog/V${entity.changelogDate}__${entity.entityInstance.toLowerCase()}.fts`, + ); + } + } +} + +export default async function writeEntityCouchbaseFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/spring-data-couchbase/entity-files.mjs b/generators/spring-data-couchbase/entity-files.mjs deleted file mode 100644 index 27d3b154fa45..000000000000 --- a/generators/spring-data-couchbase/entity-files.mjs +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { javaMainPackageTemplatesBlock } from '../server/support/index.mjs'; - -export const entityFiles = { - dbChangelog: [ - { - condition: generator => !generator.skipDbChangelog && !generator.embedded, - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/couchmove/changelog/entity.n1ql', - renameTo: generator => `config/couchmove/changelog/V${generator.changelogDate}__${generator.entityInstance.toLowerCase()}.n1ql`, - }, - ], - }, - { - condition: generator => generator.searchEngineCouchbase && !generator.skipDbChangelog && !generator.embedded, - path: SERVER_MAIN_RES_DIR, - templates: [ - { - file: 'config/couchmove/changelog/entity.fts', - renameTo: generator => - `config/couchmove/changelog/V${parseInt(generator.changelogDate, 10) + 10}__${generator.entityInstance.toLowerCase()}.fts`, - }, - ], - }, - ], - server: [ - { - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_couchbase'], - }, - { - condition: generator => !generator.embedded, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['repository/_entityClass_Repository.java'], - }, - ], -}; - -export function cleanupCouchbaseEntityFilesTask({ application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - if (this.isJhipsterVersionLessThan('7.6.1')) { - this.removeFile( - `${application.srcMainResources}config/couchmove/changelog/V${entity.changelogDate}__${entity.entityInstance.toLowerCase()}.fts`, - ); - } - } -} - -export default async function writeEntityCouchbaseFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/spring-data-couchbase/files.js b/generators/spring-data-couchbase/files.js new file mode 100644 index 000000000000..c9568fdeff73 --- /dev/null +++ b/generators/spring-data-couchbase/files.js @@ -0,0 +1,102 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; + +export const couchbaseFiles = { + serverJavaConfig: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['repository/JHipsterCouchbaseRepository.java', 'config/DatabaseConfiguration.java'], + }, + { + condition: data => data.authenticationTypeSession && !data.reactive && data.generateUserManagement, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['repository/PersistentTokenRepository_couchbase.java'], + }, + { + condition: data => data.searchEngineCouchbase, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['repository/CouchbaseSearchRepository.java'], + }, + { + condition: data => data.searchEngineCouchbase, + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['repository/CouchbaseSearchRepositoryTest.java'], + }, + ], + serverResource: [ + { + condition: data => data.generateBuiltInUserEntity, + path: SERVER_MAIN_RES_DIR, + templates: ['config/couchmove/changelog/V0__create_collections.n1ql', 'config/couchmove/changelog/V0.2__create_indexes.n1ql'], + }, + { + condition: data => data.generateBuiltInUserEntity, + path: SERVER_MAIN_RES_DIR, + templates: [ + 'config/couchmove/changelog/V0.1__initial_setup/authority/ROLE_ADMIN.json', + 'config/couchmove/changelog/V0.1__initial_setup/authority/ROLE_USER.json', + 'config/couchmove/changelog/V0.1__initial_setup/user/admin.json', + 'config/couchmove/changelog/V0.1__initial_setup/user/user.json', + ], + }, + ], + serverTestFw: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/CouchbaseTestContainer.java', 'config/EmbeddedCouchbase.java'], + }, + ], +}; + +export function cleanupCouchbaseFilesTask({ application }) { + if (this.isJhipsterVersionLessThan('7.1.1')) { + this.removeFile(`${application.javaPackageSrcDir}repository/CustomReactiveCouchbaseRepository.java `); + this.removeFile(`${application.javaPackageSrcDir}config/DatabaseConfigurationIT.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/N1qlCouchbaseRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/ReactiveN1qlCouchbaseRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/CustomN1qlCouchbaseRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/CustomCouchbaseRepository.java`); + this.removeFile(`${application.javaPackageSrcDir}repository/SearchCouchbaseRepository.java`); + this.removeFile(`${application.javaPackageTestDir}repository/CustomCouchbaseRepositoryTest.java`); + } + + if (this.isJhipsterVersionLessThan('7.6.1')) { + this.removeFile(`${application.javaPackageTestDir}repository/JHipsterCouchbaseRepositoryTest.java`); + this.removeFolder(`${application.javaPackageSrcDir}config/couchbase`); + this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0__create_indexes.n1ql`); + this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/ROLE_ADMIN.json`); + this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/ROLE_USER.json`); + this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/user__admin.json`); + this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/user__user.json`); + } +} + +export default async function writeCouchbaseFilesTask({ application }) { + await this.writeFiles({ + sections: couchbaseFiles, + context: application, + }); +} diff --git a/generators/spring-data-couchbase/files.mjs b/generators/spring-data-couchbase/files.mjs deleted file mode 100644 index b5764604dc9e..000000000000 --- a/generators/spring-data-couchbase/files.mjs +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; - -export const couchbaseFiles = { - serverJavaConfig: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['repository/JHipsterCouchbaseRepository.java', 'config/DatabaseConfiguration.java'], - }, - { - condition: data => data.authenticationTypeSession && !data.reactive && data.generateUserManagement, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['repository/PersistentTokenRepository_couchbase.java'], - }, - { - condition: data => data.searchEngineCouchbase, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['repository/CouchbaseSearchRepository.java'], - }, - { - condition: data => data.searchEngineCouchbase, - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['repository/CouchbaseSearchRepositoryTest.java'], - }, - ], - serverResource: [ - { - condition: data => data.generateBuiltInUserEntity, - path: SERVER_MAIN_RES_DIR, - templates: ['config/couchmove/changelog/V0__create_collections.n1ql', 'config/couchmove/changelog/V0.2__create_indexes.n1ql'], - }, - { - condition: data => data.generateBuiltInUserEntity, - path: SERVER_MAIN_RES_DIR, - templates: [ - 'config/couchmove/changelog/V0.1__initial_setup/authority/ROLE_ADMIN.json', - 'config/couchmove/changelog/V0.1__initial_setup/authority/ROLE_USER.json', - 'config/couchmove/changelog/V0.1__initial_setup/user/admin.json', - 'config/couchmove/changelog/V0.1__initial_setup/user/user.json', - ], - }, - ], - serverTestFw: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/CouchbaseTestContainer.java', 'config/EmbeddedCouchbase.java'], - }, - ], -}; - -export function cleanupCouchbaseFilesTask({ application }) { - if (this.isJhipsterVersionLessThan('7.1.1')) { - this.removeFile(`${application.javaPackageSrcDir}repository/CustomReactiveCouchbaseRepository.java `); - this.removeFile(`${application.javaPackageSrcDir}config/DatabaseConfigurationIT.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/N1qlCouchbaseRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/ReactiveN1qlCouchbaseRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/CustomN1qlCouchbaseRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/CustomCouchbaseRepository.java`); - this.removeFile(`${application.javaPackageSrcDir}repository/SearchCouchbaseRepository.java`); - this.removeFile(`${application.javaPackageTestDir}repository/CustomCouchbaseRepositoryTest.java`); - } - - if (this.isJhipsterVersionLessThan('7.6.1')) { - this.removeFile(`${application.javaPackageTestDir}repository/JHipsterCouchbaseRepositoryTest.java`); - this.removeFolder(`${application.javaPackageSrcDir}config/couchbase`); - this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0__create_indexes.n1ql`); - this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/ROLE_ADMIN.json`); - this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/ROLE_USER.json`); - this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/user__admin.json`); - this.removeFile(`${application.srcMainResources}config/couchmove/changelog/V0.1__initial_setup/user__user.json`); - } -} - -export default async function writeCouchbaseFilesTask({ application }) { - await this.writeFiles({ - sections: couchbaseFiles, - context: application, - }); -} diff --git a/generators/spring-data-couchbase/generator.js b/generators/spring-data-couchbase/generator.js new file mode 100644 index 000000000000..2e6f74c435b1 --- /dev/null +++ b/generators/spring-data-couchbase/generator.js @@ -0,0 +1,104 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_DATA_COUCHBASE, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeCouchbaseFilesTask, { cleanupCouchbaseFilesTask } from './files.js'; +import writeCouchbaseEntityFilesTask, { cleanupCouchbaseEntityFilesTask } from './entity-files.js'; + +export default class CouchbaseGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_COUCHBASE); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return { + cleanupCouchbaseFilesTask, + writeCouchbaseFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupCouchbaseEntityFilesTask, + writeCouchbaseEntityFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + const { reactive } = application; + if (application.buildToolMaven) { + source.addMavenDependency?.([ + { + groupId: 'commons-codec', + artifactId: 'commons-codec', + }, + { + groupId: 'com.couchbase.client', + artifactId: 'java-client', + }, + { + groupId: 'com.github.differentway', + artifactId: 'couchmove', + }, + { + groupId: 'org.springframework.boot', + artifactId: `spring-boot-starter-data-couchbase${reactive ? '-reactive' : ''}`, + }, + { + groupId: 'org.testcontainers', + artifactId: 'junit-jupiter', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'testcontainers', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'couchbase', + scope: 'test', + }, + ]); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-data-couchbase/generator.mjs b/generators/spring-data-couchbase/generator.mjs deleted file mode 100644 index 9c033ac20974..000000000000 --- a/generators/spring-data-couchbase/generator.mjs +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_DATA_COUCHBASE, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeCouchbaseFilesTask, { cleanupCouchbaseFilesTask } from './files.mjs'; -import writeCouchbaseEntityFilesTask, { cleanupCouchbaseEntityFilesTask } from './entity-files.mjs'; - -export default class CouchbaseGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_COUCHBASE); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return { - cleanupCouchbaseFilesTask, - writeCouchbaseFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupCouchbaseEntityFilesTask, - writeCouchbaseEntityFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - const { reactive } = application; - if (application.buildToolMaven) { - source.addMavenDependency?.([ - { - groupId: 'commons-codec', - artifactId: 'commons-codec', - }, - { - groupId: 'com.couchbase.client', - artifactId: 'java-client', - }, - { - groupId: 'com.github.differentway', - artifactId: 'couchmove', - }, - { - groupId: 'org.springframework.boot', - artifactId: `spring-boot-starter-data-couchbase${reactive ? '-reactive' : ''}`, - }, - { - groupId: 'org.testcontainers', - artifactId: 'junit-jupiter', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'testcontainers', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'couchbase', - scope: 'test', - }, - ]); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-data-couchbase/generator.spec.mts b/generators/spring-data-couchbase/generator.spec.mts deleted file mode 100644 index 5204cba24953..000000000000 --- a/generators/spring-data-couchbase/generator.spec.mts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildServerMatrix, - extendMatrix, - entitiesSimple as entities, - buildSamplesFromMatrix, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; - -import { databaseTypes } from '../../jdl/jhipster/index.mjs'; -import { - mockedGenerators as serverGenerators, - shouldComposeWithSpringCloudStream, - shouldComposeWithLiquibase, -} from '../server/__test-support/index.mjs'; -import { GENERATOR_SPRING_DATA_COUCHBASE } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -// compose with server generator, many conditionals at server generator -const generatorFile = join(__dirname, '../server/index.mjs'); - -const { COUCHBASE: databaseType } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const couchbaseSamples = extendMatrix(buildServerMatrix(), { - searchEngine: ['no', 'couchbase'], -}); - -const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_COUCHBASE}`); - -const testSamples = buildSamplesFromMatrix(couchbaseSamples, { commonConfig }); - -describe(`generator - ${databaseType}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { authenticationType } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct authenticationType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); - }); - it('contains correct databaseType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - shouldComposeWithLiquibase(false, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-couchbase/generator.spec.ts b/generators/spring-data-couchbase/generator.spec.ts new file mode 100644 index 000000000000..4a84d55bb5ec --- /dev/null +++ b/generators/spring-data-couchbase/generator.spec.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildServerMatrix, + extendMatrix, + entitiesSimple as entities, + buildSamplesFromMatrix, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; + +import { databaseTypes } from '../../jdl/jhipster/index.js'; +import { + mockedGenerators as serverGenerators, + shouldComposeWithSpringCloudStream, + shouldComposeWithLiquibase, +} from '../server/__test-support/index.js'; +import { GENERATOR_SPRING_DATA_COUCHBASE } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +// compose with server generator, many conditionals at server generator +const generatorFile = join(__dirname, '../server/index.js'); + +const { COUCHBASE: databaseType } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const couchbaseSamples = extendMatrix(buildServerMatrix(), { + searchEngine: ['no', 'couchbase'], +}); + +const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_COUCHBASE}`); + +const testSamples = buildSamplesFromMatrix(couchbaseSamples, { commonConfig }); + +describe(`generator - ${databaseType}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { authenticationType } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct authenticationType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); + }); + it('contains correct databaseType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + shouldComposeWithLiquibase(false, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-couchbase/index.mts b/generators/spring-data-couchbase/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-data-couchbase/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-data-couchbase/index.ts b/generators/spring-data-couchbase/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-data-couchbase/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-elasticsearch/__snapshots__/generator.spec.mts.snap b/generators/spring-data-elasticsearch/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-elasticsearch/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-elasticsearch/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.mts b/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.mts deleted file mode 100644 index 3be710c7de9c..000000000000 --- a/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.mts +++ /dev/null @@ -1,33 +0,0 @@ -import type { RunResult } from 'yeoman-test'; -import type BaseApplicationGenerator from '../../base-application/index.mjs'; -import type { SpringBootApplication } from '../../server/types.mjs'; - -import { SERVER_MAIN_SRC_DIR, JAVA_DOCKER_DIR } from '../../generator-constants.mjs'; -import { matchWrittenConfig, matchWrittenFiles } from '../../../test/support/matcher.mjs'; - -const expectedElasticsearchFiles = () => { - return [`${JAVA_DOCKER_DIR}elasticsearch.yml`]; -}; - -const expectedElasticsearchUserFiles = (resultGetter: () => RunResult) => { - const application = ((resultGetter() as any).generator as BaseApplicationGenerator).sharedData.getApplication(); - return [`${SERVER_MAIN_SRC_DIR}${application.packageFolder}/repository/search/UserSearchRepository.java`]; -}; - -const desiredConfig = { - 'generator-jhipster': { - searchEngine: 'elasticsearch', - }, -}; - -export const matchElasticSearchDocker = (resultGetter: () => RunResult, shouldMatch: boolean) => { - matchWrittenFiles('elasticsearch', resultGetter, expectedElasticsearchFiles, shouldMatch); -}; - -export const matchElasticSearch = (resultGetter: () => RunResult, shouldMatch: boolean) => { - matchWrittenConfig('elasticsearch', resultGetter, desiredConfig, shouldMatch); -}; - -export const matchElasticSearchUser = (resultGetter: () => RunResult, shouldMatch: boolean) => { - matchWrittenFiles('elasticsearch user', resultGetter, () => expectedElasticsearchUserFiles(resultGetter), shouldMatch); -}; diff --git a/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.ts b/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.ts new file mode 100644 index 000000000000..d2d2350009b6 --- /dev/null +++ b/generators/spring-data-elasticsearch/__test-support/elastic-search-matcher.ts @@ -0,0 +1,33 @@ +import type { RunResult } from 'yeoman-test'; +import type BaseApplicationGenerator from '../../base-application/index.js'; +import type { SpringBootApplication } from '../../server/types.js'; + +import { SERVER_MAIN_SRC_DIR, JAVA_DOCKER_DIR } from '../../generator-constants.js'; +import { matchWrittenConfig, matchWrittenFiles } from '../../../test/support/matcher.js'; + +const expectedElasticsearchFiles = () => { + return [`${JAVA_DOCKER_DIR}elasticsearch.yml`]; +}; + +const expectedElasticsearchUserFiles = (resultGetter: () => RunResult) => { + const application = ((resultGetter() as any).generator as BaseApplicationGenerator).sharedData.getApplication(); + return [`${SERVER_MAIN_SRC_DIR}${application.packageFolder}/repository/search/UserSearchRepository.java`]; +}; + +const desiredConfig = { + 'generator-jhipster': { + searchEngine: 'elasticsearch', + }, +}; + +export const matchElasticSearchDocker = (resultGetter: () => RunResult, shouldMatch: boolean) => { + matchWrittenFiles('elasticsearch', resultGetter, expectedElasticsearchFiles, shouldMatch); +}; + +export const matchElasticSearch = (resultGetter: () => RunResult, shouldMatch: boolean) => { + matchWrittenConfig('elasticsearch', resultGetter, desiredConfig, shouldMatch); +}; + +export const matchElasticSearchUser = (resultGetter: () => RunResult, shouldMatch: boolean) => { + matchWrittenFiles('elasticsearch user', resultGetter, () => expectedElasticsearchUserFiles(resultGetter), shouldMatch); +}; diff --git a/generators/spring-data-elasticsearch/cleanup.mjs b/generators/spring-data-elasticsearch/cleanup.js similarity index 100% rename from generators/spring-data-elasticsearch/cleanup.mjs rename to generators/spring-data-elasticsearch/cleanup.js diff --git a/generators/spring-data-elasticsearch/entity-files.js b/generators/spring-data-elasticsearch/entity-files.js new file mode 100644 index 000000000000..19c07a4825c1 --- /dev/null +++ b/generators/spring-data-elasticsearch/entity-files.js @@ -0,0 +1,44 @@ +/** + * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../server/support/index.js'; + +export const entityFiles = { + elasticSearchFiles: [ + { + condition: generator => !generator.embedded, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['repository/search/_entityClass_SearchRepository.java'], + }, + { + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.elastic_search'], + }, + ], +}; + +export function cleanupElasticsearchEntityFilesTask() {} + +export default async function writeEntityElasticsearchFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer && entity.searchEngineElasticsearch)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/spring-data-elasticsearch/entity-files.mjs b/generators/spring-data-elasticsearch/entity-files.mjs deleted file mode 100644 index 2bf7e9556ed2..000000000000 --- a/generators/spring-data-elasticsearch/entity-files.mjs +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../server/support/index.mjs'; - -export const entityFiles = { - elasticSearchFiles: [ - { - condition: generator => !generator.embedded, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['repository/search/_entityClass_SearchRepository.java'], - }, - { - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.elastic_search'], - }, - ], -}; - -export function cleanupElasticsearchEntityFilesTask() {} - -export default async function writeEntityElasticsearchFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer && entity.searchEngineElasticsearch)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/spring-data-elasticsearch/files.js b/generators/spring-data-elasticsearch/files.js new file mode 100644 index 000000000000..e0f03ab67908 --- /dev/null +++ b/generators/spring-data-elasticsearch/files.js @@ -0,0 +1,57 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; + +export const files = { + serverResource: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'config/ElasticsearchConfiguration.java', + 'web/rest/errors/ElasticsearchExceptionMapper.java', + 'web/rest/errors/QuerySyntaxException.java', + ], + }, + { + condition: generator => generator.generateBuiltInUserEntity || generator.authenticationTypeOauth2, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['repository/search/UserSearchRepository.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'config/ElasticsearchTestConfiguration.java', + 'config/ElasticsearchTestContainer.java', + 'config/EmbeddedElasticsearch.java', + 'web/rest/errors/ElasticsearchExceptionMapperTest.java', + ], + }, + ], +}; + +export default async function writeElasticsearchFilesTask({ application }) { + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/spring-data-elasticsearch/files.mjs b/generators/spring-data-elasticsearch/files.mjs deleted file mode 100644 index 72e72902afea..000000000000 --- a/generators/spring-data-elasticsearch/files.mjs +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; - -export const files = { - serverResource: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'config/ElasticsearchConfiguration.java', - 'web/rest/errors/ElasticsearchExceptionMapper.java', - 'web/rest/errors/QuerySyntaxException.java', - ], - }, - { - condition: generator => generator.generateBuiltInUserEntity || generator.authenticationTypeOauth2, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['repository/search/UserSearchRepository.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'config/ElasticsearchTestConfiguration.java', - 'config/ElasticsearchTestContainer.java', - 'config/EmbeddedElasticsearch.java', - 'web/rest/errors/ElasticsearchExceptionMapperTest.java', - ], - }, - ], -}; - -export default async function writeElasticsearchFilesTask({ application }) { - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/spring-data-elasticsearch/generator.js b/generators/spring-data-elasticsearch/generator.js new file mode 100644 index 000000000000..d7583395827f --- /dev/null +++ b/generators/spring-data-elasticsearch/generator.js @@ -0,0 +1,109 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_DATA_ELASTICSEARCH, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeElasticsearchFilesTask from './files.js'; +import cleanupElasticsearchFilesTask from './cleanup.js'; +import writeElasticsearchEntityFilesTask, { cleanupElasticsearchEntityFilesTask } from './entity-files.js'; + +export default class ElasticsearchGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_ELASTICSEARCH); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return { + cleanupElasticsearchFilesTask, + writeElasticsearchFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupElasticsearchEntityFilesTask, + writeElasticsearchEntityFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + if (application.buildToolMaven) { + source.addMavenProperty?.({ + property: 'awaitility.version', + value: application.javaDependencies.awaitility, + }); + + source.addMavenDependency?.([ + { + groupId: 'org.springframework.boot', + artifactId: 'spring-boot-starter-data-elasticsearch', + }, + { + groupId: 'org.awaitility', + artifactId: 'awaitility', + // eslint-disable-next-line no-template-curly-in-string + version: '${awaitility.version}', + scope: 'test', + }, + { + groupId: 'org.apache.commons', + artifactId: 'commons-collections4', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'junit-jupiter', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'testcontainers', + scope: 'test', + }, + { + groupId: 'org.testcontainers', + artifactId: 'elasticsearch', + scope: 'test', + }, + ]); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-data-elasticsearch/generator.mjs b/generators/spring-data-elasticsearch/generator.mjs deleted file mode 100644 index 5eec7004a8df..000000000000 --- a/generators/spring-data-elasticsearch/generator.mjs +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_DATA_ELASTICSEARCH, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeElasticsearchFilesTask from './files.mjs'; -import cleanupElasticsearchFilesTask from './cleanup.mjs'; -import writeElasticsearchEntityFilesTask, { cleanupElasticsearchEntityFilesTask } from './entity-files.mjs'; - -export default class ElasticsearchGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_ELASTICSEARCH); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return { - cleanupElasticsearchFilesTask, - writeElasticsearchFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupElasticsearchEntityFilesTask, - writeElasticsearchEntityFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - if (application.buildToolMaven) { - source.addMavenProperty?.({ - property: 'awaitility.version', - value: application.javaDependencies.awaitility, - }); - - source.addMavenDependency?.([ - { - groupId: 'org.springframework.boot', - artifactId: 'spring-boot-starter-data-elasticsearch', - }, - { - groupId: 'org.awaitility', - artifactId: 'awaitility', - // eslint-disable-next-line no-template-curly-in-string - version: '${awaitility.version}', - scope: 'test', - }, - { - groupId: 'org.apache.commons', - artifactId: 'commons-collections4', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'junit-jupiter', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'testcontainers', - scope: 'test', - }, - { - groupId: 'org.testcontainers', - artifactId: 'elasticsearch', - scope: 'test', - }, - ]); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-data-elasticsearch/generator.spec.mts b/generators/spring-data-elasticsearch/generator.spec.mts deleted file mode 100644 index 8f9841ab30dc..000000000000 --- a/generators/spring-data-elasticsearch/generator.spec.mts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildServerMatrix, - extendMatrix, - entitiesServerSamples as entities, - buildSamplesFromMatrix, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; -import { matchElasticSearch, matchElasticSearchUser } from './__test-support/elastic-search-matcher.mjs'; - -import { databaseTypes, searchEngineTypes, authenticationTypes, applicationTypes } from '../../jdl/jhipster/index.mjs'; -import { mockedGenerators, shouldComposeWithSpringCloudStream } from '../server/__test-support/index.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -// compose with server generator, many conditionals at server generator -const serverGeneratorFile = join(__dirname, '../server/index.mjs'); - -const { SQL, CASSANDRA, MONGODB, NEO4J } = databaseTypes; -const commonConfig = { baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; -const { ELASTICSEARCH } = searchEngineTypes; -const { OAUTH2 } = authenticationTypes; -const { MICROSERVICE } = applicationTypes; - -let samples = buildServerMatrix(); - -samples = extendMatrix(samples, { - databaseType: [SQL, CASSANDRA, MONGODB, NEO4J], - searchEngine: [ELASTICSEARCH], -}); - -const testSamples = buildSamplesFromMatrix(samples, { commonConfig }); - -describe('generator - elasticsearch', () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { enableTranslation } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(serverGeneratorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers.run(serverGeneratorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); - }); - - it('should compose with jhipster:common', () => { - expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); - }); - it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { - expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); - }); - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - - describe('searchEngine', () => { - const elasticsearch = sampleConfig.searchEngine === ELASTICSEARCH; - matchElasticSearch(() => runResult, elasticsearch); - matchElasticSearchUser( - () => runResult, - elasticsearch && - (sampleConfig.authenticationType === OAUTH2 || - (sampleConfig.applicationType !== MICROSERVICE && !sampleConfig.skipUserManagement)), - ); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-elasticsearch/generator.spec.ts b/generators/spring-data-elasticsearch/generator.spec.ts new file mode 100644 index 000000000000..90c7d4ff188f --- /dev/null +++ b/generators/spring-data-elasticsearch/generator.spec.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildServerMatrix, + extendMatrix, + entitiesServerSamples as entities, + buildSamplesFromMatrix, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; +import { matchElasticSearch, matchElasticSearchUser } from './__test-support/elastic-search-matcher.js'; + +import { databaseTypes, searchEngineTypes, authenticationTypes, applicationTypes } from '../../jdl/jhipster/index.js'; +import { mockedGenerators, shouldComposeWithSpringCloudStream } from '../server/__test-support/index.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +// compose with server generator, many conditionals at server generator +const serverGeneratorFile = join(__dirname, '../server/index.js'); + +const { SQL, CASSANDRA, MONGODB, NEO4J } = databaseTypes; +const commonConfig = { baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; +const { ELASTICSEARCH } = searchEngineTypes; +const { OAUTH2 } = authenticationTypes; +const { MICROSERVICE } = applicationTypes; + +let samples = buildServerMatrix(); + +samples = extendMatrix(samples, { + databaseType: [SQL, CASSANDRA, MONGODB, NEO4J], + searchEngine: [ELASTICSEARCH], +}); + +const testSamples = buildSamplesFromMatrix(samples, { commonConfig }); + +describe('generator - elasticsearch', () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { enableTranslation } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(serverGeneratorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers.run(serverGeneratorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); + }); + + it('should compose with jhipster:common', () => { + expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); + }); + it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { + expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); + }); + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + + describe('searchEngine', () => { + const elasticsearch = sampleConfig.searchEngine === ELASTICSEARCH; + matchElasticSearch(() => runResult, elasticsearch); + matchElasticSearchUser( + () => runResult, + elasticsearch && + (sampleConfig.authenticationType === OAUTH2 || + (sampleConfig.applicationType !== MICROSERVICE && !sampleConfig.skipUserManagement)), + ); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-elasticsearch/index.mts b/generators/spring-data-elasticsearch/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-data-elasticsearch/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-data-elasticsearch/index.ts b/generators/spring-data-elasticsearch/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-data-elasticsearch/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-mongodb/__snapshots__/generator.spec.mts.snap b/generators/spring-data-mongodb/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-mongodb/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-mongodb/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-mongodb/cleanup.mjs b/generators/spring-data-mongodb/cleanup.js similarity index 100% rename from generators/spring-data-mongodb/cleanup.mjs rename to generators/spring-data-mongodb/cleanup.js diff --git a/generators/spring-data-mongodb/entity-files.js b/generators/spring-data-mongodb/entity-files.js new file mode 100644 index 000000000000..854656d0a580 --- /dev/null +++ b/generators/spring-data-mongodb/entity-files.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../server/support/index.js'; + +export const entityFiles = { + server: [ + { + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_mongodb'], + }, + ], +}; + +export function cleanupMongodbEntityFilesTask() {} + +export default async function writeEntityMongodbFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/spring-data-mongodb/entity-files.mjs b/generators/spring-data-mongodb/entity-files.mjs deleted file mode 100644 index 83cd5cfde50a..000000000000 --- a/generators/spring-data-mongodb/entity-files.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../server/support/index.mjs'; - -export const entityFiles = { - server: [ - { - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_mongodb'], - }, - ], -}; - -export function cleanupMongodbEntityFilesTask() {} - -export default async function writeEntityMongodbFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/spring-data-mongodb/files.js b/generators/spring-data-mongodb/files.js new file mode 100644 index 000000000000..0abfa97a1885 --- /dev/null +++ b/generators/spring-data-mongodb/files.js @@ -0,0 +1,48 @@ +/** + * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; + +export const mongoDbFiles = { + serverResource: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/DatabaseConfiguration.java'], + }, + { + condition: generator => generator.generateBuiltInUserEntity, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/dbmigrations/InitialSetupMigration.java'], + }, + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: ['config/MongoDbTestContainer.java', 'config/EmbeddedMongo.java'], + }, + ], +}; + +export default async function writeMongodbFilesTask({ application }) { + await this.writeFiles({ + sections: mongoDbFiles, + context: application, + }); +} diff --git a/generators/spring-data-mongodb/files.mjs b/generators/spring-data-mongodb/files.mjs deleted file mode 100644 index 5705f16d5825..000000000000 --- a/generators/spring-data-mongodb/files.mjs +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2013-2023 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 { SERVER_MAIN_SRC_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; - -export const mongoDbFiles = { - serverResource: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/DatabaseConfiguration.java'], - }, - { - condition: generator => generator.generateBuiltInUserEntity, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/dbmigrations/InitialSetupMigration.java'], - }, - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: ['config/MongoDbTestContainer.java', 'config/EmbeddedMongo.java'], - }, - ], -}; - -export default async function writeMongodbFilesTask({ application }) { - await this.writeFiles({ - sections: mongoDbFiles, - context: application, - }); -} diff --git a/generators/spring-data-mongodb/generator.js b/generators/spring-data-mongodb/generator.js new file mode 100644 index 000000000000..264abf89601b --- /dev/null +++ b/generators/spring-data-mongodb/generator.js @@ -0,0 +1,101 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_DATA_MONGODB, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeMongodbFilesTask from './files.js'; +import cleanupMongodbFilesTask from './cleanup.js'; +import writeMongodbEntityFilesTask, { cleanupMongodbEntityFilesTask } from './entity-files.js'; + +export default class MongoDBGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_MONGODB); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return { + cleanupMongodbFilesTask, + writeMongodbFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupMongodbEntityFilesTask, + writeMongodbEntityFilesTask, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + const { reactive } = application; + if (application.buildToolMaven) { + source.addMavenDefinition?.({ + dependencies: [ + { groupId: 'io.mongock', artifactId: 'mongock-springboot-v3' }, + { groupId: 'org.springframework.boot', artifactId: `spring-boot-starter-data-mongodb${reactive ? '-reactive' : ''}` }, + { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'mongodb', scope: 'test' }, + ], + dependencyManagement: [ + // Fix Mongock dependencies: https://github.com/mongock/mongock-jdk17/issues/6 + { groupId: 'org.reflections', artifactId: 'reflections', version: '0.10.1' }, + ], + }); + + if (reactive) { + source.addMavenDefinition?.({ + dependencies: [ + // Mongock requires non reactive starter workaround https://github.com/mongock/mongock/issues/613. + { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-mongodb' }, + // Mongock requires non reactive driver workaround https://github.com/mongock/mongock/issues/613. + // switch to mongodb-reactive-driver + { groupId: 'io.mongock', artifactId: 'mongodb-springdata-v4-driver' }, + ], + }); + } else { + source.addMavenDefinition?.({ + dependencies: [{ groupId: 'io.mongock', artifactId: 'mongodb-springdata-v4-driver' }], + }); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-data-mongodb/generator.mjs b/generators/spring-data-mongodb/generator.mjs deleted file mode 100644 index 990e2acf013a..000000000000 --- a/generators/spring-data-mongodb/generator.mjs +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_DATA_MONGODB, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeMongodbFilesTask from './files.mjs'; -import cleanupMongodbFilesTask from './cleanup.mjs'; -import writeMongodbEntityFilesTask, { cleanupMongodbEntityFilesTask } from './entity-files.mjs'; - -export default class MongoDBGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_MONGODB); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return { - cleanupMongodbFilesTask, - writeMongodbFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupMongodbEntityFilesTask, - writeMongodbEntityFilesTask, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - const { reactive } = application; - if (application.buildToolMaven) { - source.addMavenDefinition?.({ - dependencies: [ - { groupId: 'io.mongock', artifactId: 'mongock-springboot-v3' }, - { groupId: 'org.springframework.boot', artifactId: `spring-boot-starter-data-mongodb${reactive ? '-reactive' : ''}` }, - { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'mongodb', scope: 'test' }, - ], - dependencyManagement: [ - // Fix Mongock dependencies: https://github.com/mongock/mongock-jdk17/issues/6 - { groupId: 'org.reflections', artifactId: 'reflections', version: '0.10.1' }, - ], - }); - - if (reactive) { - source.addMavenDefinition?.({ - dependencies: [ - // Mongock requires non reactive starter workaround https://github.com/mongock/mongock/issues/613. - { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-mongodb' }, - // Mongock requires non reactive driver workaround https://github.com/mongock/mongock/issues/613. - // switch to mongodb-reactive-driver - { groupId: 'io.mongock', artifactId: 'mongodb-springdata-v4-driver' }, - ], - }); - } else { - source.addMavenDefinition?.({ - dependencies: [{ groupId: 'io.mongock', artifactId: 'mongodb-springdata-v4-driver' }], - }); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-data-mongodb/generator.spec.mts b/generators/spring-data-mongodb/generator.spec.mts deleted file mode 100644 index fd1ede9d93ed..000000000000 --- a/generators/spring-data-mongodb/generator.spec.mts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildSamplesFromMatrix, - buildServerMatrix, - entitiesSimple as entities, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from '../server/index.mjs'; - -import { databaseTypes } from '../../jdl/jhipster/index.mjs'; -import { - mockedGenerators as serverGenerators, - shouldComposeWithSpringCloudStream, - shouldComposeWithLiquibase, -} from '../server/__test-support/index.mjs'; -import { GENERATOR_SPRING_DATA_MONGODB } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -// compose with server generator, many conditionals at server generator -const generatorFile = join(__dirname, '../server/index.mjs'); - -const { MONGODB: databaseType } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_MONGODB}`); - -const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); - -describe(`generator - ${databaseType}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { authenticationType } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct authenticationType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); - }); - it('contains correct databaseType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - shouldComposeWithLiquibase(false, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-mongodb/generator.spec.ts b/generators/spring-data-mongodb/generator.spec.ts new file mode 100644 index 000000000000..48bac23d8a92 --- /dev/null +++ b/generators/spring-data-mongodb/generator.spec.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2013-2023 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 { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildSamplesFromMatrix, + buildServerMatrix, + entitiesSimple as entities, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from '../server/index.js'; + +import { databaseTypes } from '../../jdl/jhipster/index.js'; +import { + mockedGenerators as serverGenerators, + shouldComposeWithSpringCloudStream, + shouldComposeWithLiquibase, +} from '../server/__test-support/index.js'; +import { GENERATOR_SPRING_DATA_MONGODB } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +// compose with server generator, many conditionals at server generator +const generatorFile = join(__dirname, '../server/index.js'); + +const { MONGODB: databaseType } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_MONGODB}`); + +const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); + +describe(`generator - ${databaseType}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { authenticationType } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(generatorFile).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers.run(generatorFile).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct authenticationType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); + }); + it('contains correct databaseType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + shouldComposeWithLiquibase(false, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-mongodb/index.mts b/generators/spring-data-mongodb/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-data-mongodb/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-data-mongodb/index.ts b/generators/spring-data-mongodb/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-data-mongodb/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-neo4j/__snapshots__/generator.spec.mts.snap b/generators/spring-data-neo4j/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-neo4j/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-neo4j/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-neo4j/cleanup.mts b/generators/spring-data-neo4j/cleanup.mts deleted file mode 100644 index ebab16cb0cb2..000000000000 --- a/generators/spring-data-neo4j/cleanup.mts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupTask(this: BaseGenerator, { application }: any) { - if (this.isJhipsterVersionLessThan('7.8.1')) { - this.removeFile(`${application.javaPackageSrcDir}AbstractNeo4jIT.java`); - } - if (this.isJhipsterVersionLessThan('7.10.0')) { - this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); - } -} diff --git a/generators/spring-data-neo4j/cleanup.ts b/generators/spring-data-neo4j/cleanup.ts new file mode 100644 index 000000000000..aa09a786b69c --- /dev/null +++ b/generators/spring-data-neo4j/cleanup.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../base-core/index.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupTask(this: BaseGenerator, { application }: any) { + if (this.isJhipsterVersionLessThan('7.8.1')) { + this.removeFile(`${application.javaPackageSrcDir}AbstractNeo4jIT.java`); + } + if (this.isJhipsterVersionLessThan('7.10.0')) { + this.removeFile(`${application.javaPackageTestDir}config/TestContainersSpringContextCustomizerFactory.java`); + } +} diff --git a/generators/spring-data-neo4j/entity-files.mts b/generators/spring-data-neo4j/entity-files.mts deleted file mode 100644 index 9a09a6e90951..000000000000 --- a/generators/spring-data-neo4j/entity-files.mts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { javaMainPackageTemplatesBlock } from '../server/support/index.mjs'; - -export const entityFiles = { - server: [ - { - condition: generator => generator.databaseTypeNeo4j, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_neo4j'], - }, - ], -}; - -export function cleanupEntitiesTask() {} - -export default async function writeEntitiesTask(this: Generator, { application, entities }) { - for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} diff --git a/generators/spring-data-neo4j/entity-files.ts b/generators/spring-data-neo4j/entity-files.ts new file mode 100644 index 000000000000..77baca6ccd13 --- /dev/null +++ b/generators/spring-data-neo4j/entity-files.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { javaMainPackageTemplatesBlock } from '../server/support/index.js'; + +export const entityFiles = { + server: [ + { + condition: generator => generator.databaseTypeNeo4j, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_neo4j'], + }, + ], +}; + +export function cleanupEntitiesTask() {} + +export default async function writeEntitiesTask(this: Generator, { application, entities }) { + for (const entity of entities.filter(entity => !entity.builtIn && !entity.skipServer)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} diff --git a/generators/spring-data-neo4j/files.mts b/generators/spring-data-neo4j/files.mts deleted file mode 100644 index 20b2f6f1f579..000000000000 --- a/generators/spring-data-neo4j/files.mts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2013-2023 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 { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.mjs'; -import { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.mjs'; -import Generator from './generator.mjs'; - -export const neo4jFiles = { - serverResource: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/DatabaseConfiguration.java_neo4j'], - }, - { - condition: generator => generator.generateBuiltInUserEntity && !generator.databaseMigrationLiquibase, - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: ['config/neo4j/Neo4jMigrations.java'], - }, - { - condition: generator => generator.generateBuiltInUserEntity && !generator.databaseMigrationLiquibase, - path: SERVER_MAIN_RES_DIR, - templates: ['config/neo4j/migrations/user__admin.json', 'config/neo4j/migrations/user__user.json'], - }, - ], - serverTestFw: [ - { - path: `${SERVER_TEST_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageTestDir, - templates: [ - 'config/Neo4jTestContainer.java', - 'config/Neo4jTestContainersSpringContextCustomizerFactory.java', - 'config/EmbeddedNeo4j.java', - ], - }, - ], -}; - -export default async function writeFilesTask(this: Generator, { application }) { - await this.writeFiles({ - sections: neo4jFiles, - context: application, - }); -} diff --git a/generators/spring-data-neo4j/files.ts b/generators/spring-data-neo4j/files.ts new file mode 100644 index 000000000000..55478e117723 --- /dev/null +++ b/generators/spring-data-neo4j/files.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2013-2023 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 { moveToJavaPackageSrcDir, moveToJavaPackageTestDir } from '../server/support/index.js'; +import { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR } from '../generator-constants.js'; +import Generator from './generator.js'; + +export const neo4jFiles = { + serverResource: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/DatabaseConfiguration.java_neo4j'], + }, + { + condition: generator => generator.generateBuiltInUserEntity && !generator.databaseMigrationLiquibase, + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: ['config/neo4j/Neo4jMigrations.java'], + }, + { + condition: generator => generator.generateBuiltInUserEntity && !generator.databaseMigrationLiquibase, + path: SERVER_MAIN_RES_DIR, + templates: ['config/neo4j/migrations/user__admin.json', 'config/neo4j/migrations/user__user.json'], + }, + ], + serverTestFw: [ + { + path: `${SERVER_TEST_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageTestDir, + templates: [ + 'config/Neo4jTestContainer.java', + 'config/Neo4jTestContainersSpringContextCustomizerFactory.java', + 'config/EmbeddedNeo4j.java', + ], + }, + ], +}; + +export default async function writeFilesTask(this: Generator, { application }) { + await this.writeFiles({ + sections: neo4jFiles, + context: application, + }); +} diff --git a/generators/spring-data-neo4j/generator.mts b/generators/spring-data-neo4j/generator.mts deleted file mode 100644 index d15facf20042..000000000000 --- a/generators/spring-data-neo4j/generator.mts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_NEO4J } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; -import writeEntitiesTask, { cleanupEntitiesTask } from './entity-files.mjs'; - -export default class Neo4jGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_NEO4J); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - const javaGenerator: any = await this.dependsOnJHipster(GENERATOR_JAVA); - javaGenerator.useJacksonIdentityInfo = true; - } - } - - get composing() { - return this.asComposingTaskGroup({ - async composing() { - if (this.jhipsterConfigWithDefaults.databaseMigration === 'liquibase') { - await this.composeWithJHipster(GENERATOR_LIQUIBASE); - } - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get preparing() { - return this.asPreparingTaskGroup({ - async preparing({ application }) { - const applicationAny = application as any; - applicationAny.devLiquibaseUrl = 'jdbc:neo4j:bolt://localhost:7687'; - applicationAny.devDatabaseUsername = ''; - applicationAny.devDatabasePassword = ''; - applicationAny.devJdbcDriver = null; - applicationAny.devHibernateDialect = null; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get configuringEachEntity() { - return this.asConfiguringEachEntityTaskGroup({ - async configuringEachEntity({ entityConfig }) { - if (entityConfig.dto && entityConfig.dto !== 'no') { - this.log.warn( - `The DTO option is not supported for Neo4j database. Neo4j persists the entire constellation, DTO causes the constelation to be incomplete. DTO is found in entity ${entityConfig.name}.`, - ); - } - }, - }); - } - - get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.configuringEachEntity); - } - - get preparingEachEntity() { - return this.asPreparingEachEntityTaskGroup({ - prepareEntity({ entity }) { - entity.relationships.forEach(relationship => { - if (relationship.persistableRelationship === undefined) { - relationship.persistableRelationship = true; - } - }); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntity); - } - - get default() { - return this.asDefaultTaskGroup({ - async checkUserRelationship({ entities }) { - entities.forEach(entity => { - if (entity.relationships.some(relationship => relationship.otherEntity.builtInUser)) { - this.log.warn( - `Relationship with User entity should be avoided for Neo4j database. Neo4j persists the entire constelation, related User entity will be updated causing security problems. Relationship with User entity is found in entity ${entity.name}.`, - ); - } - }); - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.delegateTasksToBlueprint(() => this.default); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupEntitiesTask, - writeEntitiesTask, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addTestSpringFactory({ source, application }) { - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.Neo4jTestContainersSpringContextCustomizerFactory`, - }); - }, - addDependencies({ application, source }) { - if (application.buildToolMaven) { - source.addMavenDependency?.([ - { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-neo4j' }, - { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, - { groupId: 'org.testcontainers', artifactId: 'neo4j', scope: 'test' }, - ]); - if (!application.databaseMigrationLiquibase) { - source.addMavenDependency?.([{ groupId: 'eu.michael-simons.neo4j', artifactId: 'neo4j-migrations-spring-boot-starter' }]); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } -} diff --git a/generators/spring-data-neo4j/generator.spec.mts b/generators/spring-data-neo4j/generator.spec.mts deleted file mode 100644 index 83ba839effd9..000000000000 --- a/generators/spring-data-neo4j/generator.spec.mts +++ /dev/null @@ -1,72 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; - -import { - buildSamplesFromMatrix, - buildServerMatrix, - entitiesSimple as entities, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from '../server/index.mjs'; - -import { databaseTypes } from '../../jdl/jhipster/index.mjs'; -import { mockedGenerators, shouldComposeWithSpringCloudStream, shouldComposeWithLiquibase } from '../server/__test-support/index.mjs'; -import { GENERATOR_SERVER } from '../generator-list.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -const { NEO4J: databaseType } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); - -describe(`generator - ${databaseType}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs')).GENERATOR_SPRING_DATA_NEO4J).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { authenticationType } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); - }); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct authenticationType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); - }); - it('contains correct databaseType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - shouldComposeWithLiquibase(false, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-neo4j/generator.spec.ts b/generators/spring-data-neo4j/generator.spec.ts new file mode 100644 index 000000000000..9ca61c448b82 --- /dev/null +++ b/generators/spring-data-neo4j/generator.spec.ts @@ -0,0 +1,72 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; + +import { + buildSamplesFromMatrix, + buildServerMatrix, + entitiesSimple as entities, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from '../server/index.js'; + +import { databaseTypes } from '../../jdl/jhipster/index.js'; +import { mockedGenerators, shouldComposeWithSpringCloudStream, shouldComposeWithLiquibase } from '../server/__test-support/index.js'; +import { GENERATOR_SERVER } from '../generator-list.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +const { NEO4J: databaseType } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const testSamples = buildSamplesFromMatrix(buildServerMatrix(), { commonConfig }); + +describe(`generator - ${databaseType}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js')).GENERATOR_SPRING_DATA_NEO4J).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { authenticationType } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig, entities).withMockedGenerators(mockedGenerators); + }); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct authenticationType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); + }); + it('contains correct databaseType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + shouldComposeWithLiquibase(false, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-neo4j/generator.ts b/generators/spring-data-neo4j/generator.ts new file mode 100644 index 000000000000..5f41040386c9 --- /dev/null +++ b/generators/spring-data-neo4j/generator.ts @@ -0,0 +1,169 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_NEO4J } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; +import writeEntitiesTask, { cleanupEntitiesTask } from './entity-files.js'; + +export default class Neo4jGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_NEO4J); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + const javaGenerator: any = await this.dependsOnJHipster(GENERATOR_JAVA); + javaGenerator.useJacksonIdentityInfo = true; + } + } + + get composing() { + return this.asComposingTaskGroup({ + async composing() { + if (this.jhipsterConfigWithDefaults.databaseMigration === 'liquibase') { + await this.composeWithJHipster(GENERATOR_LIQUIBASE); + } + }, + }); + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get preparing() { + return this.asPreparingTaskGroup({ + async preparing({ application }) { + const applicationAny = application as any; + applicationAny.devLiquibaseUrl = 'jdbc:neo4j:bolt://localhost:7687'; + applicationAny.devDatabaseUsername = ''; + applicationAny.devDatabasePassword = ''; + applicationAny.devJdbcDriver = null; + applicationAny.devHibernateDialect = null; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get configuringEachEntity() { + return this.asConfiguringEachEntityTaskGroup({ + async configuringEachEntity({ entityConfig }) { + if (entityConfig.dto && entityConfig.dto !== 'no') { + this.log.warn( + `The DTO option is not supported for Neo4j database. Neo4j persists the entire constellation, DTO causes the constelation to be incomplete. DTO is found in entity ${entityConfig.name}.`, + ); + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.configuringEachEntity); + } + + get preparingEachEntity() { + return this.asPreparingEachEntityTaskGroup({ + prepareEntity({ entity }) { + entity.relationships.forEach(relationship => { + if (relationship.persistableRelationship === undefined) { + relationship.persistableRelationship = true; + } + }); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntity); + } + + get default() { + return this.asDefaultTaskGroup({ + async checkUserRelationship({ entities }) { + entities.forEach(entity => { + if (entity.relationships.some(relationship => relationship.otherEntity.builtInUser)) { + this.log.warn( + `Relationship with User entity should be avoided for Neo4j database. Neo4j persists the entire constelation, related User entity will be updated causing security problems. Relationship with User entity is found in entity ${entity.name}.`, + ); + } + }); + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.delegateTasksToBlueprint(() => this.default); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupEntitiesTask, + writeEntitiesTask, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addTestSpringFactory({ source, application }) { + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.Neo4jTestContainersSpringContextCustomizerFactory`, + }); + }, + addDependencies({ application, source }) { + if (application.buildToolMaven) { + source.addMavenDependency?.([ + { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-neo4j' }, + { groupId: 'org.testcontainers', artifactId: 'junit-jupiter', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'testcontainers', scope: 'test' }, + { groupId: 'org.testcontainers', artifactId: 'neo4j', scope: 'test' }, + ]); + if (!application.databaseMigrationLiquibase) { + source.addMavenDependency?.([{ groupId: 'eu.michael-simons.neo4j', artifactId: 'neo4j-migrations-spring-boot-starter' }]); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } +} diff --git a/generators/spring-data-neo4j/index.mts b/generators/spring-data-neo4j/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-data-neo4j/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-data-neo4j/index.ts b/generators/spring-data-neo4j/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-data-neo4j/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/spring-data-relational/__snapshots__/generator.spec.mts.snap b/generators/spring-data-relational/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-data-relational/__snapshots__/generator.spec.mts.snap rename to generators/spring-data-relational/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-data-relational/__snapshots__/sql-entities.spec.mts.snap b/generators/spring-data-relational/__snapshots__/sql-entities.spec.ts.snap similarity index 100% rename from generators/spring-data-relational/__snapshots__/sql-entities.spec.mts.snap rename to generators/spring-data-relational/__snapshots__/sql-entities.spec.ts.snap diff --git a/generators/spring-data-relational/cleanup.mts b/generators/spring-data-relational/cleanup.mts deleted file mode 100644 index 22caf0696ed0..000000000000 --- a/generators/spring-data-relational/cleanup.mts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; - -/** - * Removes server files that where generated in previous JHipster versions and therefore - * need to be removed. - */ -export default function cleanupOldServerFilesTask(this: Generator, { application }: any) { - if (this.isJhipsterVersionLessThan('4.0.0')) { - if (application.devDatabaseTypeH2Any) { - this.removeFile(`${application.javaPackageSrcDir}domain/util/FixedH2Dialect.java`); - } - if (application.prodDatabaseTypePostgresql) { - this.removeFile(`${application.javaPackageSrcDir}domain/util/FixedPostgreSQL82Dialect`); - } - } - if (this.isJhipsterVersionLessThan('7.8.2')) { - this.removeFile(`${application.srcTestResources}config/application-testcontainers.yml`); - if (application.reactive) { - this.removeFile(`${application.javaPackageTestDir}ReactiveSqlTestContainerExtension.java`); - } - } - if (application.prodDatabaseTypeMysql && this.isJhipsterVersionLessThan('7.9.0')) { - this.removeFile(`${application.srcTestResources}testcontainers/mysql/my.cnf`); - } -} diff --git a/generators/spring-data-relational/cleanup.ts b/generators/spring-data-relational/cleanup.ts new file mode 100644 index 000000000000..993ff1b79a01 --- /dev/null +++ b/generators/spring-data-relational/cleanup.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; + +/** + * Removes server files that where generated in previous JHipster versions and therefore + * need to be removed. + */ +export default function cleanupOldServerFilesTask(this: Generator, { application }: any) { + if (this.isJhipsterVersionLessThan('4.0.0')) { + if (application.devDatabaseTypeH2Any) { + this.removeFile(`${application.javaPackageSrcDir}domain/util/FixedH2Dialect.java`); + } + if (application.prodDatabaseTypePostgresql) { + this.removeFile(`${application.javaPackageSrcDir}domain/util/FixedPostgreSQL82Dialect`); + } + } + if (this.isJhipsterVersionLessThan('7.8.2')) { + this.removeFile(`${application.srcTestResources}config/application-testcontainers.yml`); + if (application.reactive) { + this.removeFile(`${application.javaPackageTestDir}ReactiveSqlTestContainerExtension.java`); + } + } + if (application.prodDatabaseTypeMysql && this.isJhipsterVersionLessThan('7.9.0')) { + this.removeFile(`${application.srcTestResources}testcontainers/mysql/my.cnf`); + } +} diff --git a/generators/spring-data-relational/command.mts b/generators/spring-data-relational/command.mts deleted file mode 100644 index 5203970d9fcb..000000000000 --- a/generators/spring-data-relational/command.mts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - devDatabaseType: { - description: 'Development database', - type: String, - scope: 'storage', - }, - }, -}; - -export default command; diff --git a/generators/spring-data-relational/command.ts b/generators/spring-data-relational/command.ts new file mode 100644 index 000000000000..dcdb19dad65d --- /dev/null +++ b/generators/spring-data-relational/command.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + devDatabaseType: { + description: 'Development database', + type: String, + scope: 'storage', + }, + }, +}; + +export default command; diff --git a/generators/spring-data-relational/entity-files.js b/generators/spring-data-relational/entity-files.js new file mode 100644 index 000000000000..d6379a0c7acc --- /dev/null +++ b/generators/spring-data-relational/entity-files.js @@ -0,0 +1,95 @@ +/** + * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../java/support/index.js'; + +const sqlFiles = { + sqlFiles: [ + { + condition: generator => !generator.reactive, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.jakarta_persistence'], + }, + { + condition: generator => !generator.reactive && generator.requiresPersistableImplementation, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.jakarta_lifecycle_events'], + }, + { + condition: generator => !generator.reactive && generator.enableHibernateCache, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.hibernate_cache'], + }, + { + condition: generator => !generator.reactive && !generator.embedded && generator.containsBagRelationships, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: [ + 'repository/_entityClass_RepositoryWithBagRelationships.java', + 'repository/_entityClass_RepositoryWithBagRelationshipsImpl.java', + ], + }, + { + condition: generator => generator.reactive, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_reactive'], + }, + { + condition: generator => generator.requiresPersistableImplementation, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_.java.jhi.spring_data_persistable'], + }, + { + condition: generator => generator.reactive && generator.requiresPersistableImplementation, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_Callback.java'], + }, + { + condition: generator => generator.reactive && !generator.embedded, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: [ + 'repository/_entityClass_RepositoryInternalImpl_reactive.java', + 'repository/_entityClass_SqlHelper_reactive.java', + 'repository/rowmapper/_entityClass_RowMapper_reactive.java', + ], + }, + ], +}; + +export function cleanupEntitiesTask() {} + +export default async function writeEntitiesTask({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipServer)) { + if (entity.builtInUser) { + await this.writeFiles({ + blocks: [ + { + condition: generator => generator.reactive && generator.requiresPersistableImplementation, + ...javaMainPackageTemplatesBlock('_entityPackage_'), + templates: ['domain/_persistClass_Callback.java'], + }, + ], + context: { ...application, ...entity }, + }); + } else if (!entity.builtIn) { + await this.writeFiles({ + sections: sqlFiles, + context: { ...application, ...entity }, + }); + } + } +} diff --git a/generators/spring-data-relational/entity-files.mjs b/generators/spring-data-relational/entity-files.mjs deleted file mode 100644 index 02fda1929b44..000000000000 --- a/generators/spring-data-relational/entity-files.mjs +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2013-2023 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 { javaMainPackageTemplatesBlock } from '../java/support/index.mjs'; - -const sqlFiles = { - sqlFiles: [ - { - condition: generator => !generator.reactive, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.jakarta_persistence'], - }, - { - condition: generator => !generator.reactive && generator.requiresPersistableImplementation, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.jakarta_lifecycle_events'], - }, - { - condition: generator => !generator.reactive && generator.enableHibernateCache, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.hibernate_cache'], - }, - { - condition: generator => !generator.reactive && !generator.embedded && generator.containsBagRelationships, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: [ - 'repository/_entityClass_RepositoryWithBagRelationships.java', - 'repository/_entityClass_RepositoryWithBagRelationshipsImpl.java', - ], - }, - { - condition: generator => generator.reactive, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_reactive'], - }, - { - condition: generator => generator.requiresPersistableImplementation, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_.java.jhi.spring_data_persistable'], - }, - { - condition: generator => generator.reactive && generator.requiresPersistableImplementation, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_Callback.java'], - }, - { - condition: generator => generator.reactive && !generator.embedded, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: [ - 'repository/_entityClass_RepositoryInternalImpl_reactive.java', - 'repository/_entityClass_SqlHelper_reactive.java', - 'repository/rowmapper/_entityClass_RowMapper_reactive.java', - ], - }, - ], -}; - -export function cleanupEntitiesTask() {} - -export default async function writeEntitiesTask({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipServer)) { - if (entity.builtInUser) { - await this.writeFiles({ - blocks: [ - { - condition: generator => generator.reactive && generator.requiresPersistableImplementation, - ...javaMainPackageTemplatesBlock('_entityPackage_'), - templates: ['domain/_persistClass_Callback.java'], - }, - ], - context: { ...application, ...entity }, - }); - } else if (!entity.builtIn) { - await this.writeFiles({ - sections: sqlFiles, - context: { ...application, ...entity }, - }); - } - } -} diff --git a/generators/spring-data-relational/files.js b/generators/spring-data-relational/files.js new file mode 100644 index 000000000000..690f63d0d7b5 --- /dev/null +++ b/generators/spring-data-relational/files.js @@ -0,0 +1,135 @@ +/** + * Copyright 2013-2023 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 { mergeSections, addSectionsCondition } from '../base/support/index.js'; +import { + javaMainPackageTemplatesBlock, + javaTestPackageTemplatesBlock, + javaTestResourceTemplatesBlock, + javaMainResourceTemplatesBlock, +} from '../java/support/index.js'; + +export const sqlFiles = { + serverFiles: [ + { + ...javaMainPackageTemplatesBlock(), + templates: ['config/DatabaseConfiguration.java'], + }, + ], + reactiveJavaUserManagement: [ + { + condition: generator => generator.reactive && generator.generateBuiltInUserEntity, + ...javaMainPackageTemplatesBlock(), + templates: ['repository/UserSqlHelper_reactive.java', 'repository/rowmapper/UserRowMapper_reactive.java'], + }, + ], + reactiveCommon: [ + { + condition: generator => generator.reactive, + ...javaMainPackageTemplatesBlock(), + templates: ['repository/rowmapper/ColumnConverter_reactive.java', 'repository/EntityManager_reactive.java'], + }, + ], + hibernate: [ + { + condition: generator => !generator.reactive, + ...javaTestPackageTemplatesBlock(), + templates: [ + 'config/timezone/HibernateTimeZoneIT.java', + 'repository/timezone/DateTimeWrapper.java', + 'repository/timezone/DateTimeWrapperRepository.java', + ], + }, + ], + testContainers: [ + { + ...javaTestPackageTemplatesBlock(), + templates: ['config/EmbeddedSQL.java', 'config/SqlTestContainer.java', 'config/SqlTestContainersSpringContextCustomizerFactory.java'], + }, + { + ...javaTestResourceTemplatesBlock(), + templates: ['config/application-testdev.yml'], + }, + { + condition: generator => !generator.reactive, + ...javaTestResourceTemplatesBlock(), + templates: ['config/application-testprod.yml'], + }, + ], +}; + +export const h2Files = { + serverResource: [ + { + ...javaMainResourceTemplatesBlock(), + templates: ['.h2.server.properties'], + }, + ], +}; + +export const mysqlFiles = { + serverTestSources: [ + { + ...javaTestPackageTemplatesBlock(), + templates: ['config/MysqlTestContainer.java'], + }, + ], +}; + +export const mariadbFiles = { + serverTestSources: [ + { + ...javaTestPackageTemplatesBlock(), + templates: ['config/MariadbTestContainer.java'], + }, + ], +}; + +export const mssqlFiles = { + serverTestSources: [ + { + ...javaTestPackageTemplatesBlock(), + templates: ['config/MsSqlTestContainer.java'], + }, + ], +}; + +export const postgresFiles = { + serverTestSources: [ + { + ...javaTestPackageTemplatesBlock(), + templates: ['config/PostgreSqlTestContainer.java'], + }, + ], +}; + +export const serverFiles = mergeSections( + sqlFiles, + addSectionsCondition(h2Files, context => context.devDatabaseTypeH2Any), + addSectionsCondition(mysqlFiles, context => context.prodDatabaseTypeMysql), + addSectionsCondition(mariadbFiles, context => context.prodDatabaseTypeMariadb), + addSectionsCondition(mssqlFiles, context => context.prodDatabaseTypeMssql), + addSectionsCondition(postgresFiles, context => context.prodDatabaseTypePostgresql), +); + +export default async function writeSqlFiles({ application }) { + await this.writeFiles({ + sections: serverFiles, + context: application, + }); +} diff --git a/generators/spring-data-relational/files.mjs b/generators/spring-data-relational/files.mjs deleted file mode 100644 index 278402872c25..000000000000 --- a/generators/spring-data-relational/files.mjs +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright 2013-2023 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 { mergeSections, addSectionsCondition } from '../base/support/index.mjs'; -import { - javaMainPackageTemplatesBlock, - javaTestPackageTemplatesBlock, - javaTestResourceTemplatesBlock, - javaMainResourceTemplatesBlock, -} from '../java/support/index.mjs'; - -export const sqlFiles = { - serverFiles: [ - { - ...javaMainPackageTemplatesBlock(), - templates: ['config/DatabaseConfiguration.java'], - }, - ], - reactiveJavaUserManagement: [ - { - condition: generator => generator.reactive && generator.generateBuiltInUserEntity, - ...javaMainPackageTemplatesBlock(), - templates: ['repository/UserSqlHelper_reactive.java', 'repository/rowmapper/UserRowMapper_reactive.java'], - }, - ], - reactiveCommon: [ - { - condition: generator => generator.reactive, - ...javaMainPackageTemplatesBlock(), - templates: ['repository/rowmapper/ColumnConverter_reactive.java', 'repository/EntityManager_reactive.java'], - }, - ], - hibernate: [ - { - condition: generator => !generator.reactive, - ...javaTestPackageTemplatesBlock(), - templates: [ - 'config/timezone/HibernateTimeZoneIT.java', - 'repository/timezone/DateTimeWrapper.java', - 'repository/timezone/DateTimeWrapperRepository.java', - ], - }, - ], - testContainers: [ - { - ...javaTestPackageTemplatesBlock(), - templates: ['config/EmbeddedSQL.java', 'config/SqlTestContainer.java', 'config/SqlTestContainersSpringContextCustomizerFactory.java'], - }, - { - ...javaTestResourceTemplatesBlock(), - templates: ['config/application-testdev.yml'], - }, - { - condition: generator => !generator.reactive, - ...javaTestResourceTemplatesBlock(), - templates: ['config/application-testprod.yml'], - }, - ], -}; - -export const h2Files = { - serverResource: [ - { - ...javaMainResourceTemplatesBlock(), - templates: ['.h2.server.properties'], - }, - ], -}; - -export const mysqlFiles = { - serverTestSources: [ - { - ...javaTestPackageTemplatesBlock(), - templates: ['config/MysqlTestContainer.java'], - }, - ], -}; - -export const mariadbFiles = { - serverTestSources: [ - { - ...javaTestPackageTemplatesBlock(), - templates: ['config/MariadbTestContainer.java'], - }, - ], -}; - -export const mssqlFiles = { - serverTestSources: [ - { - ...javaTestPackageTemplatesBlock(), - templates: ['config/MsSqlTestContainer.java'], - }, - ], -}; - -export const postgresFiles = { - serverTestSources: [ - { - ...javaTestPackageTemplatesBlock(), - templates: ['config/PostgreSqlTestContainer.java'], - }, - ], -}; - -export const serverFiles = mergeSections( - sqlFiles, - addSectionsCondition(h2Files, context => context.devDatabaseTypeH2Any), - addSectionsCondition(mysqlFiles, context => context.prodDatabaseTypeMysql), - addSectionsCondition(mariadbFiles, context => context.prodDatabaseTypeMariadb), - addSectionsCondition(mssqlFiles, context => context.prodDatabaseTypeMssql), - addSectionsCondition(postgresFiles, context => context.prodDatabaseTypePostgresql), -); - -export default async function writeSqlFiles({ application }) { - await this.writeFiles({ - sections: serverFiles, - context: application, - }); -} diff --git a/generators/spring-data-relational/generator.mts b/generators/spring-data-relational/generator.mts deleted file mode 100644 index 51165920d8e9..000000000000 --- a/generators/spring-data-relational/generator.mts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_DATA_RELATIONAL, GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_LIQUIBASE } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; -import writeEntitiesTask, { cleanupEntitiesTask } from './entity-files.mjs'; -import { isReservedTableName } from '../../jdl/jhipster/reserved-keywords.js'; -import { databaseTypes } from '../../jdl/jhipster/index.mjs'; -import { GeneratorDefinition as SpringBootGeneratorDefinition } from '../server/index.mjs'; -import { getDBCExtraOption, getJdbcUrl, getR2dbcUrl } from './support/index.mjs'; -import { - getCommonMavenDefinition, - getDatabaseDriverForDatabase, - getDatabaseTypeMavenDefinition, - getH2MavenDefinition, - getImperativeMavenDefinition, - getReactiveMavenDefinition, -} from './internal/dependencies.mjs'; -import command from './command.mjs'; - -const { SQL } = databaseTypes; - -export default class SqlGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_DATA_RELATIONAL); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadOptions() { - this.parseJHipsterOptions(command.options); - }, - }); - } - - get [BaseApplicationGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get composing() { - return this.asComposingTaskGroup({ - async composing() { - await this.composeWithJHipster(GENERATOR_LIQUIBASE); - }, - }); - } - - get [BaseApplicationGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get preparing() { - return this.asPreparingTaskGroup({ - async preparing({ application }) { - const anyApp = application as any; - anyApp.devDatabaseExtraOptions = getDBCExtraOption(anyApp.devDatabaseType); - anyApp.prodDatabaseExtraOptions = getDBCExtraOption(anyApp.prodDatabaseType); - - anyApp.prodDatabaseDriver = getDatabaseDriverForDatabase(application.prodDatabaseType); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get preparingEachEntityRelationship() { - return this.asPreparingEachEntityRelationshipTaskGroup({ - prepareRelationship({ application, relationship }) { - if (application.reactive) { - relationship.relationshipSqlSafeName = isReservedTableName(relationship.relationshipName, SQL) - ? `e_${relationship.relationshipName}` - : relationship.relationshipName; - } - }, - }); - } - - get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return { - cleanupEntitiesTask, - writeEntitiesTask, - }; - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addTestSpringFactory({ source, application }) { - source.addTestSpringFactory?.({ - key: 'org.springframework.test.context.ContextCustomizerFactory', - value: `${application.packageName}.config.SqlTestContainersSpringContextCustomizerFactory`, - }); - }, - addDependencies({ application, source }) { - if (application.buildToolMaven) { - const { reactive, javaDependencies, packageFolder } = application; - const applicationAny = application as any; - const { prodDatabaseType } = applicationAny; - source.addMavenDefinition?.(getCommonMavenDefinition({ javaDependencies })); - - if (reactive) { - source.addMavenDefinition?.(getReactiveMavenDefinition({ javaDependencies })); - } else { - source.addMavenDefinition?.(getImperativeMavenDefinition({ javaDependencies })); - } - - const inProfile = applicationAny.devDatabaseTypeH2Any ? 'prod' : undefined; - if (applicationAny.devDatabaseTypeH2Any) { - const h2Definitions = getH2MavenDefinition({ prodDatabaseType, packageFolder }); - source.addMavenDefinition?.(h2Definitions.jdbc); - if (reactive) { - source.addMavenDefinition?.(h2Definitions.r2dbc); - } - } - const dbDefinitions = getDatabaseTypeMavenDefinition(prodDatabaseType, { inProfile, javaDependencies }); - source.addMavenDefinition?.(dbDefinitions.jdbc); - if (reactive) { - source.addMavenDefinition?.(dbDefinitions.r2dbc); - } - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); - } - - /** - * @private - * Returns the JDBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getJDBCUrl(databaseType, options = {}) { - return getJdbcUrl(databaseType, options); - } - - /** - * @private - * Returns the R2DBC URL for a databaseType - * - * @param {string} databaseType - * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) - */ - getR2DBCUrl(databaseType, options = {}) { - return getR2dbcUrl(databaseType, options); - } -} diff --git a/generators/spring-data-relational/generator.spec.mts b/generators/spring-data-relational/generator.spec.mts deleted file mode 100644 index 0e9392996690..000000000000 --- a/generators/spring-data-relational/generator.spec.mts +++ /dev/null @@ -1,122 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildServerMatrix, - extendMatrix, - extendFilteredMatrix, - buildSamplesFromMatrix, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from '../server/index.mjs'; - -import { databaseTypes, cacheTypes } from '../../jdl/jhipster/index.mjs'; -import { - mockedGenerators as serverGenerators, - shouldComposeWithSpringCloudStream, - shouldComposeWithLiquibase, -} from '../server/__test-support/index.mjs'; -import { GENERATOR_SERVER, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`); - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -const { SQL: databaseType, H2_DISK, H2_MEMORY, POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; -const { NO: NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; - -let sqlSamples = buildServerMatrix({ - prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE], -}); - -sqlSamples = extendMatrix(sqlSamples, { - enableHibernateCache: [false, true], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === POSTGRESQL, { - devDatabaseType: [POSTGRESQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MARIADB, { - devDatabaseType: [MARIADB, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MYSQL, { - devDatabaseType: [MYSQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MSSQL, { - devDatabaseType: [MSSQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === ORACLE, { - devDatabaseType: [ORACLE, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ reactive }) => !reactive, { - cacheProvider: [NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], -}); - -const testSamples = buildSamplesFromMatrix(sqlSamples, { commonConfig }); - -describe(`generator - ${databaseType}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { authenticationType, enableTranslation } = sampleConfig; - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - before(async () => { - await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig).withMockedGenerators(mockedGenerators); - }); - - it('should compose with jhipster:common', () => { - expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); - }); - it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { - expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); - }); - it('should compose with jhipster:liquibase', () => { - expect(runResult.mockedGenerators['jhipster:liquibase'].callCount).toBe(1); - }); - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct authenticationType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); - }); - it('contains correct databaseType', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); - }); - shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); - shouldComposeWithLiquibase(sampleConfig, () => runResult); - }); - }); -}); diff --git a/generators/spring-data-relational/generator.spec.ts b/generators/spring-data-relational/generator.spec.ts new file mode 100644 index 000000000000..c97475fdbfbe --- /dev/null +++ b/generators/spring-data-relational/generator.spec.ts @@ -0,0 +1,122 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildServerMatrix, + extendMatrix, + extendFilteredMatrix, + buildSamplesFromMatrix, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from '../server/index.js'; + +import { databaseTypes, cacheTypes } from '../../jdl/jhipster/index.js'; +import { + mockedGenerators as serverGenerators, + shouldComposeWithSpringCloudStream, + shouldComposeWithLiquibase, +} from '../server/__test-support/index.js'; +import { GENERATOR_SERVER, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +const { SQL: databaseType, H2_DISK, H2_MEMORY, POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; +const { NO: NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; + +let sqlSamples = buildServerMatrix({ + prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE], +}); + +sqlSamples = extendMatrix(sqlSamples, { + enableHibernateCache: [false, true], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === POSTGRESQL, { + devDatabaseType: [POSTGRESQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MARIADB, { + devDatabaseType: [MARIADB, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MYSQL, { + devDatabaseType: [MYSQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MSSQL, { + devDatabaseType: [MSSQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === ORACLE, { + devDatabaseType: [ORACLE, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ reactive }) => !reactive, { + cacheProvider: [NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], +}); + +const testSamples = buildSamplesFromMatrix(sqlSamples, { commonConfig }); + +describe(`generator - ${databaseType}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { authenticationType, enableTranslation } = sampleConfig; + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + before(async () => { + await helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig).withMockedGenerators(mockedGenerators); + }); + + it('should compose with jhipster:common', () => { + expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); + }); + it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { + expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); + }); + it('should compose with jhipster:liquibase', () => { + expect(runResult.mockedGenerators['jhipster:liquibase'].callCount).toBe(1); + }); + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct authenticationType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"authenticationType": "${authenticationType}"`)); + }); + it('contains correct databaseType', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"databaseType": "${databaseType}"`)); + }); + shouldComposeWithSpringCloudStream(sampleConfig, () => runResult); + shouldComposeWithLiquibase(sampleConfig, () => runResult); + }); + }); +}); diff --git a/generators/spring-data-relational/generator.ts b/generators/spring-data-relational/generator.ts new file mode 100644 index 000000000000..da518dcaa641 --- /dev/null +++ b/generators/spring-data-relational/generator.ts @@ -0,0 +1,194 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_DATA_RELATIONAL, GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_LIQUIBASE } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; +import writeEntitiesTask, { cleanupEntitiesTask } from './entity-files.js'; +import { isReservedTableName } from '../../jdl/jhipster/reserved-keywords.js'; +import { databaseTypes } from '../../jdl/jhipster/index.js'; +import { GeneratorDefinition as SpringBootGeneratorDefinition } from '../server/index.js'; +import { getDBCExtraOption, getJdbcUrl, getR2dbcUrl } from './support/index.js'; +import { + getCommonMavenDefinition, + getDatabaseDriverForDatabase, + getDatabaseTypeMavenDefinition, + getH2MavenDefinition, + getImperativeMavenDefinition, + getReactiveMavenDefinition, +} from './internal/dependencies.js'; +import command from './command.js'; + +const { SQL } = databaseTypes; + +export default class SqlGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_DATA_RELATIONAL); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadOptions() { + this.parseJHipsterOptions(command.options); + }, + }); + } + + get [BaseApplicationGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get composing() { + return this.asComposingTaskGroup({ + async composing() { + await this.composeWithJHipster(GENERATOR_LIQUIBASE); + }, + }); + } + + get [BaseApplicationGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get preparing() { + return this.asPreparingTaskGroup({ + async preparing({ application }) { + const anyApp = application as any; + anyApp.devDatabaseExtraOptions = getDBCExtraOption(anyApp.devDatabaseType); + anyApp.prodDatabaseExtraOptions = getDBCExtraOption(anyApp.prodDatabaseType); + + anyApp.prodDatabaseDriver = getDatabaseDriverForDatabase(application.prodDatabaseType); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get preparingEachEntityRelationship() { + return this.asPreparingEachEntityRelationshipTaskGroup({ + prepareRelationship({ application, relationship }) { + if (application.reactive) { + relationship.relationshipSqlSafeName = isReservedTableName(relationship.relationshipName, SQL) + ? `e_${relationship.relationshipName}` + : relationship.relationshipName; + } + }, + }); + } + + get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return { + cleanupEntitiesTask, + writeEntitiesTask, + }; + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addTestSpringFactory({ source, application }) { + source.addTestSpringFactory?.({ + key: 'org.springframework.test.context.ContextCustomizerFactory', + value: `${application.packageName}.config.SqlTestContainersSpringContextCustomizerFactory`, + }); + }, + addDependencies({ application, source }) { + if (application.buildToolMaven) { + const { reactive, javaDependencies, packageFolder } = application; + const applicationAny = application as any; + const { prodDatabaseType } = applicationAny; + source.addMavenDefinition?.(getCommonMavenDefinition({ javaDependencies })); + + if (reactive) { + source.addMavenDefinition?.(getReactiveMavenDefinition({ javaDependencies })); + } else { + source.addMavenDefinition?.(getImperativeMavenDefinition({ javaDependencies })); + } + + const inProfile = applicationAny.devDatabaseTypeH2Any ? 'prod' : undefined; + if (applicationAny.devDatabaseTypeH2Any) { + const h2Definitions = getH2MavenDefinition({ prodDatabaseType, packageFolder }); + source.addMavenDefinition?.(h2Definitions.jdbc); + if (reactive) { + source.addMavenDefinition?.(h2Definitions.r2dbc); + } + } + const dbDefinitions = getDatabaseTypeMavenDefinition(prodDatabaseType, { inProfile, javaDependencies }); + source.addMavenDefinition?.(dbDefinitions.jdbc); + if (reactive) { + source.addMavenDefinition?.(dbDefinitions.r2dbc); + } + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup(this.delegateTasksToBlueprint(() => this.postWriting)); + } + + /** + * @private + * Returns the JDBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getJDBCUrl(databaseType, options = {}) { + return getJdbcUrl(databaseType, options); + } + + /** + * @private + * Returns the R2DBC URL for a databaseType + * + * @param {string} databaseType + * @param {*} options: databaseName, and required infos that depends of databaseType (hostname, localDirectory, ...) + */ + getR2DBCUrl(databaseType, options = {}) { + return getR2dbcUrl(databaseType, options); + } +} diff --git a/generators/spring-data-relational/index.mts b/generators/spring-data-relational/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/spring-data-relational/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/spring-data-relational/index.ts b/generators/spring-data-relational/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/spring-data-relational/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/generators/spring-data-relational/internal/dependencies.mts b/generators/spring-data-relational/internal/dependencies.mts deleted file mode 100644 index 7ad5ef10d10c..000000000000 --- a/generators/spring-data-relational/internal/dependencies.mts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright 2013-2023 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 { MavenDefinition, MavenPlugin } from '../../maven/types.mjs'; - -type DatabaseTypeDependencies = { - jdbc: MavenDefinition; - r2dbc: MavenDefinition; -}; - -const testcontainerFileForDB = { - mariadb: 'MariadbTestContainer.java', - mssql: 'MsSqlTestContainer.java', - mysql: 'MysqlTestContainer.java', - postgresql: 'PostgreSqlTestContainer.java', -}; - -type JavaArtifact = { groupId: string; artifactId: string }; -export type DatabaseArtifact = { jdbc: JavaArtifact; r2dbc: JavaArtifact }; - -const databaseArtifactForDB: Record = { - mariadb: { - jdbc: { groupId: 'org.mariadb.jdbc', artifactId: 'mariadb-java-client' }, - // maria-r2dbc driver is failing. - // r2dbc: { groupId: 'org.mariadb', artifactId: 'r2dbc-mariadb' }, - r2dbc: { groupId: 'io.asyncer', artifactId: 'r2dbc-mysql' }, - }, - mssql: { - jdbc: { groupId: 'com.microsoft.sqlserver', artifactId: 'mssql-jdbc' }, - r2dbc: { groupId: 'io.r2dbc', artifactId: 'r2dbc-mssql' }, - }, - mysql: { - jdbc: { groupId: 'com.mysql', artifactId: 'mysql-connector-j' }, - r2dbc: { groupId: 'io.asyncer', artifactId: 'r2dbc-mysql' }, - }, - postgresql: { - jdbc: { groupId: 'org.postgresql', artifactId: 'postgresql' }, - r2dbc: { groupId: 'org.postgresql', artifactId: 'r2dbc-postgresql' }, - }, -}; - -export const getDatabaseDriverForDatabase = (databaseType: string) => databaseArtifactForDB[databaseType]; - -export const getCommonMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ - properties: [ - { property: 'jaxb-runtime.version', value: javaDependencies['jaxb-runtime'] }, - { property: 'validation-api.version', value: javaDependencies['validation-api'] }, - ], - dependencies: [ - { groupId: 'com.zaxxer', artifactId: 'HikariCP' }, - { groupId: 'com.fasterxml.jackson.module', artifactId: 'jackson-module-jaxb-annotations' }, - { groupId: 'org.testcontainers', artifactId: 'jdbc', scope: 'test' }, - ], - annotationProcessors: [ - // eslint-disable-next-line no-template-curly-in-string - { groupId: 'org.glassfish.jaxb', artifactId: 'jaxb-runtime', version: '${jaxb-runtime.version}' }, - ], -}); - -export const getReactiveMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ - properties: [{ property: 'commons-beanutils.version', value: javaDependencies['commons-beanutils'] }], - dependencies: [ - { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-r2dbc' }, - // eslint-disable-next-line no-template-curly-in-string - { groupId: 'commons-beanutils', artifactId: 'commons-beanutils', version: '${commons-beanutils.version}' }, - { groupId: 'jakarta.persistence', artifactId: 'jakarta.persistence-api' }, - ], -}); - -export const getImperativeMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ - properties: [{ property: 'hibernate.version', value: javaDependencies.hibernate }], - dependencies: [ - { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-jpa' }, - { groupId: 'com.fasterxml.jackson.datatype', artifactId: 'jackson-datatype-hibernate6' }, - // TODO drop forced version. Refer to https://github.com/jhipster/generator-jhipster/issues/22579 - // eslint-disable-next-line no-template-curly-in-string - { groupId: 'org.hibernate.orm', artifactId: 'hibernate-core', version: '${hibernate.version}' }, - { groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen', scope: 'provided' }, - { groupId: 'org.hibernate.validator', artifactId: 'hibernate-validator' }, - { groupId: 'org.springframework.security', artifactId: 'spring-security-data' }, - { inProfile: 'IDE', groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen' }, - ], - annotationProcessors: [ - // eslint-disable-next-line no-template-curly-in-string - { groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen', version: '${hibernate.version}' }, - ], -}); - -export const getH2MavenDefinition = ({ - prodDatabaseType, - packageFolder, -}: { - prodDatabaseType: string; - packageFolder: string; -}): DatabaseTypeDependencies => { - const testcontainerFile = testcontainerFileForDB[prodDatabaseType]; - const excludeContainerPlugin: MavenPlugin[] = testcontainerFile - ? [ - { - inProfile: 'dev', - groupId: 'org.apache.maven.plugins', - artifactId: 'maven-compiler-plugin', - additionalContent: ` - - - ${packageFolder}config/${testcontainerFile} - - -`, - }, - ] - : []; - - return { - jdbc: { - dependencies: [{ inProfile: 'dev', groupId: 'com.h2database', artifactId: 'h2' }], - plugins: excludeContainerPlugin, - }, - r2dbc: { - dependencies: [{ inProfile: 'dev', groupId: 'io.r2dbc', artifactId: 'r2dbc-h2' }], - }, - }; -}; - -// eslint-disable-next-line import/prefer-default-export -export const getDatabaseTypeMavenDefinition: ( - databaseType: string, - options: { inProfile?: string; javaDependencies: Record }, -) => DatabaseTypeDependencies = (databaseType, { inProfile }) => { - const dependenciesForType: Record = { - mariadb: { - jdbc: { - dependencies: [ - { inProfile, ...databaseArtifactForDB.mariadb.jdbc }, - { groupId: 'org.testcontainers', artifactId: 'mariadb', scope: 'test' }, - ], - }, - r2dbc: { - dependencies: [{ inProfile, ...databaseArtifactForDB.mariadb.r2dbc }], - }, - }, - mssql: { - jdbc: { - dependencies: [ - { inProfile, ...databaseArtifactForDB.mssql.jdbc }, - { groupId: 'org.testcontainers', artifactId: 'mssqlserver', scope: 'test' }, - ], - }, - r2dbc: { - dependencies: [{ inProfile, ...databaseArtifactForDB.mssql.r2dbc }], - }, - }, - mysql: { - jdbc: { - dependencies: [ - { inProfile, ...databaseArtifactForDB.mysql.jdbc }, - { groupId: 'org.testcontainers', artifactId: 'mysql', scope: 'test' }, - ], - }, - r2dbc: { - dependencies: [{ inProfile, ...databaseArtifactForDB.mysql.r2dbc }], - }, - }, - oracle: { - jdbc: { - dependencies: [ - { inProfile, groupId: 'com.oracle.database.jdbc', artifactId: 'ojdbc8' }, - { groupId: 'org.testcontainers', artifactId: 'oracle-xe', scope: 'test' }, - ], - }, - r2dbc: {}, - }, - postgresql: { - jdbc: { - dependencies: [ - { inProfile, ...databaseArtifactForDB.postgresql.jdbc }, - { groupId: 'org.testcontainers', artifactId: 'postgresql', scope: 'test' }, - ], - }, - r2dbc: { - dependencies: [{ inProfile, ...databaseArtifactForDB.postgresql.r2dbc }], - }, - }, - }; - return dependenciesForType[databaseType]; -}; diff --git a/generators/spring-data-relational/internal/dependencies.ts b/generators/spring-data-relational/internal/dependencies.ts new file mode 100644 index 000000000000..421c1fb68ab8 --- /dev/null +++ b/generators/spring-data-relational/internal/dependencies.ts @@ -0,0 +1,202 @@ +/** + * Copyright 2013-2023 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 { MavenDefinition, MavenPlugin } from '../../maven/types.js'; + +type DatabaseTypeDependencies = { + jdbc: MavenDefinition; + r2dbc: MavenDefinition; +}; + +const testcontainerFileForDB = { + mariadb: 'MariadbTestContainer.java', + mssql: 'MsSqlTestContainer.java', + mysql: 'MysqlTestContainer.java', + postgresql: 'PostgreSqlTestContainer.java', +}; + +type JavaArtifact = { groupId: string; artifactId: string }; +export type DatabaseArtifact = { jdbc: JavaArtifact; r2dbc: JavaArtifact }; + +const databaseArtifactForDB: Record = { + mariadb: { + jdbc: { groupId: 'org.mariadb.jdbc', artifactId: 'mariadb-java-client' }, + // maria-r2dbc driver is failing. + // r2dbc: { groupId: 'org.mariadb', artifactId: 'r2dbc-mariadb' }, + r2dbc: { groupId: 'io.asyncer', artifactId: 'r2dbc-mysql' }, + }, + mssql: { + jdbc: { groupId: 'com.microsoft.sqlserver', artifactId: 'mssql-jdbc' }, + r2dbc: { groupId: 'io.r2dbc', artifactId: 'r2dbc-mssql' }, + }, + mysql: { + jdbc: { groupId: 'com.mysql', artifactId: 'mysql-connector-j' }, + r2dbc: { groupId: 'io.asyncer', artifactId: 'r2dbc-mysql' }, + }, + postgresql: { + jdbc: { groupId: 'org.postgresql', artifactId: 'postgresql' }, + r2dbc: { groupId: 'org.postgresql', artifactId: 'r2dbc-postgresql' }, + }, +}; + +export const getDatabaseDriverForDatabase = (databaseType: string) => databaseArtifactForDB[databaseType]; + +export const getCommonMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ + properties: [ + { property: 'jaxb-runtime.version', value: javaDependencies['jaxb-runtime'] }, + { property: 'validation-api.version', value: javaDependencies['validation-api'] }, + ], + dependencies: [ + { groupId: 'com.zaxxer', artifactId: 'HikariCP' }, + { groupId: 'com.fasterxml.jackson.module', artifactId: 'jackson-module-jaxb-annotations' }, + { groupId: 'org.testcontainers', artifactId: 'jdbc', scope: 'test' }, + ], + annotationProcessors: [ + // eslint-disable-next-line no-template-curly-in-string + { groupId: 'org.glassfish.jaxb', artifactId: 'jaxb-runtime', version: '${jaxb-runtime.version}' }, + ], +}); + +export const getReactiveMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ + properties: [{ property: 'commons-beanutils.version', value: javaDependencies['commons-beanutils'] }], + dependencies: [ + { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-r2dbc' }, + // eslint-disable-next-line no-template-curly-in-string + { groupId: 'commons-beanutils', artifactId: 'commons-beanutils', version: '${commons-beanutils.version}' }, + { groupId: 'jakarta.persistence', artifactId: 'jakarta.persistence-api' }, + ], +}); + +export const getImperativeMavenDefinition = ({ javaDependencies }: { javaDependencies: Record }) => ({ + properties: [{ property: 'hibernate.version', value: javaDependencies.hibernate }], + dependencies: [ + { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-data-jpa' }, + { groupId: 'com.fasterxml.jackson.datatype', artifactId: 'jackson-datatype-hibernate6' }, + // TODO drop forced version. Refer to https://github.com/jhipster/generator-jhipster/issues/22579 + // eslint-disable-next-line no-template-curly-in-string + { groupId: 'org.hibernate.orm', artifactId: 'hibernate-core', version: '${hibernate.version}' }, + { groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen', scope: 'provided' }, + { groupId: 'org.hibernate.validator', artifactId: 'hibernate-validator' }, + { groupId: 'org.springframework.security', artifactId: 'spring-security-data' }, + { inProfile: 'IDE', groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen' }, + ], + annotationProcessors: [ + // eslint-disable-next-line no-template-curly-in-string + { groupId: 'org.hibernate.orm', artifactId: 'hibernate-jpamodelgen', version: '${hibernate.version}' }, + ], +}); + +export const getH2MavenDefinition = ({ + prodDatabaseType, + packageFolder, +}: { + prodDatabaseType: string; + packageFolder: string; +}): DatabaseTypeDependencies => { + const testcontainerFile = testcontainerFileForDB[prodDatabaseType]; + const excludeContainerPlugin: MavenPlugin[] = testcontainerFile + ? [ + { + inProfile: 'dev', + groupId: 'org.apache.maven.plugins', + artifactId: 'maven-compiler-plugin', + additionalContent: ` + + + ${packageFolder}config/${testcontainerFile} + + +`, + }, + ] + : []; + + return { + jdbc: { + dependencies: [{ inProfile: 'dev', groupId: 'com.h2database', artifactId: 'h2' }], + plugins: excludeContainerPlugin, + }, + r2dbc: { + dependencies: [{ inProfile: 'dev', groupId: 'io.r2dbc', artifactId: 'r2dbc-h2' }], + }, + }; +}; + +// eslint-disable-next-line import/prefer-default-export +export const getDatabaseTypeMavenDefinition: ( + databaseType: string, + options: { inProfile?: string; javaDependencies: Record }, +) => DatabaseTypeDependencies = (databaseType, { inProfile }) => { + const dependenciesForType: Record = { + mariadb: { + jdbc: { + dependencies: [ + { inProfile, ...databaseArtifactForDB.mariadb.jdbc }, + { groupId: 'org.testcontainers', artifactId: 'mariadb', scope: 'test' }, + ], + }, + r2dbc: { + dependencies: [{ inProfile, ...databaseArtifactForDB.mariadb.r2dbc }], + }, + }, + mssql: { + jdbc: { + dependencies: [ + { inProfile, ...databaseArtifactForDB.mssql.jdbc }, + { groupId: 'org.testcontainers', artifactId: 'mssqlserver', scope: 'test' }, + ], + }, + r2dbc: { + dependencies: [{ inProfile, ...databaseArtifactForDB.mssql.r2dbc }], + }, + }, + mysql: { + jdbc: { + dependencies: [ + { inProfile, ...databaseArtifactForDB.mysql.jdbc }, + { groupId: 'org.testcontainers', artifactId: 'mysql', scope: 'test' }, + ], + }, + r2dbc: { + dependencies: [{ inProfile, ...databaseArtifactForDB.mysql.r2dbc }], + }, + }, + oracle: { + jdbc: { + dependencies: [ + { inProfile, groupId: 'com.oracle.database.jdbc', artifactId: 'ojdbc8' }, + { groupId: 'org.testcontainers', artifactId: 'oracle-xe', scope: 'test' }, + ], + }, + r2dbc: {}, + }, + postgresql: { + jdbc: { + dependencies: [ + { inProfile, ...databaseArtifactForDB.postgresql.jdbc }, + { groupId: 'org.testcontainers', artifactId: 'postgresql', scope: 'test' }, + ], + }, + r2dbc: { + dependencies: [{ inProfile, ...databaseArtifactForDB.postgresql.r2dbc }], + }, + }, + }; + return dependenciesForType[databaseType]; +}; diff --git a/generators/spring-data-relational/sql-entities.spec.mts b/generators/spring-data-relational/sql-entities.spec.mts deleted file mode 100644 index 3b6e0cee35ab..000000000000 --- a/generators/spring-data-relational/sql-entities.spec.mts +++ /dev/null @@ -1,99 +0,0 @@ -import { expect } from 'esmocha'; - -import { - buildServerMatrix, - extendMatrix, - extendFilteredMatrix, - entitiesServerSamples as entities, - buildSamplesFromMatrix, - defaultHelpers as helpers, - runResult, -} from '../../test/support/index.mjs'; -import { mockedGenerators as serverGenerators } from '../server/__test-support/index.mjs'; - -import { databaseTypes, cacheTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_SERVER, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.mjs'; - -const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`); - -const { SQL: databaseType, H2_DISK, H2_MEMORY, POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE } = databaseTypes; -const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; -const { NO: NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; - -let sqlSamples = buildServerMatrix(); - -sqlSamples = extendMatrix(sqlSamples, { - prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE], - enableHibernateCache: [false, true], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === POSTGRESQL, { - devDatabaseType: [POSTGRESQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MARIADB, { - devDatabaseType: [MARIADB, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MYSQL, { - devDatabaseType: [MYSQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MSSQL, { - devDatabaseType: [MSSQL, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === ORACLE, { - devDatabaseType: [ORACLE, H2_DISK, H2_MEMORY], -}); - -sqlSamples = extendFilteredMatrix(sqlSamples, ({ reactive }) => !reactive, { - cacheProvider: [NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], -}); - -const testSamples = buildSamplesFromMatrix(sqlSamples, { commonConfig }); -const skipPriorities = ['writing', 'postWriting']; - -describe(`generator - ${databaseType} - entities`, () => { - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { enableTranslation } = sampleConfig; - - describe(name, () => { - if ( - sampleConfig.websocket && - (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') - ) { - it('should throw an error', async () => { - await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); - }); - - return; - } - - before(async () => { - await helpers - .runJHipster(GENERATOR_SERVER) - .withJHipsterConfig(sampleConfig, entities) - .withOptions({ skipPriorities }) - .withMockedGenerators(mockedGenerators); - }); - - it('should compose with jhipster:common', () => { - expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); - }); - it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { - expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); - }); - it('should compose with jhipster:liquibase', () => { - expect(runResult.mockedGenerators['jhipster:liquibase'].callCount).toBe(1); - }); - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/generators/spring-data-relational/sql-entities.spec.ts b/generators/spring-data-relational/sql-entities.spec.ts new file mode 100644 index 000000000000..9b88c7021395 --- /dev/null +++ b/generators/spring-data-relational/sql-entities.spec.ts @@ -0,0 +1,99 @@ +import { expect } from 'esmocha'; + +import { + buildServerMatrix, + extendMatrix, + extendFilteredMatrix, + entitiesServerSamples as entities, + buildSamplesFromMatrix, + defaultHelpers as helpers, + runResult, +} from '../../test/support/index.js'; +import { mockedGenerators as serverGenerators } from '../server/__test-support/index.js'; + +import { databaseTypes, cacheTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_SERVER, GENERATOR_SPRING_DATA_RELATIONAL } from '../generator-list.js'; + +const mockedGenerators = serverGenerators.filter(generator => generator !== `jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`); + +const { SQL: databaseType, H2_DISK, H2_MEMORY, POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE } = databaseTypes; +const commonConfig = { databaseType, baseName: 'jhipster', nativeLanguage: 'en', languages: ['fr', 'en'] }; +const { NO: NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; + +let sqlSamples = buildServerMatrix(); + +sqlSamples = extendMatrix(sqlSamples, { + prodDatabaseType: [POSTGRESQL, MARIADB, MYSQL, MSSQL, ORACLE], + enableHibernateCache: [false, true], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === POSTGRESQL, { + devDatabaseType: [POSTGRESQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MARIADB, { + devDatabaseType: [MARIADB, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MYSQL, { + devDatabaseType: [MYSQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === MSSQL, { + devDatabaseType: [MSSQL, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ prodDatabaseType }) => prodDatabaseType === ORACLE, { + devDatabaseType: [ORACLE, H2_DISK, H2_MEMORY], +}); + +sqlSamples = extendFilteredMatrix(sqlSamples, ({ reactive }) => !reactive, { + cacheProvider: [NO_CACHE_PROVIDER, EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS], +}); + +const testSamples = buildSamplesFromMatrix(sqlSamples, { commonConfig }); +const skipPriorities = ['writing', 'postWriting']; + +describe(`generator - ${databaseType} - entities`, () => { + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { enableTranslation } = sampleConfig; + + describe(name, () => { + if ( + sampleConfig.websocket && + (sampleConfig.reactive || sampleConfig.applicationType === 'microservice' || sampleConfig.applicationType === 'gateway') + ) { + it('should throw an error', async () => { + await expect(helpers.runJHipster(GENERATOR_SERVER).withJHipsterConfig(sampleConfig)).rejects.toThrow(); + }); + + return; + } + + before(async () => { + await helpers + .runJHipster(GENERATOR_SERVER) + .withJHipsterConfig(sampleConfig, entities) + .withOptions({ skipPriorities }) + .withMockedGenerators(mockedGenerators); + }); + + it('should compose with jhipster:common', () => { + expect(runResult.mockedGenerators['jhipster:common'].callCount).toBe(1); + }); + it(`should ${enableTranslation ? '' : 'not '}compose with jhipster:languages`, () => { + expect(runResult.mockedGenerators['jhipster:languages'].callCount).toBe(enableTranslation ? 1 : 0); + }); + it('should compose with jhipster:liquibase', () => { + expect(runResult.mockedGenerators['jhipster:liquibase'].callCount).toBe(1); + }); + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/generators/spring-data-relational/support/application-properties.mts b/generators/spring-data-relational/support/application-properties.mts deleted file mode 100644 index ee9252104b71..000000000000 --- a/generators/spring-data-relational/support/application-properties.mts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright 2013-2023 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 { getDatabaseData } from './database-data.mjs'; -import { getJdbcUrl, getR2dbcUrl } from './database-url.mjs'; -import { databaseTypes } from '../../../jdl/jhipster/index.mjs'; - -const { ORACLE, MYSQL, POSTGRESQL, MARIADB, MSSQL, H2_MEMORY, H2_DISK } = databaseTypes; - -export default function prepareSqlApplicationProperties({ application }: { application: any }) { - application.prodDatabaseTypeMariadb = application.prodDatabaseType === MARIADB; - application.prodDatabaseTypeMssql = application.prodDatabaseType === MSSQL; - application.prodDatabaseTypeMysql = application.prodDatabaseType === MYSQL; - application.prodDatabaseTypeOracle = application.prodDatabaseType === ORACLE; - application.prodDatabaseTypePostgresql = application.prodDatabaseType === POSTGRESQL; - - application.devDatabaseTypeH2Disk = application.devDatabaseType === H2_DISK; - application.devDatabaseTypeH2Memory = application.devDatabaseType === H2_MEMORY; - application.devDatabaseTypeH2Any = application.devDatabaseTypeH2Disk || application.devDatabaseTypeH2Memory; - - application.devDatabaseTypeMariadb = application.prodDatabaseTypeMariadb && !application.devDatabaseTypeH2Any; - application.devDatabaseTypeMssql = application.prodDatabaseTypeMssql && !application.devDatabaseTypeH2Any; - application.devDatabaseTypeMysql = application.prodDatabaseTypeMysql && !application.devDatabaseTypeH2Any; - application.devDatabaseTypeOracle = application.prodDatabaseTypeOracle && !application.devDatabaseTypeH2Any; - application.devDatabaseTypePostgresql = application.prodDatabaseTypePostgresql && !application.devDatabaseTypeH2Any; - application.devDatabaseTypePostgres = application.devDatabaseTypePostgresql; // Deprecated - - if (!application.databaseTypeSql) { - return; - } - - const prodDatabaseData = getDatabaseData(application.prodDatabaseType); - application.prodHibernateDialect = prodDatabaseData.hibernateDialect; - application.prodJdbcDriver = prodDatabaseData.jdbcDriver; - application.prodDatabaseUsername = prodDatabaseData.defaultUsername ?? application.baseName; - application.prodDatabasePassword = prodDatabaseData.defaultPassword ?? ''; - application.prodDatabaseName = prodDatabaseData.defaultDatabaseName ?? application.baseName; - - const prodDatabaseOptions = { - databaseName: application.prodDatabaseName, - hostname: 'localhost', - }; - - application.prodJdbcUrl = getJdbcUrl(application.prodDatabaseType, prodDatabaseOptions); - application.prodLiquibaseUrl = getJdbcUrl(application.prodDatabaseType, { - ...prodDatabaseOptions, - skipExtraOptions: true, - }); - if (application.reactive) { - application.prodR2dbcUrl = getR2dbcUrl(application.prodDatabaseType, prodDatabaseOptions); - } - - if (application.devDatabaseTypeH2Any) { - try { - const devDatabaseData = getDatabaseData(application.devDatabaseType); - application.devHibernateDialect = devDatabaseData.hibernateDialect; - application.devJdbcDriver = devDatabaseData.jdbcDriver; - application.devDatabaseUsername = devDatabaseData.defaultUsername ?? application.baseName; - application.devDatabasePassword = devDatabaseData.defaultPassword ?? ''; - application.devDatabaseName = devDatabaseData.defaultDatabaseName ?? application.baseName; - - const devDatabaseOptions = { - databaseName: application.devDatabaseName, - }; - application.devJdbcUrl = getJdbcUrl(application.devDatabaseType, { - ...devDatabaseOptions, - buildDirectory: `./${application.temporaryDir}`, - prodDatabaseType: application.prodDatabaseType, - }); - - let devLiquibaseOptions; - if (application.devDatabaseTypeH2Memory) { - devLiquibaseOptions = { - protocolSuffix: 'h2:tcp://', - localDirectory: 'localhost:18080/mem:', - }; - } else { - devLiquibaseOptions = { - // eslint-disable-next-line no-template-curly-in-string - buildDirectory: application.buildToolGradle ? `./${application.temporaryDir}` : '${project.build.directory}/', - }; - } - - application.devLiquibaseUrl = getJdbcUrl(application.devDatabaseType, { - ...devDatabaseOptions, - skipExtraOptions: true, - ...devLiquibaseOptions, - }); - - if (application.reactive) { - application.devR2dbcUrl = getR2dbcUrl(application.devDatabaseType, { - ...devDatabaseOptions, - buildDirectory: `./${application.temporaryDir}`, - prodDatabaseType: application.prodDatabaseType, - }); - } - } catch (error) { - if (application.backendTypeSpringBoot) { - throw error; - } - } - } else { - application.devJdbcUrl = application.prodJdbcUrl; - application.devLiquibaseUrl = application.prodLiquibaseUrl; - application.devR2dbcUrl = application.prodR2dbcUrl; - application.devHibernateDialect = application.prodHibernateDialect; - application.devJdbcDriver = application.prodJdbcDriver; - application.devDatabaseUsername = application.prodDatabaseUsername; - application.devDatabasePassword = application.prodDatabasePassword; - application.devDatabaseName = application.prodDatabaseName; - application.devJdbcUrl = application.prodJdbcUrl; - application.devLiquibaseUrl = application.prodLiquibaseUrl; - if (application.reactive) { - application.devR2dbcUrl = application.prodR2dbcUrl; - } - } -} diff --git a/generators/spring-data-relational/support/application-properties.ts b/generators/spring-data-relational/support/application-properties.ts new file mode 100644 index 000000000000..756b289447fa --- /dev/null +++ b/generators/spring-data-relational/support/application-properties.ts @@ -0,0 +1,133 @@ +/** + * Copyright 2013-2023 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 { getDatabaseData } from './database-data.js'; +import { getJdbcUrl, getR2dbcUrl } from './database-url.js'; +import { databaseTypes } from '../../../jdl/jhipster/index.js'; + +const { ORACLE, MYSQL, POSTGRESQL, MARIADB, MSSQL, H2_MEMORY, H2_DISK } = databaseTypes; + +export default function prepareSqlApplicationProperties({ application }: { application: any }) { + application.prodDatabaseTypeMariadb = application.prodDatabaseType === MARIADB; + application.prodDatabaseTypeMssql = application.prodDatabaseType === MSSQL; + application.prodDatabaseTypeMysql = application.prodDatabaseType === MYSQL; + application.prodDatabaseTypeOracle = application.prodDatabaseType === ORACLE; + application.prodDatabaseTypePostgresql = application.prodDatabaseType === POSTGRESQL; + + application.devDatabaseTypeH2Disk = application.devDatabaseType === H2_DISK; + application.devDatabaseTypeH2Memory = application.devDatabaseType === H2_MEMORY; + application.devDatabaseTypeH2Any = application.devDatabaseTypeH2Disk || application.devDatabaseTypeH2Memory; + + application.devDatabaseTypeMariadb = application.prodDatabaseTypeMariadb && !application.devDatabaseTypeH2Any; + application.devDatabaseTypeMssql = application.prodDatabaseTypeMssql && !application.devDatabaseTypeH2Any; + application.devDatabaseTypeMysql = application.prodDatabaseTypeMysql && !application.devDatabaseTypeH2Any; + application.devDatabaseTypeOracle = application.prodDatabaseTypeOracle && !application.devDatabaseTypeH2Any; + application.devDatabaseTypePostgresql = application.prodDatabaseTypePostgresql && !application.devDatabaseTypeH2Any; + application.devDatabaseTypePostgres = application.devDatabaseTypePostgresql; // Deprecated + + if (!application.databaseTypeSql) { + return; + } + + const prodDatabaseData = getDatabaseData(application.prodDatabaseType); + application.prodHibernateDialect = prodDatabaseData.hibernateDialect; + application.prodJdbcDriver = prodDatabaseData.jdbcDriver; + application.prodDatabaseUsername = prodDatabaseData.defaultUsername ?? application.baseName; + application.prodDatabasePassword = prodDatabaseData.defaultPassword ?? ''; + application.prodDatabaseName = prodDatabaseData.defaultDatabaseName ?? application.baseName; + + const prodDatabaseOptions = { + databaseName: application.prodDatabaseName, + hostname: 'localhost', + }; + + application.prodJdbcUrl = getJdbcUrl(application.prodDatabaseType, prodDatabaseOptions); + application.prodLiquibaseUrl = getJdbcUrl(application.prodDatabaseType, { + ...prodDatabaseOptions, + skipExtraOptions: true, + }); + if (application.reactive) { + application.prodR2dbcUrl = getR2dbcUrl(application.prodDatabaseType, prodDatabaseOptions); + } + + if (application.devDatabaseTypeH2Any) { + try { + const devDatabaseData = getDatabaseData(application.devDatabaseType); + application.devHibernateDialect = devDatabaseData.hibernateDialect; + application.devJdbcDriver = devDatabaseData.jdbcDriver; + application.devDatabaseUsername = devDatabaseData.defaultUsername ?? application.baseName; + application.devDatabasePassword = devDatabaseData.defaultPassword ?? ''; + application.devDatabaseName = devDatabaseData.defaultDatabaseName ?? application.baseName; + + const devDatabaseOptions = { + databaseName: application.devDatabaseName, + }; + application.devJdbcUrl = getJdbcUrl(application.devDatabaseType, { + ...devDatabaseOptions, + buildDirectory: `./${application.temporaryDir}`, + prodDatabaseType: application.prodDatabaseType, + }); + + let devLiquibaseOptions; + if (application.devDatabaseTypeH2Memory) { + devLiquibaseOptions = { + protocolSuffix: 'h2:tcp://', + localDirectory: 'localhost:18080/mem:', + }; + } else { + devLiquibaseOptions = { + // eslint-disable-next-line no-template-curly-in-string + buildDirectory: application.buildToolGradle ? `./${application.temporaryDir}` : '${project.build.directory}/', + }; + } + + application.devLiquibaseUrl = getJdbcUrl(application.devDatabaseType, { + ...devDatabaseOptions, + skipExtraOptions: true, + ...devLiquibaseOptions, + }); + + if (application.reactive) { + application.devR2dbcUrl = getR2dbcUrl(application.devDatabaseType, { + ...devDatabaseOptions, + buildDirectory: `./${application.temporaryDir}`, + prodDatabaseType: application.prodDatabaseType, + }); + } + } catch (error) { + if (application.backendTypeSpringBoot) { + throw error; + } + } + } else { + application.devJdbcUrl = application.prodJdbcUrl; + application.devLiquibaseUrl = application.prodLiquibaseUrl; + application.devR2dbcUrl = application.prodR2dbcUrl; + application.devHibernateDialect = application.prodHibernateDialect; + application.devJdbcDriver = application.prodJdbcDriver; + application.devDatabaseUsername = application.prodDatabaseUsername; + application.devDatabasePassword = application.prodDatabasePassword; + application.devDatabaseName = application.prodDatabaseName; + application.devJdbcUrl = application.prodJdbcUrl; + application.devLiquibaseUrl = application.prodLiquibaseUrl; + if (application.reactive) { + application.devR2dbcUrl = application.prodR2dbcUrl; + } + } +} diff --git a/generators/spring-data-relational/support/database-data.mts b/generators/spring-data-relational/support/database-data.mts deleted file mode 100644 index 29df1a62a85e..000000000000 --- a/generators/spring-data-relational/support/database-data.mts +++ /dev/null @@ -1,195 +0,0 @@ -/** - * Copyright 2013-2023 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 { databaseTypes } from '../../../jdl/jhipster/index.mjs'; - -export type DatabaseData = { - name: string; - protocolSuffix: string; - jdbcDriver: string; - hibernateDialect: string; - port?: string; - localDirectory?: string; - extraOptions?: string; - defaultUsername?: string; - defaultPassword?: string; - defaultDatabaseName?: string; - - constraintNameMaxLength?: number; - tableNameMaxLength?: number; -}; - -export type getData = (options: { - prodDatabaseType?: string; - localDirectory?: string; - buildDirectory?: string; - protocolSuffix?: string; - itests?: boolean; -}) => Partial; - -export type DatabaseDataSpec = DatabaseData & { - jdbc?: Partial; - r2dbc?: Partial; - getData?: getData; -}; - -const { H2_DISK, H2_MEMORY, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; - -const H2_PROD_DATABASE_MODE = { - [MYSQL]: ';MODE=MYSQL', - [MARIADB]: ';MODE=LEGACY', -}; - -const h2GetProdDatabaseData = ( - databaseType: string, - { extraOptions = '' }: { extraOptions?: string }, - { prodDatabaseType, buildDirectory, itests, localDirectory, protocolSuffix }: Parameters[0], -): Partial => { - const data: Partial = {}; - if (protocolSuffix) { - data.protocolSuffix = protocolSuffix; - } - if (H2_DISK === databaseType) { - if (!localDirectory && !buildDirectory) { - throw new Error(`'localDirectory' option should be provided for ${databaseType} databaseType`); - } - if (localDirectory) { - localDirectory = `${localDirectory}/`; - } else { - localDirectory = `${buildDirectory}h2db/${itests ? 'testdb/' : 'db/'}`; - } - } - - if (itests && H2_MEMORY === databaseType) { - data.port = ':12344'; - } - - const h2ProdDatabaseMode = prodDatabaseType ? H2_PROD_DATABASE_MODE[prodDatabaseType] ?? '' : ''; - return { - ...data, - localDirectory, - extraOptions: `${extraOptions}${h2ProdDatabaseMode}`, - }; -}; - -const databaseData: Record = { - [MSSQL]: { - name: 'SQL Server', - protocolSuffix: 'sqlserver://', - jdbcDriver: '', - hibernateDialect: 'org.hibernate.dialect.SQLServer2012Dialect', - port: ':1433;database=', - defaultUsername: 'SA', - defaultPassword: 'yourStrong(!)Password', - - jdbc: { - extraOptions: ';encrypt=false', - }, - r2dbc: { - protocolSuffix: 'mssql://', - port: ':1433/', - }, - }, - [MARIADB]: { - name: 'MariaDB', - protocolSuffix: 'mariadb://', - jdbcDriver: 'org.mariadb.jdbc.Driver', - hibernateDialect: 'org.hibernate.dialect.MariaDB103Dialect', - port: ':3306/', - extraOptions: '?useLegacyDatetimeCode=false', - defaultUsername: 'root', - - constraintNameMaxLength: 64, - tableNameMaxLength: 64, - r2dbc: { - // TODO switch to mariadb if r2dbc-mariadb is reinstated - protocolSuffix: 'mysql://', - }, - }, - [MYSQL]: { - name: 'MySQL', - protocolSuffix: 'mysql://', - jdbcDriver: 'com.mysql.cj.jdbc.Driver', - hibernateDialect: 'org.hibernate.dialect.MySQL8Dialect', - tableNameMaxLength: 64, - constraintNameMaxLength: 64, - port: ':3306/', - extraOptions: '?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', - defaultUsername: 'root', - }, - [ORACLE]: { - name: 'Oracle', - protocolSuffix: 'oracle:thin:@', - jdbcDriver: 'oracle.jdbc.OracleDriver', - hibernateDialect: 'org.hibernate.dialect.Oracle12cDialect', - port: ':1521:', - defaultUsername: 'system', - defaultPassword: 'oracle', - defaultDatabaseName: 'xe', - - constraintNameMaxLength: 128, - tableNameMaxLength: 128, - }, - [POSTGRESQL]: { - name: 'PostgreSQL', - protocolSuffix: 'postgresql://', - jdbcDriver: 'org.postgresql.Driver', - hibernateDialect: 'org.hibernate.dialect.PostgreSQLDialect', - port: ':5432/', - - constraintNameMaxLength: 63, - tableNameMaxLength: 63, - }, - [H2_DISK]: { - name: 'H2Disk', - protocolSuffix: 'h2:file:', - jdbcDriver: 'org.h2.Driver', - hibernateDialect: 'org.hibernate.dialect.H2Dialect', - - getData: options => h2GetProdDatabaseData(H2_DISK, { extraOptions: ';DB_CLOSE_DELAY=-1' }, options), - r2dbc: { - protocolSuffix: 'h2:file:///', - }, - }, - [H2_MEMORY]: { - name: 'H2Memory', - protocolSuffix: 'h2:mem:', - jdbcDriver: 'org.h2.Driver', - hibernateDialect: 'org.hibernate.dialect.H2Dialect', - - getData: options => h2GetProdDatabaseData(H2_MEMORY, { extraOptions: ';DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE' }, options), - r2dbc: { - protocolSuffix: 'h2:mem:///', - }, - }, -}; - -export default databaseData; - -export function getDatabaseData(databaseType: string) { - if (databaseData[databaseType] === undefined) { - throw new Error(`Database data not found for database ${databaseType}`); - } - return databaseData[databaseType]; -} - -export const getDBCExtraOption = databaseType => { - const databaseDataForType = databaseData[databaseType]; - const { extraOptions = '' } = databaseDataForType; - return extraOptions; -}; diff --git a/generators/spring-data-relational/support/database-data.ts b/generators/spring-data-relational/support/database-data.ts new file mode 100644 index 000000000000..d78896d8aece --- /dev/null +++ b/generators/spring-data-relational/support/database-data.ts @@ -0,0 +1,195 @@ +/** + * Copyright 2013-2023 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 { databaseTypes } from '../../../jdl/jhipster/index.js'; + +export type DatabaseData = { + name: string; + protocolSuffix: string; + jdbcDriver: string; + hibernateDialect: string; + port?: string; + localDirectory?: string; + extraOptions?: string; + defaultUsername?: string; + defaultPassword?: string; + defaultDatabaseName?: string; + + constraintNameMaxLength?: number; + tableNameMaxLength?: number; +}; + +export type getData = (options: { + prodDatabaseType?: string; + localDirectory?: string; + buildDirectory?: string; + protocolSuffix?: string; + itests?: boolean; +}) => Partial; + +export type DatabaseDataSpec = DatabaseData & { + jdbc?: Partial; + r2dbc?: Partial; + getData?: getData; +}; + +const { H2_DISK, H2_MEMORY, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; + +const H2_PROD_DATABASE_MODE = { + [MYSQL]: ';MODE=MYSQL', + [MARIADB]: ';MODE=LEGACY', +}; + +const h2GetProdDatabaseData = ( + databaseType: string, + { extraOptions = '' }: { extraOptions?: string }, + { prodDatabaseType, buildDirectory, itests, localDirectory, protocolSuffix }: Parameters[0], +): Partial => { + const data: Partial = {}; + if (protocolSuffix) { + data.protocolSuffix = protocolSuffix; + } + if (H2_DISK === databaseType) { + if (!localDirectory && !buildDirectory) { + throw new Error(`'localDirectory' option should be provided for ${databaseType} databaseType`); + } + if (localDirectory) { + localDirectory = `${localDirectory}/`; + } else { + localDirectory = `${buildDirectory}h2db/${itests ? 'testdb/' : 'db/'}`; + } + } + + if (itests && H2_MEMORY === databaseType) { + data.port = ':12344'; + } + + const h2ProdDatabaseMode = prodDatabaseType ? H2_PROD_DATABASE_MODE[prodDatabaseType] ?? '' : ''; + return { + ...data, + localDirectory, + extraOptions: `${extraOptions}${h2ProdDatabaseMode}`, + }; +}; + +const databaseData: Record = { + [MSSQL]: { + name: 'SQL Server', + protocolSuffix: 'sqlserver://', + jdbcDriver: '', + hibernateDialect: 'org.hibernate.dialect.SQLServer2012Dialect', + port: ':1433;database=', + defaultUsername: 'SA', + defaultPassword: 'yourStrong(!)Password', + + jdbc: { + extraOptions: ';encrypt=false', + }, + r2dbc: { + protocolSuffix: 'mssql://', + port: ':1433/', + }, + }, + [MARIADB]: { + name: 'MariaDB', + protocolSuffix: 'mariadb://', + jdbcDriver: 'org.mariadb.jdbc.Driver', + hibernateDialect: 'org.hibernate.dialect.MariaDB103Dialect', + port: ':3306/', + extraOptions: '?useLegacyDatetimeCode=false', + defaultUsername: 'root', + + constraintNameMaxLength: 64, + tableNameMaxLength: 64, + r2dbc: { + // TODO switch to mariadb if r2dbc-mariadb is reinstated + protocolSuffix: 'mysql://', + }, + }, + [MYSQL]: { + name: 'MySQL', + protocolSuffix: 'mysql://', + jdbcDriver: 'com.mysql.cj.jdbc.Driver', + hibernateDialect: 'org.hibernate.dialect.MySQL8Dialect', + tableNameMaxLength: 64, + constraintNameMaxLength: 64, + port: ':3306/', + extraOptions: '?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', + defaultUsername: 'root', + }, + [ORACLE]: { + name: 'Oracle', + protocolSuffix: 'oracle:thin:@', + jdbcDriver: 'oracle.jdbc.OracleDriver', + hibernateDialect: 'org.hibernate.dialect.Oracle12cDialect', + port: ':1521:', + defaultUsername: 'system', + defaultPassword: 'oracle', + defaultDatabaseName: 'xe', + + constraintNameMaxLength: 128, + tableNameMaxLength: 128, + }, + [POSTGRESQL]: { + name: 'PostgreSQL', + protocolSuffix: 'postgresql://', + jdbcDriver: 'org.postgresql.Driver', + hibernateDialect: 'org.hibernate.dialect.PostgreSQLDialect', + port: ':5432/', + + constraintNameMaxLength: 63, + tableNameMaxLength: 63, + }, + [H2_DISK]: { + name: 'H2Disk', + protocolSuffix: 'h2:file:', + jdbcDriver: 'org.h2.Driver', + hibernateDialect: 'org.hibernate.dialect.H2Dialect', + + getData: options => h2GetProdDatabaseData(H2_DISK, { extraOptions: ';DB_CLOSE_DELAY=-1' }, options), + r2dbc: { + protocolSuffix: 'h2:file:///', + }, + }, + [H2_MEMORY]: { + name: 'H2Memory', + protocolSuffix: 'h2:mem:', + jdbcDriver: 'org.h2.Driver', + hibernateDialect: 'org.hibernate.dialect.H2Dialect', + + getData: options => h2GetProdDatabaseData(H2_MEMORY, { extraOptions: ';DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE' }, options), + r2dbc: { + protocolSuffix: 'h2:mem:///', + }, + }, +}; + +export default databaseData; + +export function getDatabaseData(databaseType: string) { + if (databaseData[databaseType] === undefined) { + throw new Error(`Database data not found for database ${databaseType}`); + } + return databaseData[databaseType]; +} + +export const getDBCExtraOption = databaseType => { + const databaseDataForType = databaseData[databaseType]; + const { extraOptions = '' } = databaseDataForType; + return extraOptions; +}; diff --git a/generators/spring-data-relational/support/database-url.mts b/generators/spring-data-relational/support/database-url.mts deleted file mode 100644 index ee3118d165f0..000000000000 --- a/generators/spring-data-relational/support/database-url.mts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2013-2023 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 { databaseTypes } from '../../../jdl/jhipster/index.mjs'; -import databaseData, { type getData } from './database-data.mjs'; - -const { ORACLE, MYSQL, POSTGRESQL, MARIADB, MSSQL, H2_DISK, H2_MEMORY } = databaseTypes; - -type DatabaseUrlOptions = Parameters[0] & { databaseName?: string; hostname?: string; skipExtraOptions?: boolean }; - -export default function getDatabaseUrl(databaseType: string, protocol: string, options: DatabaseUrlOptions = {}): string { - if (!protocol) { - throw new Error('protocol is required'); - } - const { databaseName, hostname, skipExtraOptions } = options; - if (!databaseName) { - throw new Error("option 'databaseName' is required"); - } - if ([MYSQL, MARIADB, POSTGRESQL, ORACLE, MSSQL].includes(databaseType) && !hostname) { - throw new Error(`option 'hostname' is required for ${databaseType} databaseType`); - } else if (![MYSQL, MARIADB, POSTGRESQL, ORACLE, MSSQL, H2_DISK, H2_MEMORY].includes(databaseType)) { - throw new Error(`${databaseType} databaseType is not supported`); - } - let databaseDataForType = databaseData[databaseType]; - if (databaseDataForType[protocol]) { - databaseDataForType = { - ...databaseDataForType, - ...databaseDataForType[protocol], - }; - } - if (databaseDataForType.getData) { - databaseDataForType = { - ...databaseDataForType, - ...(databaseDataForType.getData(options) || {}), - }; - } - const { port = '', protocolSuffix = '', extraOptions = '', localDirectory = options.localDirectory } = databaseDataForType; - let url = `${protocol}:${protocolSuffix}`; - if (hostname || localDirectory) { - url = `${url}${localDirectory || hostname + port}${databaseName}`; - } else { - url = `${url}${databaseName}${port}`; - } - return `${url}${skipExtraOptions ? '' : extraOptions}`; -} - -/** - * Returns the JDBC URL for a databaseType - * - * @param databaseType - * @param options - * @returns - */ -export function getJdbcUrl(databaseType: string, options?: DatabaseUrlOptions) { - return getDatabaseUrl(databaseType, 'jdbc', options); -} - -/** - * Returns the R2DBC URL for a databaseType - * - * @param databaseType - * @param options - * @returns - */ -export function getR2dbcUrl(databaseType: string, options?: DatabaseUrlOptions) { - return getDatabaseUrl(databaseType, 'r2dbc', options); -} diff --git a/generators/spring-data-relational/support/database-url.spec.mts b/generators/spring-data-relational/support/database-url.spec.mts deleted file mode 100644 index d021559e8770..000000000000 --- a/generators/spring-data-relational/support/database-url.spec.mts +++ /dev/null @@ -1,218 +0,0 @@ -import { expect } from 'esmocha'; -import { databaseTypes } from '../../../jdl/jhipster/index.mjs'; -import { getJdbcUrl, getR2dbcUrl } from './database-url.mjs'; - -const { H2_MEMORY, H2_DISK, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; - -describe('generator - sql - database-url', () => { - describe('getJdbcUrl', () => { - describe('when called for mysql', () => { - it('return jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', () => { - expect(getJdbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( - 'jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', - ); - }); - }); - describe('when called for mysql with skipExtraOptions enabled', () => { - it('return jdbc:mysql://localhost:3306/test', () => { - expect(getJdbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( - 'jdbc:mysql://localhost:3306/test', - ); - }); - }); - describe('when called for mariadb', () => { - it('return jdbc:mariadb://localhost:3306/test?useLegacyDatetimeCode=false', () => { - expect(getJdbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost' })).toEqual( - 'jdbc:mariadb://localhost:3306/test?useLegacyDatetimeCode=false', - ); - }); - }); - describe('when called for mariadb with skipExtraOptions enabled', () => { - it('return jdbc:mariadb://localhost:3306/test', () => { - expect(getJdbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( - 'jdbc:mariadb://localhost:3306/test', - ); - }); - }); - describe('when called for postgresql', () => { - it('return jdbc:postgresql://localhost:5432/test', () => { - expect(getJdbcUrl(POSTGRESQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('jdbc:postgresql://localhost:5432/test'); - }); - }); - describe('when called for oracle', () => { - it('return jdbc:oracle:thin:@localhost:1521:test', () => { - expect(getJdbcUrl(ORACLE, { databaseName: 'test', hostname: 'localhost' })).toEqual('jdbc:oracle:thin:@localhost:1521:test'); - }); - }); - describe('when called for mssql', () => { - it('return jdbc:sqlserver://localhost:1433;database=test;encrypt=false', () => { - expect(getJdbcUrl(MSSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( - 'jdbc:sqlserver://localhost:1433;database=test;encrypt=false', - ); - }); - }); - describe('when called for h2Disk', () => { - it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { - expect(getJdbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( - 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', - ); - }); - }); - describe('when called for h2Disk and mysql as prod', () => { - it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { - expect(getJdbcUrl(H2_DISK, { prodDatabaseType: 'mysql', databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( - 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1;MODE=MYSQL', - ); - }); - }); - describe('when called for h2Disk and mariadb as prod', () => { - it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { - expect(getJdbcUrl(H2_DISK, { prodDatabaseType: 'mariadb', databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( - 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1;MODE=LEGACY', - ); - }); - }); - describe('when called for h2Disk with skipExtraOptions enabled', () => { - it('return jdbc:h2:file:./build/h2db/db/test', () => { - expect(getJdbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db', skipExtraOptions: true })).toEqual( - 'jdbc:h2:file:./build/h2db/db/test', - ); - }); - }); - describe('when called for h2Disk with missing `localDirectory` option', () => { - it('throw an error', () => { - expect(() => getJdbcUrl(H2_DISK, { databaseName: 'test' })).toThrow( - "'localDirectory' option should be provided for h2Disk databaseType", - ); - }); - }); - describe('when called for h2Memory', () => { - it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { - expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test' })).toEqual('jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE'); - }); - }); - describe('when called for h2Memory with custom protocolSuffix', () => { - it('return jdbc:h2:tcp:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { - expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test', protocolSuffix: 'h2:tcp:' })).toEqual( - 'jdbc:h2:tcp:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', - ); - }); - }); - describe('when called for h2Memory and mysql as prod', () => { - it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { - expect(getJdbcUrl(H2_MEMORY, { prodDatabaseType: 'mysql', databaseName: 'test' })).toEqual( - 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL', - ); - }); - }); - describe('when called for h2Memory and mariadb as prod', () => { - it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { - expect(getJdbcUrl(H2_MEMORY, { prodDatabaseType: 'mariadb', databaseName: 'test' })).toEqual( - 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=LEGACY', - ); - }); - }); - describe('when called for h2Memory with skipExtraOptions enabled', () => { - it('return jdbc:h2:mem:test', () => { - expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test', skipExtraOptions: true })).toEqual('jdbc:h2:mem:test'); - }); - }); - describe('when called with missing `databaseName` option', () => { - it('throw an error', () => { - expect(() => getJdbcUrl(MYSQL)).toThrow("option 'databaseName' is required"); - }); - }); - describe('when called for an unknown databaseType', () => { - it('throw an error', () => { - expect(() => getJdbcUrl('foodb', { databaseName: 'test' })).toThrow('foodb databaseType is not supported'); - }); - }); - }); - - describe('getR2dbcUrl', () => { - describe('when called for mysql', () => { - it('return r2dbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', () => { - expect(getR2dbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( - 'r2dbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', - ); - }); - }); - describe('when called for mysql with skipExtraOptions enabled', () => { - it('return r2dbc:mysql://localhost:3306/test', () => { - expect(getR2dbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( - 'r2dbc:mysql://localhost:3306/test', - ); - }); - }); - describe('when called for mariadb', () => { - it('return r2dbc:mysql://localhost:3306/test?useLegacyDatetimeCode=false', () => { - expect(getR2dbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost' })).toEqual( - 'r2dbc:mysql://localhost:3306/test?useLegacyDatetimeCode=false', - ); - }); - }); - describe('when called for mariadb with skipExtraOptions enabled', () => { - it('return r2dbc:mysql://localhost:3306/test', () => { - expect(getR2dbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( - 'r2dbc:mysql://localhost:3306/test', - ); - }); - }); - describe('when called for postgresql', () => { - it('return r2dbc:postgresql://localhost:5432/test', () => { - expect(getR2dbcUrl(POSTGRESQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:postgresql://localhost:5432/test'); - }); - }); - describe('when called for oracle', () => { - it('return r2dbc:oracle:thin:@localhost:1521:test', () => { - expect(getR2dbcUrl(ORACLE, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:oracle:thin:@localhost:1521:test'); - }); - }); - describe('when called for mssql', () => { - it('return r2dbc:mssql://localhost:1433/test', () => { - expect(getR2dbcUrl(MSSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:mssql://localhost:1433/test'); - }); - }); - describe('when called for h2Disk', () => { - it('return r2dbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { - expect(getR2dbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( - 'r2dbc:h2:file:///./build/h2db/db/test;DB_CLOSE_DELAY=-1', - ); - }); - }); - describe('when called for h2Disk with skipExtraOptions enabled', () => { - it('return r2dbc:h2:file:://./build/h2db/db/test', () => { - expect(getR2dbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db', skipExtraOptions: true })).toEqual( - 'r2dbc:h2:file:///./build/h2db/db/test', - ); - }); - }); - describe('when called for h2Disk with missing `localDirectory` option', () => { - it('throw an error', () => { - expect(() => getR2dbcUrl(H2_DISK, { databaseName: 'test' })).toThrow( - "'localDirectory' option should be provided for h2Disk databaseType", - ); - }); - }); - describe('when called for h2Memory', () => { - it('return r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { - expect(getR2dbcUrl(H2_MEMORY, { databaseName: 'test' })).toEqual('r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE'); - }); - }); - describe('when called for h2Memory with skipExtraOptions enabled', () => { - it('return r2dbc:h2:mem:///test', () => { - expect(getR2dbcUrl(H2_MEMORY, { databaseName: 'test', skipExtraOptions: true })).toEqual('r2dbc:h2:mem:///test'); - }); - }); - describe('when called with missing `databaseName` option', () => { - it('throw an error', () => { - expect(() => getR2dbcUrl(MYSQL)).toThrow("option 'databaseName' is required"); - }); - }); - describe('when called for an unknown databaseType', () => { - it('throw an error', () => { - expect(() => getR2dbcUrl('foodb', { databaseName: 'test' })).toThrow('foodb databaseType is not supported'); - }); - }); - }); -}); diff --git a/generators/spring-data-relational/support/database-url.spec.ts b/generators/spring-data-relational/support/database-url.spec.ts new file mode 100644 index 000000000000..8fd4ebe4feb2 --- /dev/null +++ b/generators/spring-data-relational/support/database-url.spec.ts @@ -0,0 +1,218 @@ +import { expect } from 'esmocha'; +import { databaseTypes } from '../../../jdl/jhipster/index.js'; +import { getJdbcUrl, getR2dbcUrl } from './database-url.js'; + +const { H2_MEMORY, H2_DISK, MARIADB, MSSQL, MYSQL, ORACLE, POSTGRESQL } = databaseTypes; + +describe('generator - sql - database-url', () => { + describe('getJdbcUrl', () => { + describe('when called for mysql', () => { + it('return jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', () => { + expect(getJdbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( + 'jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', + ); + }); + }); + describe('when called for mysql with skipExtraOptions enabled', () => { + it('return jdbc:mysql://localhost:3306/test', () => { + expect(getJdbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( + 'jdbc:mysql://localhost:3306/test', + ); + }); + }); + describe('when called for mariadb', () => { + it('return jdbc:mariadb://localhost:3306/test?useLegacyDatetimeCode=false', () => { + expect(getJdbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost' })).toEqual( + 'jdbc:mariadb://localhost:3306/test?useLegacyDatetimeCode=false', + ); + }); + }); + describe('when called for mariadb with skipExtraOptions enabled', () => { + it('return jdbc:mariadb://localhost:3306/test', () => { + expect(getJdbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( + 'jdbc:mariadb://localhost:3306/test', + ); + }); + }); + describe('when called for postgresql', () => { + it('return jdbc:postgresql://localhost:5432/test', () => { + expect(getJdbcUrl(POSTGRESQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('jdbc:postgresql://localhost:5432/test'); + }); + }); + describe('when called for oracle', () => { + it('return jdbc:oracle:thin:@localhost:1521:test', () => { + expect(getJdbcUrl(ORACLE, { databaseName: 'test', hostname: 'localhost' })).toEqual('jdbc:oracle:thin:@localhost:1521:test'); + }); + }); + describe('when called for mssql', () => { + it('return jdbc:sqlserver://localhost:1433;database=test;encrypt=false', () => { + expect(getJdbcUrl(MSSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( + 'jdbc:sqlserver://localhost:1433;database=test;encrypt=false', + ); + }); + }); + describe('when called for h2Disk', () => { + it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { + expect(getJdbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( + 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', + ); + }); + }); + describe('when called for h2Disk and mysql as prod', () => { + it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { + expect(getJdbcUrl(H2_DISK, { prodDatabaseType: 'mysql', databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( + 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1;MODE=MYSQL', + ); + }); + }); + describe('when called for h2Disk and mariadb as prod', () => { + it('return jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { + expect(getJdbcUrl(H2_DISK, { prodDatabaseType: 'mariadb', databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( + 'jdbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1;MODE=LEGACY', + ); + }); + }); + describe('when called for h2Disk with skipExtraOptions enabled', () => { + it('return jdbc:h2:file:./build/h2db/db/test', () => { + expect(getJdbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db', skipExtraOptions: true })).toEqual( + 'jdbc:h2:file:./build/h2db/db/test', + ); + }); + }); + describe('when called for h2Disk with missing `localDirectory` option', () => { + it('throw an error', () => { + expect(() => getJdbcUrl(H2_DISK, { databaseName: 'test' })).toThrow( + "'localDirectory' option should be provided for h2Disk databaseType", + ); + }); + }); + describe('when called for h2Memory', () => { + it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { + expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test' })).toEqual('jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE'); + }); + }); + describe('when called for h2Memory with custom protocolSuffix', () => { + it('return jdbc:h2:tcp:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { + expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test', protocolSuffix: 'h2:tcp:' })).toEqual( + 'jdbc:h2:tcp:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', + ); + }); + }); + describe('when called for h2Memory and mysql as prod', () => { + it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { + expect(getJdbcUrl(H2_MEMORY, { prodDatabaseType: 'mysql', databaseName: 'test' })).toEqual( + 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL', + ); + }); + }); + describe('when called for h2Memory and mariadb as prod', () => { + it('return jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { + expect(getJdbcUrl(H2_MEMORY, { prodDatabaseType: 'mariadb', databaseName: 'test' })).toEqual( + 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=LEGACY', + ); + }); + }); + describe('when called for h2Memory with skipExtraOptions enabled', () => { + it('return jdbc:h2:mem:test', () => { + expect(getJdbcUrl(H2_MEMORY, { databaseName: 'test', skipExtraOptions: true })).toEqual('jdbc:h2:mem:test'); + }); + }); + describe('when called with missing `databaseName` option', () => { + it('throw an error', () => { + expect(() => getJdbcUrl(MYSQL)).toThrow("option 'databaseName' is required"); + }); + }); + describe('when called for an unknown databaseType', () => { + it('throw an error', () => { + expect(() => getJdbcUrl('foodb', { databaseName: 'test' })).toThrow('foodb databaseType is not supported'); + }); + }); + }); + + describe('getR2dbcUrl', () => { + describe('when called for mysql', () => { + it('return r2dbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', () => { + expect(getR2dbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual( + 'r2dbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true', + ); + }); + }); + describe('when called for mysql with skipExtraOptions enabled', () => { + it('return r2dbc:mysql://localhost:3306/test', () => { + expect(getR2dbcUrl(MYSQL, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( + 'r2dbc:mysql://localhost:3306/test', + ); + }); + }); + describe('when called for mariadb', () => { + it('return r2dbc:mysql://localhost:3306/test?useLegacyDatetimeCode=false', () => { + expect(getR2dbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost' })).toEqual( + 'r2dbc:mysql://localhost:3306/test?useLegacyDatetimeCode=false', + ); + }); + }); + describe('when called for mariadb with skipExtraOptions enabled', () => { + it('return r2dbc:mysql://localhost:3306/test', () => { + expect(getR2dbcUrl(MARIADB, { databaseName: 'test', hostname: 'localhost', skipExtraOptions: true })).toEqual( + 'r2dbc:mysql://localhost:3306/test', + ); + }); + }); + describe('when called for postgresql', () => { + it('return r2dbc:postgresql://localhost:5432/test', () => { + expect(getR2dbcUrl(POSTGRESQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:postgresql://localhost:5432/test'); + }); + }); + describe('when called for oracle', () => { + it('return r2dbc:oracle:thin:@localhost:1521:test', () => { + expect(getR2dbcUrl(ORACLE, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:oracle:thin:@localhost:1521:test'); + }); + }); + describe('when called for mssql', () => { + it('return r2dbc:mssql://localhost:1433/test', () => { + expect(getR2dbcUrl(MSSQL, { databaseName: 'test', hostname: 'localhost' })).toEqual('r2dbc:mssql://localhost:1433/test'); + }); + }); + describe('when called for h2Disk', () => { + it('return r2dbc:h2:file:./build/h2db/db/test;DB_CLOSE_DELAY=-1', () => { + expect(getR2dbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db' })).toEqual( + 'r2dbc:h2:file:///./build/h2db/db/test;DB_CLOSE_DELAY=-1', + ); + }); + }); + describe('when called for h2Disk with skipExtraOptions enabled', () => { + it('return r2dbc:h2:file:://./build/h2db/db/test', () => { + expect(getR2dbcUrl(H2_DISK, { databaseName: 'test', localDirectory: './build/h2db/db', skipExtraOptions: true })).toEqual( + 'r2dbc:h2:file:///./build/h2db/db/test', + ); + }); + }); + describe('when called for h2Disk with missing `localDirectory` option', () => { + it('throw an error', () => { + expect(() => getR2dbcUrl(H2_DISK, { databaseName: 'test' })).toThrow( + "'localDirectory' option should be provided for h2Disk databaseType", + ); + }); + }); + describe('when called for h2Memory', () => { + it('return r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE', () => { + expect(getR2dbcUrl(H2_MEMORY, { databaseName: 'test' })).toEqual('r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE'); + }); + }); + describe('when called for h2Memory with skipExtraOptions enabled', () => { + it('return r2dbc:h2:mem:///test', () => { + expect(getR2dbcUrl(H2_MEMORY, { databaseName: 'test', skipExtraOptions: true })).toEqual('r2dbc:h2:mem:///test'); + }); + }); + describe('when called with missing `databaseName` option', () => { + it('throw an error', () => { + expect(() => getR2dbcUrl(MYSQL)).toThrow("option 'databaseName' is required"); + }); + }); + describe('when called for an unknown databaseType', () => { + it('throw an error', () => { + expect(() => getR2dbcUrl('foodb', { databaseName: 'test' })).toThrow('foodb databaseType is not supported'); + }); + }); + }); +}); diff --git a/generators/spring-data-relational/support/database-url.ts b/generators/spring-data-relational/support/database-url.ts new file mode 100644 index 000000000000..c744db6a5805 --- /dev/null +++ b/generators/spring-data-relational/support/database-url.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2013-2023 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 { databaseTypes } from '../../../jdl/jhipster/index.js'; +import databaseData, { type getData } from './database-data.js'; + +const { ORACLE, MYSQL, POSTGRESQL, MARIADB, MSSQL, H2_DISK, H2_MEMORY } = databaseTypes; + +type DatabaseUrlOptions = Parameters[0] & { databaseName?: string; hostname?: string; skipExtraOptions?: boolean }; + +export default function getDatabaseUrl(databaseType: string, protocol: string, options: DatabaseUrlOptions = {}): string { + if (!protocol) { + throw new Error('protocol is required'); + } + const { databaseName, hostname, skipExtraOptions } = options; + if (!databaseName) { + throw new Error("option 'databaseName' is required"); + } + if ([MYSQL, MARIADB, POSTGRESQL, ORACLE, MSSQL].includes(databaseType) && !hostname) { + throw new Error(`option 'hostname' is required for ${databaseType} databaseType`); + } else if (![MYSQL, MARIADB, POSTGRESQL, ORACLE, MSSQL, H2_DISK, H2_MEMORY].includes(databaseType)) { + throw new Error(`${databaseType} databaseType is not supported`); + } + let databaseDataForType = databaseData[databaseType]; + if (databaseDataForType[protocol]) { + databaseDataForType = { + ...databaseDataForType, + ...databaseDataForType[protocol], + }; + } + if (databaseDataForType.getData) { + databaseDataForType = { + ...databaseDataForType, + ...(databaseDataForType.getData(options) || {}), + }; + } + const { port = '', protocolSuffix = '', extraOptions = '', localDirectory = options.localDirectory } = databaseDataForType; + let url = `${protocol}:${protocolSuffix}`; + if (hostname || localDirectory) { + url = `${url}${localDirectory || hostname + port}${databaseName}`; + } else { + url = `${url}${databaseName}${port}`; + } + return `${url}${skipExtraOptions ? '' : extraOptions}`; +} + +/** + * Returns the JDBC URL for a databaseType + * + * @param databaseType + * @param options + * @returns + */ +export function getJdbcUrl(databaseType: string, options?: DatabaseUrlOptions) { + return getDatabaseUrl(databaseType, 'jdbc', options); +} + +/** + * Returns the R2DBC URL for a databaseType + * + * @param databaseType + * @param options + * @returns + */ +export function getR2dbcUrl(databaseType: string, options?: DatabaseUrlOptions) { + return getDatabaseUrl(databaseType, 'r2dbc', options); +} diff --git a/generators/spring-data-relational/support/index.mts b/generators/spring-data-relational/support/index.mts deleted file mode 100644 index 3373b23341d2..000000000000 --- a/generators/spring-data-relational/support/index.mts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2013-2023 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-data.mjs'; -export { default as databaseData } from './database-data.mjs'; -export * from './database-url.mjs'; -export { default as getDatabaseUrl } from './database-url.mjs'; -export { default as prepareSqlApplicationProperties } from './application-properties.mjs'; diff --git a/generators/spring-data-relational/support/index.ts b/generators/spring-data-relational/support/index.ts new file mode 100644 index 000000000000..5b76f1bd39c0 --- /dev/null +++ b/generators/spring-data-relational/support/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2013-2023 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-data.js'; +export { default as databaseData } from './database-data.js'; +export * from './database-url.js'; +export { default as getDatabaseUrl } from './database-url.js'; +export { default as prepareSqlApplicationProperties } from './application-properties.js'; diff --git a/generators/spring-websocket/__snapshots__/generator.spec.mts.snap b/generators/spring-websocket/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/spring-websocket/__snapshots__/generator.spec.mts.snap rename to generators/spring-websocket/__snapshots__/generator.spec.ts.snap diff --git a/generators/spring-websocket/cleanup.mts b/generators/spring-websocket/cleanup.ts similarity index 100% rename from generators/spring-websocket/cleanup.mts rename to generators/spring-websocket/cleanup.ts diff --git a/generators/spring-websocket/files.mts b/generators/spring-websocket/files.mts deleted file mode 100644 index 79049c652280..000000000000 --- a/generators/spring-websocket/files.mts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2013-2023 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 Generator from './generator.mjs'; -import { moveToJavaPackageSrcDir } from '../server/support/index.mjs'; -import { SERVER_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { WriteFileSection } from '../base/api.mjs'; -import { SpringBootApplication } from '../server/types.mjs'; - -const files: WriteFileSection = { - websocketFiles: [ - { - path: `${SERVER_MAIN_SRC_DIR}_package_/`, - renameTo: moveToJavaPackageSrcDir, - templates: [ - 'config/WebsocketConfiguration.java', - 'config/WebsocketSecurityConfiguration.java', - 'web/websocket/ActivityService.java', - 'web/websocket/dto/ActivityDTO.java', - ], - }, - ], -}; - -export default async function writeTask(this: Generator, { application }) { - await this.writeFiles({ - sections: files, - context: application, - }); -} diff --git a/generators/spring-websocket/files.ts b/generators/spring-websocket/files.ts new file mode 100644 index 000000000000..d48de5008f0b --- /dev/null +++ b/generators/spring-websocket/files.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2013-2023 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 Generator from './generator.js'; +import { moveToJavaPackageSrcDir } from '../server/support/index.js'; +import { SERVER_MAIN_SRC_DIR } from '../generator-constants.js'; +import { WriteFileSection } from '../base/api.js'; +import { SpringBootApplication } from '../server/types.js'; + +const files: WriteFileSection = { + websocketFiles: [ + { + path: `${SERVER_MAIN_SRC_DIR}_package_/`, + renameTo: moveToJavaPackageSrcDir, + templates: [ + 'config/WebsocketConfiguration.java', + 'config/WebsocketSecurityConfiguration.java', + 'web/websocket/ActivityService.java', + 'web/websocket/dto/ActivityDTO.java', + ], + }, + ], +}; + +export default async function writeTask(this: Generator, { application }) { + await this.writeFiles({ + sections: files, + context: application, + }); +} diff --git a/generators/spring-websocket/generator.mts b/generators/spring-websocket/generator.mts deleted file mode 100644 index 81e47a5d9cc8..000000000000 --- a/generators/spring-websocket/generator.mts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.mjs'; -import { GENERATOR_SPRING_WEBSOCKET, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.mjs'; -import writeTask from './files.mjs'; -import cleanupTask from './cleanup.mjs'; - -export default class SpringWebsocketGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_SPRING_WEBSOCKET); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); - } - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupTask, - writeTask, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addDependencies({ application, source }) { - if (application.buildToolMaven) { - source.addMavenDefinition?.({ - dependencies: [ - { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-websocket' }, - { groupId: 'org.springframework.security', artifactId: 'spring-security-messaging' }, - ], - }); - } - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } -} diff --git a/generators/spring-websocket/generator.spec.mts b/generators/spring-websocket/generator.spec.mts deleted file mode 100644 index 40bdcd0b531f..000000000000 --- a/generators/spring-websocket/generator.spec.mts +++ /dev/null @@ -1,35 +0,0 @@ -import { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; -import { defaultHelpers as helpers, result } from '../../test/support/index.mjs'; - -import { GENERATOR_SPRING_WEBSOCKET } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - - describe('with default config', () => { - before(async () => { - await helpers.runJHipster(GENERATOR_SPRING_WEBSOCKET).withJHipsterConfig(); - }); - - it('should match files snapshot', () => { - expect(result.getStateSnapshot()).toMatchSnapshot(); - }); - }); -}); diff --git a/generators/spring-websocket/generator.spec.ts b/generators/spring-websocket/generator.spec.ts new file mode 100644 index 000000000000..053a2c14bd4a --- /dev/null +++ b/generators/spring-websocket/generator.spec.ts @@ -0,0 +1,35 @@ +import { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; +import { defaultHelpers as helpers, result } from '../../test/support/index.js'; + +import { GENERATOR_SPRING_WEBSOCKET } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + + describe('with default config', () => { + before(async () => { + await helpers.runJHipster(GENERATOR_SPRING_WEBSOCKET).withJHipsterConfig(); + }); + + it('should match files snapshot', () => { + expect(result.getStateSnapshot()).toMatchSnapshot(); + }); + }); +}); diff --git a/generators/spring-websocket/generator.ts b/generators/spring-websocket/generator.ts new file mode 100644 index 000000000000..c3b6d93dc544 --- /dev/null +++ b/generators/spring-websocket/generator.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2013-2023 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 BaseApplicationGenerator from '../base-application/index.js'; +import { GENERATOR_SPRING_WEBSOCKET, GENERATOR_BOOTSTRAP_APPLICATION } from '../generator-list.js'; +import writeTask from './files.js'; +import cleanupTask from './cleanup.js'; + +export default class SpringWebsocketGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_SPRING_WEBSOCKET); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupTask, + writeTask, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addDependencies({ application, source }) { + if (application.buildToolMaven) { + source.addMavenDefinition?.({ + dependencies: [ + { groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-websocket' }, + { groupId: 'org.springframework.security', artifactId: 'spring-security-messaging' }, + ], + }); + } + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } +} diff --git a/generators/spring-websocket/index.mts b/generators/spring-websocket/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/spring-websocket/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/spring-websocket/index.ts b/generators/spring-websocket/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/spring-websocket/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/statistics.mts b/generators/statistics.mts deleted file mode 100644 index 029156739dd0..000000000000 --- a/generators/statistics.mts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright 2013-2023 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 os from 'os'; -import { v4 as uuid } from 'uuid'; -import Conf from 'conf'; -import { osLocaleSync } from 'os-locale'; -import axios from 'axios'; -import Insight from 'insight'; -import { packageJson as packagejs } from '../lib/index.mjs'; - -const DO_NOT_ASK_LIMIT = 100; - -const DEFAULT_JHIPSTER_ONLINE_URL = 'https://start.jhipster.tech'; - -type InsightConfig = { - clientId: string; - doNotAskCounter: number; - isLinked: boolean; -}; - -class Statistics { - config: Conf; - jhipsterOnlineUrl: string; - statisticsAPIPath: string; - clientId: string; - doNotAskCounter: number; - optOut: any; - isLinked: any; - noInsight: string | boolean | undefined; - forceInsight: boolean; - insight: any; - axiosClient: any; - axiosProxyClient: any; - statisticsAPI: string | undefined; - - constructor() { - this.config = new Conf({ - configName: 'jhipster-insight', - projectName: packagejs.name, - defaults: { - clientId: uuid(), - doNotAskCounter: 0, - isLinked: false, - }, - }); - this.jhipsterOnlineUrl = process.env.JHIPSTER_ONLINE_URL || DEFAULT_JHIPSTER_ONLINE_URL; - this.statisticsAPIPath = `${this.jhipsterOnlineUrl}/api`; - this.clientId = this.config.get('clientId'); - this.doNotAskCounter = this.config.get('doNotAskCounter'); - this.optOut = this.config.get('optOut'); - this.isLinked = this.config.get('isLinked'); - this.noInsight = process.argv.includes('--no-insight') || process.env.CI === 'true' || process.env.MOCHA_WORKER_ID; - this.forceInsight = process.argv.includes('--force-insight'); - this.configInsight(); - - if (this.noInsight) { - this.noInsightConfig(); - } else { - this.configProxy(); - } - } - - configInsight(trackingCode = 'UA-46075199-2', packageName = packagejs.name, packageVersion = packagejs.version) { - const insight = new Insight({ - trackingCode, - packageName, - packageVersion, - }); - - insight.trackWithEvent = (category, action) => { - insight.track(category, action); - insight.trackEvent({ - category, - action, - label: `${category} ${action}`, - value: 1, - }); - }; - if (this.optOut !== undefined) { - insight.optOut = this.optOut; - } - this.insight = insight; - } - - noInsightConfig() { - this.optOut = true; - } - - postRequest(url, data, force = false) { - if (!this.optOut || force) { - this.axiosClient - .post(url, data) - .then( - () => {}, - () => { - if (this.axiosProxyClient) { - this.axiosProxyClient - .post(url, data) - .then(() => {}) - .catch(() => {}); - } - }, - ) - .catch(() => {}); - } - } - - postWithProxy(url, data) { - return this.axiosProxyClient.post(url, data); - } - - configProxy() { - this.axiosClient = axios.create({ - baseURL: this.statisticsAPIPath, - }); - - const npmHttpsProxy = process.env.npm_config_https_proxy || process.env.npm_config_proxy; - const npmHttpProxy = process.env.npm_config_http_proxy || process.env.npm_config_proxy; - const envProxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; - const proxySettings = npmHttpsProxy || npmHttpProxy || envProxy; - if (proxySettings) { - const splitted = proxySettings.split(':'); - this.axiosProxyClient = axios.create({ - baseURL: this.statisticsAPI, - proxy: { host: splitted[0], port: Number(splitted[1]) }, - }); - } - } - - shouldWeAskForOptIn() { - if (this.noInsight || this.forceInsight) { - return false; - } - if (this.optOut) { - this.doNotAskCounter++; - this.config.set('doNotAskCounter', this.doNotAskCounter % DO_NOT_ASK_LIMIT); - } - - return this.optOut === undefined || (this.optOut && this.doNotAskCounter >= DO_NOT_ASK_LIMIT); - } - - setConfig(key, value) { - this.config.set(key, value); - this[key] = value; - } - - deleteConfig(key) { - this.config.delete(key); - } - - setOptOutStatus(status) { - this.setConfig('optOut', status); - } - - setLinkedStatus(status) { - this.setConfig('isLinked', status); - } - - sendYoRc(yorc, isARegeneration, generatorVersion) { - if (this.noInsight) return; - this.postRequest( - '/s/entry', - { - 'generator-jhipster': yorc, - 'generator-id': this.clientId, - 'generator-version': generatorVersion, - 'git-provider': 'local', - 'node-version': process.version, - os: `${os.platform()}:${os.release()}`, - arch: os.arch(), - cpu: os.cpus()[0].model, - cores: os.cpus().length, - memory: os.totalmem(), - 'user-language': osLocaleSync(), - isARegeneration, - }, - this.forceInsight, - ); - - this.insight.trackWithEvent('generator', 'app'); - this.insight.track('app/applicationType', yorc.applicationType); - this.insight.track('app/testFrameworks', yorc.testFrameworks); - this.insight.track('app/clientPackageManager', yorc.clientPackageManager); - } - - sendSubGenEvent(source, type, event?) { - if (this.noInsight) return; - const strEvent = event === '' ? event : JSON.stringify(event); - this.postRequest(`/s/event/${this.clientId}`, { source, type, event: strEvent }, this.forceInsight); - this.insight.trackWithEvent(source, type); - if (event) { - this.sendInsightSubGenEvents(type, event); - } - } - - /** - * Recursively send events that are contained in an Object. - * ie: - * const a = { b: 'value', c: { d: 'another value' } }; - * sendInsightSubGenEvents('foo/bar', a); - * will send : - * this.insight.track('foo/bar/b', 'value'); - * this.insight.track('foo/bar/b/c/d', 'another value'); - * - * @param {string} prefix insight event prefix - * @param {any} eventObject events that you want to send - */ - sendInsightSubGenEvents(prefix, eventObject) { - if (this.noInsight || eventObject === null || eventObject === undefined) return; - if (typeof eventObject === 'object') { - Object.keys(eventObject).forEach(key => { - const value = eventObject[key]; - if (typeof eventObject[key] === 'object') { - this.sendInsightSubGenEvents(`${prefix}/${key}`, value); - } else { - this.insight.track(`${prefix}/${key}`, value); - } - }); - } else { - this.insight.track(prefix, eventObject); - } - } - - sendEntityStats(fields, relationships, pagination, dto, service, fluentMethods) { - if (this.noInsight) return; - this.postRequest( - `/s/entity/${this.clientId}`, - { - fields, - relationships, - pagination, - dto, - service, - fluentMethods, - }, - this.forceInsight, - ); - } -} - -export default new Statistics(); diff --git a/generators/statistics.ts b/generators/statistics.ts new file mode 100644 index 000000000000..377b346f5a4f --- /dev/null +++ b/generators/statistics.ts @@ -0,0 +1,257 @@ +/** + * Copyright 2013-2023 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 os from 'os'; +import { v4 as uuid } from 'uuid'; +import Conf from 'conf'; +import { osLocaleSync } from 'os-locale'; +import axios from 'axios'; +import Insight from 'insight'; +import { packageJson as packagejs } from '../lib/index.js'; + +const DO_NOT_ASK_LIMIT = 100; + +const DEFAULT_JHIPSTER_ONLINE_URL = 'https://start.jhipster.tech'; + +type InsightConfig = { + clientId: string; + doNotAskCounter: number; + isLinked: boolean; +}; + +class Statistics { + config: Conf; + jhipsterOnlineUrl: string; + statisticsAPIPath: string; + clientId: string; + doNotAskCounter: number; + optOut: any; + isLinked: any; + noInsight: string | boolean | undefined; + forceInsight: boolean; + insight: any; + axiosClient: any; + axiosProxyClient: any; + statisticsAPI: string | undefined; + + constructor() { + this.config = new Conf({ + configName: 'jhipster-insight', + projectName: packagejs.name, + defaults: { + clientId: uuid(), + doNotAskCounter: 0, + isLinked: false, + }, + }); + this.jhipsterOnlineUrl = process.env.JHIPSTER_ONLINE_URL || DEFAULT_JHIPSTER_ONLINE_URL; + this.statisticsAPIPath = `${this.jhipsterOnlineUrl}/api`; + this.clientId = this.config.get('clientId'); + this.doNotAskCounter = this.config.get('doNotAskCounter'); + this.optOut = this.config.get('optOut'); + this.isLinked = this.config.get('isLinked'); + this.noInsight = process.argv.includes('--no-insight') || process.env.CI === 'true' || process.env.MOCHA_WORKER_ID; + this.forceInsight = process.argv.includes('--force-insight'); + this.configInsight(); + + if (this.noInsight) { + this.noInsightConfig(); + } else { + this.configProxy(); + } + } + + configInsight(trackingCode = 'UA-46075199-2', packageName = packagejs.name, packageVersion = packagejs.version) { + const insight = new Insight({ + trackingCode, + packageName, + packageVersion, + }); + + insight.trackWithEvent = (category, action) => { + insight.track(category, action); + insight.trackEvent({ + category, + action, + label: `${category} ${action}`, + value: 1, + }); + }; + if (this.optOut !== undefined) { + insight.optOut = this.optOut; + } + this.insight = insight; + } + + noInsightConfig() { + this.optOut = true; + } + + postRequest(url, data, force = false) { + if (!this.optOut || force) { + this.axiosClient + .post(url, data) + .then( + () => {}, + () => { + if (this.axiosProxyClient) { + this.axiosProxyClient + .post(url, data) + .then(() => {}) + .catch(() => {}); + } + }, + ) + .catch(() => {}); + } + } + + postWithProxy(url, data) { + return this.axiosProxyClient.post(url, data); + } + + configProxy() { + this.axiosClient = axios.create({ + baseURL: this.statisticsAPIPath, + }); + + const npmHttpsProxy = process.env.npm_config_https_proxy || process.env.npm_config_proxy; + const npmHttpProxy = process.env.npm_config_http_proxy || process.env.npm_config_proxy; + const envProxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; + const proxySettings = npmHttpsProxy || npmHttpProxy || envProxy; + if (proxySettings) { + const splitted = proxySettings.split(':'); + this.axiosProxyClient = axios.create({ + baseURL: this.statisticsAPI, + proxy: { host: splitted[0], port: Number(splitted[1]) }, + }); + } + } + + shouldWeAskForOptIn() { + if (this.noInsight || this.forceInsight) { + return false; + } + if (this.optOut) { + this.doNotAskCounter++; + this.config.set('doNotAskCounter', this.doNotAskCounter % DO_NOT_ASK_LIMIT); + } + + return this.optOut === undefined || (this.optOut && this.doNotAskCounter >= DO_NOT_ASK_LIMIT); + } + + setConfig(key, value) { + this.config.set(key, value); + this[key] = value; + } + + deleteConfig(key) { + this.config.delete(key); + } + + setOptOutStatus(status) { + this.setConfig('optOut', status); + } + + setLinkedStatus(status) { + this.setConfig('isLinked', status); + } + + sendYoRc(yorc, isARegeneration, generatorVersion) { + if (this.noInsight) return; + this.postRequest( + '/s/entry', + { + 'generator-jhipster': yorc, + 'generator-id': this.clientId, + 'generator-version': generatorVersion, + 'git-provider': 'local', + 'node-version': process.version, + os: `${os.platform()}:${os.release()}`, + arch: os.arch(), + cpu: os.cpus()[0].model, + cores: os.cpus().length, + memory: os.totalmem(), + 'user-language': osLocaleSync(), + isARegeneration, + }, + this.forceInsight, + ); + + this.insight.trackWithEvent('generator', 'app'); + this.insight.track('app/applicationType', yorc.applicationType); + this.insight.track('app/testFrameworks', yorc.testFrameworks); + this.insight.track('app/clientPackageManager', yorc.clientPackageManager); + } + + sendSubGenEvent(source, type, event?) { + if (this.noInsight) return; + const strEvent = event === '' ? event : JSON.stringify(event); + this.postRequest(`/s/event/${this.clientId}`, { source, type, event: strEvent }, this.forceInsight); + this.insight.trackWithEvent(source, type); + if (event) { + this.sendInsightSubGenEvents(type, event); + } + } + + /** + * Recursively send events that are contained in an Object. + * ie: + * const a = { b: 'value', c: { d: 'another value' } }; + * sendInsightSubGenEvents('foo/bar', a); + * will send : + * this.insight.track('foo/bar/b', 'value'); + * this.insight.track('foo/bar/b/c/d', 'another value'); + * + * @param {string} prefix insight event prefix + * @param {any} eventObject events that you want to send + */ + sendInsightSubGenEvents(prefix, eventObject) { + if (this.noInsight || eventObject === null || eventObject === undefined) return; + if (typeof eventObject === 'object') { + Object.keys(eventObject).forEach(key => { + const value = eventObject[key]; + if (typeof eventObject[key] === 'object') { + this.sendInsightSubGenEvents(`${prefix}/${key}`, value); + } else { + this.insight.track(`${prefix}/${key}`, value); + } + }); + } else { + this.insight.track(prefix, eventObject); + } + } + + sendEntityStats(fields, relationships, pagination, dto, service, fluentMethods) { + if (this.noInsight) return; + this.postRequest( + `/s/entity/${this.clientId}`, + { + fields, + relationships, + pagination, + dto, + service, + fluentMethods, + }, + this.forceInsight, + ); + } +} + +export default new Statistics(); diff --git a/generators/upgrade/command.mts b/generators/upgrade/command.mts deleted file mode 100644 index fa200d0a7e34..000000000000 --- a/generators/upgrade/command.mts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - targetJhipsterVersion: { - name: 'targetVersion', - description: 'Upgrade to a specific version instead of the latest', - type: String, - scope: 'generator', - }, - targetBlueprintVersions: { - description: 'Upgrade to specific blueprint versions instead of the latest, e.g. --target-blueprint-versions foo@0.0.1,bar@1.0.2', - type: String, - }, - silent: { - description: 'Hides output of the generation process', - type: Boolean, - default: false, - scope: 'generator', - }, - }, -}; - -export default command; diff --git a/generators/upgrade/command.ts b/generators/upgrade/command.ts new file mode 100644 index 000000000000..1586174ff401 --- /dev/null +++ b/generators/upgrade/command.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2013-2023 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 { JHipsterCommandDefinition } from '../base/api.js'; + +const command: JHipsterCommandDefinition = { + options: { + targetJhipsterVersion: { + name: 'targetVersion', + description: 'Upgrade to a specific version instead of the latest', + type: String, + scope: 'generator', + }, + targetBlueprintVersions: { + description: 'Upgrade to specific blueprint versions instead of the latest, e.g. --target-blueprint-versions foo@0.0.1,bar@1.0.2', + type: String, + }, + silent: { + description: 'Hides output of the generation process', + type: Boolean, + default: false, + scope: 'generator', + }, + }, +}; + +export default command; diff --git a/generators/upgrade/generator.js b/generators/upgrade/generator.js new file mode 100644 index 000000000000..a22ff1f7222d --- /dev/null +++ b/generators/upgrade/generator.js @@ -0,0 +1,474 @@ +/** + * Copyright 2013-2023 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 process from 'node:process'; +import fs, { rmSync, readdirSync } from 'node:fs'; +import path from 'node:path'; +import chalk from 'chalk'; +import semver from 'semver'; +import gitignore from 'parse-gitignore'; +import latestVersion from 'latest-version'; + +import BaseGenerator from '../base/index.js'; +import { SERVER_MAIN_RES_DIR } from '../generator-constants.js'; +import statistics from '../statistics.js'; +import { parseBluePrints } from '../base/internal/index.js'; +import { packageJson } from '../../lib/index.js'; +import command from './command.js'; + +/* Constants used throughout */ +const GENERATOR_JHIPSTER = 'generator-jhipster'; +const UPGRADE_BRANCH = 'jhipster_upgrade'; +const GLOBAL_VERSION = 'global'; +const GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES = '2.9.0'; +const FIRST_CLI_SUPPORTED_VERSION = '4.5.1'; // The first version in which CLI support was added + +export default class UpgradeGenerator extends BaseGenerator { + fromV7; + fromV7_9_3App; + + get [BaseGenerator.INITIALIZING]() { + return this.asInitializingTaskGroup({ + displayLogo() { + this.log.log(chalk.green('Welcome to the JHipster Upgrade Sub-Generator')); + this.log.log(chalk.green('This will upgrade your current application codebase to the latest JHipster version')); + }, + + loadOptions() { + if (!this.config.existed) { + throw new Error( + "Could not find a valid JHipster application configuration, check if the '.yo-rc.json' file exists and if the 'generator-jhipster' key exists inside it.", + ); + } + + this.parseJHipsterOptions(command.options); + + this.force = this.options.force; + this.targetBlueprintVersions = parseBluePrints(this.options.targetBlueprintVersions); + this.skipInstall = this.options.skipInstall; + + this.fromV7 = this.jhipsterConfig.jhipsterVersion && semver.satisfies(this.jhipsterConfig.jhipsterVersion, '^7.0.0'); + const currentNodeVersion = process.versions.node; + if (this.jhipsterConfig.jhipsterVersion === '7.9.3') { + if (!semver.satisfies(currentNodeVersion, '^16.0.0')) { + throw new Error('Upgrading a v7.9.3 generated application requires node 16 to upgrade'); + } + this.fromV7_9_3App = true; + } else if (!semver.satisfies(currentNodeVersion, packageJson.engines.node)) { + this.log.fatal( + `You are running Node version ${currentNodeVersion}\nJHipster requires Node version ${packageJson.engines.node}\nPlease update your version of Node.`, + ); + } + }, + parseBlueprints() { + this.blueprints = parseBluePrints(this.options.blueprints || this.config.get('blueprints') || this.config.get('blueprint')) || []; + }, + + loadConfig() { + this.currentJhipsterVersion = this.config.get('jhipsterVersion'); + this.clientPackageManager = this.config.get('clientPackageManager'); + }, + }); + } + + _rmRf(file) { + const absolutePath = path.resolve(file); + this.log.verboseInfo(`Removing ${absolutePath}`); + rmSync(absolutePath, { recursive: true, force: true }); + } + + _gitCheckout(branch, options = {}) { + const args = ['checkout', '-q', branch]; + if (options.force) { + args.push('-f'); + } + const gitCheckout = this.spawnSync('git', args, { stdio: 'pipe', reject: false }); + if (gitCheckout.exitCode !== 0) throw new Error(`Unable to checkout branch ${branch}:\n${gitCheckout.stderr}`); + this.log.ok(`Checked out branch "${branch}"`); + } + + _cleanUp() { + const ignoredFiles = gitignore(fs.readFileSync('.gitignore')).patterns || []; + const filesToKeep = ['.yo-rc.json', '.jhipster', 'node_modules', '.git', '.idea', '.mvn', ...ignoredFiles]; + const files = readdirSync(this.destinationPath()); + files.forEach(file => { + if (!filesToKeep.includes(file)) { + this._rmRf(file); + } + }); + this.log.ok('Cleaned up project directory'); + } + + _generate(jhipsterVersion, blueprintInfo, { target }) { + this.log.verboseInfo(`Regenerating application with JHipster ${jhipsterVersion}${blueprintInfo}...`); + let generatorCommand = 'yo jhipster'; + if (this.options.regenerateExecutable) { + generatorCommand = this.options.regenerateExecutable; + } else if (jhipsterVersion.startsWith(GLOBAL_VERSION)) { + this._rmRf('node_modules'); + generatorCommand = 'jhipster'; + } else if (semver.gte(jhipsterVersion, FIRST_CLI_SUPPORTED_VERSION)) { + const result = this.spawnCommandSync('npm bin', { stdio: 'pipe', reject: false }); + if (result.exitCode === 0) { + generatorCommand = `"${result.stdout.replace('\n', '')}/jhipster"`; + } else { + generatorCommand = 'npm exec --no jhipster --'; + } + } + const skipChecksOption = this.skipChecks || (!target && this.fromV7_9_3App) ? '--skip-checks' : ''; + const regenerateCmd = `${generatorCommand} ${ + this.fromV7 ? '--with-entities ' : '' + }--force --skip-install --skip-git --ignore-errors --no-insight ${skipChecksOption}`; + this.log.verboseInfo(regenerateCmd); + const result = this.spawnCommandSync(regenerateCmd); + if (result.exitCode !== 0) { + throw new Error(`Something went wrong while generating project! ${result.exitCode}`); + } + + this.log.ok(`Successfully regenerated application with JHipster ${jhipsterVersion}${blueprintInfo}`); + } + + _gitCommitAll(commitMsg) { + const gitAdd = this.spawnSync('git', ['add', '-A'], { stdio: 'pipe', reject: false }); + if (gitAdd.exitCode !== 0) throw new Error(`Unable to add resources in git:\n${gitAdd.stderr}`); + + const gitCommit = this.spawnSync('git', ['commit', '-q', '-m', commitMsg, '-a', '--allow-empty', '--no-verify'], { + stdio: 'pipe', + }); + if (gitCommit.exitCode !== 0) throw new Error(`Unable to commit in git:\n${gitCommit.stderr}`); + this.log.ok(`Committed with message "${commitMsg}"`); + } + + _regenerate(jhipsterVersion, blueprintInfo, { target }) { + this._generate(jhipsterVersion, blueprintInfo, { target }); + const keystore = `${SERVER_MAIN_RES_DIR}config/tls/keystore.p12`; + this.log.verboseInfo(`Removing ${keystore}`); + this._rmRf(keystore); + this._gitCommitAll(`Generated with JHipster ${jhipsterVersion}${blueprintInfo}`); + } + + _installNpmPackageLocally(npmPackage, version) { + this.log.verboseInfo(`Installing ${npmPackage} ${version} locally`); + const commandPrefix = 'npm install'; + const devDependencyParam = '--save-dev'; + const noPackageLockParam = '--no-package-lock'; + const generatorCommand = `${commandPrefix} ${npmPackage}@${version} ${devDependencyParam} ${noPackageLockParam} --ignore-scripts --force`; + this.log.verboseInfo(generatorCommand); + + const npmInstall = this.spawnCommandSync(generatorCommand, { stdio: 'pipe', reject: false }); + if (npmInstall.exitCode === 0) this.log.ok(`Installed ${npmPackage}@${version}`); + else throw new Error(`Something went wrong while installing ${npmPackage}! ${npmInstall.stdout} ${npmInstall.stderr}`); + } + + get [BaseGenerator.CONFIGURING]() { + return this.asConfiguringTaskGroup({ + assertJHipsterProject() { + if (!this.config.get('baseName')) { + throw new Error('Current directory does not contain a JHipster project.'); + } + }, + + async assertGitPresent() { + if (!(await this.createGit().version()).installed) { + this.log.warn(`git is not found on your computer.\n, Install git: ${chalk.yellow('https://git-scm.com/')}`); + throw new Error('Exiting the process.'); + } + }, + + checkLatestBlueprintVersions() { + if (!this.blueprints || this.blueprints.length === 0) { + this.log.warn('No blueprints detected, skipping check of last blueprint version'); + return undefined; + } + + this.log.ok('Checking for new blueprint versions'); + return Promise.all( + this.blueprints + .filter(blueprint => { + if (this.targetBlueprintVersions && this.targetBlueprintVersions.length > 0) { + const targetBlueprint = this.targetBlueprintVersions.find(elem => { + return elem.name === blueprint.name; + }); + if (targetBlueprint && targetBlueprint.version && targetBlueprint.version !== 'latest') { + this.log.warn(`Blueprint ${targetBlueprint.name} will be upgraded to target version: ${targetBlueprint.version}`); + blueprint.latestBlueprintVersion = targetBlueprint.version; + return false; + } + } + return true; + }) + .map(async blueprint => { + blueprint.latestBlueprintVersion = await latestVersion(blueprint.name); + if (semver.lt(blueprint.version, blueprint.latestBlueprintVersion)) { + this.newBlueprintVersionFound = true; + this.log.ok(`New ${blueprint.name} version found: ${blueprint.latestBlueprintVersion}`); + } else if (this.force) { + this.newBlueprintVersionFound = true; + this.log.log(chalk.yellow('Forced re-generation')); + } else { + if (this.newBlueprintVersionFound === undefined) { + this.newBlueprintVersionFound = false; + } + this.log.warn( + `${chalk.green('No update available.')} Application has already been generated with latest version for blueprint: ${ + blueprint.name + }`, + ); + } + this.log.ok(`Done checking for new version for blueprint ${blueprint.name}`); + }), + ).then(() => { + this.log.ok('Done checking for new version of blueprints'); + }); + }, + + async checkLatestJhipsterVersion() { + if (this.targetJhipsterVersion) { + if (this.targetJhipsterVersion === GLOBAL_VERSION) { + this.originalTargetJhipsterVersion = this.targetJhipsterVersion; + this.targetJhipsterVersion = packageJson.version; + } + this.log.warn(`Upgrading to the target JHipster version: ${this.targetJhipsterVersion}`); + return; + } + this.log.verboseInfo(`Looking for latest ${GENERATOR_JHIPSTER} version...`); + this.targetJhipsterVersion = await latestVersion(GENERATOR_JHIPSTER); + if (semver.lt(this.currentJhipsterVersion, this.targetJhipsterVersion)) { + this.log.ok(`New ${GENERATOR_JHIPSTER} version found: ${this.targetJhipsterVersion}`); + } else if (this.force) { + this.log.log(chalk.yellow('Forced re-generation')); + } else if (!this.newBlueprintVersionFound) { + throw new Error(`${chalk.green('No update available.')} Application has already been generated with latest version.`); + } + }, + + assertGitRepository() { + const gitInit = () => { + const gitInit = this.spawnSync('git', ['init'], { stdio: 'pipe', reject: false }); + if (gitInit.exitCode !== 0) throw new Error(`Unable to initialize a new Git repository:\n${gitInit.stdout} ${gitInit.stderr}`); + this.log.ok('Initialized a new Git repository'); + this._gitCommitAll('Initial'); + }; + const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--is-inside-work-tree'], { stdio: 'pipe', reject: false }); + if (gitRevParse.exitCode !== 0) gitInit(); + else this.log.ok('Git repository detected'); + }, + + assertNoLocalChanges() { + const gitStatus = this.spawnSync('git', ['status', '--porcelain'], { stdio: 'pipe', reject: false }); + if (gitStatus.exitCode !== 0) throw new Error(`Unable to check for local changes:\n${gitStatus.stdout} ${gitStatus.stderr}`); + if (gitStatus.stdout) { + this.log.warn(gitStatus.stdout); + throw new Error(' local changes found.\n\tPlease commit/stash them before upgrading'); + } + }, + + detectCurrentBranch() { + const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--abbrev-ref', 'HEAD'], { stdio: 'pipe', reject: false }); + if (gitRevParse.exitCode !== 0) + throw new Error(`Unable to detect current Git branch:\n${gitRevParse.stdout} ${gitRevParse.stderr}`); + this.sourceBranch = gitRevParse.stdout.replace('\n', ''); + }, + + async prepareUpgradeBranch() { + const getGitVersion = () => { + const gitVersion = this.spawnSync('git', ['--version'], { stdio: 'pipe', reject: false }); + return String(gitVersion.stdout.match(/([0-9]+\.[0-9]+\.[0-9]+)/g)); + }; + + const recordCodeHasBeenGenerated = () => { + const gitVersion = getGitVersion(); + let args; + if (semver.lt(gitVersion, GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES)) { + args = ['merge', '--strategy=ours', '-q', '--no-edit', UPGRADE_BRANCH]; + } else { + args = ['merge', '--strategy=ours', '-q', '--no-edit', '--allow-unrelated-histories', UPGRADE_BRANCH]; + } + const gitMerge = this.spawnSync('git', args, { stdio: 'pipe', reject: false }); + if (gitMerge.exitCode !== 0) { + throw new Error( + `Unable to record current code has been generated with version ${this.currentJhipsterVersion}:\n${gitMerge.stdout} ${gitMerge.stderr}`, + ); + } + this.log.ok(`Current code has been generated with version ${this.currentJhipsterVersion}`); + }; + + const installJhipsterLocally = version => { + this._installNpmPackageLocally(GENERATOR_JHIPSTER, version); + }; + + const installBlueprintsLocally = () => { + if (!this.blueprints || this.blueprints.length < 1) { + this.log.verboseInfo('Skipping local blueprint installation since no blueprint has been detected'); + return Promise.resolve(false); + } + + this.log.ok('Installing blueprints locally...'); + return Promise.all( + this.blueprints.map(blueprint => { + return new Promise(resolve => { + this._installNpmPackageLocally(blueprint.name, blueprint.version); + this.log.ok(`Done installing blueprint: ${blueprint.name}@${blueprint.version}`); + resolve(); + }); + }), + ).then(() => { + this.log.ok('Done installing blueprints locally'); + return true; + }); + }; + + const createUpgradeBranch = () => { + const gitCheckout = this.spawnSync('git', ['checkout', '--orphan', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); + if (gitCheckout.exitCode !== 0) + throw new Error(`Unable to create ${UPGRADE_BRANCH} branch:\n${gitCheckout.stdout} ${gitCheckout.stderr}`); + this.log.ok(`Created branch ${UPGRADE_BRANCH}`); + }; + + const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--verify', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); + if (gitRevParse.exitCode !== 0) { + // Create and checkout upgrade branch + createUpgradeBranch(); + // Remove/rename old files + this._cleanUp(); + // Install jhipster + if (!this.options.regenerateExecutable) { + installJhipsterLocally(this.currentJhipsterVersion); + } + // Install blueprints + await installBlueprintsLocally(); + const blueprintInfo = + this.blueprints && this.blueprints.length > 0 ? ` and ${this.blueprints.map(bp => bp.name + bp.version).join(', ')} ` : ''; + // Regenerate the project + this._regenerate(this.currentJhipsterVersion, blueprintInfo, { target: false }); + // Checkout original branch + this._gitCheckout(this.sourceBranch); + // Register reference for merging + recordCodeHasBeenGenerated(); + } + }, + }); + } + + get [BaseGenerator.DEFAULT]() { + return { + insight() { + statistics.sendSubGenEvent('generator', 'upgrade'); + }, + + checkoutUpgradeBranch() { + this._gitCheckout(UPGRADE_BRANCH); + }, + + updateJhipster() { + if (this.originalTargetJhipsterVersion === GLOBAL_VERSION || this.options.regenerateExecutable) { + return; + } + this._installNpmPackageLocally(GENERATOR_JHIPSTER, this.targetJhipsterVersion); + }, + + updateBlueprints() { + if (!this.blueprints || this.blueprints.length < 1) { + this.log.verboseInfo('Skipping blueprint update since no blueprint has been detected'); + return undefined; + } + + this.log.ok('Upgrading blueprints...'); + return Promise.all( + this.blueprints.map(blueprint => { + return new Promise(resolve => { + this._installNpmPackageLocally(blueprint.name, blueprint.latestBlueprintVersion); + this.log.ok(`Done upgrading blueprint ${blueprint.name} to version ${blueprint.latestBlueprintVersion}`); + resolve(); + }); + }), + ).then(() => { + this.log.ok('Done upgrading blueprints'); + }); + }, + + generateWithTargetVersion() { + this._cleanUp(); + + const blueprintInfo = + this.blueprints && this.blueprints.length > 0 + ? ` and ${this.blueprints.map(bp => bp.name + bp.latestBlueprintVersion).join(', ')} ` + : ''; + const targetJhipsterVersion = this.originalTargetJhipsterVersion + ? `${this.originalTargetJhipsterVersion} ${this.targetJhipsterVersion}` + : this.targetJhipsterVersion; + this._regenerate(targetJhipsterVersion, blueprintInfo, { target: true }); + }, + + checkoutSourceBranch() { + this._gitCheckout(this.sourceBranch, { force: true }); + }, + + mergeChangesBack() { + this.log.verboseInfo(`Merging changes back to ${this.sourceBranch}...`); + this.spawnSync('git', ['merge', '-q', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); + this.log.ok('Merge done!'); + }, + + checkConflictsInPackageJson() { + const gitDiff = this.spawnSync('git', ['diff', '--name-only', '--diff-filter=U', 'package.json'], { stdio: 'pipe', reject: false }); + if (gitDiff.exitCode !== 0) throw new Error(`Unable to check for conflicts in package.json:\n${gitDiff.stdout} ${gitDiff.stderr}`); + if (gitDiff.stdout) { + const installCommand = 'npm install'; + this.log.warn(`There are conflicts in package.json, please fix them and then run ${installCommand}`); + this.skipInstall = true; + } + }, + }; + } + + get [BaseGenerator.INSTALL]() { + return this.asInstallTaskGroup({ + install() { + if (!this.skipInstall) { + this.log.verboseInfo('Installing dependencies, please wait...'); + this.log.verboseInfo('Removing the node_modules directory'); + this._rmRf('node_modules'); + const installCommand = 'npm install'; + this.log.verboseInfo(installCommand); + + const pkgInstall = this.spawnSync(installCommand, { stdio: 'pipe', reject: false }); + if (pkgInstall.exitCode !== 0) { + throw new Error(`${installCommand} failed.`); + } + } else { + const logMsg = `Start your Webpack development server with:\n${chalk.yellow.bold(`${this.clientPackageManager} start`)}\n`; + this.log.ok(logMsg); + } + }, + }); + } + + get [BaseGenerator.END]() { + return { + end() { + const gitDiff = this.spawnSync('git', ['diff', '--name-only', '--diff-filter=U'], { stdio: 'pipe', reject: false }); + if (gitDiff.exitCode !== 0) throw new Error(`Unable to check for conflicts:\n${gitDiff.stdout} ${gitDiff.stderr}`); + this.log.ok(chalk.bold('Upgraded successfully.')); + if (gitDiff.stdout) { + this.log.warn(`Please fix conflicts listed below and commit!\n${gitDiff.stdout}`); + } + }, + }; + } +} diff --git a/generators/upgrade/generator.mjs b/generators/upgrade/generator.mjs deleted file mode 100644 index 68e04cc7f5ee..000000000000 --- a/generators/upgrade/generator.mjs +++ /dev/null @@ -1,474 +0,0 @@ -/** - * Copyright 2013-2023 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 process from 'node:process'; -import fs, { rmSync, readdirSync } from 'node:fs'; -import path from 'node:path'; -import chalk from 'chalk'; -import semver from 'semver'; -import gitignore from 'parse-gitignore'; -import latestVersion from 'latest-version'; - -import BaseGenerator from '../base/index.mjs'; -import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import statistics from '../statistics.mjs'; -import { parseBluePrints } from '../base/internal/index.mjs'; -import { packageJson } from '../../lib/index.mjs'; -import command from './command.mjs'; - -/* Constants used throughout */ -const GENERATOR_JHIPSTER = 'generator-jhipster'; -const UPGRADE_BRANCH = 'jhipster_upgrade'; -const GLOBAL_VERSION = 'global'; -const GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES = '2.9.0'; -const FIRST_CLI_SUPPORTED_VERSION = '4.5.1'; // The first version in which CLI support was added - -export default class UpgradeGenerator extends BaseGenerator { - fromV7; - fromV7_9_3App; - - get [BaseGenerator.INITIALIZING]() { - return this.asInitializingTaskGroup({ - displayLogo() { - this.log.log(chalk.green('Welcome to the JHipster Upgrade Sub-Generator')); - this.log.log(chalk.green('This will upgrade your current application codebase to the latest JHipster version')); - }, - - loadOptions() { - if (!this.config.existed) { - throw new Error( - "Could not find a valid JHipster application configuration, check if the '.yo-rc.json' file exists and if the 'generator-jhipster' key exists inside it.", - ); - } - - this.parseJHipsterOptions(command.options); - - this.force = this.options.force; - this.targetBlueprintVersions = parseBluePrints(this.options.targetBlueprintVersions); - this.skipInstall = this.options.skipInstall; - - this.fromV7 = this.jhipsterConfig.jhipsterVersion && semver.satisfies(this.jhipsterConfig.jhipsterVersion, '^7.0.0'); - const currentNodeVersion = process.versions.node; - if (this.jhipsterConfig.jhipsterVersion === '7.9.3') { - if (!semver.satisfies(currentNodeVersion, '^16.0.0')) { - throw new Error('Upgrading a v7.9.3 generated application requires node 16 to upgrade'); - } - this.fromV7_9_3App = true; - } else if (!semver.satisfies(currentNodeVersion, packageJson.engines.node)) { - this.log.fatal( - `You are running Node version ${currentNodeVersion}\nJHipster requires Node version ${packageJson.engines.node}\nPlease update your version of Node.`, - ); - } - }, - parseBlueprints() { - this.blueprints = parseBluePrints(this.options.blueprints || this.config.get('blueprints') || this.config.get('blueprint')) || []; - }, - - loadConfig() { - this.currentJhipsterVersion = this.config.get('jhipsterVersion'); - this.clientPackageManager = this.config.get('clientPackageManager'); - }, - }); - } - - _rmRf(file) { - const absolutePath = path.resolve(file); - this.log.verboseInfo(`Removing ${absolutePath}`); - rmSync(absolutePath, { recursive: true, force: true }); - } - - _gitCheckout(branch, options = {}) { - const args = ['checkout', '-q', branch]; - if (options.force) { - args.push('-f'); - } - const gitCheckout = this.spawnSync('git', args, { stdio: 'pipe', reject: false }); - if (gitCheckout.exitCode !== 0) throw new Error(`Unable to checkout branch ${branch}:\n${gitCheckout.stderr}`); - this.log.ok(`Checked out branch "${branch}"`); - } - - _cleanUp() { - const ignoredFiles = gitignore(fs.readFileSync('.gitignore')).patterns || []; - const filesToKeep = ['.yo-rc.json', '.jhipster', 'node_modules', '.git', '.idea', '.mvn', ...ignoredFiles]; - const files = readdirSync(this.destinationPath()); - files.forEach(file => { - if (!filesToKeep.includes(file)) { - this._rmRf(file); - } - }); - this.log.ok('Cleaned up project directory'); - } - - _generate(jhipsterVersion, blueprintInfo, { target }) { - this.log.verboseInfo(`Regenerating application with JHipster ${jhipsterVersion}${blueprintInfo}...`); - let generatorCommand = 'yo jhipster'; - if (this.options.regenerateExecutable) { - generatorCommand = this.options.regenerateExecutable; - } else if (jhipsterVersion.startsWith(GLOBAL_VERSION)) { - this._rmRf('node_modules'); - generatorCommand = 'jhipster'; - } else if (semver.gte(jhipsterVersion, FIRST_CLI_SUPPORTED_VERSION)) { - const result = this.spawnCommandSync('npm bin', { stdio: 'pipe', reject: false }); - if (result.exitCode === 0) { - generatorCommand = `"${result.stdout.replace('\n', '')}/jhipster"`; - } else { - generatorCommand = 'npm exec --no jhipster --'; - } - } - const skipChecksOption = this.skipChecks || (!target && this.fromV7_9_3App) ? '--skip-checks' : ''; - const regenerateCmd = `${generatorCommand} ${ - this.fromV7 ? '--with-entities ' : '' - }--force --skip-install --skip-git --ignore-errors --no-insight ${skipChecksOption}`; - this.log.verboseInfo(regenerateCmd); - const result = this.spawnCommandSync(regenerateCmd); - if (result.exitCode !== 0) { - throw new Error(`Something went wrong while generating project! ${result.exitCode}`); - } - - this.log.ok(`Successfully regenerated application with JHipster ${jhipsterVersion}${blueprintInfo}`); - } - - _gitCommitAll(commitMsg) { - const gitAdd = this.spawnSync('git', ['add', '-A'], { stdio: 'pipe', reject: false }); - if (gitAdd.exitCode !== 0) throw new Error(`Unable to add resources in git:\n${gitAdd.stderr}`); - - const gitCommit = this.spawnSync('git', ['commit', '-q', '-m', commitMsg, '-a', '--allow-empty', '--no-verify'], { - stdio: 'pipe', - }); - if (gitCommit.exitCode !== 0) throw new Error(`Unable to commit in git:\n${gitCommit.stderr}`); - this.log.ok(`Committed with message "${commitMsg}"`); - } - - _regenerate(jhipsterVersion, blueprintInfo, { target }) { - this._generate(jhipsterVersion, blueprintInfo, { target }); - const keystore = `${SERVER_MAIN_RES_DIR}config/tls/keystore.p12`; - this.log.verboseInfo(`Removing ${keystore}`); - this._rmRf(keystore); - this._gitCommitAll(`Generated with JHipster ${jhipsterVersion}${blueprintInfo}`); - } - - _installNpmPackageLocally(npmPackage, version) { - this.log.verboseInfo(`Installing ${npmPackage} ${version} locally`); - const commandPrefix = 'npm install'; - const devDependencyParam = '--save-dev'; - const noPackageLockParam = '--no-package-lock'; - const generatorCommand = `${commandPrefix} ${npmPackage}@${version} ${devDependencyParam} ${noPackageLockParam} --ignore-scripts --force`; - this.log.verboseInfo(generatorCommand); - - const npmInstall = this.spawnCommandSync(generatorCommand, { stdio: 'pipe', reject: false }); - if (npmInstall.exitCode === 0) this.log.ok(`Installed ${npmPackage}@${version}`); - else throw new Error(`Something went wrong while installing ${npmPackage}! ${npmInstall.stdout} ${npmInstall.stderr}`); - } - - get [BaseGenerator.CONFIGURING]() { - return this.asConfiguringTaskGroup({ - assertJHipsterProject() { - if (!this.config.get('baseName')) { - throw new Error('Current directory does not contain a JHipster project.'); - } - }, - - async assertGitPresent() { - if (!(await this.createGit().version()).installed) { - this.log.warn(`git is not found on your computer.\n, Install git: ${chalk.yellow('https://git-scm.com/')}`); - throw new Error('Exiting the process.'); - } - }, - - checkLatestBlueprintVersions() { - if (!this.blueprints || this.blueprints.length === 0) { - this.log.warn('No blueprints detected, skipping check of last blueprint version'); - return undefined; - } - - this.log.ok('Checking for new blueprint versions'); - return Promise.all( - this.blueprints - .filter(blueprint => { - if (this.targetBlueprintVersions && this.targetBlueprintVersions.length > 0) { - const targetBlueprint = this.targetBlueprintVersions.find(elem => { - return elem.name === blueprint.name; - }); - if (targetBlueprint && targetBlueprint.version && targetBlueprint.version !== 'latest') { - this.log.warn(`Blueprint ${targetBlueprint.name} will be upgraded to target version: ${targetBlueprint.version}`); - blueprint.latestBlueprintVersion = targetBlueprint.version; - return false; - } - } - return true; - }) - .map(async blueprint => { - blueprint.latestBlueprintVersion = await latestVersion(blueprint.name); - if (semver.lt(blueprint.version, blueprint.latestBlueprintVersion)) { - this.newBlueprintVersionFound = true; - this.log.ok(`New ${blueprint.name} version found: ${blueprint.latestBlueprintVersion}`); - } else if (this.force) { - this.newBlueprintVersionFound = true; - this.log.log(chalk.yellow('Forced re-generation')); - } else { - if (this.newBlueprintVersionFound === undefined) { - this.newBlueprintVersionFound = false; - } - this.log.warn( - `${chalk.green('No update available.')} Application has already been generated with latest version for blueprint: ${ - blueprint.name - }`, - ); - } - this.log.ok(`Done checking for new version for blueprint ${blueprint.name}`); - }), - ).then(() => { - this.log.ok('Done checking for new version of blueprints'); - }); - }, - - async checkLatestJhipsterVersion() { - if (this.targetJhipsterVersion) { - if (this.targetJhipsterVersion === GLOBAL_VERSION) { - this.originalTargetJhipsterVersion = this.targetJhipsterVersion; - this.targetJhipsterVersion = packageJson.version; - } - this.log.warn(`Upgrading to the target JHipster version: ${this.targetJhipsterVersion}`); - return; - } - this.log.verboseInfo(`Looking for latest ${GENERATOR_JHIPSTER} version...`); - this.targetJhipsterVersion = await latestVersion(GENERATOR_JHIPSTER); - if (semver.lt(this.currentJhipsterVersion, this.targetJhipsterVersion)) { - this.log.ok(`New ${GENERATOR_JHIPSTER} version found: ${this.targetJhipsterVersion}`); - } else if (this.force) { - this.log.log(chalk.yellow('Forced re-generation')); - } else if (!this.newBlueprintVersionFound) { - throw new Error(`${chalk.green('No update available.')} Application has already been generated with latest version.`); - } - }, - - assertGitRepository() { - const gitInit = () => { - const gitInit = this.spawnSync('git', ['init'], { stdio: 'pipe', reject: false }); - if (gitInit.exitCode !== 0) throw new Error(`Unable to initialize a new Git repository:\n${gitInit.stdout} ${gitInit.stderr}`); - this.log.ok('Initialized a new Git repository'); - this._gitCommitAll('Initial'); - }; - const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--is-inside-work-tree'], { stdio: 'pipe', reject: false }); - if (gitRevParse.exitCode !== 0) gitInit(); - else this.log.ok('Git repository detected'); - }, - - assertNoLocalChanges() { - const gitStatus = this.spawnSync('git', ['status', '--porcelain'], { stdio: 'pipe', reject: false }); - if (gitStatus.exitCode !== 0) throw new Error(`Unable to check for local changes:\n${gitStatus.stdout} ${gitStatus.stderr}`); - if (gitStatus.stdout) { - this.log.warn(gitStatus.stdout); - throw new Error(' local changes found.\n\tPlease commit/stash them before upgrading'); - } - }, - - detectCurrentBranch() { - const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--abbrev-ref', 'HEAD'], { stdio: 'pipe', reject: false }); - if (gitRevParse.exitCode !== 0) - throw new Error(`Unable to detect current Git branch:\n${gitRevParse.stdout} ${gitRevParse.stderr}`); - this.sourceBranch = gitRevParse.stdout.replace('\n', ''); - }, - - async prepareUpgradeBranch() { - const getGitVersion = () => { - const gitVersion = this.spawnSync('git', ['--version'], { stdio: 'pipe', reject: false }); - return String(gitVersion.stdout.match(/([0-9]+\.[0-9]+\.[0-9]+)/g)); - }; - - const recordCodeHasBeenGenerated = () => { - const gitVersion = getGitVersion(); - let args; - if (semver.lt(gitVersion, GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES)) { - args = ['merge', '--strategy=ours', '-q', '--no-edit', UPGRADE_BRANCH]; - } else { - args = ['merge', '--strategy=ours', '-q', '--no-edit', '--allow-unrelated-histories', UPGRADE_BRANCH]; - } - const gitMerge = this.spawnSync('git', args, { stdio: 'pipe', reject: false }); - if (gitMerge.exitCode !== 0) { - throw new Error( - `Unable to record current code has been generated with version ${this.currentJhipsterVersion}:\n${gitMerge.stdout} ${gitMerge.stderr}`, - ); - } - this.log.ok(`Current code has been generated with version ${this.currentJhipsterVersion}`); - }; - - const installJhipsterLocally = version => { - this._installNpmPackageLocally(GENERATOR_JHIPSTER, version); - }; - - const installBlueprintsLocally = () => { - if (!this.blueprints || this.blueprints.length < 1) { - this.log.verboseInfo('Skipping local blueprint installation since no blueprint has been detected'); - return Promise.resolve(false); - } - - this.log.ok('Installing blueprints locally...'); - return Promise.all( - this.blueprints.map(blueprint => { - return new Promise(resolve => { - this._installNpmPackageLocally(blueprint.name, blueprint.version); - this.log.ok(`Done installing blueprint: ${blueprint.name}@${blueprint.version}`); - resolve(); - }); - }), - ).then(() => { - this.log.ok('Done installing blueprints locally'); - return true; - }); - }; - - const createUpgradeBranch = () => { - const gitCheckout = this.spawnSync('git', ['checkout', '--orphan', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); - if (gitCheckout.exitCode !== 0) - throw new Error(`Unable to create ${UPGRADE_BRANCH} branch:\n${gitCheckout.stdout} ${gitCheckout.stderr}`); - this.log.ok(`Created branch ${UPGRADE_BRANCH}`); - }; - - const gitRevParse = this.spawnSync('git', ['rev-parse', '-q', '--verify', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); - if (gitRevParse.exitCode !== 0) { - // Create and checkout upgrade branch - createUpgradeBranch(); - // Remove/rename old files - this._cleanUp(); - // Install jhipster - if (!this.options.regenerateExecutable) { - installJhipsterLocally(this.currentJhipsterVersion); - } - // Install blueprints - await installBlueprintsLocally(); - const blueprintInfo = - this.blueprints && this.blueprints.length > 0 ? ` and ${this.blueprints.map(bp => bp.name + bp.version).join(', ')} ` : ''; - // Regenerate the project - this._regenerate(this.currentJhipsterVersion, blueprintInfo, { target: false }); - // Checkout original branch - this._gitCheckout(this.sourceBranch); - // Register reference for merging - recordCodeHasBeenGenerated(); - } - }, - }); - } - - get [BaseGenerator.DEFAULT]() { - return { - insight() { - statistics.sendSubGenEvent('generator', 'upgrade'); - }, - - checkoutUpgradeBranch() { - this._gitCheckout(UPGRADE_BRANCH); - }, - - updateJhipster() { - if (this.originalTargetJhipsterVersion === GLOBAL_VERSION || this.options.regenerateExecutable) { - return; - } - this._installNpmPackageLocally(GENERATOR_JHIPSTER, this.targetJhipsterVersion); - }, - - updateBlueprints() { - if (!this.blueprints || this.blueprints.length < 1) { - this.log.verboseInfo('Skipping blueprint update since no blueprint has been detected'); - return undefined; - } - - this.log.ok('Upgrading blueprints...'); - return Promise.all( - this.blueprints.map(blueprint => { - return new Promise(resolve => { - this._installNpmPackageLocally(blueprint.name, blueprint.latestBlueprintVersion); - this.log.ok(`Done upgrading blueprint ${blueprint.name} to version ${blueprint.latestBlueprintVersion}`); - resolve(); - }); - }), - ).then(() => { - this.log.ok('Done upgrading blueprints'); - }); - }, - - generateWithTargetVersion() { - this._cleanUp(); - - const blueprintInfo = - this.blueprints && this.blueprints.length > 0 - ? ` and ${this.blueprints.map(bp => bp.name + bp.latestBlueprintVersion).join(', ')} ` - : ''; - const targetJhipsterVersion = this.originalTargetJhipsterVersion - ? `${this.originalTargetJhipsterVersion} ${this.targetJhipsterVersion}` - : this.targetJhipsterVersion; - this._regenerate(targetJhipsterVersion, blueprintInfo, { target: true }); - }, - - checkoutSourceBranch() { - this._gitCheckout(this.sourceBranch, { force: true }); - }, - - mergeChangesBack() { - this.log.verboseInfo(`Merging changes back to ${this.sourceBranch}...`); - this.spawnSync('git', ['merge', '-q', UPGRADE_BRANCH], { stdio: 'pipe', reject: false }); - this.log.ok('Merge done!'); - }, - - checkConflictsInPackageJson() { - const gitDiff = this.spawnSync('git', ['diff', '--name-only', '--diff-filter=U', 'package.json'], { stdio: 'pipe', reject: false }); - if (gitDiff.exitCode !== 0) throw new Error(`Unable to check for conflicts in package.json:\n${gitDiff.stdout} ${gitDiff.stderr}`); - if (gitDiff.stdout) { - const installCommand = 'npm install'; - this.log.warn(`There are conflicts in package.json, please fix them and then run ${installCommand}`); - this.skipInstall = true; - } - }, - }; - } - - get [BaseGenerator.INSTALL]() { - return this.asInstallTaskGroup({ - install() { - if (!this.skipInstall) { - this.log.verboseInfo('Installing dependencies, please wait...'); - this.log.verboseInfo('Removing the node_modules directory'); - this._rmRf('node_modules'); - const installCommand = 'npm install'; - this.log.verboseInfo(installCommand); - - const pkgInstall = this.spawnSync(installCommand, { stdio: 'pipe', reject: false }); - if (pkgInstall.exitCode !== 0) { - throw new Error(`${installCommand} failed.`); - } - } else { - const logMsg = `Start your Webpack development server with:\n${chalk.yellow.bold(`${this.clientPackageManager} start`)}\n`; - this.log.ok(logMsg); - } - }, - }); - } - - get [BaseGenerator.END]() { - return { - end() { - const gitDiff = this.spawnSync('git', ['diff', '--name-only', '--diff-filter=U'], { stdio: 'pipe', reject: false }); - if (gitDiff.exitCode !== 0) throw new Error(`Unable to check for conflicts:\n${gitDiff.stdout} ${gitDiff.stderr}`); - this.log.ok(chalk.bold('Upgraded successfully.')); - if (gitDiff.stdout) { - this.log.warn(`Please fix conflicts listed below and commit!\n${gitDiff.stdout}`); - } - }, - }; - } -} diff --git a/generators/upgrade/generator.spec.js b/generators/upgrade/generator.spec.js new file mode 100644 index 000000000000..ced54155a097 --- /dev/null +++ b/generators/upgrade/generator.spec.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import Generator from './index.js'; +import { shouldSupportFeatures } from '../../test/support/tests.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); +}); diff --git a/generators/upgrade/generator.spec.mjs b/generators/upgrade/generator.spec.mjs deleted file mode 100644 index 3640f7017d60..000000000000 --- a/generators/upgrade/generator.spec.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import Generator from './index.mjs'; -import { shouldSupportFeatures } from '../../test/support/tests.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); -}); diff --git a/generators/upgrade/index.mts b/generators/upgrade/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/upgrade/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/upgrade/index.ts b/generators/upgrade/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/upgrade/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/upgrade/upgrade.spec.mts b/generators/upgrade/upgrade.spec.mts deleted file mode 100644 index f261f99b3379..000000000000 --- a/generators/upgrade/upgrade.spec.mts +++ /dev/null @@ -1,127 +0,0 @@ -import path, { dirname, resolve } from 'path'; -import { fileURLToPath } from 'url'; -import fse from 'fs-extra'; -import * as _ from 'lodash-es'; -import { expect } from 'esmocha'; - -import { execaCommandSync } from 'execa'; -import { packageJson } from '../../lib/index.mjs'; -import { GENERATOR_APP, GENERATOR_UPGRADE } from '../generator-list.mjs'; -import { basicHelpers as helpers, getGenerator, result as runResult } from '../../test/support/index.mjs'; - -const { escapeRegExp } = _; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -describe('generator - upgrade', function () { - describe('default application', () => { - before(async () => { - await helpers - .runJHipster(GENERATOR_APP) - .withJHipsterConfig({ - skipClient: true, - skipServer: true, - baseName: 'upgradeTest', - }) - .withOptions({ useVersionPlaceholders: false }); - await runResult - .create(getGenerator(GENERATOR_UPGRADE)) - .withOptions({ - regenerateExecutable: resolve(__dirname, '../../bin/jhipster.cjs'), - force: true, - silent: false, - targetVersion: packageJson.version, - useVersionPlaceholders: false, - }) - .run(); - }); - - it('generated git commits to match snapshot', () => { - const commits = execaCommandSync('git log --pretty=format:%s', { stdio: 'pipe', reject: false }).stdout; - expect(commits.replace(new RegExp(escapeRegExp(packageJson.version), 'g'), 'VERSION')).toMatchInlineSnapshot(` -"Merge branch 'jhipster_upgrade' -Generated with JHipster VERSION -Merge branch 'jhipster_upgrade' -Generated with JHipster undefined -Initial version of upgradeTest generated by generator-jhipster@undefined" -`); - }); - - it('generates expected number of commits', () => { - const commitsCount = execaCommandSync('git rev-list --count HEAD', { stdio: 'pipe', reject: false }).stdout.replace('\n', ''); - // Expecting 5 commits in history (because we used `force` option): - // - master: initial commit - // - jhipster_upgrade; initial generation - // - master: block-merge commit of jhipster_upgrade - // - jhipster_upgrade: new generation in jhipster_upgrade - // - master: merge commit of jhipster_upgrade - expect(commitsCount).toBe('5'); - }); - }); - describe.skip('blueprint application', () => { - const blueprintName = 'generator-jhipster-sample-blueprint'; - const blueprintVersion = '0.1.1'; - before(async () => { - await helpers.prepareTemporaryDir(); - const dir = process.cwd(); - /* eslint-disable-next-line no-console */ - console.log(`Generating JHipster application in directory: ${dir}`); - // Fake the presence of the blueprint in node_modules: we don't install it, but we need its version - const packagejs = { - name: blueprintName, - version: blueprintVersion, - }; - const fakeBlueprintModuleDir = path.join(dir, `node_modules/${blueprintName}`); - fse.ensureDirSync(path.join(fakeBlueprintModuleDir, 'generators', 'fake')); - fse.writeJsonSync(path.join(fakeBlueprintModuleDir, 'package.json'), packagejs); - // Create an fake generator, otherwise env.lookup doesn't find it. - fse.writeFileSync(path.join(fakeBlueprintModuleDir, 'generators', 'fake', 'index.js'), ''); - return helpers - .create(path.join(__dirname, '../generators/app/index.mjs'), { tmpdir: false }) - .withJHipsterConfig({ - skipClient: true, - skipServer: true, - baseName: 'upgradeTest', - }) - .withOptions({ - blueprints: blueprintName, - }) - .run() - .then(() => { - return helpers - .create(path.join(__dirname, '../generators/upgrade/index.mjs'), { tmpdir: false }) - .withOptions({ - force: true, - silent: false, - targetVersion: packageJson.version, - }) - .run(); - }); - }); - - it('generated git commits to match snapshot', () => { - const commits = execaCommandSync('git log --pretty=format:%s', { stdio: 'pipe', reject: false }).stdout; - expect(commits.replace(new RegExp(escapeRegExp(packageJson.version), 'g'), 'VERSION')).toMatchInlineSnapshot(` -`); - }); - - it('generates expected number of commits', () => { - const commitsCount = execaCommandSync('git rev-list --count HEAD', { stdio: 'pipe', reject: false }).stdout.replace('\n', ''); - // Expecting 5 commits in history (because we used `force` option): - // - master: initial commit - // - jhipster_upgrade; initial generation - // - master: block-merge commit of jhipster_upgrade - // - jhipster_upgrade: new generation in jhipster_upgrade - // - master: merge commit of jhipster_upgrade - expect(commitsCount).toBe('5'); - }); - - it('still contains blueprint information', () => { - runResult.assertJsonFileContent('.yo-rc.json', { - 'generator-jhipster': { blueprints: [{ name: blueprintName, version: blueprintVersion }] }, - }); - runResult.assertFileContent('package.json', new RegExp(`"${blueprintName}": "${blueprintVersion}"`)); - }); - }); -}); diff --git a/generators/upgrade/upgrade.spec.ts b/generators/upgrade/upgrade.spec.ts new file mode 100644 index 000000000000..da3e8671728e --- /dev/null +++ b/generators/upgrade/upgrade.spec.ts @@ -0,0 +1,127 @@ +import path, { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; +import fse from 'fs-extra'; +import * as _ from 'lodash-es'; +import { expect } from 'esmocha'; + +import { execaCommandSync } from 'execa'; +import { packageJson } from '../../lib/index.js'; +import { GENERATOR_APP, GENERATOR_UPGRADE } from '../generator-list.js'; +import { basicHelpers as helpers, getGenerator, result as runResult } from '../../test/support/index.js'; + +const { escapeRegExp } = _; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +describe('generator - upgrade', function () { + describe('default application', () => { + before(async () => { + await helpers + .runJHipster(GENERATOR_APP) + .withJHipsterConfig({ + skipClient: true, + skipServer: true, + baseName: 'upgradeTest', + }) + .withOptions({ useVersionPlaceholders: false }); + await runResult + .create(getGenerator(GENERATOR_UPGRADE)) + .withOptions({ + regenerateExecutable: resolve(__dirname, '../../bin/jhipster.cjs'), + force: true, + silent: false, + targetVersion: packageJson.version, + useVersionPlaceholders: false, + }) + .run(); + }); + + it('generated git commits to match snapshot', () => { + const commits = execaCommandSync('git log --pretty=format:%s', { stdio: 'pipe', reject: false }).stdout; + expect(commits.replace(new RegExp(escapeRegExp(packageJson.version), 'g'), 'VERSION')).toMatchInlineSnapshot(` +"Merge branch 'jhipster_upgrade' +Generated with JHipster VERSION +Merge branch 'jhipster_upgrade' +Generated with JHipster undefined +Initial version of upgradeTest generated by generator-jhipster@undefined" +`); + }); + + it('generates expected number of commits', () => { + const commitsCount = execaCommandSync('git rev-list --count HEAD', { stdio: 'pipe', reject: false }).stdout.replace('\n', ''); + // Expecting 5 commits in history (because we used `force` option): + // - master: initial commit + // - jhipster_upgrade; initial generation + // - master: block-merge commit of jhipster_upgrade + // - jhipster_upgrade: new generation in jhipster_upgrade + // - master: merge commit of jhipster_upgrade + expect(commitsCount).toBe('5'); + }); + }); + describe.skip('blueprint application', () => { + const blueprintName = 'generator-jhipster-sample-blueprint'; + const blueprintVersion = '0.1.1'; + before(async () => { + await helpers.prepareTemporaryDir(); + const dir = process.cwd(); + /* eslint-disable-next-line no-console */ + console.log(`Generating JHipster application in directory: ${dir}`); + // Fake the presence of the blueprint in node_modules: we don't install it, but we need its version + const packagejs = { + name: blueprintName, + version: blueprintVersion, + }; + const fakeBlueprintModuleDir = path.join(dir, `node_modules/${blueprintName}`); + fse.ensureDirSync(path.join(fakeBlueprintModuleDir, 'generators', 'fake')); + fse.writeJsonSync(path.join(fakeBlueprintModuleDir, 'package.json'), packagejs); + // Create an fake generator, otherwise env.lookup doesn't find it. + fse.writeFileSync(path.join(fakeBlueprintModuleDir, 'generators', 'fake', 'index.js'), ''); + return helpers + .create(path.join(__dirname, '../generators/app/index.js'), { tmpdir: false }) + .withJHipsterConfig({ + skipClient: true, + skipServer: true, + baseName: 'upgradeTest', + }) + .withOptions({ + blueprints: blueprintName, + }) + .run() + .then(() => { + return helpers + .create(path.join(__dirname, '../generators/upgrade/index.js'), { tmpdir: false }) + .withOptions({ + force: true, + silent: false, + targetVersion: packageJson.version, + }) + .run(); + }); + }); + + it('generated git commits to match snapshot', () => { + const commits = execaCommandSync('git log --pretty=format:%s', { stdio: 'pipe', reject: false }).stdout; + expect(commits.replace(new RegExp(escapeRegExp(packageJson.version), 'g'), 'VERSION')).toMatchInlineSnapshot(` +`); + }); + + it('generates expected number of commits', () => { + const commitsCount = execaCommandSync('git rev-list --count HEAD', { stdio: 'pipe', reject: false }).stdout.replace('\n', ''); + // Expecting 5 commits in history (because we used `force` option): + // - master: initial commit + // - jhipster_upgrade; initial generation + // - master: block-merge commit of jhipster_upgrade + // - jhipster_upgrade: new generation in jhipster_upgrade + // - master: merge commit of jhipster_upgrade + expect(commitsCount).toBe('5'); + }); + + it('still contains blueprint information', () => { + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { blueprints: [{ name: blueprintName, version: blueprintVersion }] }, + }); + runResult.assertFileContent('package.json', new RegExp(`"${blueprintName}": "${blueprintVersion}"`)); + }); + }); +}); diff --git a/generators/vue/__snapshots__/generator.spec.mts.snap b/generators/vue/__snapshots__/generator.spec.ts.snap similarity index 100% rename from generators/vue/__snapshots__/generator.spec.mts.snap rename to generators/vue/__snapshots__/generator.spec.ts.snap diff --git a/generators/vue/cleanup.mjs b/generators/vue/cleanup.js similarity index 100% rename from generators/vue/cleanup.mjs rename to generators/vue/cleanup.js diff --git a/generators/vue/entity-files-vue.js b/generators/vue/entity-files-vue.js new file mode 100644 index 000000000000..fccb159916c2 --- /dev/null +++ b/generators/vue/entity-files-vue.js @@ -0,0 +1,106 @@ +/** + * Copyright 2013-2023 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 { clientApplicationTemplatesBlock } from '../client/support/files.js'; + +export const entityFiles = { + client: [ + clientApplicationTemplatesBlock({ + relativePath: 'shared/model/', + templates: ['_entityModel_.model.ts'], + }), + { + condition: generator => !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/_entityFolder_/_entityFile_-details.vue', + 'entities/_entityFolder_/_entityFile_-details.component.ts', + 'entities/_entityFolder_/_entityFile_-details.component.spec.ts', + 'entities/_entityFolder_/_entityFile_.vue', + 'entities/_entityFolder_/_entityFile_.component.ts', + 'entities/_entityFolder_/_entityFile_.component.spec.ts', + 'entities/_entityFolder_/_entityFile_.service.ts', + 'entities/_entityFolder_/_entityFile_.service.spec.ts', + ], + }, + { + condition: generator => !generator.readOnly && !generator.embedded, + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/_entityFolder_/_entityFile_-update.vue', + 'entities/_entityFolder_/_entityFile_-update.component.ts', + 'entities/_entityFolder_/_entityFile_-update.component.spec.ts', + ], + }, + ], +}; + +export async function writeEntityFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + await this.writeFiles({ + sections: entityFiles, + context: { ...application, ...entity }, + }); + } +} + +export async function postWriteEntityFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + if (!entity.embedded) { + const { enableTranslation } = application; + const { + entityInstance, + entityClass, + entityAngularName, + entityFolderName, + entityFileName, + entityUrl, + microserviceName, + readOnly, + entityClassPlural, + i18nKeyPrefix, + pageTitle = enableTranslation ? `${i18nKeyPrefix}.home.title` : entityClassPlural, + } = entity; + + this.addEntityToModule( + entityInstance, + entityClass, + entityAngularName, + entityFolderName, + entityFileName, + entityUrl, + microserviceName, + readOnly, + pageTitle, + ); + this.addEntityToMenu(entity.entityPage, application.enableTranslation, entity.entityTranslationKeyMenu, entity.entityClassHumanized); + } + } +} + +export function cleanupEntitiesFiles({ application, entities }) { + for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { + const { entityFolderName, entityFileName } = entity; + if (this.isJhipsterVersionLessThan('8.0.0-beta.3')) { + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.component.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-detail.component.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-update.component.spec.ts`); + this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); + } + } +} diff --git a/generators/vue/entity-files-vue.mjs b/generators/vue/entity-files-vue.mjs deleted file mode 100644 index 67a6fd3942d0..000000000000 --- a/generators/vue/entity-files-vue.mjs +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2013-2023 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 { clientApplicationTemplatesBlock } from '../client/support/files.mjs'; - -export const entityFiles = { - client: [ - clientApplicationTemplatesBlock({ - relativePath: 'shared/model/', - templates: ['_entityModel_.model.ts'], - }), - { - condition: generator => !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/_entityFolder_/_entityFile_-details.vue', - 'entities/_entityFolder_/_entityFile_-details.component.ts', - 'entities/_entityFolder_/_entityFile_-details.component.spec.ts', - 'entities/_entityFolder_/_entityFile_.vue', - 'entities/_entityFolder_/_entityFile_.component.ts', - 'entities/_entityFolder_/_entityFile_.component.spec.ts', - 'entities/_entityFolder_/_entityFile_.service.ts', - 'entities/_entityFolder_/_entityFile_.service.spec.ts', - ], - }, - { - condition: generator => !generator.readOnly && !generator.embedded, - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/_entityFolder_/_entityFile_-update.vue', - 'entities/_entityFolder_/_entityFile_-update.component.ts', - 'entities/_entityFolder_/_entityFile_-update.component.spec.ts', - ], - }, - ], -}; - -export async function writeEntityFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - await this.writeFiles({ - sections: entityFiles, - context: { ...application, ...entity }, - }); - } -} - -export async function postWriteEntityFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - if (!entity.embedded) { - const { enableTranslation } = application; - const { - entityInstance, - entityClass, - entityAngularName, - entityFolderName, - entityFileName, - entityUrl, - microserviceName, - readOnly, - entityClassPlural, - i18nKeyPrefix, - pageTitle = enableTranslation ? `${i18nKeyPrefix}.home.title` : entityClassPlural, - } = entity; - - this.addEntityToModule( - entityInstance, - entityClass, - entityAngularName, - entityFolderName, - entityFileName, - entityUrl, - microserviceName, - readOnly, - pageTitle, - ); - this.addEntityToMenu(entity.entityPage, application.enableTranslation, entity.entityTranslationKeyMenu, entity.entityClassHumanized); - } - } -} - -export function cleanupEntitiesFiles({ application, entities }) { - for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { - const { entityFolderName, entityFileName } = entity; - if (this.isJhipsterVersionLessThan('8.0.0-beta.3')) { - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.component.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-detail.component.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}-update.component.spec.ts`); - this.removeFile(`${application.clientTestDir}/spec/app/entities/${entityFolderName}/${entityFileName}.service.spec.ts`); - } - } -} diff --git a/generators/vue/files-vue.js b/generators/vue/files-vue.js new file mode 100644 index 000000000000..ce958b345992 --- /dev/null +++ b/generators/vue/files-vue.js @@ -0,0 +1,313 @@ +/** + * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.js'; + +export const vueFiles = { + common: [ + clientRootTemplatesBlock({ + templates: [ + 'package.json', + 'tsconfig.json', + 'tsconfig.app.json', + 'tsconfig.node.json', + 'tsconfig.vitest.json', + '.postcssrc.js', + '.eslintrc.cjs', + 'vite.config.ts', + 'vitest.config.ts', + ], + }), + ], + microfrontend: [ + clientRootTemplatesBlock({ + condition: generator => generator.microfrontend, + templates: [ + 'webpack/config.js', + 'webpack/webpack.common.js', + 'webpack/webpack.dev.js', + 'webpack/webpack.prod.js', + 'webpack/vue.utils.js', + 'webpack/webpack.microfrontend.js.jhi.vue', + ], + }), + { + condition: generator => generator.microfrontend, + ...clientApplicationTemplatesBlock(), + templates: ['index.ts', 'core/error/error-loading.vue'], + }, + { + condition: generator => generator.microfrontend, + ...clientSrcTemplatesBlock(), + templates: [ + 'microfrontends/entities-menu.component-test.ts', + 'microfrontends/entities-menu-test.vue', + 'microfrontends/entities-router-test.ts', + ], + }, + { + condition: generator => generator.applicationTypeMicroservice, + ...clientApplicationTemplatesBlock(), + templates: ['entities/entities-menu.spec.ts'], + }, + ], + sass: [ + { + ...clientSrcTemplatesBlock(), + templates: ['content/scss/_bootstrap-variables.scss', 'content/scss/global.scss', 'content/scss/vendor.scss'], + }, + ], + vueApp: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'app.vue', + 'app.component.ts', + 'shims-vue.d.ts', + 'constants.ts', + 'declarations.d.ts', + 'main.ts', + 'shared/alert/alert.service.ts', + 'shared/alert/alert.service.spec.ts', + 'shared/config/axios-interceptor.ts', + 'shared/config/axios-interceptor.spec.ts', + 'shared/config/config.ts', + 'shared/config/config-bootstrap-vue.ts', + 'shared/config/dayjs.ts', + 'shared/config/store/account-store.ts', + 'shared/security/authority.ts', + 'store.ts', + 'router/index.ts', + 'router/admin.ts', + 'router/pages.ts', + 'test-setup.ts', + ], + }, + ], + i18n: [ + { + condition: generator => generator.enableTranslation, + ...clientApplicationTemplatesBlock(), + templates: ['locale/translation.service.ts', 'shared/config/store/translation-store.ts', 'shared/config/languages.ts'], + }, + ], + sharedVueApp: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'core/home/home.vue', + 'core/home/home.component.ts', + 'core/home/home.component.spec.ts', + 'core/error/error.vue', + 'core/error/error.component.ts', + 'core/error/error.component.spec.ts', + 'core/jhi-footer/jhi-footer.vue', + 'core/jhi-footer/jhi-footer.component.ts', + 'core/jhi-navbar/jhi-navbar.vue', + 'core/jhi-navbar/jhi-navbar.component.ts', + 'core/jhi-navbar/jhi-navbar.component.spec.ts', + 'core/ribbon/ribbon.vue', + 'core/ribbon/ribbon.component.ts', + 'core/ribbon/ribbon.component.spec.ts', + 'shared/composables/date-format.ts', + 'shared/composables/index.ts', + 'shared/composables/validation.ts', + 'shared/computables/arrays.ts', + 'shared/computables/index.ts', + 'shared/sort/jhi-sort-indicator.component.ts', + 'shared/sort/jhi-sort-indicator.vue', + 'shared/sort/sorts.ts', + 'shared/sort/sorts.spec.ts', + 'shared/data/data-utils.service.ts', + 'shared/data/data-utils.service.spec.ts', + 'shared/jhi-item-count.component.ts', + 'shared/jhi-item-count.vue', + 'shared/model/user.model.ts', + ], + }, + ], + accountModule: [ + { + ...clientApplicationTemplatesBlock(), + templates: ['account/account.service.ts', 'account/account.service.spec.ts'], + }, + { + condition: generator => !generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: [ + 'account/login-form/login-form.vue', + 'account/login-form/login-form.component.ts', + 'account/login-form/login-form.component.spec.ts', + 'account/login.service.ts', + 'router/account.ts', + ], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'account/change-password/change-password.vue', + 'account/change-password/change-password.component.ts', + 'account/change-password/change-password.component.spec.ts', + 'account/register/register.vue', + 'account/register/register.component.ts', + 'account/register/register.component.spec.ts', + 'account/register/register.service.ts', + 'account/reset-password/init/reset-password-init.vue', + 'account/reset-password/init/reset-password-init.component.ts', + 'account/reset-password/init/reset-password-init.component.spec.ts', + 'account/reset-password/finish/reset-password-finish.vue', + 'account/reset-password/finish/reset-password-finish.component.ts', + 'account/reset-password/finish/reset-password-finish.component.spec.ts', + 'account/settings/settings.vue', + 'account/settings/settings.component.ts', + 'account/settings/settings.component.spec.ts', + 'account/activate/activate.component.ts', + 'account/activate/activate.component.spec.ts', + 'account/activate/activate.service.ts', + 'account/activate/activate.vue', + ], + }, + { + condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'account/sessions/sessions.vue', + 'account/sessions/sessions.component.ts', + 'account/sessions/sessions.component.spec.ts', + 'account/login.service.spec.ts', + ], + }, + { + condition: generator => generator.authenticationTypeOauth2, + ...clientApplicationTemplatesBlock(), + templates: ['account/login.service.ts', 'account/login.service.spec.ts'], + }, + ], + adminModule: [ + { + ...clientApplicationTemplatesBlock(), + templates: ['admin/docs/docs.vue', 'admin/docs/docs.component.ts'], + }, + { + ...clientApplicationTemplatesBlock(), + condition: generator => generator.withAdminUi, + templates: [ + 'admin/configuration/configuration.vue', + 'admin/configuration/configuration.component.ts', + 'admin/configuration/configuration.component.spec.ts', + 'admin/configuration/configuration.service.ts', + 'admin/health/health.vue', + 'admin/health/health.component.ts', + 'admin/health/health.component.spec.ts', + 'admin/health/health-modal.vue', + 'admin/health/health-modal.component.ts', + 'admin/health/health-modal.component.spec.ts', + 'admin/health/health.service.ts', + 'admin/health/health.service.spec.ts', + 'admin/logs/logs.vue', + 'admin/logs/logs.component.ts', + 'admin/logs/logs.component.spec.ts', + 'admin/logs/logs.service.ts', + 'admin/metrics/metrics.vue', + 'admin/metrics/metrics.component.ts', + 'admin/metrics/metrics.component.spec.ts', + 'admin/metrics/metrics.service.ts', + 'admin/metrics/metrics-modal.vue', + 'admin/metrics/metrics-modal.component.ts', + 'admin/metrics/metrics-modal.component.spec.ts', + ], + }, + { + condition: generator => generator.communicationSpringWebsocket, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/tracker/tracker.vue', + 'admin/tracker/tracker.component.ts', + 'admin/tracker/tracker.component.spec.ts', + 'admin/tracker/tracker.service.ts', + 'admin/tracker/tracker.service.spec.ts', + ], + }, + { + condition: generator => generator.generateUserManagement, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/user-management/user-management.vue', + 'admin/user-management/user-management.component.ts', + 'admin/user-management/user-management.component.spec.ts', + 'admin/user-management/user-management-view.vue', + 'admin/user-management/user-management-view.component.ts', + 'admin/user-management/user-management-view.component.spec.ts', + 'admin/user-management/user-management-edit.vue', + 'admin/user-management/user-management-edit.component.ts', + 'admin/user-management/user-management-edit.component.spec.ts', + 'admin/user-management/user-management.service.ts', + ], + }, + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, + ...clientApplicationTemplatesBlock(), + templates: [ + 'admin/gateway/gateway.vue', + 'admin/gateway/gateway.component.ts', + 'admin/gateway/gateway.component.spec.ts', + 'admin/gateway/gateway.service.ts', + ], + }, + { + condition: generator => generator.generateBuiltInUserEntity, + ...clientApplicationTemplatesBlock(), + templates: ['entities/user/user.service.ts'], + }, + ], +}; + +export const entitiesFiles = { + entities: [ + { + ...clientApplicationTemplatesBlock(), + templates: [ + 'entities/entities.component.ts', + 'entities/entities.vue', + 'entities/entities-menu.component.ts', + 'entities/entities-menu.vue', + 'router/entities.ts', + ], + }, + ], +}; + +export async function writeFiles({ application }) { + await this.writeFiles({ + sections: vueFiles, + context: application, + }); +} + +export async function writeEntitiesFiles({ application, entities }) { + entities = entities.filter(entity => !entity.skipClient && !entity.builtIn); + await this.writeFiles({ + sections: entitiesFiles, + context: { + ...application, + entities, + }, + }); +} diff --git a/generators/vue/files-vue.mjs b/generators/vue/files-vue.mjs deleted file mode 100644 index 1c21084e80d8..000000000000 --- a/generators/vue/files-vue.mjs +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright 2013-2023 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 { clientApplicationTemplatesBlock, clientRootTemplatesBlock, clientSrcTemplatesBlock } from '../client/support/files.mjs'; - -export const vueFiles = { - common: [ - clientRootTemplatesBlock({ - templates: [ - 'package.json', - 'tsconfig.json', - 'tsconfig.app.json', - 'tsconfig.node.json', - 'tsconfig.vitest.json', - '.postcssrc.js', - '.eslintrc.cjs', - 'vite.config.ts', - 'vitest.config.ts', - ], - }), - ], - microfrontend: [ - clientRootTemplatesBlock({ - condition: generator => generator.microfrontend, - templates: [ - 'webpack/config.js', - 'webpack/webpack.common.js', - 'webpack/webpack.dev.js', - 'webpack/webpack.prod.js', - 'webpack/vue.utils.js', - 'webpack/webpack.microfrontend.js.jhi.vue', - ], - }), - { - condition: generator => generator.microfrontend, - ...clientApplicationTemplatesBlock(), - templates: ['index.ts', 'core/error/error-loading.vue'], - }, - { - condition: generator => generator.microfrontend, - ...clientSrcTemplatesBlock(), - templates: [ - 'microfrontends/entities-menu.component-test.ts', - 'microfrontends/entities-menu-test.vue', - 'microfrontends/entities-router-test.ts', - ], - }, - { - condition: generator => generator.applicationTypeMicroservice, - ...clientApplicationTemplatesBlock(), - templates: ['entities/entities-menu.spec.ts'], - }, - ], - sass: [ - { - ...clientSrcTemplatesBlock(), - templates: ['content/scss/_bootstrap-variables.scss', 'content/scss/global.scss', 'content/scss/vendor.scss'], - }, - ], - vueApp: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'app.vue', - 'app.component.ts', - 'shims-vue.d.ts', - 'constants.ts', - 'declarations.d.ts', - 'main.ts', - 'shared/alert/alert.service.ts', - 'shared/alert/alert.service.spec.ts', - 'shared/config/axios-interceptor.ts', - 'shared/config/axios-interceptor.spec.ts', - 'shared/config/config.ts', - 'shared/config/config-bootstrap-vue.ts', - 'shared/config/dayjs.ts', - 'shared/config/store/account-store.ts', - 'shared/security/authority.ts', - 'store.ts', - 'router/index.ts', - 'router/admin.ts', - 'router/pages.ts', - 'test-setup.ts', - ], - }, - ], - i18n: [ - { - condition: generator => generator.enableTranslation, - ...clientApplicationTemplatesBlock(), - templates: ['locale/translation.service.ts', 'shared/config/store/translation-store.ts', 'shared/config/languages.ts'], - }, - ], - sharedVueApp: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'core/home/home.vue', - 'core/home/home.component.ts', - 'core/home/home.component.spec.ts', - 'core/error/error.vue', - 'core/error/error.component.ts', - 'core/error/error.component.spec.ts', - 'core/jhi-footer/jhi-footer.vue', - 'core/jhi-footer/jhi-footer.component.ts', - 'core/jhi-navbar/jhi-navbar.vue', - 'core/jhi-navbar/jhi-navbar.component.ts', - 'core/jhi-navbar/jhi-navbar.component.spec.ts', - 'core/ribbon/ribbon.vue', - 'core/ribbon/ribbon.component.ts', - 'core/ribbon/ribbon.component.spec.ts', - 'shared/composables/date-format.ts', - 'shared/composables/index.ts', - 'shared/composables/validation.ts', - 'shared/computables/arrays.ts', - 'shared/computables/index.ts', - 'shared/sort/jhi-sort-indicator.component.ts', - 'shared/sort/jhi-sort-indicator.vue', - 'shared/sort/sorts.ts', - 'shared/sort/sorts.spec.ts', - 'shared/data/data-utils.service.ts', - 'shared/data/data-utils.service.spec.ts', - 'shared/jhi-item-count.component.ts', - 'shared/jhi-item-count.vue', - 'shared/model/user.model.ts', - ], - }, - ], - accountModule: [ - { - ...clientApplicationTemplatesBlock(), - templates: ['account/account.service.ts', 'account/account.service.spec.ts'], - }, - { - condition: generator => !generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: [ - 'account/login-form/login-form.vue', - 'account/login-form/login-form.component.ts', - 'account/login-form/login-form.component.spec.ts', - 'account/login.service.ts', - 'router/account.ts', - ], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'account/change-password/change-password.vue', - 'account/change-password/change-password.component.ts', - 'account/change-password/change-password.component.spec.ts', - 'account/register/register.vue', - 'account/register/register.component.ts', - 'account/register/register.component.spec.ts', - 'account/register/register.service.ts', - 'account/reset-password/init/reset-password-init.vue', - 'account/reset-password/init/reset-password-init.component.ts', - 'account/reset-password/init/reset-password-init.component.spec.ts', - 'account/reset-password/finish/reset-password-finish.vue', - 'account/reset-password/finish/reset-password-finish.component.ts', - 'account/reset-password/finish/reset-password-finish.component.spec.ts', - 'account/settings/settings.vue', - 'account/settings/settings.component.ts', - 'account/settings/settings.component.spec.ts', - 'account/activate/activate.component.ts', - 'account/activate/activate.component.spec.ts', - 'account/activate/activate.service.ts', - 'account/activate/activate.vue', - ], - }, - { - condition: generator => generator.authenticationTypeSession && generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'account/sessions/sessions.vue', - 'account/sessions/sessions.component.ts', - 'account/sessions/sessions.component.spec.ts', - 'account/login.service.spec.ts', - ], - }, - { - condition: generator => generator.authenticationTypeOauth2, - ...clientApplicationTemplatesBlock(), - templates: ['account/login.service.ts', 'account/login.service.spec.ts'], - }, - ], - adminModule: [ - { - ...clientApplicationTemplatesBlock(), - templates: ['admin/docs/docs.vue', 'admin/docs/docs.component.ts'], - }, - { - ...clientApplicationTemplatesBlock(), - condition: generator => generator.withAdminUi, - templates: [ - 'admin/configuration/configuration.vue', - 'admin/configuration/configuration.component.ts', - 'admin/configuration/configuration.component.spec.ts', - 'admin/configuration/configuration.service.ts', - 'admin/health/health.vue', - 'admin/health/health.component.ts', - 'admin/health/health.component.spec.ts', - 'admin/health/health-modal.vue', - 'admin/health/health-modal.component.ts', - 'admin/health/health-modal.component.spec.ts', - 'admin/health/health.service.ts', - 'admin/health/health.service.spec.ts', - 'admin/logs/logs.vue', - 'admin/logs/logs.component.ts', - 'admin/logs/logs.component.spec.ts', - 'admin/logs/logs.service.ts', - 'admin/metrics/metrics.vue', - 'admin/metrics/metrics.component.ts', - 'admin/metrics/metrics.component.spec.ts', - 'admin/metrics/metrics.service.ts', - 'admin/metrics/metrics-modal.vue', - 'admin/metrics/metrics-modal.component.ts', - 'admin/metrics/metrics-modal.component.spec.ts', - ], - }, - { - condition: generator => generator.communicationSpringWebsocket, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/tracker/tracker.vue', - 'admin/tracker/tracker.component.ts', - 'admin/tracker/tracker.component.spec.ts', - 'admin/tracker/tracker.service.ts', - 'admin/tracker/tracker.service.spec.ts', - ], - }, - { - condition: generator => generator.generateUserManagement, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/user-management/user-management.vue', - 'admin/user-management/user-management.component.ts', - 'admin/user-management/user-management.component.spec.ts', - 'admin/user-management/user-management-view.vue', - 'admin/user-management/user-management-view.component.ts', - 'admin/user-management/user-management-view.component.spec.ts', - 'admin/user-management/user-management-edit.vue', - 'admin/user-management/user-management-edit.component.ts', - 'admin/user-management/user-management-edit.component.spec.ts', - 'admin/user-management/user-management.service.ts', - ], - }, - { - condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryAny, - ...clientApplicationTemplatesBlock(), - templates: [ - 'admin/gateway/gateway.vue', - 'admin/gateway/gateway.component.ts', - 'admin/gateway/gateway.component.spec.ts', - 'admin/gateway/gateway.service.ts', - ], - }, - { - condition: generator => generator.generateBuiltInUserEntity, - ...clientApplicationTemplatesBlock(), - templates: ['entities/user/user.service.ts'], - }, - ], -}; - -export const entitiesFiles = { - entities: [ - { - ...clientApplicationTemplatesBlock(), - templates: [ - 'entities/entities.component.ts', - 'entities/entities.vue', - 'entities/entities-menu.component.ts', - 'entities/entities-menu.vue', - 'router/entities.ts', - ], - }, - ], -}; - -export async function writeFiles({ application }) { - await this.writeFiles({ - sections: vueFiles, - context: application, - }); -} - -export async function writeEntitiesFiles({ application, entities }) { - entities = entities.filter(entity => !entity.skipClient && !entity.builtIn); - await this.writeFiles({ - sections: entitiesFiles, - context: { - ...application, - entities, - }, - }); -} diff --git a/generators/vue/generator.js b/generators/vue/generator.js new file mode 100644 index 000000000000..7db7ca97aaf5 --- /dev/null +++ b/generators/vue/generator.js @@ -0,0 +1,305 @@ +/** + * Copyright 2013-2023 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 { relative } from 'path'; +import chalk from 'chalk'; +import * as _ from 'lodash-es'; +import { isFileStateModified } from 'mem-fs-editor/state'; + +import BaseApplicationGenerator from '../base-application/index.js'; +import { fieldTypes, clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { GENERATOR_VUE, GENERATOR_CLIENT, GENERATOR_LANGUAGES } from '../generator-list.js'; +import { writeEntityFiles, postWriteEntityFiles, cleanupEntitiesFiles } from './entity-files-vue.js'; +import cleanupOldFilesTask from './cleanup.js'; +import { writeFiles, writeEntitiesFiles } from './files-vue.js'; +import { + generateEntityClientEnumImports as getClientEnumImportsFormat, + generateEntityClientFields as getHydratedEntityClientFields, + generateEntityClientImports as formatEntityClientImports, + generateTestEntityId as getTestEntityId, + getTypescriptKeyType as getTSKeyType, +} from '../client/support/index.js'; +import { convertTranslationsSupport, isTranslatedVueFile, translateVueFilesTransform } from './support/index.js'; +import { createNeedleCallback } from '../base/support/index.js'; + +const { CommonDBTypes } = fieldTypes; +const { VUE } = clientFrameworkTypes; +const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN; + +export default class VueGenerator extends BaseApplicationGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_VUE); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_CLIENT); + await this.dependsOnJHipster(GENERATOR_LANGUAGES); + } + } + + get loading() { + return this.asLoadingTaskGroup({ + loadPackageJson({ application }) { + this.loadNodeDependenciesFromPackageJson( + application.nodeDependencies, + this.fetchFromInstalledJHipster(GENERATOR_VUE, 'resources', 'package.json'), + ); + }, + }); + } + + get [BaseApplicationGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); + } + + get preparing() { + return this.asPreparingTaskGroup({ + prepareForTemplates({ application, source }) { + application.clientWebappDir = `${application.clientSrcDir}app/`; + application.webappEnumerationsDir = `${application.clientWebappDir}shared/model/enumerations/`; + application.clientSpecDir = `${application.clientTestDir}spec/`; + + // Can be dropped if tests are moved near implementation + application.applicationRootRelativeToClientTestDir = `${relative(application.clientSpecDir, '.')}/`; + application.clientSrcDirRelativeToClientTestDir = `${relative(application.clientSpecDir, application.clientWebappDir)}/`; + + source.addWebpackConfig = args => { + const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; + const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; + this.editFile( + webpackPath, + { ignoreNonExisting }, + createNeedleCallback({ + needle: 'jhipster-needle-add-webpack-config', + contentToAdd: `,${args.config}`, + }), + ); + }; + }, + }); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.delegateTasksToBlueprint(() => this.preparing); + } + + get preparingEachEntity() { + return this.asPreparingEachEntityTaskGroup({ + prepareEntityForTemplates({ entity }) { + // Can be dropped if tests are moved near implementation + entity.relativeToEntityFolderName = relative(entity.entityFolderName, '.'); + }, + }); + } + + get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { + return this.delegateTasksToBlueprint(() => this.preparingEachEntity); + } + + get default() { + return this.asDefaultTaskGroup({ + async queueTranslateTransform({ control, application }) { + const { enableTranslation, clientSrcDir } = application; + const { getWebappTranslation } = control; + this.queueTransformStream( + { + name: 'translating vue application', + filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedVueFile(file), + refresh: false, + }, + translateVueFilesTransform.call(this, { enableTranslation, getWebappTranslation }), + ); + if (enableTranslation) { + const { transform, isTranslationFile } = convertTranslationsSupport({ clientSrcDir }); + this.queueTransformStream( + { + name: 'converting vue translations', + filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslationFile(file), + refresh: false, + }, + transform, + ); + } + }, + }); + } + + get [BaseApplicationGenerator.DEFAULT]() { + return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); + } + + get writing() { + return this.asWritingTaskGroup({ + cleanupOldFilesTask, + writeFiles, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.delegateTasksToBlueprint(() => this.writing); + } + + get writingEntities() { + return this.asWritingEntitiesTaskGroup({ + cleanupEntitiesFiles, + writeEntitiesFiles, + writeEntityFiles, + }); + } + + get [BaseApplicationGenerator.WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.writingEntities); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + addIndexAsset({ source, application }) { + if (application.microfrontend) return; + source.addExternalResourceToRoot({ + resource: '', + comment: 'Workaround https://github.com/axios/axios/issues/5622', + }); + source.addExternalResourceToRoot({ + resource: ``, + comment: 'Load vue main', + }); + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + get postWritingEntities() { + return this.asPostWritingEntitiesTaskGroup({ + postWriteEntityFiles, + }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.delegateTasksToBlueprint(() => this.postWritingEntities); + } + + get end() { + return this.asEndTaskGroup({ + end({ application }) { + this.log.ok('Vue application generated successfully.'); + this.log.log( + chalk.green(` Start your Webpack development server with: + ${chalk.yellow.bold(`${application.nodePackageManager} start`)} +`), + ); + }, + }); + } + + get [BaseApplicationGenerator.END]() { + return this.delegateTasksToBlueprint(() => this.end); + } + + /** + * @private + * Add a new entity in the "entities" menu. + * + * @param {string} routerName - The name of the Angular router (which by default is the name of the entity). + * @param {boolean} enableTranslation - If translations are enabled or not + * @param {string} clientFramework - The name of the client framework + * @param {string} entityTranslationKeyMenu - i18n key for entity entry in menu + * @param {string} entityTranslationValue - i18n value for entity entry in menu + */ + addEntityToMenu( + routerName, + enableTranslation, + entityTranslationKeyMenu = _.camelCase(routerName), + entityTranslationValue = _.startCase(routerName), + ) { + this.needleApi.clientVue.addEntityToMenu(routerName, enableTranslation, entityTranslationKeyMenu, entityTranslationValue); + } + + /** + * @private + * Add a new entity in the TS modules file. + * + * @param {string} entityInstance - Entity Instance + * @param {string} entityClass - Entity Class + * @param {string} entityName - Entity Name + * @param {string} entityFolderName - Entity Folder Name + * @param {string} entityFileName - Entity File Name + * @param {string} entityUrl - Entity router URL + * @param {string} microserviceName - Microservice Name + * @param {boolean} readOnly - If the entity is read-only or not + * @param {string} pageTitle - The translation key or the text for the page title in the browser + */ + addEntityToModule( + entityInstance = this.entityInstance, + entityClass = this.entityClass, + entityName = this.entityAngularName, + entityFolderName = this.entityFolderName, + entityFileName = this.entityFileName, + _entityUrl = this.entityUrl, + _microserviceName = this.microserviceName, + readOnly = this.readOnly, + _pageTitle = this.enableTranslation ? `${this.i18nKeyPrefix}.home.title` : this.entityClassPlural, + ) { + this.needleApi.clientVue.addEntityToRouterImport(entityName, entityFileName, entityFolderName, readOnly); + this.needleApi.clientVue.addEntityToRouter(entityInstance, entityName, entityFileName, readOnly); + this.needleApi.clientVue.addEntityServiceToEntitiesComponentImport(entityName, entityClass, entityFileName, entityFolderName); + this.needleApi.clientVue.addEntityServiceToEntitiesComponent(entityInstance, entityName); + } + + /** + * @private + * Generate Entity Client Field Default Values + * + * @param {Array|Object} fields - array of fields + * @returns {Array} defaultVariablesValues + */ + generateEntityClientFieldDefaultValues(fields) { + const defaultVariablesValues = {}; + fields.forEach(field => { + const fieldType = field.fieldType; + const fieldName = field.fieldName; + if (fieldType === TYPE_BOOLEAN) { + defaultVariablesValues[fieldName] = `this.${fieldName} = this.${fieldName} ?? false;`; + } + }); + return defaultVariablesValues; + } + + getTypescriptKeyType(primaryKey) { + return getTSKeyType(primaryKey); + } + + generateEntityClientFields(primaryKey, fields, relationships, dto, customDateType = 'dayjs.Dayjs', embedded = false) { + return getHydratedEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded, VUE); + } + + generateEntityClientImports(relationships, dto) { + return formatEntityClientImports(relationships, dto, VUE); + } + + generateEntityClientEnumImports(fields) { + return getClientEnumImportsFormat(fields, VUE); + } + + generateTestEntityId(primaryKey, index = 0, wrapped = true) { + return getTestEntityId(primaryKey, index, wrapped); + } +} diff --git a/generators/vue/generator.mjs b/generators/vue/generator.mjs deleted file mode 100644 index 87fa8faad077..000000000000 --- a/generators/vue/generator.mjs +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Copyright 2013-2023 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 { relative } from 'path'; -import chalk from 'chalk'; -import * as _ from 'lodash-es'; -import { isFileStateModified } from 'mem-fs-editor/state'; - -import BaseApplicationGenerator from '../base-application/index.mjs'; -import { fieldTypes, clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { GENERATOR_VUE, GENERATOR_CLIENT, GENERATOR_LANGUAGES } from '../generator-list.mjs'; -import { writeEntityFiles, postWriteEntityFiles, cleanupEntitiesFiles } from './entity-files-vue.mjs'; -import cleanupOldFilesTask from './cleanup.mjs'; -import { writeFiles, writeEntitiesFiles } from './files-vue.mjs'; -import { - generateEntityClientEnumImports as getClientEnumImportsFormat, - generateEntityClientFields as getHydratedEntityClientFields, - generateEntityClientImports as formatEntityClientImports, - generateTestEntityId as getTestEntityId, - getTypescriptKeyType as getTSKeyType, -} from '../client/support/index.mjs'; -import { convertTranslationsSupport, isTranslatedVueFile, translateVueFilesTransform } from './support/index.mjs'; -import { createNeedleCallback } from '../base/support/index.mjs'; - -const { CommonDBTypes } = fieldTypes; -const { VUE } = clientFrameworkTypes; -const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN; - -export default class VueGenerator extends BaseApplicationGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_VUE); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_CLIENT); - await this.dependsOnJHipster(GENERATOR_LANGUAGES); - } - } - - get loading() { - return this.asLoadingTaskGroup({ - loadPackageJson({ application }) { - this.loadNodeDependenciesFromPackageJson( - application.nodeDependencies, - this.fetchFromInstalledJHipster(GENERATOR_VUE, 'resources', 'package.json'), - ); - }, - }); - } - - get [BaseApplicationGenerator.LOADING]() { - return this.delegateTasksToBlueprint(() => this.loading); - } - - get preparing() { - return this.asPreparingTaskGroup({ - prepareForTemplates({ application, source }) { - application.clientWebappDir = `${application.clientSrcDir}app/`; - application.webappEnumerationsDir = `${application.clientWebappDir}shared/model/enumerations/`; - application.clientSpecDir = `${application.clientTestDir}spec/`; - - // Can be dropped if tests are moved near implementation - application.applicationRootRelativeToClientTestDir = `${relative(application.clientSpecDir, '.')}/`; - application.clientSrcDirRelativeToClientTestDir = `${relative(application.clientSpecDir, application.clientWebappDir)}/`; - - source.addWebpackConfig = args => { - const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; - const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; - this.editFile( - webpackPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-webpack-config', - contentToAdd: `,${args.config}`, - }), - ); - }; - }, - }); - } - - get [BaseApplicationGenerator.PREPARING]() { - return this.delegateTasksToBlueprint(() => this.preparing); - } - - get preparingEachEntity() { - return this.asPreparingEachEntityTaskGroup({ - prepareEntityForTemplates({ entity }) { - // Can be dropped if tests are moved near implementation - entity.relativeToEntityFolderName = relative(entity.entityFolderName, '.'); - }, - }); - } - - get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { - return this.delegateTasksToBlueprint(() => this.preparingEachEntity); - } - - get default() { - return this.asDefaultTaskGroup({ - async queueTranslateTransform({ control, application }) { - const { enableTranslation, clientSrcDir } = application; - const { getWebappTranslation } = control; - this.queueTransformStream( - { - name: 'translating vue application', - filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedVueFile(file), - refresh: false, - }, - translateVueFilesTransform.call(this, { enableTranslation, getWebappTranslation }), - ); - if (enableTranslation) { - const { transform, isTranslationFile } = convertTranslationsSupport({ clientSrcDir }); - this.queueTransformStream( - { - name: 'converting vue translations', - filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslationFile(file), - refresh: false, - }, - transform, - ); - } - }, - }); - } - - get [BaseApplicationGenerator.DEFAULT]() { - return this.asDefaultTaskGroup(this.delegateTasksToBlueprint(() => this.default)); - } - - get writing() { - return this.asWritingTaskGroup({ - cleanupOldFilesTask, - writeFiles, - }); - } - - get [BaseApplicationGenerator.WRITING]() { - return this.delegateTasksToBlueprint(() => this.writing); - } - - get writingEntities() { - return this.asWritingEntitiesTaskGroup({ - cleanupEntitiesFiles, - writeEntitiesFiles, - writeEntityFiles, - }); - } - - get [BaseApplicationGenerator.WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.writingEntities); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - addIndexAsset({ source, application }) { - if (application.microfrontend) return; - source.addExternalResourceToRoot({ - resource: '', - comment: 'Workaround https://github.com/axios/axios/issues/5622', - }); - source.addExternalResourceToRoot({ - resource: ``, - comment: 'Load vue main', - }); - }, - }); - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - get postWritingEntities() { - return this.asPostWritingEntitiesTaskGroup({ - postWriteEntityFiles, - }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.delegateTasksToBlueprint(() => this.postWritingEntities); - } - - get end() { - return this.asEndTaskGroup({ - end({ application }) { - this.log.ok('Vue application generated successfully.'); - this.log.log( - chalk.green(` Start your Webpack development server with: - ${chalk.yellow.bold(`${application.nodePackageManager} start`)} -`), - ); - }, - }); - } - - get [BaseApplicationGenerator.END]() { - return this.delegateTasksToBlueprint(() => this.end); - } - - /** - * @private - * Add a new entity in the "entities" menu. - * - * @param {string} routerName - The name of the Angular router (which by default is the name of the entity). - * @param {boolean} enableTranslation - If translations are enabled or not - * @param {string} clientFramework - The name of the client framework - * @param {string} entityTranslationKeyMenu - i18n key for entity entry in menu - * @param {string} entityTranslationValue - i18n value for entity entry in menu - */ - addEntityToMenu( - routerName, - enableTranslation, - entityTranslationKeyMenu = _.camelCase(routerName), - entityTranslationValue = _.startCase(routerName), - ) { - this.needleApi.clientVue.addEntityToMenu(routerName, enableTranslation, entityTranslationKeyMenu, entityTranslationValue); - } - - /** - * @private - * Add a new entity in the TS modules file. - * - * @param {string} entityInstance - Entity Instance - * @param {string} entityClass - Entity Class - * @param {string} entityName - Entity Name - * @param {string} entityFolderName - Entity Folder Name - * @param {string} entityFileName - Entity File Name - * @param {string} entityUrl - Entity router URL - * @param {string} microserviceName - Microservice Name - * @param {boolean} readOnly - If the entity is read-only or not - * @param {string} pageTitle - The translation key or the text for the page title in the browser - */ - addEntityToModule( - entityInstance = this.entityInstance, - entityClass = this.entityClass, - entityName = this.entityAngularName, - entityFolderName = this.entityFolderName, - entityFileName = this.entityFileName, - _entityUrl = this.entityUrl, - _microserviceName = this.microserviceName, - readOnly = this.readOnly, - _pageTitle = this.enableTranslation ? `${this.i18nKeyPrefix}.home.title` : this.entityClassPlural, - ) { - this.needleApi.clientVue.addEntityToRouterImport(entityName, entityFileName, entityFolderName, readOnly); - this.needleApi.clientVue.addEntityToRouter(entityInstance, entityName, entityFileName, readOnly); - this.needleApi.clientVue.addEntityServiceToEntitiesComponentImport(entityName, entityClass, entityFileName, entityFolderName); - this.needleApi.clientVue.addEntityServiceToEntitiesComponent(entityInstance, entityName); - } - - /** - * @private - * Generate Entity Client Field Default Values - * - * @param {Array|Object} fields - array of fields - * @returns {Array} defaultVariablesValues - */ - generateEntityClientFieldDefaultValues(fields) { - const defaultVariablesValues = {}; - fields.forEach(field => { - const fieldType = field.fieldType; - const fieldName = field.fieldName; - if (fieldType === TYPE_BOOLEAN) { - defaultVariablesValues[fieldName] = `this.${fieldName} = this.${fieldName} ?? false;`; - } - }); - return defaultVariablesValues; - } - - getTypescriptKeyType(primaryKey) { - return getTSKeyType(primaryKey); - } - - generateEntityClientFields(primaryKey, fields, relationships, dto, customDateType = 'dayjs.Dayjs', embedded = false) { - return getHydratedEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded, VUE); - } - - generateEntityClientImports(relationships, dto) { - return formatEntityClientImports(relationships, dto, VUE); - } - - generateEntityClientEnumImports(fields) { - return getClientEnumImportsFormat(fields, VUE); - } - - generateTestEntityId(primaryKey, index = 0, wrapped = true) { - return getTestEntityId(primaryKey, index, wrapped); - } -} diff --git a/generators/vue/generator.spec.mts b/generators/vue/generator.spec.mts deleted file mode 100644 index a17467679576..000000000000 --- a/generators/vue/generator.spec.mts +++ /dev/null @@ -1,176 +0,0 @@ -import { basename, dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { - buildClientSamples, - entitiesClientSamples as entities, - checkEnforcements, - defaultHelpers as helpers, -} from '../../test/support/index.mjs'; -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './index.mjs'; - -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.mjs'; -import { GENERATOR_VUE } from '../generator-list.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); -const generatorFile = join(__dirname, 'index.mts'); - -const { VUE: clientFramework } = clientFrameworkTypes; -const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; - -const testSamples = buildClientSamples(commonConfig); - -const clientAdminFiles = clientSrcDir => [ - `${clientSrcDir}app/admin/configuration/configuration.component.ts`, - `${clientSrcDir}app/admin/configuration/configuration.component.spec.ts`, - `${clientSrcDir}app/admin/configuration/configuration.vue`, - `${clientSrcDir}app/admin/configuration/configuration.service.ts`, - - `${clientSrcDir}app/admin/health/health.component.ts`, - `${clientSrcDir}app/admin/health/health.component.spec.ts`, - `${clientSrcDir}app/admin/health/health.vue`, - `${clientSrcDir}app/admin/health/health-modal.vue`, - `${clientSrcDir}app/admin/health/health-modal.component.ts`, - `${clientSrcDir}app/admin/health/health-modal.component.spec.ts`, - `${clientSrcDir}app/admin/health/health.service.ts`, - `${clientSrcDir}app/admin/health/health.service.spec.ts`, - - `${clientSrcDir}app/admin/logs/logs.component.ts`, - `${clientSrcDir}app/admin/logs/logs.component.spec.ts`, - `${clientSrcDir}app/admin/logs/logs.service.ts`, - - `${clientSrcDir}app/admin/metrics/metrics.component.ts`, - `${clientSrcDir}app/admin/metrics/metrics.component.spec.ts`, - `${clientSrcDir}app/admin/metrics/metrics-modal.component.ts`, - `${clientSrcDir}app/admin/metrics/metrics-modal.component.spec.ts`, - `${clientSrcDir}app/admin/metrics/metrics.vue`, - `${clientSrcDir}app/admin/metrics/metrics-modal.vue`, - `${clientSrcDir}app/admin/metrics/metrics.service.ts`, -]; - -describe(`generator - ${clientFramework}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); - checkEnforcements({ client: true }, GENERATOR_VUE); - - it('samples matrix should match snapshot', () => { - expect(testSamples).toMatchSnapshot(); - }); - - Object.entries(testSamples).forEach(([name, sampleConfig]) => { - const { clientRootDir = '' } = sampleConfig; - - describe(name, () => { - let runResult; - - before(async () => { - runResult = await helpers - .run(generatorFile) - .withJHipsterConfig(sampleConfig, entities) - .withControl({ getWebappTranslation: () => 'translations' }) - .withMockedGenerators(['jhipster:common', 'jhipster:languages']); - }); - - after(() => runResult.cleanup()); - - it('should match generated files snapshot', () => { - expect(runResult.getStateSnapshot()).toMatchSnapshot(); - }); - it('contains correct clientFramework', () => { - runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); - }); - it('should not contain version placeholders at package.json', () => { - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); - runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); - }); - - describe('withAdminUi', () => { - const { applicationType, withAdminUi } = sampleConfig; - const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; - const generateAdminUi = applicationType !== 'microservice' && withAdminUi; - const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; - - it(adminUiComponents, () => { - const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); - assertion(clientAdminFiles(clientSrcDir)); - }); - - if (applicationType !== 'microservice') { - const adminUiRoutingTitle = generateAdminUi ? 'should generate admin related code' : 'should not generate admin related code'; - const assertion = (...args) => (generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args)); - - it(adminUiRoutingTitle, () => { - assertion( - `${clientSrcDir}app/router/admin.ts`, - " const JhiConfigurationComponent = () => import('@/admin/configuration/configuration.vue');\n" + - " const JhiHealthComponent = () => import('@/admin/health/health.vue');\n" + - " const JhiLogsComponent = () => import('@/admin/logs/logs.vue');\n" + - " const JhiMetricsComponent = () => import('@/admin/metrics/metrics.vue');", - ); - assertion( - `${clientSrcDir}app/router/admin.ts`, - ` - { - path: '/admin/health', - name: 'JhiHealthComponent', - component: JhiHealthComponent, - meta: { authorities: [Authority.ADMIN] } - }, - { - path: '/admin/logs', - name: 'JhiLogsComponent', - component: JhiLogsComponent, - meta: { authorities: [Authority.ADMIN] } - }, - { - path: '/admin/metrics', - name: 'JhiMetricsComponent', - component: JhiMetricsComponent, - meta: { authorities: [Authority.ADMIN] } - }, - { - path: '/admin/configuration', - name: 'JhiConfigurationComponent', - component: JhiConfigurationComponent, - meta: { authorities: [Authority.ADMIN] } - },`, - ); - assertion( - `${clientSrcDir}app/core/jhi-navbar/jhi-navbar.vue`, - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' ', - ); - }); - } - }); - }); - }); -}); diff --git a/generators/vue/generator.spec.ts b/generators/vue/generator.spec.ts new file mode 100644 index 000000000000..677f46e95e52 --- /dev/null +++ b/generators/vue/generator.spec.ts @@ -0,0 +1,176 @@ +import { basename, dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { + buildClientSamples, + entitiesClientSamples as entities, + checkEnforcements, + defaultHelpers as helpers, +} from '../../test/support/index.js'; +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './index.js'; + +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../generator-constants.js'; +import { GENERATOR_VUE } from '../generator-list.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); +const generatorFile = join(__dirname, 'index.ts'); + +const { VUE: clientFramework } = clientFrameworkTypes; +const commonConfig = { clientFramework, nativeLanguage: 'en', languages: ['fr', 'en'] }; + +const testSamples = buildClientSamples(commonConfig); + +const clientAdminFiles = clientSrcDir => [ + `${clientSrcDir}app/admin/configuration/configuration.component.ts`, + `${clientSrcDir}app/admin/configuration/configuration.component.spec.ts`, + `${clientSrcDir}app/admin/configuration/configuration.vue`, + `${clientSrcDir}app/admin/configuration/configuration.service.ts`, + + `${clientSrcDir}app/admin/health/health.component.ts`, + `${clientSrcDir}app/admin/health/health.component.spec.ts`, + `${clientSrcDir}app/admin/health/health.vue`, + `${clientSrcDir}app/admin/health/health-modal.vue`, + `${clientSrcDir}app/admin/health/health-modal.component.ts`, + `${clientSrcDir}app/admin/health/health-modal.component.spec.ts`, + `${clientSrcDir}app/admin/health/health.service.ts`, + `${clientSrcDir}app/admin/health/health.service.spec.ts`, + + `${clientSrcDir}app/admin/logs/logs.component.ts`, + `${clientSrcDir}app/admin/logs/logs.component.spec.ts`, + `${clientSrcDir}app/admin/logs/logs.service.ts`, + + `${clientSrcDir}app/admin/metrics/metrics.component.ts`, + `${clientSrcDir}app/admin/metrics/metrics.component.spec.ts`, + `${clientSrcDir}app/admin/metrics/metrics-modal.component.ts`, + `${clientSrcDir}app/admin/metrics/metrics-modal.component.spec.ts`, + `${clientSrcDir}app/admin/metrics/metrics.vue`, + `${clientSrcDir}app/admin/metrics/metrics-modal.vue`, + `${clientSrcDir}app/admin/metrics/metrics.service.ts`, +]; + +describe(`generator - ${clientFramework}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); + checkEnforcements({ client: true }, GENERATOR_VUE); + + it('samples matrix should match snapshot', () => { + expect(testSamples).toMatchSnapshot(); + }); + + Object.entries(testSamples).forEach(([name, sampleConfig]) => { + const { clientRootDir = '' } = sampleConfig; + + describe(name, () => { + let runResult; + + before(async () => { + runResult = await helpers + .run(generatorFile) + .withJHipsterConfig(sampleConfig, entities) + .withControl({ getWebappTranslation: () => 'translations' }) + .withMockedGenerators(['jhipster:common', 'jhipster:languages']); + }); + + after(() => runResult.cleanup()); + + it('should match generated files snapshot', () => { + expect(runResult.getStateSnapshot()).toMatchSnapshot(); + }); + it('contains correct clientFramework', () => { + runResult.assertFileContent('.yo-rc.json', new RegExp(`"clientFramework": "${clientFramework}"`)); + }); + it('should not contain version placeholders at package.json', () => { + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_COMMON/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_ANGULAR/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_REACT/); + runResult.assertNoFileContent(`${clientRootDir}package.json`, /VERSION_MANAGED_BY_CLIENT_VUE/); + }); + + describe('withAdminUi', () => { + const { applicationType, withAdminUi } = sampleConfig; + const clientSrcDir = `${clientRootDir}${CLIENT_MAIN_SRC_DIR}`; + const generateAdminUi = applicationType !== 'microservice' && withAdminUi; + const adminUiComponents = generateAdminUi ? 'should generate admin ui components' : 'should not generate admin ui components'; + + it(adminUiComponents, () => { + const assertion = (...args) => (generateAdminUi ? runResult.assertFile(...args) : runResult.assertNoFile(...args)); + assertion(clientAdminFiles(clientSrcDir)); + }); + + if (applicationType !== 'microservice') { + const adminUiRoutingTitle = generateAdminUi ? 'should generate admin related code' : 'should not generate admin related code'; + const assertion = (...args) => (generateAdminUi ? runResult.assertFileContent(...args) : runResult.assertNoFileContent(...args)); + + it(adminUiRoutingTitle, () => { + assertion( + `${clientSrcDir}app/router/admin.ts`, + " const JhiConfigurationComponent = () => import('@/admin/configuration/configuration.vue');\n" + + " const JhiHealthComponent = () => import('@/admin/health/health.vue');\n" + + " const JhiLogsComponent = () => import('@/admin/logs/logs.vue');\n" + + " const JhiMetricsComponent = () => import('@/admin/metrics/metrics.vue');", + ); + assertion( + `${clientSrcDir}app/router/admin.ts`, + ` + { + path: '/admin/health', + name: 'JhiHealthComponent', + component: JhiHealthComponent, + meta: { authorities: [Authority.ADMIN] } + }, + { + path: '/admin/logs', + name: 'JhiLogsComponent', + component: JhiLogsComponent, + meta: { authorities: [Authority.ADMIN] } + }, + { + path: '/admin/metrics', + name: 'JhiMetricsComponent', + component: JhiMetricsComponent, + meta: { authorities: [Authority.ADMIN] } + }, + { + path: '/admin/configuration', + name: 'JhiConfigurationComponent', + component: JhiConfigurationComponent, + meta: { authorities: [Authority.ADMIN] } + },`, + ); + assertion( + `${clientSrcDir}app/core/jhi-navbar/jhi-navbar.vue`, + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' ', + ); + }); + } + }); + }); + }); +}); diff --git a/generators/vue/index.mts b/generators/vue/index.mts deleted file mode 100644 index cfcecfbd2954..000000000000 --- a/generators/vue/index.mts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; diff --git a/generators/vue/index.ts b/generators/vue/index.ts new file mode 100644 index 000000000000..39cddf31d5ad --- /dev/null +++ b/generators/vue/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2013-2023 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 './generator.js'; diff --git a/generators/vue/support/convert-translation.mts b/generators/vue/support/convert-translation.ts similarity index 100% rename from generators/vue/support/convert-translation.mts rename to generators/vue/support/convert-translation.ts diff --git a/generators/vue/support/index.mts b/generators/vue/support/index.mts deleted file mode 100644 index 5e055211f35c..000000000000 --- a/generators/vue/support/index.mts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2013-2023 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 as convertTranslationsSupport } from './convert-translation.mjs'; -export * from './needles.mjs'; -export * from './translate-vue.mjs'; -export { default as translateVueFilesTransform } from './translate-vue.mjs'; -export { default as updateLanguagesTask } from './update-languages.mjs'; diff --git a/generators/vue/support/index.ts b/generators/vue/support/index.ts new file mode 100644 index 000000000000..d5ec19c8d092 --- /dev/null +++ b/generators/vue/support/index.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2013-2023 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 as convertTranslationsSupport } from './convert-translation.js'; +export * from './needles.js'; +export * from './translate-vue.js'; +export { default as translateVueFilesTransform } from './translate-vue.js'; +export { default as updateLanguagesTask } from './update-languages.js'; diff --git a/generators/vue/support/needles.mjs b/generators/vue/support/needles.js similarity index 100% rename from generators/vue/support/needles.mjs rename to generators/vue/support/needles.js diff --git a/generators/vue/support/translate-vue.mts b/generators/vue/support/translate-vue.mts deleted file mode 100644 index 431eec8b7473..000000000000 --- a/generators/vue/support/translate-vue.mts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; -import { Minimatch } from 'minimatch'; -import CoreGenerator from '../../base-core/index.mjs'; - -type GetWebappTranslation = (s: string, data?: Record) => string; - -function replaceTranslationAttributes({ content, getWebappTranslation }: { content: string; getWebappTranslation: GetWebappTranslation }) { - return content.replaceAll(/v-bind:(?(?:placeholder|title|label))="(?t\$\([^"]+\))"/g, (_complete, ...args) => { - const groups: Record = args.pop(); - if (groups.translate.includes('+')) { - return ''; - } - const translated = replaceTranslations({ content: groups.translate, type: 'vue', getWebappTranslation }); - return `${groups.tag}="${translated}"`; - }); -} - -export function removeDeclarations({ content }: { content: string }) { - return content - .replaceAll(/\nimport {\s*useI18n\s*} from 'vue-i18n';/g, '') - .replaceAll(/\n\s*t\$,/g, '') - .replaceAll(/\n\s*t\$:\s*useI18n\(\).t,/g, ''); -} - -export function replaceTranslationTags( - this: CoreGenerator | void, - { - body, - enableTranslation, - getWebappTranslation, - }: { - body: string; - enableTranslation: boolean; - getWebappTranslation: GetWebappTranslation; - }, -) { - body = body.replaceAll( - /(?(?<(?a|b-(?:badge|link|button|alert)|button|div|h[1-9]|input|label|p|router-link|small|span|t[hd])(?:[^>]*)) v-(?:text|html)="(?t\$\([^)]*\))"(?(?:(?!\/?>)[^/>])*>))(?(?:(?!<\/\k(?:>|\s|\n)).|\n)*)/g, - (_complete, ...args) => { - const groups: Record = args.pop(); - if (new RegExp(`<${groups.tagName}[\\s>]`, 'g').test(groups.tagContent)) { - throw new Error(`Nested tags identical to the translated tag are not supported: ${groups.tagWithAttributes}${groups.tagContent}`); - } - if (enableTranslation) { - return groups.tagWithAttributes; - } - if (groups.translate) { - if (groups.translate.includes('+')) { - this?.log.info(`Ignoring dynamic translation ${groups.translate}`); - } else { - const translated = replaceTranslations({ - type: 'vue', - content: groups.translate, - getWebappTranslation, - }); - if (translated !== groups.translate) { - return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${translated}`; - } - throw new Error(`${translated}, ${groups.translate}`); - } - } - return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${groups.tagContent}`; - }, - ); - if (enableTranslation) { - return body; - } - return replaceTranslationAttributes({ content: body, getWebappTranslation }); -} - -export function replaceTranslations({ - content, - type, - getWebappTranslation, -}: { - content: string; - type: 'vue' | 'ts'; - getWebappTranslation: GetWebappTranslation; -}) { - const regex = - type === 'ts' - ? /(?:this.)?(t\$|i18n.t)\((?[^),]*)(?:,\s*{(?[^)]*)})?\)\.toString\(\)/g - : /t\$\((?[^),]*)(?:,\s*{(?[^)]*)})?\)/g; - return content.replaceAll(regex, (_complete, ...args) => { - const groups: Record = args.pop(); - const key = groups.key.substring(1, groups.key.length - 1).replaceAll("\\'", "'"); - let data; - if (groups.data) { - const interpolateMatches = groups.data.matchAll(/(?[^{\s:,}]+)(?:\s*:\s*(?[^,}]+))?/g); - data = {}; - let templateLiteral = false; - for (const interpolateMatch of interpolateMatches) { - let { field, value }: { field?: string; value?: string | number } = interpolateMatch.groups || {}; - if (/^'.*'$/.test(field) || /^".*"$/.test(field)) { - // unwrap field - field = field.substring(1, field.length - 1); - } - if (value === undefined) { - value = field; - } - value = value.trim(); - if (/^\d+$/.test(value)) { - // convert integer - value = parseInt(value, 10); - } else if (/^'.*'$/.test(value) || /^".*"$/.test(value)) { - // extract string value - value = value.substring(1, value.length - 1); - } else { - // wrap expression - if (type === 'vue') { - value = `{{ ${value} }}`; - } else { - value = `\${${value}}`; - } - templateLiteral = true; - } - data[field] = value; - } - if (templateLiteral && type === 'ts') { - return `\`${getWebappTranslation(key, data)}\``; - } - } - if (type === 'vue') { - return getWebappTranslation(key, data); - } - return `'${getWebappTranslation(key, data)}'`; - }); -} - -const minimatch = new Minimatch('**/*.{vue,ts}'); -export const isTranslatedVueFile = file => minimatch.match(file.path); - -function translateVueFilesTransform( - this: CoreGenerator | void, - { - enableTranslation, - getWebappTranslation, - }: { - enableTranslation: boolean; - getWebappTranslation: GetWebappTranslation; - }, -) { - return passthrough(file => { - let newContent; - if (file.path.endsWith('.vue')) { - newContent = replaceTranslationTags.call(this, { body: file.contents.toString(), enableTranslation, getWebappTranslation }); - } else if (!enableTranslation && file.path.endsWith('.ts')) { - newContent = replaceTranslations({ type: 'ts', content: file.contents.toString(), getWebappTranslation }); - newContent = removeDeclarations({ content: newContent }); - } - if (newContent) { - if (!enableTranslation && newContent.includes('t$')) { - throw new Error(`Could not translate ${file.path}: -${newContent}`); - } - file.contents = Buffer.from(newContent); - } - }); -} - -export default translateVueFilesTransform; diff --git a/generators/vue/support/translate-vue.spec.mts b/generators/vue/support/translate-vue.spec.mts deleted file mode 100644 index a7a106670a88..000000000000 --- a/generators/vue/support/translate-vue.spec.mts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright 2013-2023 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 { inspect } from 'node:util'; -import { expect } from 'esmocha'; - -import { replaceTranslationTags, replaceTranslations, removeDeclarations } from './translate-vue.mjs'; - -const FULL_BODY = ` -Your user account has been activated. Please -Did you forget your password? - - - - - - -

    - - - - - - -`; -const getWebappTranslation = (s, data) => `getWebappTranslation('${s}'${data ? `, ${inspect(data)}` : ''})`; - -describe('generator - vue - transform', () => { - describe('removeDeclarations', () => { - it('should remove i18n declarations', () => { - expect( - removeDeclarations({ - content: ` -import { useI18n } from 'vue-i18n'; -return { - t$, - foo, - t$: useI18n().t, -} -`, - }), - ).toMatchInlineSnapshot(` -" -return { - foo, -} -" -`); - }); - }); - describe('replaceTranslations', () => { - it('should replace t$ and interpolate at ts file', () => { - expect( - replaceTranslations({ - getWebappTranslation, - type: 'ts', - content: ` -t$('msg').toString(); -t$('msg', {exp:foo}).toString(); -t$('msg', { num : 1 }).toString(); -t$('msg', { str : 'a' }).toString(); -t$('msg', { exp:foo,num : 1 , str : 'a' }).toString(); -`, - }), - ).toMatchInlineSnapshot(` -" -'getWebappTranslation('msg')'; -\`getWebappTranslation('msg', { exp: '\${foo}' })\`; -'getWebappTranslation('msg', { num: 1 })'; -'getWebappTranslation('msg', { str: 'a' })'; -\`getWebappTranslation('msg', { exp: '\${foo}', num: 1, str: 'a' })\`; -" -`); - }); - it('should replace t$ and interpolate at vue file', () => { - expect( - replaceTranslations({ - type: 'vue', - getWebappTranslation, - content: ` -t$('msg') -t$('msg', {exp:foo}) -t$('msg', { num : 1 }) -t$('msg', { str : 'a' }) -t$('msg', { exp:foo,num : 1 , str : 'a' }) -`, - }), - ).toMatchInlineSnapshot(` -" -getWebappTranslation('msg') -getWebappTranslation('msg', { exp: '{{ foo }}' }) -getWebappTranslation('msg', { num: 1 }) -getWebappTranslation('msg', { str: 'a' }) -getWebappTranslation('msg', { exp: '{{ foo }}', num: 1, str: 'a' }) -" -`); - }); - }); - describe('replaceTranslationTags', () => { - describe('with nested tag', () => { - it('should throw', () => { - expect(() => - replaceTranslationTags({ - getWebappTranslation, - body: '
    fooo
    fooo
    ', - enableTranslation: false, - }), - ).toThrow(/Nested tags identical to the translated tag are not supported:/); - }); - }); - describe('with translation disabled', () => { - it('should return the body without translation attributes', () => { - expect(replaceTranslationTags({ getWebappTranslation, body: FULL_BODY, enableTranslation: false })).toMatchInlineSnapshot(` -" -getWebappTranslation('activate.messages.success') -getWebappTranslation('login.password.forgot') - - - - - - - - - - - - - -" -`); - }); - }); - - describe('with translation enabled', () => { - it('should return the body without translation tags contents', () => { - expect(replaceTranslationTags({ getWebappTranslation, body: FULL_BODY, enableTranslation: true })).toMatchInlineSnapshot(` -" - - - - - - - - - - - - - - - -" -`); - }); - }); - }); -}); diff --git a/generators/vue/support/translate-vue.spec.ts b/generators/vue/support/translate-vue.spec.ts new file mode 100644 index 000000000000..5c482a28db25 --- /dev/null +++ b/generators/vue/support/translate-vue.spec.ts @@ -0,0 +1,219 @@ +/** + * Copyright 2013-2023 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 { inspect } from 'node:util'; +import { expect } from 'esmocha'; + +import { replaceTranslationTags, replaceTranslations, removeDeclarations } from './translate-vue.js'; + +const FULL_BODY = ` +Your user account has been activated. Please +Did you forget your password? + + + + + + + + + + + + + +`; +const getWebappTranslation = (s, data) => `getWebappTranslation('${s}'${data ? `, ${inspect(data)}` : ''})`; + +describe('generator - vue - transform', () => { + describe('removeDeclarations', () => { + it('should remove i18n declarations', () => { + expect( + removeDeclarations({ + content: ` +import { useI18n } from 'vue-i18n'; +return { + t$, + foo, + t$: useI18n().t, +} +`, + }), + ).toMatchInlineSnapshot(` +" +return { + foo, +} +" +`); + }); + }); + describe('replaceTranslations', () => { + it('should replace t$ and interpolate at ts file', () => { + expect( + replaceTranslations({ + getWebappTranslation, + type: 'ts', + content: ` +t$('msg').toString(); +t$('msg', {exp:foo}).toString(); +t$('msg', { num : 1 }).toString(); +t$('msg', { str : 'a' }).toString(); +t$('msg', { exp:foo,num : 1 , str : 'a' }).toString(); +`, + }), + ).toMatchInlineSnapshot(` +" +'getWebappTranslation('msg')'; +\`getWebappTranslation('msg', { exp: '\${foo}' })\`; +'getWebappTranslation('msg', { num: 1 })'; +'getWebappTranslation('msg', { str: 'a' })'; +\`getWebappTranslation('msg', { exp: '\${foo}', num: 1, str: 'a' })\`; +" +`); + }); + it('should replace t$ and interpolate at vue file', () => { + expect( + replaceTranslations({ + type: 'vue', + getWebappTranslation, + content: ` +t$('msg') +t$('msg', {exp:foo}) +t$('msg', { num : 1 }) +t$('msg', { str : 'a' }) +t$('msg', { exp:foo,num : 1 , str : 'a' }) +`, + }), + ).toMatchInlineSnapshot(` +" +getWebappTranslation('msg') +getWebappTranslation('msg', { exp: '{{ foo }}' }) +getWebappTranslation('msg', { num: 1 }) +getWebappTranslation('msg', { str: 'a' }) +getWebappTranslation('msg', { exp: '{{ foo }}', num: 1, str: 'a' }) +" +`); + }); + }); + describe('replaceTranslationTags', () => { + describe('with nested tag', () => { + it('should throw', () => { + expect(() => + replaceTranslationTags({ + getWebappTranslation, + body: '
    fooo
    fooo
    ', + enableTranslation: false, + }), + ).toThrow(/Nested tags identical to the translated tag are not supported:/); + }); + }); + describe('with translation disabled', () => { + it('should return the body without translation attributes', () => { + expect(replaceTranslationTags({ getWebappTranslation, body: FULL_BODY, enableTranslation: false })).toMatchInlineSnapshot(` +" +getWebappTranslation('activate.messages.success') +getWebappTranslation('login.password.forgot') + + + + + + + + + + + + + +" +`); + }); + }); + + describe('with translation enabled', () => { + it('should return the body without translation tags contents', () => { + expect(replaceTranslationTags({ getWebappTranslation, body: FULL_BODY, enableTranslation: true })).toMatchInlineSnapshot(` +" + + + + + + + + + + + + + + + +" +`); + }); + }); + }); +}); diff --git a/generators/vue/support/translate-vue.ts b/generators/vue/support/translate-vue.ts new file mode 100644 index 000000000000..6a83afc758a1 --- /dev/null +++ b/generators/vue/support/translate-vue.ts @@ -0,0 +1,179 @@ +/** + * Copyright 2013-2023 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 { passthrough } from '@yeoman/transform'; +import { Minimatch } from 'minimatch'; +import CoreGenerator from '../../base-core/index.js'; + +type GetWebappTranslation = (s: string, data?: Record) => string; + +function replaceTranslationAttributes({ content, getWebappTranslation }: { content: string; getWebappTranslation: GetWebappTranslation }) { + return content.replaceAll(/v-bind:(?(?:placeholder|title|label))="(?t\$\([^"]+\))"/g, (_complete, ...args) => { + const groups: Record = args.pop(); + if (groups.translate.includes('+')) { + return ''; + } + const translated = replaceTranslations({ content: groups.translate, type: 'vue', getWebappTranslation }); + return `${groups.tag}="${translated}"`; + }); +} + +export function removeDeclarations({ content }: { content: string }) { + return content + .replaceAll(/\nimport {\s*useI18n\s*} from 'vue-i18n';/g, '') + .replaceAll(/\n\s*t\$,/g, '') + .replaceAll(/\n\s*t\$:\s*useI18n\(\).t,/g, ''); +} + +export function replaceTranslationTags( + this: CoreGenerator | void, + { + body, + enableTranslation, + getWebappTranslation, + }: { + body: string; + enableTranslation: boolean; + getWebappTranslation: GetWebappTranslation; + }, +) { + body = body.replaceAll( + /(?(?<(?a|b-(?:badge|link|button|alert)|button|div|h[1-9]|input|label|p|router-link|small|span|t[hd])(?:[^>]*)) v-(?:text|html)="(?t\$\([^)]*\))"(?(?:(?!\/?>)[^/>])*>))(?(?:(?!<\/\k(?:>|\s|\n)).|\n)*)/g, + (_complete, ...args) => { + const groups: Record = args.pop(); + if (new RegExp(`<${groups.tagName}[\\s>]`, 'g').test(groups.tagContent)) { + throw new Error(`Nested tags identical to the translated tag are not supported: ${groups.tagWithAttributes}${groups.tagContent}`); + } + if (enableTranslation) { + return groups.tagWithAttributes; + } + if (groups.translate) { + if (groups.translate.includes('+')) { + this?.log.info(`Ignoring dynamic translation ${groups.translate}`); + } else { + const translated = replaceTranslations({ + type: 'vue', + content: groups.translate, + getWebappTranslation, + }); + if (translated !== groups.translate) { + return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${translated}`; + } + throw new Error(`${translated}, ${groups.translate}`); + } + } + return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${groups.tagContent}`; + }, + ); + if (enableTranslation) { + return body; + } + return replaceTranslationAttributes({ content: body, getWebappTranslation }); +} + +export function replaceTranslations({ + content, + type, + getWebappTranslation, +}: { + content: string; + type: 'vue' | 'ts'; + getWebappTranslation: GetWebappTranslation; +}) { + const regex = + type === 'ts' + ? /(?:this.)?(t\$|i18n.t)\((?[^),]*)(?:,\s*{(?[^)]*)})?\)\.toString\(\)/g + : /t\$\((?[^),]*)(?:,\s*{(?[^)]*)})?\)/g; + return content.replaceAll(regex, (_complete, ...args) => { + const groups: Record = args.pop(); + const key = groups.key.substring(1, groups.key.length - 1).replaceAll("\\'", "'"); + let data; + if (groups.data) { + const interpolateMatches = groups.data.matchAll(/(?[^{\s:,}]+)(?:\s*:\s*(?[^,}]+))?/g); + data = {}; + let templateLiteral = false; + for (const interpolateMatch of interpolateMatches) { + let { field, value }: { field?: string; value?: string | number } = interpolateMatch.groups || {}; + if (/^'.*'$/.test(field) || /^".*"$/.test(field)) { + // unwrap field + field = field.substring(1, field.length - 1); + } + if (value === undefined) { + value = field; + } + value = value.trim(); + if (/^\d+$/.test(value)) { + // convert integer + value = parseInt(value, 10); + } else if (/^'.*'$/.test(value) || /^".*"$/.test(value)) { + // extract string value + value = value.substring(1, value.length - 1); + } else { + // wrap expression + if (type === 'vue') { + value = `{{ ${value} }}`; + } else { + value = `\${${value}}`; + } + templateLiteral = true; + } + data[field] = value; + } + if (templateLiteral && type === 'ts') { + return `\`${getWebappTranslation(key, data)}\``; + } + } + if (type === 'vue') { + return getWebappTranslation(key, data); + } + return `'${getWebappTranslation(key, data)}'`; + }); +} + +const minimatch = new Minimatch('**/*.{vue,ts}'); +export const isTranslatedVueFile = file => minimatch.match(file.path); + +function translateVueFilesTransform( + this: CoreGenerator | void, + { + enableTranslation, + getWebappTranslation, + }: { + enableTranslation: boolean; + getWebappTranslation: GetWebappTranslation; + }, +) { + return passthrough(file => { + let newContent; + if (file.path.endsWith('.vue')) { + newContent = replaceTranslationTags.call(this, { body: file.contents.toString(), enableTranslation, getWebappTranslation }); + } else if (!enableTranslation && file.path.endsWith('.ts')) { + newContent = replaceTranslations({ type: 'ts', content: file.contents.toString(), getWebappTranslation }); + newContent = removeDeclarations({ content: newContent }); + } + if (newContent) { + if (!enableTranslation && newContent.includes('t$')) { + throw new Error(`Could not translate ${file.path}: +${newContent}`); + } + file.contents = Buffer.from(newContent); + } + }); +} + +export default translateVueFilesTransform; diff --git a/generators/vue/support/update-languages.mts b/generators/vue/support/update-languages.mts deleted file mode 100644 index 22f2580919cb..000000000000 --- a/generators/vue/support/update-languages.mts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright 2013-2023 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 BaseGenerator from '../../base/index.mjs'; -import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/update-languages.mjs'; -import { generateLanguagesWebappOptions } from '../../languages/support/index.mjs'; - -function generateDateTimeFormat(language: string, index: number, length: number): string { - let config = ` '${language}': {\n`; - - config += ' short: {\n'; - config += " year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric'\n"; - config += ' },\n'; - config += ' medium: {\n'; - config += " year: 'numeric', month: 'short', day: 'numeric',\n"; - config += " weekday: 'short', hour: 'numeric', minute: 'numeric'\n"; - config += ' },\n'; - config += ' long: {\n'; - config += " year: 'numeric', month: 'long', day: 'numeric',\n"; - config += " weekday: 'long', hour: 'numeric', minute: 'numeric'\n"; - config += ' }\n'; - config += ' }'; - if (index !== length - 1) { - config += ','; - } - config += '\n'; - return config; -} - -function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languagesDefinition = [] } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - const newContent = `$1{ - ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')}, - // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object - }`; - this.editFile(`${clientSrcDir}app/shared/config/languages.ts`, { ignoreNonExisting }, content => - content.replace(/(languages =.*)\{([^\]]*jhipster-needle-i18n-language-key-pipe[^}]*)}/g, newContent), - ); -} - -function updateLanguagesInConfigTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - // Add i18n config snippets for all languages - let i18nConfig = 'const datetimeFormats: IntlDateTimeFormats = {\n'; - languages?.forEach((ln, i) => { - i18nConfig += generateDateTimeFormat(ln, i, languages.length); - }); - i18nConfig += ' // jhipster-needle-i18n-language-date-time-format - JHipster will add/remove format options in this object\n'; - i18nConfig += '}'; - - this.editFile(`${clientSrcDir}app/shared/config/config.ts`, { ignoreNonExisting }, content => - content.replace(/const datetimeFormats.*\{([^\]]*jhipster-needle-i18n-language-date-time-format[^}]*)}/g, i18nConfig), - ); -} - -function updateLanguagesInWebpackTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'groupBy: [\n'; - languages?.forEach(language => { - newContent += ` { pattern: './${clientSrcDir}i18n/${language}/*.json', fileName: './i18n/${language}.json' },\n`; - }); - newContent += ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n ]'; - - this.editFile('webpack/webpack.common.js', { ignoreNonExisting }, content => - content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), - ); -} - -export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateClientLanguagesTaskParam) { - updateLanguagesInPipeTask.call(this, taskParam); - updateLanguagesInConfigTask.call(this, taskParam); - if (taskParam.application.microfrontend) { - updateLanguagesInWebpackTask.call(this, taskParam); - } - updateLanguagesInDayjsConfigurationTask.call(this, taskParam, { - configurationFile: `${taskParam.application.clientSrcDir}app/shared/config/dayjs.ts`, - commonjs: true, - }); -} diff --git a/generators/vue/support/update-languages.ts b/generators/vue/support/update-languages.ts new file mode 100644 index 000000000000..8089bd74155f --- /dev/null +++ b/generators/vue/support/update-languages.ts @@ -0,0 +1,97 @@ +/** + * Copyright 2013-2023 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 BaseGenerator from '../../base/index.js'; +import { type UpdateClientLanguagesTaskParam, updateLanguagesInDayjsConfigurationTask } from '../../client/support/update-languages.js'; +import { generateLanguagesWebappOptions } from '../../languages/support/index.js'; + +function generateDateTimeFormat(language: string, index: number, length: number): string { + let config = ` '${language}': {\n`; + + config += ' short: {\n'; + config += " year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric'\n"; + config += ' },\n'; + config += ' medium: {\n'; + config += " year: 'numeric', month: 'short', day: 'numeric',\n"; + config += " weekday: 'short', hour: 'numeric', minute: 'numeric'\n"; + config += ' },\n'; + config += ' long: {\n'; + config += " year: 'numeric', month: 'long', day: 'numeric',\n"; + config += " weekday: 'long', hour: 'numeric', minute: 'numeric'\n"; + config += ' }\n'; + config += ' }'; + if (index !== length - 1) { + config += ','; + } + config += '\n'; + return config; +} + +function updateLanguagesInPipeTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languagesDefinition = [] } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + const newContent = `$1{ + ${generateLanguagesWebappOptions(languagesDefinition).join(',\n ')}, + // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object + }`; + this.editFile(`${clientSrcDir}app/shared/config/languages.ts`, { ignoreNonExisting }, content => + content.replace(/(languages =.*)\{([^\]]*jhipster-needle-i18n-language-key-pipe[^}]*)}/g, newContent), + ); +} + +function updateLanguagesInConfigTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languages } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + // Add i18n config snippets for all languages + let i18nConfig = 'const datetimeFormats: IntlDateTimeFormats = {\n'; + languages?.forEach((ln, i) => { + i18nConfig += generateDateTimeFormat(ln, i, languages.length); + }); + i18nConfig += ' // jhipster-needle-i18n-language-date-time-format - JHipster will add/remove format options in this object\n'; + i18nConfig += '}'; + + this.editFile(`${clientSrcDir}app/shared/config/config.ts`, { ignoreNonExisting }, content => + content.replace(/const datetimeFormats.*\{([^\]]*jhipster-needle-i18n-language-date-time-format[^}]*)}/g, i18nConfig), + ); +} + +function updateLanguagesInWebpackTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { + const { clientSrcDir, languages } = application; + const { ignoreNeedlesError: ignoreNonExisting } = control; + let newContent = 'groupBy: [\n'; + languages?.forEach(language => { + newContent += ` { pattern: './${clientSrcDir}i18n/${language}/*.json', fileName: './i18n/${language}.json' },\n`; + }); + newContent += ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n ]'; + + this.editFile('webpack/webpack.common.js', { ignoreNonExisting }, content => + content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), + ); +} + +export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateClientLanguagesTaskParam) { + updateLanguagesInPipeTask.call(this, taskParam); + updateLanguagesInConfigTask.call(this, taskParam); + if (taskParam.application.microfrontend) { + updateLanguagesInWebpackTask.call(this, taskParam); + } + updateLanguagesInDayjsConfigurationTask.call(this, taskParam, { + configurationFile: `${taskParam.application.clientSrcDir}app/shared/config/dayjs.ts`, + commonjs: true, + }); +} diff --git a/generators/workspaces/command.mts b/generators/workspaces/command.mts deleted file mode 100644 index dc04e3aea13f..000000000000 --- a/generators/workspaces/command.mts +++ /dev/null @@ -1,33 +0,0 @@ -import { JHipsterCommandDefinition } from '../base/api.mjs'; -import { GENERATOR_APP, GENERATOR_GIT } from '../generator-list.mjs'; - -const command: JHipsterCommandDefinition = { - options: { - appsFolders: { - name: 'workspacesFolders', - type: Array, - description: 'Folders to use as monorepository workspace', - default: [], - scope: 'generator', - }, - workspaces: { - type: Boolean, - description: 'Generate workspaces for multiples applications', - scope: 'generator', - }, - generateApplications: { - type: Function, - scope: 'generator', - hide: true, - }, - generateWith: { - type: String, - default: GENERATOR_APP, - scope: 'generator', - hide: true, - }, - }, - import: [GENERATOR_GIT], -}; - -export default command; diff --git a/generators/workspaces/command.ts b/generators/workspaces/command.ts new file mode 100644 index 000000000000..56b57c974559 --- /dev/null +++ b/generators/workspaces/command.ts @@ -0,0 +1,33 @@ +import { JHipsterCommandDefinition } from '../base/api.js'; +import { GENERATOR_APP, GENERATOR_GIT } from '../generator-list.js'; + +const command: JHipsterCommandDefinition = { + options: { + appsFolders: { + name: 'workspacesFolders', + type: Array, + description: 'Folders to use as monorepository workspace', + default: [], + scope: 'generator', + }, + workspaces: { + type: Boolean, + description: 'Generate workspaces for multiples applications', + scope: 'generator', + }, + generateApplications: { + type: Function, + scope: 'generator', + hide: true, + }, + generateWith: { + type: String, + default: GENERATOR_APP, + scope: 'generator', + hide: true, + }, + }, + import: [GENERATOR_GIT], +}; + +export default command; diff --git a/generators/workspaces/generator.js b/generators/workspaces/generator.js new file mode 100644 index 000000000000..e853919dd75e --- /dev/null +++ b/generators/workspaces/generator.js @@ -0,0 +1,211 @@ +/** + * Copyright 2013-2023 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 { existsSync } from 'fs'; + +import { GENERATOR_ANGULAR, GENERATOR_BOOTSTRAP_WORKSPACES, GENERATOR_GIT, GENERATOR_WORKSPACES } from '../generator-list.js'; + +import BaseWorkspacesGenerator from '../base-workspaces/index.js'; +import command from './command.js'; + +/** + * Base class for a generator that can be extended through a blueprint. + * + * @class + * @extends {BaseWorkspacesGenerator} + */ +export default class WorkspacesGenerator extends BaseWorkspacesGenerator { + workspaces; + generateApplications; + generateWith; + + generateWorkspaces; + + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints(GENERATOR_WORKSPACES); + } + + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_WORKSPACES, { generatorOptions: { customWorkspacesConfig: true } }); + } + } + + get initializing() { + return this.asInitializingTaskGroup({ + loadConfig() { + this.parseJHipsterOptions(command.options); + + // Generate workspaces file only when option passed or regenerating + this.generateWorkspaces = this.workspaces !== false || !!this.packageJson?.get('workspaces'); + + // When generating workspaces, save to .yo-rc.json. Use a dummy config otherwise. + this.workspacesConfig = this.generateWorkspaces ? this.jhipsterConfig : {}; + }, + }); + } + + get [BaseWorkspacesGenerator.INITIALIZING]() { + return this.delegateTasksToBlueprint(() => this.initializing); + } + + get configuring() { + return this.asConfiguringTaskGroup({ + async configure() { + this.jhipsterConfig.baseName = this.jhipsterConfig.baseName || 'workspaces'; + }, + async configureUsingFiles() { + if (!this.generateWorkspaces) return; + + if (existsSync(this.destinationPath('docker-compose'))) { + this.workspacesConfig.dockerCompose = true; + } + this.workspacesConfig.appsFolders = [...new Set([...(this.workspacesConfig.packages ?? []), ...this.appsFolders])]; + delete this.workspacesConfig.packages; + }, + }); + } + + get [BaseWorkspacesGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + + get composing() { + return this.asComposingTaskGroup({ + async composeGit() { + if (this.options.monorepository || this.jhipsterConfig.monorepository) { + await this.composeWithJHipster(GENERATOR_GIT); + } + }, + async generateApplications() { + if (!this.generateApplications) { + return; + } + + if (typeof this.generateApplications === 'function') { + await this.generateApplications.call(this); + } else { + for (const appName of this.appsFolders) { + await this.composeWithJHipster(this.generateWith, { generatorOptions: { destinationRoot: this.destinationPath(appName) } }); + } + } + }, + }); + } + + get [BaseWorkspacesGenerator.COMPOSING]() { + return this.delegateTasksToBlueprint(() => this.composing); + } + + get loadingWorkspaces() { + return this.asDefaultTaskGroup({ + configurePackageManager({ applications }) { + if (this.workspacesConfig.clientPackageManager || !this.generateWorkspaces) return; + + this.workspacesConfig.clientPackageManager = + this.options.clientPackageManager ?? applications.find(app => app.clientPackageManager)?.clientPackageManager ?? 'npm'; + }, + async loadConfig() { + if (!this.generateWorkspaces) return; + + this.dockerCompose = this.workspacesConfig.dockerCompose; + this.env.options.nodePackageManager = this.workspacesConfig.clientPackageManager; + }, + }); + } + + get [BaseWorkspacesGenerator.LOADING_WORKSPACES]() { + return this.delegateTasksToBlueprint(() => this.loadingWorkspaces); + } + + get postWriting() { + return this.asPostWritingTaskGroup({ + generatePackageJson({ applications }) { + if (!this.generateWorkspaces) return; + + const findDependencyVersion = dependency => + applications.find(app => app.nodeDependencies[dependency])?.nodeDependencies[dependency]; + this.packageJson.merge({ + workspaces: { + packages: this.appsFolders, + }, + devDependencies: { + concurrently: findDependencyVersion('concurrently'), + }, + scripts: { + 'ci:e2e:package': 'npm run ci:docker:build --workspaces --if-present && npm run java:docker --workspaces --if-present', + 'ci:e2e:run': 'npm run e2e:headless --workspaces --if-present', + ...this.getOtherScripts(), + ...this.createConcurrentlyScript('watch', 'backend:build-cache', 'java:docker', 'java:docker:arm64'), + ...this.createWorkspacesScript('ci:backend:test', 'ci:frontend:test', 'webapp:test'), + }, + }); + + if (applications.some(app => app.clientFrameworkAngular)) { + const { + dependencies: { rxjs }, + devDependencies: { webpack: webpackVersion }, + } = this.fs.readJSON(this.fetchFromInstalledJHipster(GENERATOR_ANGULAR, 'resources', 'package.json')); + + this.packageJson.merge({ + devDependencies: { + rxjs, // Set version to workaround https://github.com/npm/cli/issues/4437 + }, + overrides: { + webpack: webpackVersion, + }, + }); + } + }, + }); + } + + get [BaseWorkspacesGenerator.POST_WRITING]() { + return this.delegateTasksToBlueprint(() => this.postWriting); + } + + getOtherScripts() { + if (this.dockerCompose) { + return { + 'docker-compose': 'docker compose -f docker-compose/docker-compose.yml up --wait', + 'ci:e2e:prepare': 'npm run docker-compose', + 'ci:e2e:teardown': 'docker compose -f docker-compose/docker-compose.yml down -v', + }; + } + return {}; + } + + createConcurrentlyScript(...scripts) { + const scriptsList = scripts + .map(script => { + const packageScripts = this.appsFolders.map(packageName => [ + `${script}:${packageName}`, + `npm run ${script} --workspace ${packageName} --if-present`, + ]); + packageScripts.push([script, `concurrently ${this.appsFolders.map(packageName => `npm:${script}:${packageName}`).join(' ')}`]); + return packageScripts; + }) + .flat(); + return Object.fromEntries(scriptsList); + } + + createWorkspacesScript(...scripts) { + return Object.fromEntries(scripts.map(script => [`${script}`, `npm run ${script} --workspaces --if-present`])); + } +} diff --git a/generators/workspaces/generator.mjs b/generators/workspaces/generator.mjs deleted file mode 100644 index 109d6a2ed7be..000000000000 --- a/generators/workspaces/generator.mjs +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright 2013-2023 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 { existsSync } from 'fs'; - -import { GENERATOR_ANGULAR, GENERATOR_BOOTSTRAP_WORKSPACES, GENERATOR_GIT, GENERATOR_WORKSPACES } from '../generator-list.mjs'; - -import BaseWorkspacesGenerator from '../base-workspaces/index.mjs'; -import command from './command.mjs'; - -/** - * Base class for a generator that can be extended through a blueprint. - * - * @class - * @extends {BaseWorkspacesGenerator} - */ -export default class WorkspacesGenerator extends BaseWorkspacesGenerator { - workspaces; - generateApplications; - generateWith; - - generateWorkspaces; - - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints(GENERATOR_WORKSPACES); - } - - if (!this.delegateToBlueprint) { - await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_WORKSPACES, { generatorOptions: { customWorkspacesConfig: true } }); - } - } - - get initializing() { - return this.asInitializingTaskGroup({ - loadConfig() { - this.parseJHipsterOptions(command.options); - - // Generate workspaces file only when option passed or regenerating - this.generateWorkspaces = this.workspaces !== false || !!this.packageJson?.get('workspaces'); - - // When generating workspaces, save to .yo-rc.json. Use a dummy config otherwise. - this.workspacesConfig = this.generateWorkspaces ? this.jhipsterConfig : {}; - }, - }); - } - - get [BaseWorkspacesGenerator.INITIALIZING]() { - return this.delegateTasksToBlueprint(() => this.initializing); - } - - get configuring() { - return this.asConfiguringTaskGroup({ - async configure() { - this.jhipsterConfig.baseName = this.jhipsterConfig.baseName || 'workspaces'; - }, - async configureUsingFiles() { - if (!this.generateWorkspaces) return; - - if (existsSync(this.destinationPath('docker-compose'))) { - this.workspacesConfig.dockerCompose = true; - } - this.workspacesConfig.appsFolders = [...new Set([...(this.workspacesConfig.packages ?? []), ...this.appsFolders])]; - delete this.workspacesConfig.packages; - }, - }); - } - - get [BaseWorkspacesGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); - } - - get composing() { - return this.asComposingTaskGroup({ - async composeGit() { - if (this.options.monorepository || this.jhipsterConfig.monorepository) { - await this.composeWithJHipster(GENERATOR_GIT); - } - }, - async generateApplications() { - if (!this.generateApplications) { - return; - } - - if (typeof this.generateApplications === 'function') { - await this.generateApplications.call(this); - } else { - for (const appName of this.appsFolders) { - await this.composeWithJHipster(this.generateWith, { generatorOptions: { destinationRoot: this.destinationPath(appName) } }); - } - } - }, - }); - } - - get [BaseWorkspacesGenerator.COMPOSING]() { - return this.delegateTasksToBlueprint(() => this.composing); - } - - get loadingWorkspaces() { - return this.asDefaultTaskGroup({ - configurePackageManager({ applications }) { - if (this.workspacesConfig.clientPackageManager || !this.generateWorkspaces) return; - - this.workspacesConfig.clientPackageManager = - this.options.clientPackageManager ?? applications.find(app => app.clientPackageManager)?.clientPackageManager ?? 'npm'; - }, - async loadConfig() { - if (!this.generateWorkspaces) return; - - this.dockerCompose = this.workspacesConfig.dockerCompose; - this.env.options.nodePackageManager = this.workspacesConfig.clientPackageManager; - }, - }); - } - - get [BaseWorkspacesGenerator.LOADING_WORKSPACES]() { - return this.delegateTasksToBlueprint(() => this.loadingWorkspaces); - } - - get postWriting() { - return this.asPostWritingTaskGroup({ - generatePackageJson({ applications }) { - if (!this.generateWorkspaces) return; - - const findDependencyVersion = dependency => - applications.find(app => app.nodeDependencies[dependency])?.nodeDependencies[dependency]; - this.packageJson.merge({ - workspaces: { - packages: this.appsFolders, - }, - devDependencies: { - concurrently: findDependencyVersion('concurrently'), - }, - scripts: { - 'ci:e2e:package': 'npm run ci:docker:build --workspaces --if-present && npm run java:docker --workspaces --if-present', - 'ci:e2e:run': 'npm run e2e:headless --workspaces --if-present', - ...this.getOtherScripts(), - ...this.createConcurrentlyScript('watch', 'backend:build-cache', 'java:docker', 'java:docker:arm64'), - ...this.createWorkspacesScript('ci:backend:test', 'ci:frontend:test', 'webapp:test'), - }, - }); - - if (applications.some(app => app.clientFrameworkAngular)) { - const { - dependencies: { rxjs }, - devDependencies: { webpack: webpackVersion }, - } = this.fs.readJSON(this.fetchFromInstalledJHipster(GENERATOR_ANGULAR, 'resources', 'package.json')); - - this.packageJson.merge({ - devDependencies: { - rxjs, // Set version to workaround https://github.com/npm/cli/issues/4437 - }, - overrides: { - webpack: webpackVersion, - }, - }); - } - }, - }); - } - - get [BaseWorkspacesGenerator.POST_WRITING]() { - return this.delegateTasksToBlueprint(() => this.postWriting); - } - - getOtherScripts() { - if (this.dockerCompose) { - return { - 'docker-compose': 'docker compose -f docker-compose/docker-compose.yml up --wait', - 'ci:e2e:prepare': 'npm run docker-compose', - 'ci:e2e:teardown': 'docker compose -f docker-compose/docker-compose.yml down -v', - }; - } - return {}; - } - - createConcurrentlyScript(...scripts) { - const scriptsList = scripts - .map(script => { - const packageScripts = this.appsFolders.map(packageName => [ - `${script}:${packageName}`, - `npm run ${script} --workspace ${packageName} --if-present`, - ]); - packageScripts.push([script, `concurrently ${this.appsFolders.map(packageName => `npm:${script}:${packageName}`).join(' ')}`]); - return packageScripts; - }) - .flat(); - return Object.fromEntries(scriptsList); - } - - createWorkspacesScript(...scripts) { - return Object.fromEntries(scripts.map(script => [`${script}`, `npm run ${script} --workspaces --if-present`])); - } -} diff --git a/generators/workspaces/generator.spec.js b/generators/workspaces/generator.spec.js new file mode 100644 index 000000000000..aed48150a103 --- /dev/null +++ b/generators/workspaces/generator.spec.js @@ -0,0 +1,40 @@ +/** + * Copyright 2013-2023 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 { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'esmocha'; +import lodash from 'lodash'; + +import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.js'; +import Generator from './generator.js'; + +const { snakeCase } = lodash; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const generator = basename(__dirname); + +describe(`generator - ${generator}`, () => { + it('generator-list constant matches folder name', async () => { + await expect((await import('../generator-list.js'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); + }); + shouldSupportFeatures(Generator); + describe('blueprint support', () => testBlueprintSupport(generator)); +}); diff --git a/generators/workspaces/generator.spec.mjs b/generators/workspaces/generator.spec.mjs deleted file mode 100644 index 549bd327b5ed..000000000000 --- a/generators/workspaces/generator.spec.mjs +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2013-2023 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 { basename, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'esmocha'; -import lodash from 'lodash'; - -import { shouldSupportFeatures, testBlueprintSupport } from '../../test/support/tests.mjs'; -import Generator from './generator.mjs'; - -const { snakeCase } = lodash; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const generator = basename(__dirname); - -describe(`generator - ${generator}`, () => { - it('generator-list constant matches folder name', async () => { - await expect((await import('../generator-list.mjs'))[`GENERATOR_${snakeCase(generator).toUpperCase()}`]).toBe(generator); - }); - shouldSupportFeatures(Generator); - describe('blueprint support', () => testBlueprintSupport(generator)); -}); diff --git a/generators/workspaces/index.mts b/generators/workspaces/index.mts deleted file mode 100644 index 855a438fcc02..000000000000 --- a/generators/workspaces/index.mts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2013-2023 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 './generator.mjs'; -export { default as command } from './command.mjs'; diff --git a/generators/workspaces/index.ts b/generators/workspaces/index.ts new file mode 100644 index 000000000000..0c6b0ea70de0 --- /dev/null +++ b/generators/workspaces/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2013-2023 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 './generator.js'; +export { default as command } from './command.js'; diff --git a/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-converter.ts b/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-converter.ts index 68d97d71668e..538c2e3132d9 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-converter.ts @@ -20,7 +20,7 @@ import JSONEntity from '../../jhipster/json-entity.js'; import formatComment from '../../utils/format-utils.js'; import getTableNameFromEntityName from '../../jhipster/entity-table-name-creator.js'; -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity } from '../../models/index.js'; export default { convert, diff --git a/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-information-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-information-converter.spec.ts index 979734af39b2..e9938beb0bf2 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-information-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-basic-entity-information-converter.spec.ts @@ -24,7 +24,7 @@ import sinonChai from 'sinon-chai'; chai.use(sinonChai); -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity } from '../../models/index.js'; import { convert } from './jdl-to-json-basic-entity-converter.js'; describe('jdl - JDLToJSONBasicEntityConverter', () => { diff --git a/jdl/converters/jdl-to-json/jdl-to-json-field-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-to-json-field-converter.spec.ts index f39b7e53c2a7..d9ec588070c1 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-field-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-field-converter.spec.ts @@ -20,10 +20,10 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import JDLObject from '../../models/jdl-object.js'; -import { JDLEntity, JDLEnum } from '../../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../../models/index.js'; import JDLField from '../../models/jdl-field.js'; import JDLValidation from '../../models/jdl-validation.js'; -import { fieldTypes, validations } from '../../jhipster/index.mjs'; +import { fieldTypes, validations } from '../../jhipster/index.js'; import { convert } from './jdl-to-json-field-converter.js'; const { CommonDBTypes } = fieldTypes; diff --git a/jdl/converters/jdl-to-json/jdl-to-json-field-converter.ts b/jdl/converters/jdl-to-json/jdl-to-json-field-converter.ts index 6715f68d7f4b..4b9054e3f512 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-field-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-field-converter.ts @@ -18,13 +18,13 @@ */ import * as _ from 'lodash-es'; -import { validations } from '../../jhipster/index.mjs'; +import { validations } from '../../jhipster/index.js'; import formatComment from '../../utils/format-utils.js'; import { camelCase } from '../../utils/string-utils.js'; import fieldTypes from '../../jhipster/field-types.js'; import JDLObject from '../../models/jdl-object.js'; import { Field } from '../types.js'; -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity } from '../../models/index.js'; const { Validations: { UNIQUE, REQUIRED }, diff --git a/jdl/converters/jdl-to-json/jdl-to-json-option-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-to-json-option-converter.spec.ts index 3ec8ed648d22..0b0bcbfdb84a 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-option-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-option-converter.spec.ts @@ -25,10 +25,10 @@ import sinonChai from 'sinon-chai'; chai.use(sinonChai); import JDLObject from '../../models/jdl-object.js'; -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity } from '../../models/index.js'; import JDLUnaryOption from '../../models/jdl-unary-option.js'; import JDLBinaryOption from '../../models/jdl-binary-option.js'; -import { unaryOptions, binaryOptions } from '../../jhipster/index.mjs'; +import { unaryOptions, binaryOptions } from '../../jhipster/index.js'; import { convert } from './jdl-to-json-option-converter.js'; import logger from '../../utils/objects/logger.js'; diff --git a/jdl/converters/jdl-to-json/jdl-to-json-option-converter.ts b/jdl/converters/jdl-to-json/jdl-to-json-option-converter.ts index e653d6ca540c..665012a9c9d3 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-option-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-option-converter.ts @@ -18,7 +18,7 @@ */ import logger from '../../utils/objects/logger.js'; -import { unaryOptions, binaryOptions, entityOptions, searchEngineTypes } from '../../jhipster/index.mjs'; +import { unaryOptions, binaryOptions, entityOptions, searchEngineTypes } from '../../jhipster/index.js'; import JDLObject from '../../models/jdl-object.js'; import JDLApplication from '../../models/jdl-application.js'; diff --git a/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.spec.ts index 79074201e5aa..522ec2394acc 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.spec.ts @@ -22,7 +22,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import JDLRelationship from '../../models/jdl-relationship.js'; import { convert } from './jdl-to-json-relationship-converter.js'; -import { relationshipTypes, relationshipOptions } from '../../jhipster/index.mjs'; +import { relationshipTypes, relationshipOptions } from '../../jhipster/index.js'; const { ONE_TO_ONE, MANY_TO_MANY, MANY_TO_ONE, ONE_TO_MANY } = relationshipTypes; const { BUILT_IN_ENTITY } = relationshipOptions; diff --git a/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.ts b/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.ts index 93544a4a35d2..d7b9bc28039e 100644 --- a/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.ts @@ -18,7 +18,7 @@ */ import * as _ from 'lodash-es'; -import { relationshipOptions, validations } from '../../jhipster/index.mjs'; +import { relationshipOptions, validations } from '../../jhipster/index.js'; import { camelCase, lowerFirst } from '../../utils/string-utils.js'; import JDLRelationship from '../../models/jdl-relationship.js'; diff --git a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts index 35598a6dd5ca..18cc59e55ae2 100644 --- a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts @@ -34,10 +34,10 @@ import { binaryOptions, relationshipTypes, relationshipOptions, -} from '../../jhipster/index.mjs'; +} from '../../jhipster/index.js'; import createJDLApplication from '../../models/jdl-application-factory.js'; import JDLObject from '../../models/jdl-object.js'; -import { JDLEntity, JDLEnum } from '../../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../../models/index.js'; import JDLField from '../../models/jdl-field.js'; import JDLValidation from '../../models/jdl-validation.js'; import JDLRelationship from '../../models/jdl-relationship.js'; diff --git a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts index 42c6f1ecdfb4..26c3dc5668ca 100644 --- a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts @@ -36,10 +36,10 @@ import { binaryOptions, relationshipTypes, relationshipOptions, -} from '../../jhipster/index.mjs'; +} from '../../jhipster/index.js'; import JDLObject from '../../models/jdl-object.js'; -import { JDLEntity, JDLEnum } from '../../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../../models/index.js'; import JDLField from '../../models/jdl-field.js'; import JDLValidation from '../../models/jdl-validation.js'; import JDLRelationship from '../../models/jdl-relationship.js'; diff --git a/jdl/converters/json-to-jdl-application-converter.spec.ts b/jdl/converters/json-to-jdl-application-converter.spec.ts index 0387323c16b5..e5fe5228551a 100644 --- a/jdl/converters/json-to-jdl-application-converter.spec.ts +++ b/jdl/converters/json-to-jdl-application-converter.spec.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import createJDLApplication from '../models/jdl-application-factory.js'; import { convertApplicationsToJDL } from '../converters/json-to-jdl-application-converter.js'; import JDLObject from '../models/jdl-object.js'; -import { applicationTypes } from '../jhipster/index.mjs'; +import { applicationTypes } from '../jhipster/index.js'; const { MONOLITH } = applicationTypes; diff --git a/jdl/converters/json-to-jdl-converter.spec.ts b/jdl/converters/json-to-jdl-converter.spec.ts index 7ef8bbc34f62..f701f7e17ac4 100644 --- a/jdl/converters/json-to-jdl-converter.spec.ts +++ b/jdl/converters/json-to-jdl-converter.spec.ts @@ -24,7 +24,7 @@ import { fileURLToPath } from 'url'; import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import { convertToJDL, convertSingleContentToJDL } from '../converters/json-to-jdl-converter.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/jdl/converters/json-to-jdl-converter.ts b/jdl/converters/json-to-jdl-converter.ts index bb5935c0d158..8449405b4d94 100644 --- a/jdl/converters/json-to-jdl-converter.ts +++ b/jdl/converters/json-to-jdl-converter.ts @@ -27,8 +27,8 @@ import { convertApplicationToJDL } from './json-to-jdl-application-converter.js' import { convertEntitiesToJDL } from './json-to-jdl-entity-converter.js'; import exportJDLObject from '../exporters/jdl-exporter.js'; import { Entity } from './types.js'; -import { removeFieldsWithNullishValues } from '../../generators/base/support/config.mjs'; -import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.mjs'; +import { removeFieldsWithNullishValues } from '../../generators/base/support/config.js'; +import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.js'; export default { convertToJDL, diff --git a/jdl/converters/json-to-jdl-entity-converter.spec.ts b/jdl/converters/json-to-jdl-entity-converter.spec.ts index 6c95f68c8efa..f46b2f3d5057 100644 --- a/jdl/converters/json-to-jdl-entity-converter.spec.ts +++ b/jdl/converters/json-to-jdl-entity-converter.spec.ts @@ -24,7 +24,7 @@ import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { convertEntitiesToJDL } from '../converters/json-to-jdl-entity-converter.js'; -import { unaryOptions, relationshipOptions, binaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions, relationshipOptions, binaryOptions } from '../jhipster/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/jdl/converters/json-to-jdl-entity-converter.ts b/jdl/converters/json-to-jdl-entity-converter.ts index 8f74024fcf87..2ff5b508f968 100644 --- a/jdl/converters/json-to-jdl-entity-converter.ts +++ b/jdl/converters/json-to-jdl-entity-converter.ts @@ -18,7 +18,7 @@ */ import JDLObject from '../models/jdl-object.js'; -import { JDLEntity, JDLEnum } from '../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLValidation from '../models/jdl-validation.js'; import JDLRelationship, { JDLRelationshipModel, JDLRelationshipOptions } from '../models/jdl-relationship.js'; @@ -27,7 +27,7 @@ import JDLBinaryOption from '../models/jdl-binary-option.js'; import { lowerFirst, upperFirst } from '../utils/string-utils.js'; -import { fieldTypes, unaryOptions, binaryOptions, relationshipOptions } from '../jhipster/index.mjs'; +import { fieldTypes, unaryOptions, binaryOptions, relationshipOptions } from '../jhipster/index.js'; import { Entity, Field, Relationship } from './types.js'; import { asJdlRelationshipType } from '../jhipster/relationship-types.js'; diff --git a/jdl/converters/json-to-jdl-option-converter.spec.ts b/jdl/converters/json-to-jdl-option-converter.spec.ts index b312029e5dcb..405667edbd9d 100644 --- a/jdl/converters/json-to-jdl-option-converter.spec.ts +++ b/jdl/converters/json-to-jdl-option-converter.spec.ts @@ -23,7 +23,7 @@ import { expect } from 'chai'; import { convertServerOptionsToJDL } from '../converters/json-to-jdl-option-converter.js'; import JDLObject from '../models/jdl-object.js'; import JDLUnaryOption from '../models/jdl-unary-option.js'; -import { unaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions } from '../jhipster/index.js'; const { SKIP_CLIENT } = unaryOptions; diff --git a/jdl/converters/json-to-jdl-option-converter.ts b/jdl/converters/json-to-jdl-option-converter.ts index 83688ef81dc0..0243cf013a6b 100644 --- a/jdl/converters/json-to-jdl-option-converter.ts +++ b/jdl/converters/json-to-jdl-option-converter.ts @@ -19,7 +19,7 @@ import JDLObject from '../models/jdl-object.js'; import JDLUnaryOption from '../models/jdl-unary-option.js'; -import { unaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions } from '../jhipster/index.js'; const { SKIP_CLIENT, SKIP_SERVER } = unaryOptions; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/application-converter.spec.ts b/jdl/converters/parsed-jdl-to-jdl-object/application-converter.spec.ts index 28c54f75008e..c0b1ee96c81d 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/application-converter.spec.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/application-converter.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'chai'; -import { applicationTypes } from '../../jhipster/index.mjs'; +import { applicationTypes } from '../../jhipster/index.js'; import createJDLApplication from '../../models/jdl-application-factory.js'; import { convertApplications } from './application-converter.js'; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts index a4c2fbc5e014..8c8740469dbd 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts @@ -18,7 +18,7 @@ */ import { lowerFirst } from 'lodash-es'; -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity } from '../../models/index.js'; import { formatComment } from '../../utils/format-utils.js'; export default { convertEntities }; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/enum-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/enum-converter.ts index c9900b87f12d..a85abaf35871 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/enum-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/enum-converter.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { JDLEnum } from '../../models/index.mjs'; +import { JDLEnum } from '../../models/index.js'; import { formatComment } from '../../utils/format-utils.js'; export default { convertEnums }; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/option-converter.spec.ts b/jdl/converters/parsed-jdl-to-jdl-object/option-converter.spec.ts index 3648a691e177..4da0b162c19e 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/option-converter.spec.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/option-converter.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'esmocha'; -import { unaryOptions, binaryOptions, entityOptions, searchEngineTypes } from '../../jhipster/index.mjs'; +import { unaryOptions, binaryOptions, entityOptions, searchEngineTypes } from '../../jhipster/index.js'; import { convertOptions } from './option-converter.js'; const { MapperTypes, PaginationTypes } = entityOptions; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/option-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/option-converter.ts index d83ebd6a3955..f9583530f678 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/option-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/option-converter.ts @@ -19,7 +19,7 @@ import JDLUnaryOption from '../../models/jdl-unary-option.js'; import JDLBinaryOption from '../../models/jdl-binary-option.js'; -import { unaryOptions, binaryOptions } from '../../jhipster/index.mjs'; +import { unaryOptions, binaryOptions } from '../../jhipster/index.js'; const { OptionValues, getOptionName } = binaryOptions; export default { convertOptions }; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts index 655cd79d3537..87b3d084ff17 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts @@ -25,7 +25,7 @@ import { expect } from 'chai'; import matchEntity from '../../matchers/entity-matcher.js'; import * as JDLReader from '../../readers/jdl-reader.js'; import ParsedJDLToJDLObjectConverter from './parsed-jdl-to-jdl-object-converter.js'; -import { JDLEntity, JDLEnum } from '../../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../../models/index.js'; import JDLField from '../../models/jdl-field.js'; import JDLValidation from '../../models/jdl-validation.js'; import JDLUnaryOption from '../../models/jdl-unary-option.js'; @@ -38,7 +38,7 @@ import { applicationOptions, entityOptions, binaryOptions, -} from '../../jhipster/index.mjs'; +} from '../../jhipster/index.js'; const { GATEWAY, MICROSERVICE, MONOLITH } = applicationTypes; const { OptionNames } = applicationOptions; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts index 2ef091927dd8..4ebabcc91cbb 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts @@ -19,7 +19,7 @@ import * as _ from 'lodash-es'; import JDLObject from '../../models/jdl-object.js'; import JDLBinaryOption from '../../models/jdl-binary-option.js'; -import { applicationTypes, binaryOptions } from '../../jhipster/index.mjs'; +import { applicationTypes, binaryOptions } from '../../jhipster/index.js'; import { convertApplications } from './application-converter.js'; import { convertEntities } from './entity-converter.js'; diff --git a/jdl/converters/parsed-jdl-to-jdl-object/validation-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/validation-converter.ts index e7203ae6e2b0..690ebcd3fd7b 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/validation-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/validation-converter.ts @@ -18,7 +18,7 @@ */ import JDLValidation from '../../models/jdl-validation.js'; -import { validations } from '../../jhipster/index.mjs'; +import { validations } from '../../jhipster/index.js'; const { Validations: { PATTERN }, diff --git a/jdl/exporters/export-utils.spec.ts b/jdl/exporters/export-utils.spec.ts index bcac7263be65..3c171453c048 100644 --- a/jdl/exporters/export-utils.spec.ts +++ b/jdl/exporters/export-utils.spec.ts @@ -23,7 +23,7 @@ import { dirname } from 'path'; import { fileURLToPath } from 'url'; import { expect } from 'esmocha'; import { writeConfigFile } from '../exporters/export-utils.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/jdl/exporters/jdl-exporter.spec.ts b/jdl/exporters/jdl-exporter.spec.ts index 791fb76e4518..662717fb5c86 100644 --- a/jdl/exporters/jdl-exporter.spec.ts +++ b/jdl/exporters/jdl-exporter.spec.ts @@ -23,11 +23,11 @@ import { expect } from 'chai'; import { jestExpect } from 'esmocha'; import JDLObject from '../models/jdl-object.js'; -import { JDLEntity } from '../models/index.mjs'; +import { JDLEntity } from '../models/index.js'; import exportToJDL from '../exporters/jdl-exporter.js'; import JDLApplication from '../models/jdl-application.js'; -import { applicationOptions, clientFrameworkTypes } from '../jhipster/index.mjs'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { applicationOptions, clientFrameworkTypes } from '../jhipster/index.js'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const NO_CLIENT_FRAMEWORK = clientFrameworkTypes.NO; const { diff --git a/jdl/exporters/jhipster-deployment-exporter.spec.ts b/jdl/exporters/jhipster-deployment-exporter.spec.ts index f7052eb3c9ff..29612b7002d0 100644 --- a/jdl/exporters/jhipster-deployment-exporter.spec.ts +++ b/jdl/exporters/jhipster-deployment-exporter.spec.ts @@ -25,8 +25,8 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import exportDeployments from '../exporters/jhipster-deployment-exporter.js'; import JDLDeployment from '../models/jdl-deployment.js'; -import { deploymentOptions } from '../jhipster/index.mjs'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { deploymentOptions } from '../jhipster/index.js'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const { DeploymentTypes: { DOCKERCOMPOSE, KUBERNETES }, diff --git a/jdl/exporters/jhipster-entity-exporter.spec.ts b/jdl/exporters/jhipster-entity-exporter.spec.ts index 48383971b1fa..922b82be63aa 100644 --- a/jdl/exporters/jhipster-entity-exporter.spec.ts +++ b/jdl/exporters/jhipster-entity-exporter.spec.ts @@ -21,11 +21,11 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import exportEntities from '../exporters/jhipster-entity-exporter.js'; -import { applicationTypes } from '../jhipster/index.mjs'; +import { applicationTypes } from '../jhipster/index.js'; import entityOptions from '../jhipster/entity-options.js'; import { doesDirectoryExist } from '../utils/file-utils.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const { MapperTypes, PaginationTypes, ServiceTypes } = entityOptions; const { MONOLITH, MICROSERVICE } = applicationTypes; diff --git a/jdl/exporters/jhipster-entity-exporter.ts b/jdl/exporters/jhipster-entity-exporter.ts index 9e6a5f4ee9e8..ba63fceab297 100644 --- a/jdl/exporters/jhipster-entity-exporter.ts +++ b/jdl/exporters/jhipster-entity-exporter.ts @@ -19,7 +19,7 @@ import path from 'path'; -import { applicationTypes } from '../jhipster/index.mjs'; +import { applicationTypes } from '../jhipster/index.js'; import { toFilePath, readJSONFile } from '../readers/json-file-reader.js'; import { doesFileExist } from '../utils/file-utils.js'; diff --git a/jdl/index.ts b/jdl/index.ts index c5a79f7f0490..b0671d634f43 100644 --- a/jdl/index.ts +++ b/jdl/index.ts @@ -1,4 +1,4 @@ export * from './jdl-importer.js'; export * from './parsing/api.js'; -export * from './jhipster/index.mjs'; +export * from './jhipster/index.js'; export * from './exporters/config.js'; diff --git a/jdl/integration-test.spec.ts b/jdl/integration-test.spec.ts index 7fbc7d36a982..06d2a9ab076d 100644 --- a/jdl/integration-test.spec.ts +++ b/jdl/integration-test.spec.ts @@ -23,11 +23,11 @@ import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { jestExpect } from 'esmocha'; -import { applicationTypes } from './jhipster/index.mjs'; +import { applicationTypes } from './jhipster/index.js'; import { parseFromContent, parseFromFiles } from './readers/jdl-reader.js'; import DocumentParser from './converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.js'; import exportToJDL from './exporters/jdl-exporter.js'; -import { basicHelpers as helpers } from '../test/support/index.mjs'; +import { basicHelpers as helpers } from '../test/support/index.js'; import { convert as convertWithoutApplication } from './converters/jdl-to-json/jdl-without-application-to-json-converter.js'; import { ApplicationWithEntities, createImporterFromContent } from './jdl-importer.js'; diff --git a/jdl/jdl-importer.spec.ts b/jdl/jdl-importer.spec.ts index 7a974f2664de..0fcadeba5481 100644 --- a/jdl/jdl-importer.spec.ts +++ b/jdl/jdl-importer.spec.ts @@ -24,7 +24,7 @@ import { jestExpect } from 'esmocha'; import fse from 'fs-extra'; import { expect } from 'chai'; -import { applicationTypes, clientFrameworkTypes, databaseTypes } from './jhipster/index.mjs'; +import { applicationTypes, clientFrameworkTypes, databaseTypes } from './jhipster/index.js'; import { createImporterFromFiles, createImporterFromContent } from './jdl-importer.js'; const { MONOLITH } = applicationTypes; diff --git a/jdl/jdl-importer.ts b/jdl/jdl-importer.ts index f22faac5e795..5379c9ff5fad 100644 --- a/jdl/jdl-importer.ts +++ b/jdl/jdl-importer.ts @@ -28,7 +28,7 @@ import exportDeployments from './exporters/jhipster-deployment-exporter.js'; import exportEntities from './exporters/jhipster-entity-exporter.js'; import createWithApplicationValidator from './validators/jdl-with-application-validator.js'; import createWithoutApplicationValidator from './validators/jdl-without-application-validator.js'; -import { applicationOptions } from './jhipster/index.mjs'; +import { applicationOptions } from './jhipster/index.js'; const { OptionNames } = applicationOptions; const { APPLICATION_TYPE, BASE_NAME } = OptionNames; diff --git a/jdl/jhipster/application-options.ts b/jdl/jhipster/application-options.ts index d02afb9af580..26f0d3b3bc2a 100644 --- a/jdl/jhipster/application-options.ts +++ b/jdl/jhipster/application-options.ts @@ -27,7 +27,7 @@ import buildToolTypes from './build-tool-types.js'; import searchEngineTypes from './search-engine-types.js'; import testFrameworkTypes from './test-framework-types.js'; import websocketTypes from './websocket-types.js'; -import jhipsterDefinition from '../../generators/app/jdl/application-options.mjs'; +import jhipsterDefinition from '../../generators/app/jdl/application-options.js'; import { JDLApplicationOptionType, JDLApplicationOptionTypeValue, diff --git a/jdl/jhipster/binary-options.spec.ts b/jdl/jhipster/binary-options.spec.ts index e2508fee044d..db943e7dff8a 100644 --- a/jdl/jhipster/binary-options.spec.ts +++ b/jdl/jhipster/binary-options.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { binaryOptions } from '../jhipster/index.mjs'; +import { binaryOptions } from '../jhipster/index.js'; describe('jdl - BinaryOptions', () => { it('should match values', () => { diff --git a/jdl/jhipster/database-types.spec.ts b/jdl/jhipster/database-types.spec.ts index 47638b295978..de3618bf9bd8 100644 --- a/jdl/jhipster/database-types.spec.ts +++ b/jdl/jhipster/database-types.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from 'chai'; -import { databaseTypes } from '../jhipster/index.mjs'; +import { databaseTypes } from '../jhipster/index.js'; const { CASSANDRA, COUCHBASE, MARIADB, MONGODB, MSSQL, MYSQL, NO, ORACLE, POSTGRESQL, SQL } = databaseTypes; diff --git a/jdl/jhipster/default-application-options.spec.ts b/jdl/jhipster/default-application-options.spec.ts index 78a1db931e8c..df1797a1ff33 100644 --- a/jdl/jhipster/default-application-options.spec.ts +++ b/jdl/jhipster/default-application-options.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; -import { defaultApplicationOptions } from '../jhipster/index.mjs'; +import { defaultApplicationOptions } from '../jhipster/index.js'; const { getConfigForMonolithApplication, diff --git a/jdl/jhipster/default-application-options.ts b/jdl/jhipster/default-application-options.ts index 49db12508747..7f515e90653b 100644 --- a/jdl/jhipster/default-application-options.ts +++ b/jdl/jhipster/default-application-options.ts @@ -25,7 +25,7 @@ import cacheTypes from './cache-types.js'; import serviceDiscoveryTypes from './service-discovery-types.js'; import clientFrameworkTypes from './client-framework-types.js'; import buildToolTypes from './build-tool-types.js'; -import { MESSAGE_BROKER, MESSAGE_BROKER_NO } from '../../generators/server/options/index.mjs'; +import { MESSAGE_BROKER, MESSAGE_BROKER_NO } from '../../generators/server/options/index.js'; const { MONOLITH, MICROSERVICE, GATEWAY } = applicationTypes; const { CONSUL } = serviceDiscoveryTypes; diff --git a/jdl/jhipster/deployment-options.spec.ts b/jdl/jhipster/deployment-options.spec.ts index d21039b0f051..f7dde0ced14e 100644 --- a/jdl/jhipster/deployment-options.spec.ts +++ b/jdl/jhipster/deployment-options.spec.ts @@ -21,7 +21,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from 'chai'; -import { deploymentOptions } from '../jhipster/index.mjs'; +import { deploymentOptions } from '../jhipster/index.js'; const { DeploymentTypes, Options } = deploymentOptions; diff --git a/jdl/jhipster/entity-table-name-creator.spec.ts b/jdl/jhipster/entity-table-name-creator.spec.ts index 6636988dcd7c..804d24b2e8a7 100644 --- a/jdl/jhipster/entity-table-name-creator.spec.ts +++ b/jdl/jhipster/entity-table-name-creator.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'chai'; -import { entityTableNameCreator } from '../jhipster/index.mjs'; +import { entityTableNameCreator } from '../jhipster/index.js'; const getTableNameFromEntityName = entityTableNameCreator; diff --git a/jdl/jhipster/field-types.spec.ts b/jdl/jhipster/field-types.spec.ts index a3f1f77c04ab..343ff90415b5 100644 --- a/jdl/jhipster/field-types.spec.ts +++ b/jdl/jhipster/field-types.spec.ts @@ -21,8 +21,8 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; -import { databaseTypes, fieldTypes, validations } from '../jhipster/index.mjs'; -import { JDLEnum } from '../models/index.mjs'; +import { databaseTypes, fieldTypes, validations } from '../jhipster/index.js'; +import { JDLEnum } from '../models/index.js'; const { Validations: { MIN, MAXLENGTH, PATTERN }, diff --git a/jdl/jhipster/index.mts b/jdl/jhipster/index.ts similarity index 100% rename from jdl/jhipster/index.mts rename to jdl/jhipster/index.ts diff --git a/jdl/jhipster/json-entity.spec.ts b/jdl/jhipster/json-entity.spec.ts index fc0161828e9b..e2269770a461 100644 --- a/jdl/jhipster/json-entity.spec.ts +++ b/jdl/jhipster/json-entity.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new,no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { jsonEntity as JSONEntity } from '../jhipster/index.mjs'; +import { jsonEntity as JSONEntity } from '../jhipster/index.js'; describe('jdl - JSONEntity', () => { describe('new', () => { diff --git a/jdl/jhipster/relationship-options.spec.ts b/jdl/jhipster/relationship-options.spec.ts index f000eb6d871e..fa387f46a7c2 100644 --- a/jdl/jhipster/relationship-options.spec.ts +++ b/jdl/jhipster/relationship-options.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from 'chai'; -import { relationshipOptions } from '../jhipster/index.mjs'; +import { relationshipOptions } from '../jhipster/index.js'; const { BUILT_IN_ENTITY, exists } = relationshipOptions; diff --git a/jdl/jhipster/relationship-types.spec.ts b/jdl/jhipster/relationship-types.spec.ts index e36dc94d048d..4f6a9cdfba24 100644 --- a/jdl/jhipster/relationship-types.spec.ts +++ b/jdl/jhipster/relationship-types.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; -import { relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipTypes } from '../jhipster/index.js'; import { relationshipTypeExists } from './relationship-types.js'; describe('jdl - RelationshipTypes', () => { diff --git a/jdl/jhipster/reserved-keywords.spec.ts b/jdl/jhipster/reserved-keywords.spec.ts index 1e9dbda43824..20d8e6855a55 100644 --- a/jdl/jhipster/reserved-keywords.spec.ts +++ b/jdl/jhipster/reserved-keywords.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; -import { reservedKeywords } from '../jhipster/index.mjs'; +import { reservedKeywords } from '../jhipster/index.js'; describe('jdl - ReservedKeywords', () => { describe('isReserved', () => { diff --git a/jdl/jhipster/unary-options.spec.ts b/jdl/jhipster/unary-options.spec.ts index 42901ab24122..22761a5b61ca 100644 --- a/jdl/jhipster/unary-options.spec.ts +++ b/jdl/jhipster/unary-options.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { unaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions } from '../jhipster/index.js'; describe('jdl - UnaryOptions', () => { describe('exists', () => { diff --git a/jdl/jhipster/validations.spec.ts b/jdl/jhipster/validations.spec.ts index 91d5d7b18e56..98d37f63be74 100644 --- a/jdl/jhipster/validations.spec.ts +++ b/jdl/jhipster/validations.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; -import { validations } from '../jhipster/index.mjs'; +import { validations } from '../jhipster/index.js'; const { Validations: { REQUIRED, MAXLENGTH, MAXBYTES }, diff --git a/jdl/linters/issues/issues.spec.ts b/jdl/linters/issues/issues.spec.ts index 31089454a63a..b9c33f6c27bc 100644 --- a/jdl/linters/issues/issues.spec.ts +++ b/jdl/linters/issues/issues.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import Issues from './issues.js'; import { rulesNames } from '../rules.js'; -import { relationshipTypes } from '../../jhipster/index.mjs'; +import { relationshipTypes } from '../../jhipster/index.js'; import EntityIssue from './entity-issue.js'; import FieldIssue from './field-issue.js'; import EnumIssue from './enum-issue.js'; diff --git a/jdl/linters/jdl-linter.spec.ts b/jdl/linters/jdl-linter.spec.ts index e88876993855..c2ab72f1d893 100644 --- a/jdl/linters/jdl-linter.spec.ts +++ b/jdl/linters/jdl-linter.spec.ts @@ -28,7 +28,7 @@ import { createJDLLinterFromFile, createJDLLinterFromContent, JDLLinter } from ' import Issues from './issues/issues.js'; import EnumIssue from './issues/enum-issue.js'; import relationshipIssue from './issues/relationship-issue.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/jdl/linters/relationship-linter.ts b/jdl/linters/relationship-linter.ts index 6f9ded09fa82..e7c920ef36e1 100644 --- a/jdl/linters/relationship-linter.ts +++ b/jdl/linters/relationship-linter.ts @@ -20,7 +20,7 @@ import RelationshipIssue from './issues/relationship-issue.js'; import { rulesNames } from './rules.js'; -import { relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipTypes } from '../jhipster/index.js'; let issues: RelationshipIssue[]; diff --git a/jdl/models/abstract-jdl-option.spec.ts b/jdl/models/abstract-jdl-option.spec.ts index 8e13a2599a7c..d170188423ae 100644 --- a/jdl/models/abstract-jdl-option.spec.ts +++ b/jdl/models/abstract-jdl-option.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'esmocha'; import JDLBinaryOption from '../models/jdl-binary-option.js'; -import { binaryOptions } from '../jhipster/index.mjs'; +import { binaryOptions } from '../jhipster/index.js'; describe('jdl - AbstractJDLOption', () => { describe('resolveEntityNames', () => { diff --git a/jdl/models/index.mts b/jdl/models/index.ts similarity index 100% rename from jdl/models/index.mts rename to jdl/models/index.ts diff --git a/jdl/models/jdl-application-configuration-factory.spec.ts b/jdl/models/jdl-application-configuration-factory.spec.ts index 2fa6f305f0dc..cdc9ab20eed0 100644 --- a/jdl/models/jdl-application-configuration-factory.spec.ts +++ b/jdl/models/jdl-application-configuration-factory.spec.ts @@ -19,7 +19,7 @@ import { expect } from 'esmocha'; import createApplicationConfigurationFromObject from '../models/jdl-application-configuration-factory.js'; -import { applicationOptions } from '../jhipster/index.mjs'; +import { applicationOptions } from '../jhipster/index.js'; const { OptionNames } = applicationOptions; diff --git a/jdl/models/jdl-application-configuration.spec.ts b/jdl/models/jdl-application-configuration.spec.ts index 149f1ec1b61c..97bf42fefbb3 100644 --- a/jdl/models/jdl-application-configuration.spec.ts +++ b/jdl/models/jdl-application-configuration.spec.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import JDLApplicationConfiguration from '../models/jdl-application-configuration.js'; import StringJDLApplicationConfigurationOption from '../models/string-jdl-application-configuration-option.js'; -import { applicationOptions } from '../jhipster/index.mjs'; +import { applicationOptions } from '../jhipster/index.js'; const { OptionNames } = applicationOptions; diff --git a/jdl/models/jdl-application-definition.spec.ts b/jdl/models/jdl-application-definition.spec.ts index b8e9102eb949..ebbd27f75021 100644 --- a/jdl/models/jdl-application-definition.spec.ts +++ b/jdl/models/jdl-application-definition.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from 'chai'; -import { applicationOptions } from '../jhipster/index.mjs'; +import { applicationOptions } from '../jhipster/index.js'; import JDLApplicationDefinition from './jdl-application-definition.js'; const { OptionNames } = applicationOptions; diff --git a/jdl/models/jdl-application-factory.spec.ts b/jdl/models/jdl-application-factory.spec.ts index 0677d593229b..87eca9d4d4a1 100644 --- a/jdl/models/jdl-application-factory.spec.ts +++ b/jdl/models/jdl-application-factory.spec.ts @@ -19,7 +19,7 @@ import { expect } from 'chai'; import createJDLApplication from '../models/jdl-application-factory.js'; -import { applicationTypes } from '../jhipster/index.mjs'; +import { applicationTypes } from '../jhipster/index.js'; const { MONOLITH, MICROSERVICE, GATEWAY } = applicationTypes; diff --git a/jdl/models/jdl-application.spec.ts b/jdl/models/jdl-application.spec.ts index 67ecdf9fb623..ac88c53449de 100644 --- a/jdl/models/jdl-application.spec.ts +++ b/jdl/models/jdl-application.spec.ts @@ -20,7 +20,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { applicationOptions, binaryOptions } from '../jhipster/index.mjs'; +import { applicationOptions, binaryOptions } from '../jhipster/index.js'; import StringJDLApplicationConfigurationOption from '../models/string-jdl-application-configuration-option.js'; import JDLApplication from '../models/jdl-application.js'; import JDLBinaryOption from '../models/jdl-binary-option.js'; diff --git a/jdl/models/jdl-binary-option.spec.ts b/jdl/models/jdl-binary-option.spec.ts index 406eb077ddf0..b1c5e44a0655 100644 --- a/jdl/models/jdl-binary-option.spec.ts +++ b/jdl/models/jdl-binary-option.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; import JDLBinaryOption from '../models/jdl-binary-option.js'; -import { binaryOptions } from '../jhipster/index.mjs'; +import { binaryOptions } from '../jhipster/index.js'; describe('jdl - JDLBinaryOption', () => { describe('new', () => { diff --git a/jdl/models/jdl-deployment.ts b/jdl/models/jdl-deployment.ts index ddd46220f8c0..527617b2f126 100644 --- a/jdl/models/jdl-deployment.ts +++ b/jdl/models/jdl-deployment.ts @@ -17,7 +17,7 @@ * limitations under the License. */ import * as _ from 'lodash-es'; -import { deploymentOptions, applicationOptions, serviceDiscoveryTypes } from '../jhipster/index.mjs'; +import { deploymentOptions, applicationOptions, serviceDiscoveryTypes } from '../jhipster/index.js'; import { merge } from '../utils/object-utils.js'; import { join } from '../utils/set-utils.js'; diff --git a/jdl/models/jdl-entity.spec.ts b/jdl/models/jdl-entity.spec.ts index 685aa952573d..5972450743d8 100644 --- a/jdl/models/jdl-entity.spec.ts +++ b/jdl/models/jdl-entity.spec.ts @@ -19,7 +19,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; -import { JDLEntity } from '../models/index.mjs'; +import { JDLEntity } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLValidation from '../models/jdl-validation.js'; diff --git a/jdl/models/jdl-enum.spec.ts b/jdl/models/jdl-enum.spec.ts index 818243708f50..1b46eaadccaf 100644 --- a/jdl/models/jdl-enum.spec.ts +++ b/jdl/models/jdl-enum.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { JDLEnum } from '../models/index.mjs'; +import { JDLEnum } from '../models/index.js'; describe('jdl - JDLEnum', () => { describe('new', () => { diff --git a/jdl/models/jdl-enums.spec.ts b/jdl/models/jdl-enums.spec.ts index 0804e7d12baf..9bd34b79a01e 100644 --- a/jdl/models/jdl-enums.spec.ts +++ b/jdl/models/jdl-enums.spec.ts @@ -21,7 +21,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import JDLEnums from '../models/jdl-enums.js'; -import { JDLEnum } from '../models/index.mjs'; +import { JDLEnum } from '../models/index.js'; describe('jdl - JDLEnums', () => { describe('add', () => { diff --git a/jdl/models/jdl-field.spec.ts b/jdl/models/jdl-field.spec.ts index 6f6343cee894..2acfefb01281 100644 --- a/jdl/models/jdl-field.spec.ts +++ b/jdl/models/jdl-field.spec.ts @@ -23,7 +23,7 @@ import { expect } from 'chai'; import matchField from '../matchers/field-matcher.js'; import JDLField from '../models/jdl-field.js'; import JDLValidation from '../models/jdl-validation.js'; -import { validations } from '../jhipster/index.mjs'; +import { validations } from '../jhipster/index.js'; const { Validations: { MIN }, diff --git a/jdl/models/jdl-object-merger.spec.ts b/jdl/models/jdl-object-merger.spec.ts index 72c827c25e7f..28f458d3a891 100644 --- a/jdl/models/jdl-object-merger.spec.ts +++ b/jdl/models/jdl-object-merger.spec.ts @@ -18,10 +18,10 @@ */ import { expect } from 'chai'; -import { applicationTypes, fieldTypes, unaryOptions, relationshipTypes } from '../jhipster/index.mjs'; +import { applicationTypes, fieldTypes, unaryOptions, relationshipTypes } from '../jhipster/index.js'; import JDLObject from '../models/jdl-object.js'; import createJDLApplication from '../models/jdl-application-factory.js'; -import { JDLEntity, JDLEnum } from '../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLRelationship from '../models/jdl-relationship.js'; import JDLUnaryOption from '../models/jdl-unary-option.js'; diff --git a/jdl/models/jdl-object.spec.ts b/jdl/models/jdl-object.spec.ts index d462ce30f1ec..006cca5179d1 100644 --- a/jdl/models/jdl-object.spec.ts +++ b/jdl/models/jdl-object.spec.ts @@ -20,12 +20,12 @@ /* eslint-disable no-new, no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { applicationTypes, binaryOptions, unaryOptions, relationshipTypes } from '../jhipster/index.mjs'; +import { applicationTypes, binaryOptions, unaryOptions, relationshipTypes } from '../jhipster/index.js'; import JDLObject from '../models/jdl-object.js'; import createJDLApplication from '../models/jdl-application-factory.js'; import JDLDeployment from '../models/jdl-deployment.js'; -import { JDLEntity, JDLEnum } from '../models/index.mjs'; +import { JDLEntity, JDLEnum } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLValidation from '../models/jdl-validation.js'; import JDLRelationship from '../models/jdl-relationship.js'; diff --git a/jdl/models/jdl-object.ts b/jdl/models/jdl-object.ts index 30e395018515..ab0df09096b1 100644 --- a/jdl/models/jdl-object.ts +++ b/jdl/models/jdl-object.ts @@ -20,7 +20,7 @@ import JDLEnums from './jdl-enums.js'; import JDLRelationships from './jdl-relationships.js'; import JDLOptions from './jdl-options.js'; -import { binaryOptions } from '../jhipster/index.mjs'; +import { binaryOptions } from '../jhipster/index.js'; import JDLEntity from './jdl-entity.js'; import JDLRelationship from './jdl-relationship.js'; diff --git a/jdl/models/jdl-options.spec.ts b/jdl/models/jdl-options.spec.ts index ce16599fa9ff..903fd659f9ea 100644 --- a/jdl/models/jdl-options.spec.ts +++ b/jdl/models/jdl-options.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-unused-expressions */ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; -import { unaryOptions, binaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions, binaryOptions } from '../jhipster/index.js'; import JDLOptions from '../models/jdl-options.js'; import JDLUnaryOption from '../models/jdl-unary-option.js'; import JDLBinaryOption from '../models/jdl-binary-option.js'; diff --git a/jdl/models/jdl-relationship.spec.ts b/jdl/models/jdl-relationship.spec.ts index cb5b3a7aa5b7..b3a38fc335a0 100644 --- a/jdl/models/jdl-relationship.spec.ts +++ b/jdl/models/jdl-relationship.spec.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import JDLRelationship from '../models/jdl-relationship.js'; -import { relationshipTypes, relationshipOptions } from '../jhipster/index.mjs'; +import { relationshipTypes, relationshipOptions } from '../jhipster/index.js'; const { BUILT_IN_ENTITY } = relationshipOptions; diff --git a/jdl/models/jdl-relationship.ts b/jdl/models/jdl-relationship.ts index 0d02132c8bb2..e551877066c7 100644 --- a/jdl/models/jdl-relationship.ts +++ b/jdl/models/jdl-relationship.ts @@ -18,7 +18,7 @@ */ import { upperFirst } from 'lodash-es'; import { RelationshipSide, JDLRelationshipType } from '../basic-types/relationships.js'; -import { Validations } from '../jhipster/index.mjs'; +import { Validations } from '../jhipster/index.js'; import { relationshipTypeExists } from '../jhipster/relationship-types.js'; const { REQUIRED } = Validations; diff --git a/jdl/models/jdl-relationships.spec.ts b/jdl/models/jdl-relationships.spec.ts index 15d495c4bba9..75262f0a5d57 100644 --- a/jdl/models/jdl-relationships.spec.ts +++ b/jdl/models/jdl-relationships.spec.ts @@ -22,7 +22,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import JDLRelationship from '../models/jdl-relationship.js'; -import { relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipTypes } from '../jhipster/index.js'; import JDLRelationships from '../models/jdl-relationships.js'; describe('jdl - JDLRelationships', () => { diff --git a/jdl/models/jdl-relationships.ts b/jdl/models/jdl-relationships.ts index 22b8ebd51576..dfd1a169863a 100644 --- a/jdl/models/jdl-relationships.ts +++ b/jdl/models/jdl-relationships.ts @@ -18,7 +18,7 @@ */ import { JDLRelationshipType } from '../basic-types/relationships.js'; -import { relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipTypes } from '../jhipster/index.js'; import { relationshipTypeExists } from '../jhipster/relationship-types.js'; import JDLRelationship from './jdl-relationship.js'; diff --git a/jdl/models/jdl-unary-option.spec.ts b/jdl/models/jdl-unary-option.spec.ts index 72b3664db5df..8c1266794f16 100644 --- a/jdl/models/jdl-unary-option.spec.ts +++ b/jdl/models/jdl-unary-option.spec.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import JDLUnaryOption from '../models/jdl-unary-option.js'; -import { unaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions } from '../jhipster/index.js'; describe('jdl - JDLUnaryOption', () => { describe('new', () => { diff --git a/jdl/models/jdl-validation.spec.ts b/jdl/models/jdl-validation.spec.ts index 912167837704..81a36d68eb06 100644 --- a/jdl/models/jdl-validation.spec.ts +++ b/jdl/models/jdl-validation.spec.ts @@ -20,7 +20,7 @@ /* eslint-disable no-new, no-unused-expressions */ import { expect } from 'chai'; import JDLValidation from '../models/jdl-validation.js'; -import { validations } from '../jhipster/index.mjs'; +import { validations } from '../jhipster/index.js'; const { Validations: { PATTERN }, diff --git a/jdl/models/jdl-validation.ts b/jdl/models/jdl-validation.ts index 5ab9a0b9232a..6f9e10bc65b7 100644 --- a/jdl/models/jdl-validation.ts +++ b/jdl/models/jdl-validation.ts @@ -18,7 +18,7 @@ */ import { merge } from '../utils/object-utils.js'; -import { validations } from '../jhipster/index.mjs'; +import { validations } from '../jhipster/index.js'; const { Validations: { REQUIRED, PATTERN }, diff --git a/jdl/parsing/grammar.spec.ts b/jdl/parsing/grammar.spec.ts index 977d3ee107e5..8507864b8016 100644 --- a/jdl/parsing/grammar.spec.ts +++ b/jdl/parsing/grammar.spec.ts @@ -22,7 +22,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import { parseFromContent } from '../readers/jdl-reader.js'; -import { relationshipTypes, validations, unaryOptions, binaryOptions } from '../jhipster/index.mjs'; +import { relationshipTypes, validations, unaryOptions, binaryOptions } from '../jhipster/index.js'; const { ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANY, ONE_TO_ONE } = relationshipTypes; const { diff --git a/jdl/parsing/jdl-ast-builder-visitor.ts b/jdl/parsing/jdl-ast-builder-visitor.ts index 9944f9d31a12..91df0dedf107 100644 --- a/jdl/parsing/jdl-ast-builder-visitor.ts +++ b/jdl/parsing/jdl-ast-builder-visitor.ts @@ -19,7 +19,7 @@ import JDLParser from './jdl-parser.js'; import deduplicate from '../utils/array-utils.js'; -import { applicationOptions, entityOptions, validations, relationshipOptions } from '../jhipster/index.mjs'; +import { applicationOptions, entityOptions, validations, relationshipOptions } from '../jhipster/index.js'; const { BUILT_IN_ENTITY } = relationshipOptions; const { OptionNames } = applicationOptions; diff --git a/jdl/parsing/lexer/application-tokens.ts b/jdl/parsing/lexer/application-tokens.ts index 314ba1669682..91078409757f 100644 --- a/jdl/parsing/lexer/application-tokens.ts +++ b/jdl/parsing/lexer/application-tokens.ts @@ -21,8 +21,8 @@ import { ITokenConfig, Lexer } from 'chevrotain'; import createTokenFromConfig from './token-creator.js'; import { UNARY_OPTION, KEYWORD } from './shared-tokens.js'; -import { applicationOptions } from '../../jhipster/index.mjs'; -import jhipsterDefinition from '../../../generators/app/jdl/index.mjs'; +import { applicationOptions } from '../../jhipster/index.js'; +import jhipsterDefinition from '../../../generators/app/jdl/index.js'; const { OptionNames } = applicationOptions; diff --git a/jdl/parsing/lexer/lexer.ts b/jdl/parsing/lexer/lexer.ts index da265ceda71d..80ac714d1fa5 100644 --- a/jdl/parsing/lexer/lexer.ts +++ b/jdl/parsing/lexer/lexer.ts @@ -20,7 +20,7 @@ import { Lexer } from 'chevrotain'; import { NAME, UNARY_OPTION, BINARY_OPTION } from './shared-tokens.js'; -import { relationshipOptions } from '../../jhipster/index.mjs'; +import { relationshipOptions } from '../../jhipster/index.js'; import ValidationTokens from './validation-tokens.js'; import ApplicationTokens from './application-tokens.js'; diff --git a/jdl/parsing/validator.ts b/jdl/parsing/validator.ts index 1c8e8738ea01..2540fd074ea8 100644 --- a/jdl/parsing/validator.ts +++ b/jdl/parsing/validator.ts @@ -24,7 +24,7 @@ import { tokenMatcher as matchesToken } from 'chevrotain'; import JDLParser from './jdl-parser.js'; import { tokens as LexerTokens } from './lexer/lexer.js'; import { checkConfigKeys } from './self-checks/parsing-system-checker.js'; -import jhipsterDefinition from '../../generators/app/jdl/index.mjs'; +import jhipsterDefinition from '../../generators/app/jdl/index.js'; const CONSTANT_PATTERN = /^[A-Z_]+$/; const ENTITY_NAME_PATTERN = /^[A-Z][A-Za-z0-9]*$/; diff --git a/jdl/readers/file-reader.spec.ts b/jdl/readers/file-reader.spec.ts index 30ac5c0784ab..be3722610147 100644 --- a/jdl/readers/file-reader.spec.ts +++ b/jdl/readers/file-reader.spec.ts @@ -21,7 +21,7 @@ import fs from 'fs'; import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import { readFile, readFiles } from '../readers/file-reader.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; describe('jdl - FileReader', () => { beforeEach(async () => { diff --git a/jdl/readers/jdl-reader.spec.ts b/jdl/readers/jdl-reader.spec.ts index f64979d14a10..a9c71c978b81 100644 --- a/jdl/readers/jdl-reader.spec.ts +++ b/jdl/readers/jdl-reader.spec.ts @@ -24,7 +24,7 @@ import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import { expect } from 'chai'; import * as JDLReader from '../readers/jdl-reader.js'; -import { basicHelpers as helpers } from '../../test/support/index.mjs'; +import { basicHelpers as helpers } from '../../test/support/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/jdl/readers/json-reader.spec.ts b/jdl/readers/json-reader.spec.ts index e330a1648132..fbc18fa47705 100644 --- a/jdl/readers/json-reader.spec.ts +++ b/jdl/readers/json-reader.spec.ts @@ -24,7 +24,7 @@ import { fileURLToPath } from 'url'; import { expect } from 'chai'; import parseFromDir from '../readers/json-reader.js'; -import { unaryOptions } from '../jhipster/index.mjs'; +import { unaryOptions } from '../jhipster/index.js'; const { SKIP_CLIENT, SKIP_SERVER } = unaryOptions; diff --git a/jdl/types/types.d.mts b/jdl/types/types.d.ts similarity index 100% rename from jdl/types/types.d.mts rename to jdl/types/types.d.ts diff --git a/jdl/validators/deployment-validator.spec.ts b/jdl/validators/deployment-validator.spec.ts index 2bcbbf454e47..73614fc04776 100644 --- a/jdl/validators/deployment-validator.spec.ts +++ b/jdl/validators/deployment-validator.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'chai'; -import { deploymentOptions, applicationTypes, databaseTypes, searchEngineTypes } from '../jhipster/index.mjs'; +import { deploymentOptions, applicationTypes, databaseTypes, searchEngineTypes } from '../jhipster/index.js'; import DeploymentValidator from '../validators/deployment-validator.js'; diff --git a/jdl/validators/deployment-validator.ts b/jdl/validators/deployment-validator.ts index 4e78546bb222..2226c9b356fe 100644 --- a/jdl/validators/deployment-validator.ts +++ b/jdl/validators/deployment-validator.ts @@ -18,7 +18,7 @@ */ import Validator from './validator.js'; -import { applicationTypes, databaseTypes, searchEngineTypes, deploymentOptions, applicationOptions } from '../jhipster/index.mjs'; +import { applicationTypes, databaseTypes, searchEngineTypes, deploymentOptions, applicationOptions } from '../jhipster/index.js'; const { Options } = deploymentOptions; const { MICROSERVICE } = applicationTypes; diff --git a/jdl/validators/entity-validator.spec.ts b/jdl/validators/entity-validator.spec.ts index 4d47ccae944e..df1c2e754815 100644 --- a/jdl/validators/entity-validator.spec.ts +++ b/jdl/validators/entity-validator.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'chai'; -import { JDLEntity } from '../models/index.mjs'; +import { JDLEntity } from '../models/index.js'; import EntityValidator from '../validators/entity-validator.js'; describe('jdl - EntityValidator', () => { diff --git a/jdl/validators/entity-validator.ts b/jdl/validators/entity-validator.ts index 3b4995b30d2d..58ed0f98e98f 100644 --- a/jdl/validators/entity-validator.ts +++ b/jdl/validators/entity-validator.ts @@ -18,7 +18,7 @@ */ import Validator, { ValidatorOptions } from './validator.js'; -import { reservedKeywords } from '../jhipster/index.mjs'; +import { reservedKeywords } from '../jhipster/index.js'; const { isReservedClassName } = reservedKeywords; diff --git a/jdl/validators/enum-validator.spec.ts b/jdl/validators/enum-validator.spec.ts index e28730af20af..7126fdfd78c7 100644 --- a/jdl/validators/enum-validator.spec.ts +++ b/jdl/validators/enum-validator.spec.ts @@ -18,7 +18,7 @@ */ import { expect } from 'chai'; -import { JDLEnum } from '../models/index.mjs'; +import { JDLEnum } from '../models/index.js'; import EnumValidator from '../validators/enum-validator.js'; describe('jdl - EnumValidator', () => { diff --git a/jdl/validators/enum-validator.ts b/jdl/validators/enum-validator.ts index a263f17bd4bd..3ee6632d0365 100644 --- a/jdl/validators/enum-validator.ts +++ b/jdl/validators/enum-validator.ts @@ -18,7 +18,7 @@ */ import Validator, { ValidatorOptions } from './validator.js'; -import { reservedKeywords } from '../jhipster/index.mjs'; +import { reservedKeywords } from '../jhipster/index.js'; const { isReservedClassName } = reservedKeywords; export default class EnumValidator extends Validator { diff --git a/jdl/validators/jdl-with-application-validator.spec.ts b/jdl/validators/jdl-with-application-validator.spec.ts index 99dd77af660c..6cdd2fdc8428 100644 --- a/jdl/validators/jdl-with-application-validator.spec.ts +++ b/jdl/validators/jdl-with-application-validator.spec.ts @@ -19,11 +19,11 @@ import { expect } from 'chai'; -import { applicationTypes, binaryOptions, databaseTypes, fieldTypes, relationshipTypes, validations } from '../jhipster/index.mjs'; +import { applicationTypes, binaryOptions, databaseTypes, fieldTypes, relationshipTypes, validations } from '../jhipster/index.js'; import JDLObject from '../models/jdl-object.js'; import createJDLApplication from '../models/jdl-application-factory.js'; import JDLBinaryOption from '../models/jdl-binary-option.js'; -import { JDLEntity } from '../models/index.mjs'; +import { JDLEntity } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLRelationship from '../models/jdl-relationship.js'; import JDLValidation from '../models/jdl-validation.js'; diff --git a/jdl/validators/jdl-with-application-validator.ts b/jdl/validators/jdl-with-application-validator.ts index 2f53d70adc78..bedc11e608d6 100644 --- a/jdl/validators/jdl-with-application-validator.ts +++ b/jdl/validators/jdl-with-application-validator.ts @@ -19,7 +19,7 @@ import EntityValidator from './entity-validator.js'; import FieldValidator from './field-validator.js'; -import { fieldTypes, applicationOptions, relationshipOptions } from '../jhipster/index.mjs'; +import { fieldTypes, applicationOptions, relationshipOptions } from '../jhipster/index.js'; import ValidationValidator from './validation-validator.js'; import RelationshipValidator from './relationship-validator.js'; import EnumValidator from './enum-validator.js'; diff --git a/jdl/validators/jdl-without-application-validator.spec.ts b/jdl/validators/jdl-without-application-validator.spec.ts index 0afc709e75d6..d8efbc6b1b75 100644 --- a/jdl/validators/jdl-without-application-validator.spec.ts +++ b/jdl/validators/jdl-without-application-validator.spec.ts @@ -19,12 +19,12 @@ import { expect } from 'chai'; import JDLObject from '../models/jdl-object.js'; -import { JDLEntity } from '../models/index.mjs'; +import { JDLEntity } from '../models/index.js'; import JDLField from '../models/jdl-field.js'; import JDLValidation from '../models/jdl-validation.js'; import JDLRelationship from '../models/jdl-relationship.js'; import JDLBinaryOption from '../models/jdl-binary-option.js'; -import { applicationTypes, databaseTypes, fieldTypes, validations, relationshipTypes, binaryOptions } from '../jhipster/index.mjs'; +import { applicationTypes, databaseTypes, fieldTypes, validations, relationshipTypes, binaryOptions } from '../jhipster/index.js'; import createValidator from '../validators/jdl-without-application-validator.js'; const { GATEWAY } = applicationTypes; diff --git a/jdl/validators/jdl-without-application-validator.ts b/jdl/validators/jdl-without-application-validator.ts index 822613b8b88c..69efa6e85758 100644 --- a/jdl/validators/jdl-without-application-validator.ts +++ b/jdl/validators/jdl-without-application-validator.ts @@ -19,7 +19,7 @@ import EntityValidator from './entity-validator.js'; import FieldValidator from './field-validator.js'; -import { fieldTypes, applicationTypes, databaseTypes, binaryOptions, relationshipOptions } from '../jhipster/index.mjs'; +import { fieldTypes, applicationTypes, databaseTypes, binaryOptions, relationshipOptions } from '../jhipster/index.js'; import ValidationValidator from './validation-validator.js'; import RelationshipValidator from './relationship-validator.js'; import EnumValidator from './enum-validator.js'; diff --git a/jdl/validators/relationship-validator.spec.ts b/jdl/validators/relationship-validator.spec.ts index 69d6a0546e69..69ed479e151b 100644 --- a/jdl/validators/relationship-validator.spec.ts +++ b/jdl/validators/relationship-validator.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import JDLRelationship from '../models/jdl-relationship.js'; import RelationshipValidator from '../validators/relationship-validator.js'; -import { relationshipOptions, relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipOptions, relationshipTypes } from '../jhipster/index.js'; const { BUILT_IN_ENTITY } = relationshipOptions; const { ONE_TO_ONE, MANY_TO_MANY, MANY_TO_ONE, ONE_TO_MANY } = relationshipTypes; diff --git a/jdl/validators/relationship-validator.ts b/jdl/validators/relationship-validator.ts index 19d7c7732004..12c2ddce600a 100644 --- a/jdl/validators/relationship-validator.ts +++ b/jdl/validators/relationship-validator.ts @@ -18,7 +18,7 @@ */ import Validator from './validator.js'; -import { relationshipTypes } from '../jhipster/index.mjs'; +import { relationshipTypes } from '../jhipster/index.js'; import JDLRelationship from '../models/jdl-relationship.js'; import { relationshipTypeExists } from '../jhipster/relationship-types.js'; diff --git a/jdl/validators/validation-validator.ts b/jdl/validators/validation-validator.ts index 77b1529a8a2f..af1448c64af2 100644 --- a/jdl/validators/validation-validator.ts +++ b/jdl/validators/validation-validator.ts @@ -18,7 +18,7 @@ */ import Validator from './validator.js'; -import { validations } from '../jhipster/index.mjs'; +import { validations } from '../jhipster/index.js'; const { Validations: { exists, needsValue, MINLENGTH, MAXLENGTH, MAXBYTES, MINBYTES }, diff --git a/lib/index.mts b/lib/index.ts similarity index 100% rename from lib/index.mts rename to lib/index.ts diff --git a/lib/internal/config-def.mts b/lib/internal/config-def.mts deleted file mode 100644 index 76ece69887ed..000000000000 --- a/lib/internal/config-def.mts +++ /dev/null @@ -1,62 +0,0 @@ -import type { ConfigSpec, JHipsterConfigs, JHipsterOption } from '../../generators/base/api.mjs'; -import type CoreGenerator from '../../generators/base-core/index.mjs'; -import { upperFirstCamelCase } from '../../generators/base/support/string.mjs'; - -export const convertConfigToOption = (name: string, config?: ConfigSpec): JHipsterOption | undefined => { - if (!config?.cli?.type) return undefined; - const choices = config.choices?.map(choice => (typeof choice === 'string' ? choice : choice.value)) as any; - return { - name, - description: config.description, - choices, - scope: config.scope ?? 'storage', - ...(config.prompt && typeof config.default !== 'function' ? {} : { default: config.default }), - ...config.cli, - }; -}; - -export function loadConfig( - this: CoreGenerator | void, - configsDef: JHipsterConfigs | undefined, - { application, config }: { application: any; config?: any }, -) { - if (configsDef) { - for (const [name, def] of Object.entries(configsDef)) { - let value = application[name]; - if (value === undefined || value === null) { - let source = config; - if (!source) { - if (def.scope === 'generator') { - // eslint-disable-next-line @typescript-eslint/no-this-alias - source = this; - } else if (def.scope === 'blueprint') { - source = (this as any).blueprintStorage.getAll(); - } else { - source = (this as any).jhipsterConfigWithDefaults; - } - } - - value = application[name] = source[name] ?? undefined; - if (value === undefined && def.default) { - application[name] = typeof def.default === 'function' ? def.default(source) : def.default; - } - } - } - } -} - -export const loadDerivedConfig = (configsDef: JHipsterConfigs | undefined, { application }) => { - if (configsDef) { - for (const [name, def] of Object.entries(configsDef)) { - if (def.choices) { - const configVal = application[name]; - for (const choice of def.choices) { - const choiceVal = typeof choice === 'string' ? choice : choice.value; - const prop = `${name}${upperFirstCamelCase(choiceVal)}`; - application[prop] = application[prop] ?? ([].concat(configVal) as any).includes(choiceVal); - } - application[`${name}Any`] = application[`${name}Any`] ?? !application[`${name}No`]; - } - } - } -}; diff --git a/lib/internal/config-def.ts b/lib/internal/config-def.ts new file mode 100644 index 000000000000..edd380c29638 --- /dev/null +++ b/lib/internal/config-def.ts @@ -0,0 +1,62 @@ +import type { ConfigSpec, JHipsterConfigs, JHipsterOption } from '../../generators/base/api.js'; +import type CoreGenerator from '../../generators/base-core/index.js'; +import { upperFirstCamelCase } from '../../generators/base/support/string.js'; + +export const convertConfigToOption = (name: string, config?: ConfigSpec): JHipsterOption | undefined => { + if (!config?.cli?.type) return undefined; + const choices = config.choices?.map(choice => (typeof choice === 'string' ? choice : choice.value)) as any; + return { + name, + description: config.description, + choices, + scope: config.scope ?? 'storage', + ...(config.prompt && typeof config.default !== 'function' ? {} : { default: config.default }), + ...config.cli, + }; +}; + +export function loadConfig( + this: CoreGenerator | void, + configsDef: JHipsterConfigs | undefined, + { application, config }: { application: any; config?: any }, +) { + if (configsDef) { + for (const [name, def] of Object.entries(configsDef)) { + let value = application[name]; + if (value === undefined || value === null) { + let source = config; + if (!source) { + if (def.scope === 'generator') { + // eslint-disable-next-line @typescript-eslint/no-this-alias + source = this; + } else if (def.scope === 'blueprint') { + source = (this as any).blueprintStorage.getAll(); + } else { + source = (this as any).jhipsterConfigWithDefaults; + } + } + + value = application[name] = source[name] ?? undefined; + if (value === undefined && def.default) { + application[name] = typeof def.default === 'function' ? def.default(source) : def.default; + } + } + } + } +} + +export const loadDerivedConfig = (configsDef: JHipsterConfigs | undefined, { application }) => { + if (configsDef) { + for (const [name, def] of Object.entries(configsDef)) { + if (def.choices) { + const configVal = application[name]; + for (const choice of def.choices) { + const choiceVal = typeof choice === 'string' ? choice : choice.value; + const prop = `${name}${upperFirstCamelCase(choiceVal)}`; + application[prop] = application[prop] ?? ([].concat(configVal) as any).includes(choiceVal); + } + application[`${name}Any`] = application[`${name}Any`] ?? !application[`${name}No`]; + } + } + } +}; diff --git a/lib/internal/index.mts b/lib/internal/index.mts deleted file mode 100644 index f77c772d620e..000000000000 --- a/lib/internal/index.mts +++ /dev/null @@ -1 +0,0 @@ -export * from './config-def.mjs'; diff --git a/lib/internal/index.ts b/lib/internal/index.ts new file mode 100644 index 000000000000..fcb8b45e3e41 --- /dev/null +++ b/lib/internal/index.ts @@ -0,0 +1 @@ +export * from './config-def.js'; diff --git a/package.json b/package.json index a2c102d5ea9a..64b16041889a 100644 --- a/package.json +++ b/package.json @@ -46,24 +46,24 @@ }, "./package.json": "./package.json", "./generators": { - "types": "./dist/types/generators/generator-list.d.mts", - "default": "./dist/generators/generator-list.mjs" + "types": "./dist/types/generators/generator-list.d.ts", + "default": "./dist/generators/generator-list.js" }, "./generators/*": { "types": "./dist/types/generators/*/types-export.d.ts", - "default": "./dist/generators/*/index.mjs" + "default": "./dist/generators/*/index.js" }, "./generators/*/support": { - "types": "./dist/types/generators/*/support/index.d.mts", - "default": "./dist/generators/*/support/index.mjs" + "types": "./dist/types/generators/*/support/index.d.ts", + "default": "./dist/generators/*/support/index.js" }, "./jdl": { "types": "./dist/types/jdl/index.d.ts", "default": "./dist/jdl/index.js" }, "./testing": { - "types": "./dist/types/testing/index.d.mts", - "default": "./dist/testing/index.mjs" + "types": "./dist/types/testing/index.d.ts", + "default": "./dist/testing/index.js" } }, "main": "./dist/generators/index.js", diff --git a/test-integration/scripts/00-init-env.sh b/test-integration/scripts/00-init-env.sh index 3064abdd89e6..0370278bd2fb 100755 --- a/test-integration/scripts/00-init-env.sh +++ b/test-integration/scripts/00-init-env.sh @@ -79,7 +79,7 @@ fi # jdk version if [[ "$JHI_JDK" == "" ]]; then - JHI_JDK=$(grep -o "JAVA_VERSION = '[^']*'" $JHI_HOME/generators/generator-constants.mjs | cut -f2 -d "'") + JHI_JDK=$(grep -o "JAVA_VERSION = '[^']*'" $JHI_HOME/generators/generator-constants.js | cut -f2 -d "'") fi # set correct OpenJDK version @@ -92,7 +92,7 @@ if [[ "$JHI_CLI" == "" ]]; then fi # node version -JHI_NODE_VERSION=$(grep -o "NODE_VERSION = '[^']*'" $JHI_HOME/generators/generator-constants.mjs | cut -f2 -d "'") +JHI_NODE_VERSION=$(grep -o "NODE_VERSION = '[^']*'" $JHI_HOME/generators/generator-constants.js | cut -f2 -d "'") # npm version JHI_NPM_VERSION=$(grep -o '"npm": "[^"]*"' $JHI_HOME/generators/common/resources/package.json | cut -f4 -d '"') diff --git a/test-integration/scripts/99-write-matrix.js b/test-integration/scripts/99-write-matrix.js index 53326df8f255..6f90b1810565 100755 --- a/test-integration/scripts/99-write-matrix.js +++ b/test-integration/scripts/99-write-matrix.js @@ -2,7 +2,7 @@ import { writeFileSync, readFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; -import { JAVA_VERSION, NODE_VERSION } from '../../generators/generator-constants.mjs'; +import { JAVA_VERSION, NODE_VERSION } from '../../generators/generator-constants.js'; const __filename = fileURLToPath(import.meta.url); const packageRoot = join(dirname(__filename), '../..'); diff --git a/test/__snapshots__/api.spec.mjs.snap b/test/__snapshots__/api.spec.js.snap similarity index 100% rename from test/__snapshots__/api.spec.mjs.snap rename to test/__snapshots__/api.spec.js.snap diff --git a/test/api.spec.mjs b/test/api.spec.js similarity index 100% rename from test/api.spec.mjs rename to test/api.spec.js diff --git a/test/integration-test.spec.mts b/test/integration-test.spec.mts deleted file mode 100644 index c5c486dfb8a5..000000000000 --- a/test/integration-test.spec.mts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright 2013-2023 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 assert from 'assert'; -import fs from 'fs'; -import path, { dirname } from 'path'; -import { fileURLToPath } from 'url'; -import fse from 'fs-extra'; -import sortKeys from 'sort-keys'; - -import { applicationTypes, authenticationTypes } from '../jdl/jhipster/index.mjs'; -import { formatDateForChangelog } from '../generators/base/support/index.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const { JWT, SESSION } = authenticationTypes; -const { GATEWAY, MICROSERVICE } = applicationTypes; - -const fixSamples = process.argv.includes('--fix-samples'); -const itSamplesPath = path.join(__dirname, '..', 'test-integration', 'samples'); -const dailyBuildsSamplesPath = path.join(__dirname, '..', 'test-integration', 'daily-builds'); -const itEntitiesSamplesPath = path.join(__dirname, '..', 'test-integration', 'samples', '.jhipster'); -const REMEMBER_ME_KEY = 'a5e93fdeb16e2ee2dc4a629b5dbdabb30f968e418dfc0483c53afdc695cfac96d06cf5c581cbefb93e3aaa241880857fcafe'; -const JWT_SECRET_KEY = - 'ZjY4MTM4YjI5YzMwZjhjYjI2OTNkNTRjMWQ5Y2Q0Y2YwOWNmZTE2NzRmYzU3NTMwM2NjOTE3MTllOTM3MWRkMzcyYTljMjVmNmQ0Y2MxOTUzODc0MDhhMTlkMDIxMzI2YzQzZDM2ZDE3MmQ3NjVkODk3OTVmYzljYTQyZDNmMTQ='; - -const itSamplesEntries = fs - .readdirSync(itSamplesPath, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory()) - .map(({ name }) => name) - .map(name => [name, path.join(itSamplesPath, name, '.yo-rc.json')]) - .filter(([_name, yoFile]) => fs.existsSync(yoFile)); -const dailyBuildEntries = fs - .readdirSync(dailyBuildsSamplesPath, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory()) - .map(({ name }) => name) - .map(name => [name, path.join(dailyBuildsSamplesPath, name, '.yo-rc.json')]) - .filter(([_name, yoFile]) => fs.existsSync(yoFile)); - -const itEntitiesSamplesEntries = fs - .readdirSync(itEntitiesSamplesPath, { withFileTypes: true }) - .filter(dirent => dirent.isFile()) - .map(({ name }) => name) - .map(name => [name, path.join(itEntitiesSamplesPath, name)]); - -describe('integration-test', () => { - describe('::application samples', () => { - for (const [name, yoFile] of [...itSamplesEntries, ...dailyBuildEntries]) { - let yoJson = fse.readJsonSync(yoFile); - const writeConfig = () => fse.writeJsonSync(yoFile, yoJson); - const config = yoJson['generator-jhipster']; - describe(`${name} test`, () => { - before(() => { - if (fixSamples) { - if (!config.creationTimestamp) { - config.creationTimestamp = 1596513172471; - fse.writeJsonSync(yoFile, yoJson); - } - if (config.authenticationType === SESSION && config.rememberMeKey !== REMEMBER_ME_KEY) { - config.rememberMeKey = REMEMBER_ME_KEY; - fse.writeJsonSync(yoFile, yoJson); - } else if ( - (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) && - config.jwtSecretKey !== JWT_SECRET_KEY - ) { - config.jwtSecretKey = JWT_SECRET_KEY; - fse.writeJsonSync(yoFile, yoJson); - } - const yoJsonOrdered = sortKeys(yoJson, { deep: true }); - if (JSON.stringify(yoJson) !== JSON.stringify(yoJsonOrdered)) { - fse.writeJsonSync(yoFile, yoJsonOrdered); - yoJson = yoJsonOrdered; - } - } - }); - it('should contain creationTimestamp', () => { - assert(config.creationTimestamp); - }); - if (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) { - it('should contain jwtSecretKey', () => { - assert(config.jwtSecretKey); - }); - } else if (config.authenticationType === SESSION) { - it('should contain rememberMeKey', () => { - assert(config.rememberMeKey); - }); - } - it('should be ordered', () => { - assert(JSON.stringify(yoJson) === JSON.stringify(sortKeys(yoJson, { deep: true }))); - }); - it('should have matching skipClient/clientFrameworkNo', () => { - const clientFrameworkNo = config.clientFramework === 'no'; - const clientFrameworkAny = config.clientFramework && config.clientFramework !== 'no'; - if (clientFrameworkAny && config.skipClient) { - if (fixSamples) { - if (config.microfrontend) { - delete config.skipClient; - } else { - delete config.clientFramework; - } - writeConfig(); - } else { - throw new Error('Conflict'); - } - } - if (clientFrameworkNo) { - if (config.skipClient === false) { - if (fixSamples) { - delete config.skipClient; - writeConfig(); - } else { - throw new Error('Conflict'); - } - } - if (config.microfrontend) { - if (fixSamples) { - delete config.microfrontend; - writeConfig(); - } else { - throw new Error('Conflict'); - } - } - } - }); - it('cypress should not added to skipClient and clientFrameworkNo', () => { - if (config.skipClient || config.clientFramework === 'no') { - const includesCypress = config.testFrameworks?.includes('cypress'); - if (fixSamples && includesCypress) { - config.testFrameworks = config.testFrameworks.filter(test => test !== 'cypress'); - writeConfig(); - } else { - assert(!includesCypress); - } - } - }); - }); - } - }); - - describe('::entities samples reproducibility', () => { - const changelogDates = []; - for (const [name, entitySample] of itEntitiesSamplesEntries) { - let entityJson = fse.readJsonSync(entitySample); - before(() => { - if (fixSamples) { - const entityJsonOrdered = sortKeys(entityJson, { deep: true }); - if (JSON.stringify(entityJson) !== JSON.stringify(entityJsonOrdered)) { - fse.writeJsonSync(entitySample, entityJsonOrdered); - entityJson = entityJsonOrdered; - } - } - }); - it(`${name} contains changelogDate`, () => { - if (fixSamples) { - if (!entityJson.changelogDate) { - entityJson.changelogDate = formatDateForChangelog(new Date()); - fse.writeJsonSync(entitySample, entityJson); - } - } - assert(entityJson.changelogDate); - }); - it(`${name} does not contains duplicate changelogDate`, () => { - if (fixSamples) { - while (changelogDates.includes(entityJson.changelogDate)) { - entityJson.changelogDate = formatDateForChangelog(new Date()); - fse.writeJsonSync(entitySample, entityJson); - } - } - assert(!changelogDates.includes(entityJson.changelogDate)); - changelogDates.push(entityJson.changelogDate); - }); - it(`${name} should be ordered`, () => { - assert.strictEqual(JSON.stringify(entityJson), JSON.stringify(sortKeys(entityJson, { deep: true }))); - }); - } - }); -}); diff --git a/test/integration-test.spec.ts b/test/integration-test.spec.ts new file mode 100644 index 000000000000..5f1af2e37511 --- /dev/null +++ b/test/integration-test.spec.ts @@ -0,0 +1,193 @@ +/** + * Copyright 2013-2023 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 assert from 'assert'; +import fs from 'fs'; +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import fse from 'fs-extra'; +import sortKeys from 'sort-keys'; + +import { applicationTypes, authenticationTypes } from '../jdl/jhipster/index.js'; +import { formatDateForChangelog } from '../generators/base/support/index.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const { JWT, SESSION } = authenticationTypes; +const { GATEWAY, MICROSERVICE } = applicationTypes; + +const fixSamples = process.argv.includes('--fix-samples'); +const itSamplesPath = path.join(__dirname, '..', 'test-integration', 'samples'); +const dailyBuildsSamplesPath = path.join(__dirname, '..', 'test-integration', 'daily-builds'); +const itEntitiesSamplesPath = path.join(__dirname, '..', 'test-integration', 'samples', '.jhipster'); +const REMEMBER_ME_KEY = 'a5e93fdeb16e2ee2dc4a629b5dbdabb30f968e418dfc0483c53afdc695cfac96d06cf5c581cbefb93e3aaa241880857fcafe'; +const JWT_SECRET_KEY = + 'ZjY4MTM4YjI5YzMwZjhjYjI2OTNkNTRjMWQ5Y2Q0Y2YwOWNmZTE2NzRmYzU3NTMwM2NjOTE3MTllOTM3MWRkMzcyYTljMjVmNmQ0Y2MxOTUzODc0MDhhMTlkMDIxMzI2YzQzZDM2ZDE3MmQ3NjVkODk3OTVmYzljYTQyZDNmMTQ='; + +const itSamplesEntries = fs + .readdirSync(itSamplesPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(({ name }) => name) + .map(name => [name, path.join(itSamplesPath, name, '.yo-rc.json')]) + .filter(([_name, yoFile]) => fs.existsSync(yoFile)); +const dailyBuildEntries = fs + .readdirSync(dailyBuildsSamplesPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(({ name }) => name) + .map(name => [name, path.join(dailyBuildsSamplesPath, name, '.yo-rc.json')]) + .filter(([_name, yoFile]) => fs.existsSync(yoFile)); + +const itEntitiesSamplesEntries = fs + .readdirSync(itEntitiesSamplesPath, { withFileTypes: true }) + .filter(dirent => dirent.isFile()) + .map(({ name }) => name) + .map(name => [name, path.join(itEntitiesSamplesPath, name)]); + +describe('integration-test', () => { + describe('::application samples', () => { + for (const [name, yoFile] of [...itSamplesEntries, ...dailyBuildEntries]) { + let yoJson = fse.readJsonSync(yoFile); + const writeConfig = () => fse.writeJsonSync(yoFile, yoJson); + const config = yoJson['generator-jhipster']; + describe(`${name} test`, () => { + before(() => { + if (fixSamples) { + if (!config.creationTimestamp) { + config.creationTimestamp = 1596513172471; + fse.writeJsonSync(yoFile, yoJson); + } + if (config.authenticationType === SESSION && config.rememberMeKey !== REMEMBER_ME_KEY) { + config.rememberMeKey = REMEMBER_ME_KEY; + fse.writeJsonSync(yoFile, yoJson); + } else if ( + (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) && + config.jwtSecretKey !== JWT_SECRET_KEY + ) { + config.jwtSecretKey = JWT_SECRET_KEY; + fse.writeJsonSync(yoFile, yoJson); + } + const yoJsonOrdered = sortKeys(yoJson, { deep: true }); + if (JSON.stringify(yoJson) !== JSON.stringify(yoJsonOrdered)) { + fse.writeJsonSync(yoFile, yoJsonOrdered); + yoJson = yoJsonOrdered; + } + } + }); + it('should contain creationTimestamp', () => { + assert(config.creationTimestamp); + }); + if (config.authenticationType === JWT || config.applicationType === MICROSERVICE || config.applicationType === GATEWAY) { + it('should contain jwtSecretKey', () => { + assert(config.jwtSecretKey); + }); + } else if (config.authenticationType === SESSION) { + it('should contain rememberMeKey', () => { + assert(config.rememberMeKey); + }); + } + it('should be ordered', () => { + assert(JSON.stringify(yoJson) === JSON.stringify(sortKeys(yoJson, { deep: true }))); + }); + it('should have matching skipClient/clientFrameworkNo', () => { + const clientFrameworkNo = config.clientFramework === 'no'; + const clientFrameworkAny = config.clientFramework && config.clientFramework !== 'no'; + if (clientFrameworkAny && config.skipClient) { + if (fixSamples) { + if (config.microfrontend) { + delete config.skipClient; + } else { + delete config.clientFramework; + } + writeConfig(); + } else { + throw new Error('Conflict'); + } + } + if (clientFrameworkNo) { + if (config.skipClient === false) { + if (fixSamples) { + delete config.skipClient; + writeConfig(); + } else { + throw new Error('Conflict'); + } + } + if (config.microfrontend) { + if (fixSamples) { + delete config.microfrontend; + writeConfig(); + } else { + throw new Error('Conflict'); + } + } + } + }); + it('cypress should not added to skipClient and clientFrameworkNo', () => { + if (config.skipClient || config.clientFramework === 'no') { + const includesCypress = config.testFrameworks?.includes('cypress'); + if (fixSamples && includesCypress) { + config.testFrameworks = config.testFrameworks.filter(test => test !== 'cypress'); + writeConfig(); + } else { + assert(!includesCypress); + } + } + }); + }); + } + }); + + describe('::entities samples reproducibility', () => { + const changelogDates = []; + for (const [name, entitySample] of itEntitiesSamplesEntries) { + let entityJson = fse.readJsonSync(entitySample); + before(() => { + if (fixSamples) { + const entityJsonOrdered = sortKeys(entityJson, { deep: true }); + if (JSON.stringify(entityJson) !== JSON.stringify(entityJsonOrdered)) { + fse.writeJsonSync(entitySample, entityJsonOrdered); + entityJson = entityJsonOrdered; + } + } + }); + it(`${name} contains changelogDate`, () => { + if (fixSamples) { + if (!entityJson.changelogDate) { + entityJson.changelogDate = formatDateForChangelog(new Date()); + fse.writeJsonSync(entitySample, entityJson); + } + } + assert(entityJson.changelogDate); + }); + it(`${name} does not contains duplicate changelogDate`, () => { + if (fixSamples) { + while (changelogDates.includes(entityJson.changelogDate)) { + entityJson.changelogDate = formatDateForChangelog(new Date()); + fse.writeJsonSync(entitySample, entityJson); + } + } + assert(!changelogDates.includes(entityJson.changelogDate)); + changelogDates.push(entityJson.changelogDate); + }); + it(`${name} should be ordered`, () => { + assert.strictEqual(JSON.stringify(entityJson), JSON.stringify(sortKeys(entityJson, { deep: true }))); + }); + } + }); +}); diff --git a/test/needle-api/needle-base.spec.mts b/test/needle-api/needle-base.spec.mts deleted file mode 100644 index 86ba6e345eef..000000000000 --- a/test/needle-api/needle-base.spec.mts +++ /dev/null @@ -1,34 +0,0 @@ -import { expect } from 'esmocha'; -import NeedleApiBase from '../../generators/needle-base.mjs'; - -describe('needle-api - base', () => { - let needleApiBase; - before(() => { - needleApiBase = new NeedleApiBase(); - }); - describe('generate a file model without path', () => { - let generatedModel; - before(() => { - generatedModel = needleApiBase.generateFileModel('dummyFile', 'a-needle-tag', '

    My content added

    '); - }); - it('creates expected default files for server and angular', () => { - expect(generatedModel.file).toBe('dummyFile'); - expect(generatedModel.needle).toBe('a-needle-tag'); - expect(generatedModel.splicable).toEqual(['

    My content added

    ']); - }); - }); - - describe('generate a file model with a path', () => { - let generatedModel; - before(() => { - generatedModel = needleApiBase.generateFileModelWithPath('aPath', 'dummyFile', 'a-needle-tag', '

    My content added

    '); - }); - - it('creates expected default files for server and angular', () => { - expect(generatedModel.path).toBe('aPath'); - expect(generatedModel.file).toBe('dummyFile'); - expect(generatedModel.needle).toBe('a-needle-tag'); - expect(generatedModel.splicable).toEqual(new Array('

    My content added

    ')); - }); - }); -}); diff --git a/test/needle-api/needle-base.spec.ts b/test/needle-api/needle-base.spec.ts new file mode 100644 index 000000000000..57dadc562fa0 --- /dev/null +++ b/test/needle-api/needle-base.spec.ts @@ -0,0 +1,34 @@ +import { expect } from 'esmocha'; +import NeedleApiBase from '../../generators/needle-base.js'; + +describe('needle-api - base', () => { + let needleApiBase; + before(() => { + needleApiBase = new NeedleApiBase(); + }); + describe('generate a file model without path', () => { + let generatedModel; + before(() => { + generatedModel = needleApiBase.generateFileModel('dummyFile', 'a-needle-tag', '

    My content added

    '); + }); + it('creates expected default files for server and angular', () => { + expect(generatedModel.file).toBe('dummyFile'); + expect(generatedModel.needle).toBe('a-needle-tag'); + expect(generatedModel.splicable).toEqual(['

    My content added

    ']); + }); + }); + + describe('generate a file model with a path', () => { + let generatedModel; + before(() => { + generatedModel = needleApiBase.generateFileModelWithPath('aPath', 'dummyFile', 'a-needle-tag', '

    My content added

    '); + }); + + it('creates expected default files for server and angular', () => { + expect(generatedModel.path).toBe('aPath'); + expect(generatedModel.file).toBe('dummyFile'); + expect(generatedModel.needle).toBe('a-needle-tag'); + expect(generatedModel.splicable).toEqual(new Array('

    My content added

    ')); + }); + }); +}); diff --git a/test/needle-api/needle-client-angular-generator.spec.mts b/test/needle-api/needle-client-angular-generator.spec.mts deleted file mode 100644 index 66cde98bd7b0..000000000000 --- a/test/needle-api/needle-client-angular-generator.spec.mts +++ /dev/null @@ -1,105 +0,0 @@ -import { defaultHelpers as helpers, runResult } from '../support/index.mjs'; - -import AngularGenerator from '../../generators/angular/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.mjs'; - -import BaseApplicationGenerator from '../../generators/base-application/index.mjs'; - -const mockAngularBlueprintSubGen = class extends AngularGenerator { - constructor(args, opts, features) { - super(args, opts, { ...features, sbsBlueprint: true }); - } - - get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { - return this.asPostWritingEntitiesTaskGroup({ - addToMenuStep() { - this.addElementToAdminMenu('routerName2', 'iconName2', true); - }, - addToModuleStep({ application, source }) { - source.addEntitiesToClient({ - application, - entities: [ - { - name: 'entityName', - entityInstance: 'entityInstance', - entityClass: 'entityClass', - entityFolderName: 'entityFolderName', - entityFileName: 'entityFileName', - entityUrl: 'entityUrl', - i18nKeyPrefix: 'entity', - entityPage: 'entityPage', - entityTranslationKeyMenu: 'entityTranslationKeyMenu', - entityClassHumanized: 'entityClassHumanized', - } as any, - ], - }); - }, - }); - } -}; - -describe('needle API Angular angular generator : JHipster with blueprint', () => { - before(async () => { - await helpers - .runJHipster('angular') - .withJHipsterConfig({ - skipServer: true, - }) - .withOptions({ - blueprint: 'myblueprint2', - }) - .withGenerators([[mockAngularBlueprintSubGen, { namespace: 'jhipster-myblueprint2:angular' }]]); - }); - - it('entity menu contains the entity added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, - ` -
  • - - - entityClassHumanized - -
  • -`, - ); - }); - - it('admin menu contains the admin element added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, - ` -
  • - - - Router Name 2 - -
  • -`, - ); - }); - - it('icon imports contains a new icon added by a new admin menu method of needle api ', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/config/font-awesome-icons.ts`, ' faIconName2'); - }); - - it('entity module contains the microservice object added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/entities/entity.routes.ts`, - ' {\n' + - " path: 'entityUrl',\n" + - " data: { pageTitle: 'entity.home.title' },\n" + - " loadChildren: () => import('./entityFolderName/entityFileName.routes'),\n" + - ' }', - ); - }); - it('should bail on any file change adding same needles again', async () => { - await runResult - .create('jhipster:angular') - .withGenerators([[mockAngularBlueprintSubGen, { namespace: 'jhipster-myblueprint2:angular' }]]) - .withOptions({ - blueprint: 'myblueprint2', - force: false, - }); - }); -}); diff --git a/test/needle-api/needle-client-angular-generator.spec.ts b/test/needle-api/needle-client-angular-generator.spec.ts new file mode 100644 index 000000000000..dcfa6d27c07f --- /dev/null +++ b/test/needle-api/needle-client-angular-generator.spec.ts @@ -0,0 +1,105 @@ +import { defaultHelpers as helpers, runResult } from '../support/index.js'; + +import AngularGenerator from '../../generators/angular/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.js'; + +import BaseApplicationGenerator from '../../generators/base-application/index.js'; + +const mockAngularBlueprintSubGen = class extends AngularGenerator { + constructor(args, opts, features) { + super(args, opts, { ...features, sbsBlueprint: true }); + } + + get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() { + return this.asPostWritingEntitiesTaskGroup({ + addToMenuStep() { + this.addElementToAdminMenu('routerName2', 'iconName2', true); + }, + addToModuleStep({ application, source }) { + source.addEntitiesToClient({ + application, + entities: [ + { + name: 'entityName', + entityInstance: 'entityInstance', + entityClass: 'entityClass', + entityFolderName: 'entityFolderName', + entityFileName: 'entityFileName', + entityUrl: 'entityUrl', + i18nKeyPrefix: 'entity', + entityPage: 'entityPage', + entityTranslationKeyMenu: 'entityTranslationKeyMenu', + entityClassHumanized: 'entityClassHumanized', + } as any, + ], + }); + }, + }); + } +}; + +describe('needle API Angular angular generator : JHipster with blueprint', () => { + before(async () => { + await helpers + .runJHipster('angular') + .withJHipsterConfig({ + skipServer: true, + }) + .withOptions({ + blueprint: 'myblueprint2', + }) + .withGenerators([[mockAngularBlueprintSubGen, { namespace: 'jhipster-myblueprint2:angular' }]]); + }); + + it('entity menu contains the entity added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, + ` +
  • + + + entityClassHumanized + +
  • +`, + ); + }); + + it('admin menu contains the admin element added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, + ` +
  • + + + Router Name 2 + +
  • +`, + ); + }); + + it('icon imports contains a new icon added by a new admin menu method of needle api ', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/config/font-awesome-icons.ts`, ' faIconName2'); + }); + + it('entity module contains the microservice object added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/entities/entity.routes.ts`, + ' {\n' + + " path: 'entityUrl',\n" + + " data: { pageTitle: 'entity.home.title' },\n" + + " loadChildren: () => import('./entityFolderName/entityFileName.routes'),\n" + + ' }', + ); + }); + it('should bail on any file change adding same needles again', async () => { + await runResult + .create('jhipster:angular') + .withGenerators([[mockAngularBlueprintSubGen, { namespace: 'jhipster-myblueprint2:angular' }]]) + .withOptions({ + blueprint: 'myblueprint2', + force: false, + }); + }); +}); diff --git a/test/needle-api/needle-client-angular.spec.mts b/test/needle-api/needle-client-angular.spec.mts deleted file mode 100644 index 45a45fc3921d..000000000000 --- a/test/needle-api/needle-client-angular.spec.mts +++ /dev/null @@ -1,114 +0,0 @@ -import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; - -import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.mjs'; -import BaseApplicationGenerator from '../../generators/base-application/index.mjs'; -import AngularGenerator from '../../generators/angular/index.mjs'; - -const { ANGULAR } = clientFrameworkTypes; - -const mockBlueprintSubGen = class extends AngularGenerator { - constructor(args, opts, features) { - super(args, opts, features); - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - return { - addCssStylesProperty() { - this.addMainSCSSStyle('@import style_without_comment;'); - this.addMainSCSSStyle('@import style;', 'my comment'); - this.addVendorSCSSStyle('@import style;', 'my comment'); - this.addVendorSCSSStyle('@import style_without_comment;'); - }, - addToMenuStep() { - this.addElementToMenu('routerName1', 'iconName1', true, ANGULAR); - }, - addToModuleStep() { - this.addAngularModule('appName', 'angularName', 'folderName', 'fileName', true, ANGULAR); - this.addAdminRoute('entity-audit', './entity-audit/entity-audit.module', 'EntityAuditModule', 'entityAudit.home.title'); - }, - }; - } -}; - -describe('needle API Angular: JHipster angular generator with blueprint', () => { - before(async () => { - await helpers - .create(getGenerator('angular')) - .withJHipsterConfig({ - skipServer: true, - }) - .withOptions({ - blueprint: 'myblueprint', - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:angular' }]]) - .run(); - }); - - it('vendor.scss contains the specific change (without comment) added by needle api', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /\n@import style_without_comment;\n/); - }); - - it('global.scss contains the specific change (without comment) added by needle api', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /\n@import style_without_comment;\n/); - }); - - it('vendor.scss contains the specific change added by needle api', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /\n@import style;\n/); - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, - '* ==========================================================================\n' + - 'my comment\n' + - '========================================================================== */\n', - ); - }); - - it('global.scss contains the specific change added by needle api', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /\n@import style;\n/); - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, - '* ==========================================================================\n' + - 'my comment\n' + - '========================================================================== */\n', - ); - }); - - it('menu contains the element added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, - ` - -`, - ); - }); - - it('icon imports contains a new icon added by a new menu method of needle api ', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/config/font-awesome-icons.ts`, ' faIconName1'); - }); - - it('admin routes contains the routing added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/admin/admin.routes.ts`, - ' },\n' + - ' {\n' + - " path: 'entity-audit',\n" + - " data: { pageTitle: 'entityAudit.home.title' },\n" + - " loadChildren: () => import('./entity-audit/entity-audit.module').then(m => m.EntityAuditModule),\n" + - ' },', - ); - }); - - it('app module contains the import and the module added by needle api', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/app.config.ts`, - "import { appNameangularNameModule } from './folderName/fileName.module';", - ); - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.config.ts`, 'appNameangularNameModule,'); - }); -}); diff --git a/test/needle-api/needle-client-angular.spec.ts b/test/needle-api/needle-client-angular.spec.ts new file mode 100644 index 000000000000..2ab8d7d32a36 --- /dev/null +++ b/test/needle-api/needle-client-angular.spec.ts @@ -0,0 +1,114 @@ +import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; + +import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.js'; +import BaseApplicationGenerator from '../../generators/base-application/index.js'; +import AngularGenerator from '../../generators/angular/index.js'; + +const { ANGULAR } = clientFrameworkTypes; + +const mockBlueprintSubGen = class extends AngularGenerator { + constructor(args, opts, features) { + super(args, opts, features); + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return { + addCssStylesProperty() { + this.addMainSCSSStyle('@import style_without_comment;'); + this.addMainSCSSStyle('@import style;', 'my comment'); + this.addVendorSCSSStyle('@import style;', 'my comment'); + this.addVendorSCSSStyle('@import style_without_comment;'); + }, + addToMenuStep() { + this.addElementToMenu('routerName1', 'iconName1', true, ANGULAR); + }, + addToModuleStep() { + this.addAngularModule('appName', 'angularName', 'folderName', 'fileName', true, ANGULAR); + this.addAdminRoute('entity-audit', './entity-audit/entity-audit.module', 'EntityAuditModule', 'entityAudit.home.title'); + }, + }; + } +}; + +describe('needle API Angular: JHipster angular generator with blueprint', () => { + before(async () => { + await helpers + .create(getGenerator('angular')) + .withJHipsterConfig({ + skipServer: true, + }) + .withOptions({ + blueprint: 'myblueprint', + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:angular' }]]) + .run(); + }); + + it('vendor.scss contains the specific change (without comment) added by needle api', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /\n@import style_without_comment;\n/); + }); + + it('global.scss contains the specific change (without comment) added by needle api', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /\n@import style_without_comment;\n/); + }); + + it('vendor.scss contains the specific change added by needle api', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /\n@import style;\n/); + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, + '* ==========================================================================\n' + + 'my comment\n' + + '========================================================================== */\n', + ); + }); + + it('global.scss contains the specific change added by needle api', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /\n@import style;\n/); + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, + '* ==========================================================================\n' + + 'my comment\n' + + '========================================================================== */\n', + ); + }); + + it('menu contains the element added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/layouts/navbar/navbar.component.html`, + ` + +`, + ); + }); + + it('icon imports contains a new icon added by a new menu method of needle api ', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/config/font-awesome-icons.ts`, ' faIconName1'); + }); + + it('admin routes contains the routing added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/admin/admin.routes.ts`, + ' },\n' + + ' {\n' + + " path: 'entity-audit',\n" + + " data: { pageTitle: 'entityAudit.home.title' },\n" + + " loadChildren: () => import('./entity-audit/entity-audit.module').then(m => m.EntityAuditModule),\n" + + ' },', + ); + }); + + it('app module contains the import and the module added by needle api', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/app.config.ts`, + "import { appNameangularNameModule } from './folderName/fileName.module';", + ); + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.config.ts`, 'appNameangularNameModule,'); + }); +}); diff --git a/test/needle-api/needle-client-react-generator.spec.mts b/test/needle-api/needle-client-react-generator.spec.mts deleted file mode 100644 index b795f6875e2d..000000000000 --- a/test/needle-api/needle-client-react-generator.spec.mts +++ /dev/null @@ -1,78 +0,0 @@ -import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import ReactGenerator from '../../generators/react/index.mjs'; -import BaseApplicationGenerator from '../../generators/base-application/index.mjs'; - -const { REACT } = clientFrameworkTypes; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockReactBlueprintSubGen: any = class extends ReactGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - if (!this.jhipsterContext) { - throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); - } - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - const customPhaseSteps = { - addEntityToMenuStep() { - this.addEntityToMenu('routerName', false, false); - }, - addEntityToModuleStep({ application }) { - const { applicationTypeMicroservice, clientSrcDir } = application; - this.addEntityToModule('entityInstance', 'entityClass', 'entityName', 'entityFolderName', 'entityFileName', { - applicationTypeMicroservice, - clientSrcDir, - }); - }, - }; - return { ...customPhaseSteps }; - } -}; - -describe('needle API React: JHipster client generator with blueprint', () => { - let result; - - before(async () => { - result = await helpers - .run(getGenerator('react')) - .withOptions({ - build: 'maven', - auth: 'jwt', - db: 'mysql', - blueprint: 'myblueprint', - ignoreNeedlesError: true, - }) - .withGenerators([[mockReactBlueprintSubGen, { namespace: 'jhipster-myblueprint:react' }]]) - .withAnswers({ - baseName: 'jhipster', - clientFramework: REACT, - enableTranslation: true, - nativeLanguage: 'en', - languages: ['en', 'fr'], - }); - }); - - it('Assert entity is added to module', () => { - const indexModulePath = `${CLIENT_MAIN_SRC_DIR}app/entities/routes.tsx`; - const indexReducerPath = `${CLIENT_MAIN_SRC_DIR}app/entities/reducers.ts`; - - runResult.assertFileContent(indexModulePath, "import entityName from './entityFolderName';"); - runResult.assertFileContent(indexModulePath, '} />'); - - runResult.assertFileContent(indexReducerPath, "import entityInstance from 'app/entities/entityFolderName/entityFileName.reducer';"); - runResult.assertFileContent(indexReducerPath, 'entityInstance,'); - }); - - it('Assert entity is added to menu', () => { - result.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/entities/menu.tsx`, - '\n Router Name\n ', - ); - }); -}); diff --git a/test/needle-api/needle-client-react-generator.spec.ts b/test/needle-api/needle-client-react-generator.spec.ts new file mode 100644 index 000000000000..f27d0936f65e --- /dev/null +++ b/test/needle-api/needle-client-react-generator.spec.ts @@ -0,0 +1,78 @@ +import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import ReactGenerator from '../../generators/react/index.js'; +import BaseApplicationGenerator from '../../generators/base-application/index.js'; + +const { REACT } = clientFrameworkTypes; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockReactBlueprintSubGen: any = class extends ReactGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + if (!this.jhipsterContext) { + throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); + } + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + const customPhaseSteps = { + addEntityToMenuStep() { + this.addEntityToMenu('routerName', false, false); + }, + addEntityToModuleStep({ application }) { + const { applicationTypeMicroservice, clientSrcDir } = application; + this.addEntityToModule('entityInstance', 'entityClass', 'entityName', 'entityFolderName', 'entityFileName', { + applicationTypeMicroservice, + clientSrcDir, + }); + }, + }; + return { ...customPhaseSteps }; + } +}; + +describe('needle API React: JHipster client generator with blueprint', () => { + let result; + + before(async () => { + result = await helpers + .run(getGenerator('react')) + .withOptions({ + build: 'maven', + auth: 'jwt', + db: 'mysql', + blueprint: 'myblueprint', + ignoreNeedlesError: true, + }) + .withGenerators([[mockReactBlueprintSubGen, { namespace: 'jhipster-myblueprint:react' }]]) + .withAnswers({ + baseName: 'jhipster', + clientFramework: REACT, + enableTranslation: true, + nativeLanguage: 'en', + languages: ['en', 'fr'], + }); + }); + + it('Assert entity is added to module', () => { + const indexModulePath = `${CLIENT_MAIN_SRC_DIR}app/entities/routes.tsx`; + const indexReducerPath = `${CLIENT_MAIN_SRC_DIR}app/entities/reducers.ts`; + + runResult.assertFileContent(indexModulePath, "import entityName from './entityFolderName';"); + runResult.assertFileContent(indexModulePath, '} />'); + + runResult.assertFileContent(indexReducerPath, "import entityInstance from 'app/entities/entityFolderName/entityFileName.reducer';"); + runResult.assertFileContent(indexReducerPath, 'entityInstance,'); + }); + + it('Assert entity is added to menu', () => { + result.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/entities/menu.tsx`, + '\n Router Name\n ', + ); + }); +}); diff --git a/test/needle-api/needle-client-react.spec.mts b/test/needle-api/needle-client-react.spec.mts deleted file mode 100644 index 4730483ff0fd..000000000000 --- a/test/needle-api/needle-client-react.spec.mts +++ /dev/null @@ -1,64 +0,0 @@ -import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.mjs'; -import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import BaseApplicationGenerator from '../../generators/base-application/index.mjs'; -import ReactGenerator from '../../generators/react/index.mjs'; - -const { REACT } = clientFrameworkTypes; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends ReactGenerator { - constructor(args, opts, features) { - super(args, opts, features); - - if (!this.jhipsterContext) { - throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); - } - - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - const customPhaseSteps = { - addAppCssStep() { - // please change this to public API when it will be available see https://github.com/jhipster/generator-jhipster/issues/9234 - this.addAppSCSSStyle('@import without-comment'); - this.addAppSCSSStyle('@import with-comment', 'my comment'); - }, - }; - return { ...customPhaseSteps }; - } -}; - -describe('needle API React: JHipster react generator with blueprint', () => { - before(async () => { - await helpers - .run(getGenerator('react')) - .withOptions({ - build: 'maven', - auth: 'jwt', - db: 'mysql', - blueprint: 'myblueprint', - ignoreNeedlesError: true, - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:react' }]]) - .withAnswers({ - baseName: 'jhipster', - clientFramework: REACT, - enableTranslation: true, - nativeLanguage: 'en', - languages: ['en', 'fr'], - }); - }); - - it('Assert app.scss is updated', () => { - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import without-comment'); - runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import with-comment'); - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/app.scss`, - '* ==========================================================================\n' + - 'my comment\n' + - '========================================================================== */\n', - ); - }); -}); diff --git a/test/needle-api/needle-client-react.spec.ts b/test/needle-api/needle-client-react.spec.ts new file mode 100644 index 000000000000..1b5041de1a79 --- /dev/null +++ b/test/needle-api/needle-client-react.spec.ts @@ -0,0 +1,64 @@ +import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.js'; +import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import BaseApplicationGenerator from '../../generators/base-application/index.js'; +import ReactGenerator from '../../generators/react/index.js'; + +const { REACT } = clientFrameworkTypes; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends ReactGenerator { + constructor(args, opts, features) { + super(args, opts, features); + + if (!this.jhipsterContext) { + throw new Error('This is a JHipster blueprint and should be used only like jhipster --blueprints myblueprint'); + } + + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + const customPhaseSteps = { + addAppCssStep() { + // please change this to public API when it will be available see https://github.com/jhipster/generator-jhipster/issues/9234 + this.addAppSCSSStyle('@import without-comment'); + this.addAppSCSSStyle('@import with-comment', 'my comment'); + }, + }; + return { ...customPhaseSteps }; + } +}; + +describe('needle API React: JHipster react generator with blueprint', () => { + before(async () => { + await helpers + .run(getGenerator('react')) + .withOptions({ + build: 'maven', + auth: 'jwt', + db: 'mysql', + blueprint: 'myblueprint', + ignoreNeedlesError: true, + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:react' }]]) + .withAnswers({ + baseName: 'jhipster', + clientFramework: REACT, + enableTranslation: true, + nativeLanguage: 'en', + languages: ['en', 'fr'], + }); + }); + + it('Assert app.scss is updated', () => { + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import without-comment'); + runResult.assertFileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import with-comment'); + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/app.scss`, + '* ==========================================================================\n' + + 'my comment\n' + + '========================================================================== */\n', + ); + }); +}); diff --git a/test/needle-api/needle-client-vue-generator.spec.mts b/test/needle-api/needle-client-vue-generator.spec.mts deleted file mode 100644 index 746ce2470786..000000000000 --- a/test/needle-api/needle-client-vue-generator.spec.mts +++ /dev/null @@ -1,132 +0,0 @@ -import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.mjs'; - -import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; -import VueGenerator from '../../generators/vue/index.mjs'; -import BaseApplicationGenerator from '../../generators/base-application/index.mjs'; - -const { VUE } = clientFrameworkTypes; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends VueGenerator { - constructor(args, opts, features) { - super(args, opts, features); - if (!this.jhipsterContext) { - throw new Error("This is a JHipster blueprint and should be used only like 'jhipster --blueprints myblueprint')}"); - } - this.sbsBlueprint = true; - } - - get [BaseApplicationGenerator.POST_WRITING]() { - const customPhaseSteps = { - addCustomMethods() { - this.addEntityToMenu('routerName', false); - }, - addToModuleStep() { - this.addEntityToModule( - 'entityInstance', - 'entityClass', - 'entityName', - 'entityFolderName', - 'entityFileName', - 'entityUrl', - 'microserviceName', - ); - }, - }; - return { ...customPhaseSteps }; - } -}; - -describe('needle API Vue: JHipster client generator with blueprint', () => { - before(() => - helpers - .run(getGenerator('vue')) - .withOptions({ - build: 'maven', - auth: 'jwt', - db: 'mysql', - blueprint: 'myblueprint', - }) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:vue' }]]) - .withAnswers({ - baseName: 'jhipster', - clientFramework: VUE, - enableTranslation: false, - nativeLanguage: 'en', - languages: ['fr'], - }), - ); - - it('menu contains the item and the root', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/entities/entities-menu.vue`, - ` - - - Router Name - -`, - ); - }); - - it('menu contains the item in router import', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/router/entities.ts`, - ` -// prettier-ignore -const entityName = () => import('@/entities/entityFolderName/entityFileName.vue'); -// prettier-ignore -const entityNameUpdate = () => import('@/entities/entityFolderName/entityFileName-update.vue'); -// prettier-ignore -const entityNameDetails = () => import('@/entities/entityFolderName/entityFileName-details.vue'); -`, - ); - }); - - it('menu contains the item in router', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/router/entities.ts`, - ` - { - path: 'entityFileName', - name: 'entityName', - component: entityName, - meta: { authorities: [Authority.USER] }, - }, - { - path: 'entityFileName/new', - name: 'entityNameCreate', - component: entityNameUpdate, - meta: { authorities: [Authority.USER] }, - }, - { - path: 'entityFileName/:entityInstanceId/edit', - name: 'entityNameEdit', - component: entityNameUpdate, - meta: { authorities: [Authority.USER] }, - }, - { - path: 'entityFileName/:entityInstanceId/view', - name: 'entityNameView', - component: entityNameDetails, - meta: { authorities: [Authority.USER] }, - }, -`, - ); - }); - - it('menu contains the item in service import', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/entities/entities.component.ts`, - "import entityNameService from './entityFolderName/entityFileName.service';", - ); - }); - - it('menu contains the item in service', () => { - runResult.assertFileContent( - `${CLIENT_MAIN_SRC_DIR}app/entities/entities.component.ts`, - "provide('entityInstanceService', () => new entityNameService());", - ); - }); -}); diff --git a/test/needle-api/needle-client-vue-generator.spec.ts b/test/needle-api/needle-client-vue-generator.spec.ts new file mode 100644 index 000000000000..6d4f50540f1d --- /dev/null +++ b/test/needle-api/needle-client-vue-generator.spec.ts @@ -0,0 +1,132 @@ +import { basicHelpers as helpers, result as runResult, getGenerator } from '../support/index.js'; + +import { CLIENT_MAIN_SRC_DIR } from '../../generators/generator-constants.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; +import VueGenerator from '../../generators/vue/index.js'; +import BaseApplicationGenerator from '../../generators/base-application/index.js'; + +const { VUE } = clientFrameworkTypes; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends VueGenerator { + constructor(args, opts, features) { + super(args, opts, features); + if (!this.jhipsterContext) { + throw new Error("This is a JHipster blueprint and should be used only like 'jhipster --blueprints myblueprint')}"); + } + this.sbsBlueprint = true; + } + + get [BaseApplicationGenerator.POST_WRITING]() { + const customPhaseSteps = { + addCustomMethods() { + this.addEntityToMenu('routerName', false); + }, + addToModuleStep() { + this.addEntityToModule( + 'entityInstance', + 'entityClass', + 'entityName', + 'entityFolderName', + 'entityFileName', + 'entityUrl', + 'microserviceName', + ); + }, + }; + return { ...customPhaseSteps }; + } +}; + +describe('needle API Vue: JHipster client generator with blueprint', () => { + before(() => + helpers + .run(getGenerator('vue')) + .withOptions({ + build: 'maven', + auth: 'jwt', + db: 'mysql', + blueprint: 'myblueprint', + }) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:vue' }]]) + .withAnswers({ + baseName: 'jhipster', + clientFramework: VUE, + enableTranslation: false, + nativeLanguage: 'en', + languages: ['fr'], + }), + ); + + it('menu contains the item and the root', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/entities/entities-menu.vue`, + ` + + + Router Name + +`, + ); + }); + + it('menu contains the item in router import', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/router/entities.ts`, + ` +// prettier-ignore +const entityName = () => import('@/entities/entityFolderName/entityFileName.vue'); +// prettier-ignore +const entityNameUpdate = () => import('@/entities/entityFolderName/entityFileName-update.vue'); +// prettier-ignore +const entityNameDetails = () => import('@/entities/entityFolderName/entityFileName-details.vue'); +`, + ); + }); + + it('menu contains the item in router', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/router/entities.ts`, + ` + { + path: 'entityFileName', + name: 'entityName', + component: entityName, + meta: { authorities: [Authority.USER] }, + }, + { + path: 'entityFileName/new', + name: 'entityNameCreate', + component: entityNameUpdate, + meta: { authorities: [Authority.USER] }, + }, + { + path: 'entityFileName/:entityInstanceId/edit', + name: 'entityNameEdit', + component: entityNameUpdate, + meta: { authorities: [Authority.USER] }, + }, + { + path: 'entityFileName/:entityInstanceId/view', + name: 'entityNameView', + component: entityNameDetails, + meta: { authorities: [Authority.USER] }, + }, +`, + ); + }); + + it('menu contains the item in service import', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/entities/entities.component.ts`, + "import entityNameService from './entityFolderName/entityFileName.service';", + ); + }); + + it('menu contains the item in service', () => { + runResult.assertFileContent( + `${CLIENT_MAIN_SRC_DIR}app/entities/entities.component.ts`, + "provide('entityInstanceService', () => new entityNameService());", + ); + }); +}); diff --git a/test/needle-api/needle-client-vue.spec.mts b/test/needle-api/needle-client-vue.spec.mts deleted file mode 100644 index 891ce4b1188d..000000000000 --- a/test/needle-api/needle-client-vue.spec.mts +++ /dev/null @@ -1,40 +0,0 @@ -import { basicHelpers as helpers, getGenerator } from '../support/index.mjs'; - -import ClientGenerator from '../../generators/client/index.mjs'; -import { clientFrameworkTypes } from '../../jdl/jhipster/index.mjs'; - -const { VUE } = clientFrameworkTypes; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockBlueprintSubGen: any = class extends ClientGenerator { - constructor(args, opts, features) { - super(args, opts, features); - const jhContext = (this.jhipsterContext = this.options.jhipsterContext); - if (!jhContext) { - throw new Error("This is a JHipster blueprint and should be used only like 'jhipster --blueprints myblueprint')}"); - } - this.sbsBlueprint = true; - } -}; - -describe('needle API Vue: JHipster client generator with blueprint', () => { - before(() => - helpers - .run(getGenerator('client')) - .withOptions({ - build: 'maven', - auth: 'jwt', - db: 'mysql', - blueprint: 'myblueprint', - }) - .withMockedGenerators(['jhipster:languages']) - .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:client' }]]) - .withAnswers({ - baseName: 'jhipster', - clientFramework: VUE, - enableTranslation: false, - nativeLanguage: 'en', - languages: ['fr'], - }), - ); -}); diff --git a/test/needle-api/needle-client-vue.spec.ts b/test/needle-api/needle-client-vue.spec.ts new file mode 100644 index 000000000000..864b6b7d6735 --- /dev/null +++ b/test/needle-api/needle-client-vue.spec.ts @@ -0,0 +1,40 @@ +import { basicHelpers as helpers, getGenerator } from '../support/index.js'; + +import ClientGenerator from '../../generators/client/index.js'; +import { clientFrameworkTypes } from '../../jdl/jhipster/index.js'; + +const { VUE } = clientFrameworkTypes; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockBlueprintSubGen: any = class extends ClientGenerator { + constructor(args, opts, features) { + super(args, opts, features); + const jhContext = (this.jhipsterContext = this.options.jhipsterContext); + if (!jhContext) { + throw new Error("This is a JHipster blueprint and should be used only like 'jhipster --blueprints myblueprint')}"); + } + this.sbsBlueprint = true; + } +}; + +describe('needle API Vue: JHipster client generator with blueprint', () => { + before(() => + helpers + .run(getGenerator('client')) + .withOptions({ + build: 'maven', + auth: 'jwt', + db: 'mysql', + blueprint: 'myblueprint', + }) + .withMockedGenerators(['jhipster:languages']) + .withGenerators([[mockBlueprintSubGen, { namespace: 'jhipster-myblueprint:client' }]]) + .withAnswers({ + baseName: 'jhipster', + clientFramework: VUE, + enableTranslation: false, + nativeLanguage: 'en', + languages: ['fr'], + }), + ); +}); diff --git a/test/support/application-samples.mts b/test/support/application-samples.mts deleted file mode 100644 index 18d572063263..000000000000 --- a/test/support/application-samples.mts +++ /dev/null @@ -1,27 +0,0 @@ -import { applicationTypes, authenticationTypes } from '../../jdl/jhipster/index.mjs'; - -const { JWT, OAUTH2, SESSION } = authenticationTypes; -const { GATEWAY, MICROSERVICE, MONOLITH } = applicationTypes; -export const AuthenticationTypeMatrix = { - authenticationType: [OAUTH2, JWT, SESSION], -}; - -export const MatrixMonolith = { - applicationType: [MONOLITH], - ...AuthenticationTypeMatrix, -}; - -export const MatrixMicroservice = { - applicationType: [MICROSERVICE], - authenticationType: [OAUTH2, JWT], -}; - -export const MatrixGateway = { - applicationType: [GATEWAY], - authenticationType: [OAUTH2, JWT], -}; - -export const MatrixMicroserviceGateway = { - applicationType: [MICROSERVICE, GATEWAY], - authenticationType: [OAUTH2, JWT], -}; diff --git a/test/support/application-samples.ts b/test/support/application-samples.ts new file mode 100644 index 000000000000..56081d69234a --- /dev/null +++ b/test/support/application-samples.ts @@ -0,0 +1,27 @@ +import { applicationTypes, authenticationTypes } from '../../jdl/jhipster/index.js'; + +const { JWT, OAUTH2, SESSION } = authenticationTypes; +const { GATEWAY, MICROSERVICE, MONOLITH } = applicationTypes; +export const AuthenticationTypeMatrix = { + authenticationType: [OAUTH2, JWT, SESSION], +}; + +export const MatrixMonolith = { + applicationType: [MONOLITH], + ...AuthenticationTypeMatrix, +}; + +export const MatrixMicroservice = { + applicationType: [MICROSERVICE], + authenticationType: [OAUTH2, JWT], +}; + +export const MatrixGateway = { + applicationType: [GATEWAY], + authenticationType: [OAUTH2, JWT], +}; + +export const MatrixMicroserviceGateway = { + applicationType: [MICROSERVICE, GATEWAY], + authenticationType: [OAUTH2, JWT], +}; diff --git a/test/support/check-enforcements.mts b/test/support/check-enforcements.mts deleted file mode 100644 index ecef59a72f7c..000000000000 --- a/test/support/check-enforcements.mts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright 2013-2023 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 assert from 'assert'; -import fs, { readFileSync } from 'fs'; -import path, { basename } from 'path'; -import fse from 'fs-extra'; -import { getGeneratorFolder } from '../../testing/get-generator.mjs'; - -const fixEnforcements = process.argv.includes('--fix-enforcements'); - -const readDir = dirPath => { - const files: string[] = []; - const dir = fs.opendirSync(dirPath); - let dirent = dir.readSync(); - while (dirent !== null) { - const childPath = path.join(dirPath, dirent.name); - if (dirent.isFile()) { - files.push(childPath); - } else { - files.push(...readDir(childPath)); - } - dirent = dir.readSync(); - } - dir.closeSync(); - return files; -}; - -export default function checkEnforcements({ client }: { client?: boolean }, ...generators: string[]) { - describe('enforce some developments patterns', () => { - for (const generator of generators) { - const allFiles = readDir(getGeneratorFolder(generator)); - allFiles - .filter(file => !/\.spec\.[mc]?[jt]s(.snap)?$/.test(file)) - .forEach(file => { - describe(`file ${path.basename(file)}`, () => { - let content; - before(() => { - content = fse.readFileSync(file, 'utf-8'); - }); - - [ - ['src/main/webapp', '<%= clientSrcDir %>'], - ['src/test/javascript', '<%= clientTestDir %>'], - ...(client - ? [ - ['jhiTranslate', '<%= jhiPrefix %>Translate'], - [' Java ', ' <%= backendType %> '], - ] - : []), - ].forEach(([notSpected, replacement]) => { - const regex = new RegExp(notSpected, 'g'); - const regexSeparator = new RegExp(`${notSpected}/`, 'g'); - before(() => { - if (!fixEnforcements || !replacement) return; - if (file.endsWith('.ejs')) { - if (regexSeparator.test(content)) { - fse.writeFileSync(file, content.replace(regexSeparator, replacement)); - content = fse.readFileSync(file, 'utf-8'); - } - if (regex.test(content)) { - fse.writeFileSync(file, content.replace(regex, replacement)); - content = fse.readFileSync(file, 'utf-8'); - } - } - }); - it(`should not contain ${notSpected}`, () => { - assert(!regex.test(content), `file ${file} should not contain ${notSpected}`); - }); - }); - }); - }); - const templateFiles = allFiles - .filter(file => file.endsWith('.ejs')) - .filter(file => { - return ( - !/DatabaseConfiguration_.*.java.ejs/.test(file) && - !/docker\/.*.yml.ejs/.test(file) && - !/OAuth2.*RefreshTokensWebFilter.java.ejs/.test(file) - ); - }); - const jsFiles = allFiles - .filter(file => file.endsWith('.mjs') || file.endsWith('.mts') || file.endsWith('.ejs')) - .sort((a, b) => { - if (a.includes('files')) return -1; - if (b.includes('files')) return 1; - if (a.includes('generator.')) return -1; - if (b.includes('generator.')) return 1; - if (a.endsWith('.ejs')) return 1; - if (b.endsWith('.ejs')) return -1; - return 0; - }); - templateFiles.forEach(templateFile => { - const reference = basename(templateFile, '.ejs').replace('_reactive.java', '_').replace('_imperative.java', '_'); - it(`${templateFile} must have referenced with ${reference}`, () => { - const found = jsFiles.find(jsFile => { - const content = readFileSync(jsFile).toString(); - return content.includes(`/${reference}`) || content.includes(`'${reference}`); - }); - if (!found) throw new Error(`File ${templateFile} is not referenced`); - }); - }); - } - }); -} diff --git a/test/support/check-enforcements.ts b/test/support/check-enforcements.ts new file mode 100644 index 000000000000..383ba2540bcd --- /dev/null +++ b/test/support/check-enforcements.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2013-2023 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 assert from 'assert'; +import fs, { readFileSync } from 'fs'; +import path, { basename } from 'path'; +import fse from 'fs-extra'; +import { getGeneratorFolder } from '../../testing/get-generator.js'; + +const fixEnforcements = process.argv.includes('--fix-enforcements'); + +const readDir = dirPath => { + const files: string[] = []; + const dir = fs.opendirSync(dirPath); + let dirent = dir.readSync(); + while (dirent !== null) { + const childPath = path.join(dirPath, dirent.name); + if (dirent.isFile()) { + files.push(childPath); + } else { + files.push(...readDir(childPath)); + } + dirent = dir.readSync(); + } + dir.closeSync(); + return files; +}; + +export default function checkEnforcements({ client }: { client?: boolean }, ...generators: string[]) { + describe('enforce some developments patterns', () => { + for (const generator of generators) { + const allFiles = readDir(getGeneratorFolder(generator)); + allFiles + .filter(file => !/\.spec\.[mc]?[jt]s(.snap)?$/.test(file)) + .forEach(file => { + describe(`file ${path.basename(file)}`, () => { + let content; + before(() => { + content = fse.readFileSync(file, 'utf-8'); + }); + + [ + ['src/main/webapp', '<%= clientSrcDir %>'], + ['src/test/javascript', '<%= clientTestDir %>'], + ...(client + ? [ + ['jhiTranslate', '<%= jhiPrefix %>Translate'], + [' Java ', ' <%= backendType %> '], + ] + : []), + ].forEach(([notSpected, replacement]) => { + const regex = new RegExp(notSpected, 'g'); + const regexSeparator = new RegExp(`${notSpected}/`, 'g'); + before(() => { + if (!fixEnforcements || !replacement) return; + if (file.endsWith('.ejs')) { + if (regexSeparator.test(content)) { + fse.writeFileSync(file, content.replace(regexSeparator, replacement)); + content = fse.readFileSync(file, 'utf-8'); + } + if (regex.test(content)) { + fse.writeFileSync(file, content.replace(regex, replacement)); + content = fse.readFileSync(file, 'utf-8'); + } + } + }); + it(`should not contain ${notSpected}`, () => { + assert(!regex.test(content), `file ${file} should not contain ${notSpected}`); + }); + }); + }); + }); + const templateFiles = allFiles + .filter(file => file.endsWith('.ejs')) + .filter(file => { + return ( + !/DatabaseConfiguration_.*.java.ejs/.test(file) && + !/docker\/.*.yml.ejs/.test(file) && + !/OAuth2.*RefreshTokensWebFilter.java.ejs/.test(file) + ); + }); + const jsFiles = allFiles + .filter(file => file.endsWith('.js') || file.endsWith('.ts') || file.endsWith('.ejs')) + .sort((a, b) => { + if (a.includes('files')) return -1; + if (b.includes('files')) return 1; + if (a.includes('generator.')) return -1; + if (b.includes('generator.')) return 1; + if (a.endsWith('.ejs')) return 1; + if (b.endsWith('.ejs')) return -1; + return 0; + }); + templateFiles.forEach(templateFile => { + const reference = basename(templateFile, '.ejs').replace('_reactive.java', '_').replace('_imperative.java', '_'); + it(`${templateFile} must have referenced with ${reference}`, () => { + const found = jsFiles.find(jsFile => { + const content = readFileSync(jsFile).toString(); + return content.includes(`/${reference}`) || content.includes(`'${reference}`); + }); + if (!found) throw new Error(`File ${templateFile} is not referenced`); + }); + }); + } + }); +} diff --git a/test/support/client-samples.mts b/test/support/client-samples.mts deleted file mode 100644 index 42a922c59bd5..000000000000 --- a/test/support/client-samples.mts +++ /dev/null @@ -1,28 +0,0 @@ -import { MatrixMonolith, MatrixMicroserviceGateway } from './application-samples.mjs'; -import { buildSamplesFromMatrix, extendFilteredMatrix, extendMatrix, fromMatrix } from './matrix-utils.mjs'; - -const CLIENT_ADDITIONAL_CONFIG_MATRIX = { - withAdminUi: [false, true], - skipJhipsterDependencies: [false, true], - enableTranslation: [false, true], - clientRootDir: [undefined, { value: 'clientRoot/' }, { value: '' }], - websocket: [false, true], -}; - -export const buildClientSamples = (commonConfig?: Record): Record> => { - let clientMatrix = { - ...fromMatrix(MatrixMonolith), - ...fromMatrix(MatrixMicroserviceGateway), - }; - - clientMatrix = extendFilteredMatrix(clientMatrix, sample => sample.authenticationType !== 'oauth2', { - skipUserManagement: [false, true], - }); - - clientMatrix = extendMatrix(clientMatrix, CLIENT_ADDITIONAL_CONFIG_MATRIX); - - return buildSamplesFromMatrix(clientMatrix, { commonConfig }); -}; - -// eslint-disable-next-line import/prefer-default-export -export const clientSamples = buildClientSamples(); diff --git a/test/support/client-samples.ts b/test/support/client-samples.ts new file mode 100644 index 000000000000..5afeae21506c --- /dev/null +++ b/test/support/client-samples.ts @@ -0,0 +1,28 @@ +import { MatrixMonolith, MatrixMicroserviceGateway } from './application-samples.js'; +import { buildSamplesFromMatrix, extendFilteredMatrix, extendMatrix, fromMatrix } from './matrix-utils.js'; + +const CLIENT_ADDITIONAL_CONFIG_MATRIX = { + withAdminUi: [false, true], + skipJhipsterDependencies: [false, true], + enableTranslation: [false, true], + clientRootDir: [undefined, { value: 'clientRoot/' }, { value: '' }], + websocket: [false, true], +}; + +export const buildClientSamples = (commonConfig?: Record): Record> => { + let clientMatrix = { + ...fromMatrix(MatrixMonolith), + ...fromMatrix(MatrixMicroserviceGateway), + }; + + clientMatrix = extendFilteredMatrix(clientMatrix, sample => sample.authenticationType !== 'oauth2', { + skipUserManagement: [false, true], + }); + + clientMatrix = extendMatrix(clientMatrix, CLIENT_ADDITIONAL_CONFIG_MATRIX); + + return buildSamplesFromMatrix(clientMatrix, { commonConfig }); +}; + +// eslint-disable-next-line import/prefer-default-export +export const clientSamples = buildClientSamples(); diff --git a/test/support/deployment-samples.mts b/test/support/deployment-samples.ts similarity index 100% rename from test/support/deployment-samples.mts rename to test/support/deployment-samples.ts diff --git a/test/support/entity-samples.mts b/test/support/entity-samples.mts deleted file mode 100644 index 70487907e067..000000000000 --- a/test/support/entity-samples.mts +++ /dev/null @@ -1,68 +0,0 @@ -import { fieldTypes } from '../../jdl/jhipster/index.mjs'; - -const { - CommonDBTypes: { UUID }, -} = fieldTypes; - -export const entitySimple = { - name: 'Simple', - changelogDate: '20220129000100', - jpaMetamodelFiltering: true, - fields: [{ fieldName: 'simpleName', fieldType: 'String' }], -}; - -export const entityAnotherSimple = { - name: 'AnotherSimple', - changelogDate: '20220129000200', - fields: [{ fieldName: 'simpleName', fieldType: 'String' }], - dto: 'mapstruct', - service: 'serviceImpl', - pagination: 'pagination', - clientRootFolder: 'test-root', -}; - -export const entitiesSimple = [entitySimple, entityAnotherSimple]; - -export const entitiesWithRelationships = [ - entitySimple, - entityAnotherSimple, - { - name: 'RelationshipWithSimple', - changelogDate: '20220129001000', - fields: [{ fieldName: 'twoName', fieldType: 'String' }], - relationships: [{ relationshipName: 'relationship', otherEntityName: 'Simple', relationshipType: 'many-to-one' }], - }, -]; - -export const entityCustomId = { - name: 'EntityWithCustomId', - changelogDate: '20220129002000', - entityPackage: 'custom', - fields: [ - { - fieldName: 'id', - fieldType: UUID, - }, - ], -}; - -export const entitiesMicroservice = { - name: 'Microservice', - skipFakeData: true, - changelogDate: '20220129000300', - fields: [{ fieldName: 'simpleName', fieldType: 'String' }], - microserviceName: 'microservice1', - service: 'service', - pagination: 'infiniscroll', -}; - -export const entitySkipClient = { - name: 'SkipClient', - changelogDate: '20220129000400', - skipClient: true, - fields: [{ fieldName: 'skipClientName', fieldType: 'String' }], -}; - -export const entitiesServerSamples = [entitySimple, entityAnotherSimple, entityCustomId, entitiesMicroservice]; - -export const entitiesClientSamples = [entitySimple, entityCustomId, entitiesMicroservice, entitySkipClient]; diff --git a/test/support/entity-samples.ts b/test/support/entity-samples.ts new file mode 100644 index 000000000000..2fea276495ac --- /dev/null +++ b/test/support/entity-samples.ts @@ -0,0 +1,68 @@ +import { fieldTypes } from '../../jdl/jhipster/index.js'; + +const { + CommonDBTypes: { UUID }, +} = fieldTypes; + +export const entitySimple = { + name: 'Simple', + changelogDate: '20220129000100', + jpaMetamodelFiltering: true, + fields: [{ fieldName: 'simpleName', fieldType: 'String' }], +}; + +export const entityAnotherSimple = { + name: 'AnotherSimple', + changelogDate: '20220129000200', + fields: [{ fieldName: 'simpleName', fieldType: 'String' }], + dto: 'mapstruct', + service: 'serviceImpl', + pagination: 'pagination', + clientRootFolder: 'test-root', +}; + +export const entitiesSimple = [entitySimple, entityAnotherSimple]; + +export const entitiesWithRelationships = [ + entitySimple, + entityAnotherSimple, + { + name: 'RelationshipWithSimple', + changelogDate: '20220129001000', + fields: [{ fieldName: 'twoName', fieldType: 'String' }], + relationships: [{ relationshipName: 'relationship', otherEntityName: 'Simple', relationshipType: 'many-to-one' }], + }, +]; + +export const entityCustomId = { + name: 'EntityWithCustomId', + changelogDate: '20220129002000', + entityPackage: 'custom', + fields: [ + { + fieldName: 'id', + fieldType: UUID, + }, + ], +}; + +export const entitiesMicroservice = { + name: 'Microservice', + skipFakeData: true, + changelogDate: '20220129000300', + fields: [{ fieldName: 'simpleName', fieldType: 'String' }], + microserviceName: 'microservice1', + service: 'service', + pagination: 'infiniscroll', +}; + +export const entitySkipClient = { + name: 'SkipClient', + changelogDate: '20220129000400', + skipClient: true, + fields: [{ fieldName: 'skipClientName', fieldType: 'String' }], +}; + +export const entitiesServerSamples = [entitySimple, entityAnotherSimple, entityCustomId, entitiesMicroservice]; + +export const entitiesClientSamples = [entitySimple, entityCustomId, entitiesMicroservice, entitySkipClient]; diff --git a/test/support/index.mts b/test/support/index.mts deleted file mode 100644 index ce997c60b81a..000000000000 --- a/test/support/index.mts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './application-samples.mjs'; -export * from './client-samples.mjs'; -export { default as checkEnforcements } from './check-enforcements.mjs'; -export * from './entity-samples.mjs'; -export { default as getGenerator } from '../../testing/get-generator.mjs'; -export * from '../../testing/helpers.mjs'; -export * from './matrix-utils.mjs'; -export * from './matcher.mjs'; -export * from './server-samples.mjs'; diff --git a/test/support/index.ts b/test/support/index.ts new file mode 100644 index 000000000000..236deeae66f6 --- /dev/null +++ b/test/support/index.ts @@ -0,0 +1,9 @@ +export * from './application-samples.js'; +export * from './client-samples.js'; +export { default as checkEnforcements } from './check-enforcements.js'; +export * from './entity-samples.js'; +export { default as getGenerator } from '../../testing/get-generator.js'; +export * from '../../testing/helpers.js'; +export * from './matrix-utils.js'; +export * from './matcher.js'; +export * from './server-samples.js'; diff --git a/test/support/matcher.mts b/test/support/matcher.ts similarity index 100% rename from test/support/matcher.mts rename to test/support/matcher.ts diff --git a/test/support/matrix-utils.mts b/test/support/matrix-utils.ts similarity index 100% rename from test/support/matrix-utils.mts rename to test/support/matrix-utils.ts diff --git a/test/support/server-samples.mts b/test/support/server-samples.mts deleted file mode 100644 index 08f587b226ab..000000000000 --- a/test/support/server-samples.mts +++ /dev/null @@ -1,56 +0,0 @@ -import { MatrixMonolith, MatrixMicroservice, MatrixGateway } from './application-samples.mjs'; -import { fromMatrix, extendMatrix, extendFilteredMatrix, buildSamplesFromMatrix } from './matrix-utils.mjs'; - -// eslint-disable-next-line import/prefer-default-export -export const buildServerMatrix = (matrix: Record = {}) => { - let serverMatrix = { - ...fromMatrix({ - ...MatrixMonolith, - ...matrix, - reactive: [false, true], - }), - ...fromMatrix({ - ...MatrixMicroservice, - ...matrix, - reactive: [false, true], - }), - ...fromMatrix({ - ...MatrixGateway, - ...matrix, - }), - }; - - serverMatrix = extendMatrix(serverMatrix, { - buildTool: ['maven', 'gradle'], - enableTranslation: [false, true], - packageName: ['tech.jhipster', 'com.package'], - jhiPrefix: ['jhi', 'fix'], - entitySuffix: ['Entity', ''], - dtoSuffix: ['DTO', 'Rest'], - skipCommitHook: [false, true], - testFrameworks: [[], ['gatling'], ['cucumber']], - serverSideOptions: [[], ['enableSwaggerCodegen:true']], - }); - - serverMatrix = extendFilteredMatrix(serverMatrix, sample => !sample.reactive, { - websocket: [false, true], - }); - - serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType !== 'oauth2', { - skipUserManagement: [false, true], - }); - - serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType === 'session', { - serviceDiscoveryType: ['no', 'consul'], - }); - - serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType !== 'session', { - serviceDiscoveryType: ['no', 'consul', 'eureka'], - }); - - return serverMatrix; -}; - -export const buildServerSamples = (commonConfig?: Record, matrix?: Record) => { - return buildSamplesFromMatrix(buildServerMatrix(matrix), { commonConfig }); -}; diff --git a/test/support/server-samples.ts b/test/support/server-samples.ts new file mode 100644 index 000000000000..85a19b91fbdd --- /dev/null +++ b/test/support/server-samples.ts @@ -0,0 +1,56 @@ +import { MatrixMonolith, MatrixMicroservice, MatrixGateway } from './application-samples.js'; +import { fromMatrix, extendMatrix, extendFilteredMatrix, buildSamplesFromMatrix } from './matrix-utils.js'; + +// eslint-disable-next-line import/prefer-default-export +export const buildServerMatrix = (matrix: Record = {}) => { + let serverMatrix = { + ...fromMatrix({ + ...MatrixMonolith, + ...matrix, + reactive: [false, true], + }), + ...fromMatrix({ + ...MatrixMicroservice, + ...matrix, + reactive: [false, true], + }), + ...fromMatrix({ + ...MatrixGateway, + ...matrix, + }), + }; + + serverMatrix = extendMatrix(serverMatrix, { + buildTool: ['maven', 'gradle'], + enableTranslation: [false, true], + packageName: ['tech.jhipster', 'com.package'], + jhiPrefix: ['jhi', 'fix'], + entitySuffix: ['Entity', ''], + dtoSuffix: ['DTO', 'Rest'], + skipCommitHook: [false, true], + testFrameworks: [[], ['gatling'], ['cucumber']], + serverSideOptions: [[], ['enableSwaggerCodegen:true']], + }); + + serverMatrix = extendFilteredMatrix(serverMatrix, sample => !sample.reactive, { + websocket: [false, true], + }); + + serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType !== 'oauth2', { + skipUserManagement: [false, true], + }); + + serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType === 'session', { + serviceDiscoveryType: ['no', 'consul'], + }); + + serverMatrix = extendFilteredMatrix(serverMatrix, sample => sample.authenticationType !== 'session', { + serviceDiscoveryType: ['no', 'consul', 'eureka'], + }); + + return serverMatrix; +}; + +export const buildServerSamples = (commonConfig?: Record, matrix?: Record) => { + return buildSamplesFromMatrix(buildServerMatrix(matrix), { commonConfig }); +}; diff --git a/test/support/tests.js b/test/support/tests.js new file mode 100644 index 000000000000..936ce9d67e29 --- /dev/null +++ b/test/support/tests.js @@ -0,0 +1,435 @@ +import path, { dirname } from 'path'; +import { existsSync } from 'fs'; +import { fileURLToPath } from 'url'; +import sinon from 'sinon'; +import { expect } from 'esmocha'; + +import { buildJHipster } from '../../cli/index.mjs'; +import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.js'; +import { skipPrettierHelpers as helpers } from '../../testing/index.js'; +import * as GeneratorList from '../../generators/generator-list.js'; +import { PRIORITY_NAMES, ENTITY_PRIORITY_NAMES, PRIORITY_NAMES_LIST } from '../../generators/base-application/priorities.js'; +import { WORKSPACES_PRIORITY_NAMES } from '../../generators/base-workspaces/priorities.js'; + +const workspacesPriorityList = Object.values(WORKSPACES_PRIORITY_NAMES); + +const { + CONFIGURING_EACH_ENTITY, + LOADING_ENTITIES, + PREPARING_EACH_ENTITY, + PREPARING_EACH_ENTITY_FIELD, + PREPARING_EACH_ENTITY_RELATIONSHIP, + POST_PREPARING_EACH_ENTITY, + WRITING_ENTITIES, + POST_WRITING_ENTITIES, +} = PRIORITY_NAMES; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const getCommandHelpOutput = async command => { + const program = await buildJHipster(); + const cmd = command ? program.commands.find(cmd => cmd.name() === command) : program; + if (!cmd) { + throw new Error(`Command ${command} not found.`); + } + if (command) { + await cmd._lazyBuildCommandCallBack(); + } + return cmd.configureOutput({ getOutHelpWidth: () => 1000, getErrHelpWidth: () => 1000 }).helpInformation(); +}; + +export const testOptions = data => { + const { generatorPath, customOptions, contextBuilder = () => helpers.create(generatorPath) } = data; + let runResult; + before(async () => { + runResult = await contextBuilder() + .withOptions({ ...customOptions }) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should write options to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customOptions }); + }); +}; + +const skipWritingPriorities = ['writing', 'writingEntities', 'postWriting', 'postWritingEntities']; + +export const basicTests = data => { + const { + generatorPath, + customPrompts, + requiredConfig, + defaultConfig, + getTemplateData = generator => generator.sharedData.getApplication(), + contextBuilder = () => helpers.create(generatorPath), + } = data; + describe('with default options', () => { + let runResult; + before(async () => { + runResult = await contextBuilder() + .withOptions({ + skipPrompts: true, + configure: true, + skipPriorities: skipWritingPriorities, + }) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should write default config to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); + }); + it('should load default config into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining(defaultConfig)); + }); + }); + describe('with defaults option', () => { + let runResult; + before(async () => { + runResult = await contextBuilder().withOptions({ defaults: true, skipPriorities: skipWritingPriorities }).run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should write default config to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); + }); + it('should load default config into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining(requiredConfig)); + }); + }); + describe('with custom prompt values', () => { + let runResult; + describe('and default options', () => { + before(async () => { + runResult = await contextBuilder() + .withOptions({ configure: true, skipPriorities: skipWritingPriorities }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should show prompts and write prompt values to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customPrompts }); + }); + it('should load default config with prompt values into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...customPrompts })); + }); + }); + describe('and defaults option', () => { + before(async () => { + runResult = await contextBuilder() + .withOptions({ defaults: true, skipPriorities: skipWritingPriorities }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should not show prompts and write default config to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); + }); + it('should load default config into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig })); + }); + }); + describe('and skipPrompts option', () => { + let runResult; + before(async () => { + runResult = await contextBuilder() + .withOptions({ skipPrompts: true, skipPriorities: skipWritingPriorities }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should not show prompts and write required config to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); + }); + it('should load default config and required config into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig })); + }); + }); + describe('and existing config', () => { + let runResult; + const existing = { baseName: 'existing' }; + before(async () => { + runResult = await contextBuilder() + .withJHipsterConfig(existing) + .withOptions({ skipPriorities: skipWritingPriorities }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should not show prompts and write required config to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { ...requiredConfig, ...existing } }); + }); + it('should load default config and required config into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig, ...existing })); + }); + }); + describe('and askAnswered option on an existing project', () => { + let runResult; + before(async () => { + runResult = await contextBuilder() + .withJHipsterConfig({ baseName: 'existing' }) + .withOptions({ + askAnswered: true, + skipPriorities: ['writing', 'writingEntities', 'postWriting', 'postWritingEntities'], + }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should show prompts and write prompt values to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customPrompts }); + }); + it('should load default config and prompt values into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...customPrompts })); + }); + }); + describe('and add option on an existing project', () => { + let runResult; + const existingConfig = { baseName: 'existing' }; + before(async () => { + runResult = await contextBuilder() + .withJHipsterConfig(existingConfig) + .withOptions({ + add: true, + skipPriorities: ['writing', 'writingEntities', 'postWriting', 'postWritingEntities'], + }) + .withAnswers(customPrompts) + .run(); + }); + after(() => { + runResult.cleanup(); + }); + it('should show prompts and write prompt values to .yo-rc.json', () => { + runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { ...customPrompts, ...existingConfig } }); + }); + it('should load default config and prompt values into the context', () => { + expect(getTemplateData(runResult.generator)).toEqual( + expect.objectContaining({ ...defaultConfig, ...customPrompts, ...existingConfig }), + ); + }); + }); + }); +}; + +export const testBlueprintSupport = (generatorName, options = {}) => { + if (typeof options === 'boolean') { + options = { skipSbsBlueprint: options }; + } + const { skipSbsBlueprint = false, entity = false } = options; + + let generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.cjs`); + if (!existsSync(generatorPath)) { + generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.js`); + } + if (!existsSync(generatorPath)) { + generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.ts`); + } + if (!existsSync(generatorPath)) { + generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.js`); + } + const addSpies = generator => { + const { taskPrefix = '' } = generator.features; + const apiPrefix = taskPrefix ? '' : '_'; + const prioritiesSpy = sinon.spy(); + const prioritiesTasks = []; + let prioritiesCount = 0; + [...PRIORITY_NAMES_LIST, ...workspacesPriorityList].forEach(priority => { + let callback; + if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(generator), `${taskPrefix}${priority}`)) { + prioritiesCount++; + callback = prioritiesSpy; + } else { + callback = () => { + throw new Error(`${apiPrefix}${priority} should not be called`); + }; + } + const property = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(generator), `${apiPrefix}${priority}`); + if (property) { + const task = sinon.spy(); + prioritiesTasks[priority] = task; + if (property.value && typeof property.value === 'function') { + generator[`${apiPrefix}${priority}`] = () => { + callback(); + return { task }; + }; + } else if (property.get) { + Object.defineProperty(generator, `${apiPrefix}${priority}`, { + get() { + callback(); + return { task }; + }, + enumerable: true, + configurable: true, + }); + } + } + }); + return { prioritiesSpy, prioritiesCount, prioritiesTasks }; + }; + describe('with blueprint', () => { + let result; + let spy; + before(async () => { + result = await helpers + .run(generatorPath) + .withMockedGenerators([ + `jhipster-foo:${generatorName}`, + // Mock every generator except the generator been tested + ...Object.values(GeneratorList) + .filter(gen => gen !== generatorName) + .map(gen => `jhipster:${gen}`), + ]) + .withJHipsterConfig() + .withOptions({ blueprint: 'foo' }) + .onGenerator(generator => { + spy = addSpies(generator); + }); + }); + after(() => { + result.cleanup(); + }); + it(`should compose with jhipster-foo:${generatorName} blueprint once`, () => { + expect(result.mockedGenerators[`jhipster-foo:${generatorName}`].callCount).toBe(1); + }); + it('should not call any priority', () => { + expect(spy.prioritiesSpy.callCount).toBe(0); + }); + }); + describe('with sbs blueprint', () => { + let result; + let spy; + before(async function () { + if (skipSbsBlueprint) { + this.skip(); + } + const context = helpers + .run(generatorPath) + .withMockedGenerators([ + `jhipster-foo-sbs:${generatorName}`, + // Mock every generator except the generator been tested + ...Object.values(GeneratorList) + .filter(gen => gen !== generatorName) + .map(gen => `jhipster:${gen}`), + ]) + .withJHipsterConfig( + {}, + entity + ? [ + { + name: 'One', + fields: [{ fieldName: 'id', fieldType: 'Long' }], + relationships: [{ relationshipName: 'relationship', otherEntityName: 'Two', relationshipType: 'many-to-one' }], + }, + { + name: 'Two', + fields: [ + { fieldName: 'id', fieldType: 'Long' }, + { fieldName: 'name', fieldType: 'String' }, + ], + relationships: [ + { relationshipName: 'relationship1', otherEntityName: 'One', relationshipType: 'many-to-one' }, + { relationshipName: 'relationship2', otherEntityName: 'Two', relationshipType: 'many-to-one' }, + ], + }, + ] + : undefined, + ) + .commitFiles() + .withOptions({ blueprint: 'foo-sbs' }) + .onGenerator(generator => { + spy = addSpies(generator); + }); + + // simulate a sbs blueprint + Object.defineProperty(context.mockedGenerators[`jhipster-foo-sbs:${generatorName}`].prototype, 'sbsBlueprint', { + get() { + return true; + }, + enumerable: true, + configurable: true, + }); + + result = await context; + }); + after(() => { + result.cleanup(); + }); + it(`should compose with jhipster-foo:${generatorName} blueprint once`, () => { + expect(result.mockedGenerators[`jhipster-foo-sbs:${generatorName}`].callCount).toBe(1); + }); + it('should call every priority', () => { + expect(spy.prioritiesSpy.callCount).toBe(spy.prioritiesCount); + }); + [...PRIORITY_NAMES_LIST, ...workspacesPriorityList] + .filter(priority => !Object.values(ENTITY_PRIORITY_NAMES).includes(priority)) + .forEach(priority => { + it(`should call ${priority} tasks if implemented`, function () { + if (!spy.prioritiesTasks[priority]) { + this.skip(); + return; + } + expect(spy.prioritiesTasks[priority].callCount).toBe(1); + }); + }); + if (entity) { + [LOADING_ENTITIES, WRITING_ENTITIES, POST_WRITING_ENTITIES].forEach(priority => { + it(`should call ${priority} tasks once`, function () { + if (!spy.prioritiesTasks[priority]) { + this.skip(); + return; + } + expect(spy.prioritiesTasks[priority].callCount).toBe(1); + }); + }); + [CONFIGURING_EACH_ENTITY, PREPARING_EACH_ENTITY, POST_PREPARING_EACH_ENTITY].forEach(priority => { + it(`should call ${priority} tasks twice`, function () { + if (!spy.prioritiesTasks[priority]) { + this.skip(); + return; + } + expect(spy.prioritiesTasks[priority].callCount).toBe(2); + }); + }); + it(`should call ${PREPARING_EACH_ENTITY_FIELD} tasks 3 times`, function () { + if (!spy.prioritiesTasks[PREPARING_EACH_ENTITY_FIELD]) { + this.skip(); + return; + } + expect(spy.prioritiesTasks[PREPARING_EACH_ENTITY_FIELD].callCount).toBe(3); + }); + it(`should call ${PREPARING_EACH_ENTITY_RELATIONSHIP} tasks 3 times`, function () { + if (!spy.prioritiesTasks[PREPARING_EACH_ENTITY_RELATIONSHIP]) { + this.skip(); + return; + } + expect(spy.prioritiesTasks[PREPARING_EACH_ENTITY_RELATIONSHIP].callCount).toBe(3); + }); + } + }); +}; + +export const shouldSupportFeatures = Generator => { + it('should support features parameter', () => { + const instance = new Generator( + [], + { help: true, namespace: 'foo', resolved: 'bar', env: { cwd: 'foo' }, sharedData: {} }, + { unique: 'bar' }, + ); + expect(instance.features.unique).toBe('bar'); + }); +}; diff --git a/test/support/tests.mjs b/test/support/tests.mjs deleted file mode 100644 index 697d499bc244..000000000000 --- a/test/support/tests.mjs +++ /dev/null @@ -1,435 +0,0 @@ -import path, { dirname } from 'path'; -import { existsSync } from 'fs'; -import { fileURLToPath } from 'url'; -import sinon from 'sinon'; -import { expect } from 'esmocha'; - -import { buildJHipster } from '../../cli/index.mjs'; -import { GENERATOR_JHIPSTER } from '../../generators/generator-constants.mjs'; -import { skipPrettierHelpers as helpers } from '../../testing/index.mjs'; -import * as GeneratorList from '../../generators/generator-list.mjs'; -import { PRIORITY_NAMES, ENTITY_PRIORITY_NAMES, PRIORITY_NAMES_LIST } from '../../generators/base-application/priorities.mjs'; -import { WORKSPACES_PRIORITY_NAMES } from '../../generators/base-workspaces/priorities.mjs'; - -const workspacesPriorityList = Object.values(WORKSPACES_PRIORITY_NAMES); - -const { - CONFIGURING_EACH_ENTITY, - LOADING_ENTITIES, - PREPARING_EACH_ENTITY, - PREPARING_EACH_ENTITY_FIELD, - PREPARING_EACH_ENTITY_RELATIONSHIP, - POST_PREPARING_EACH_ENTITY, - WRITING_ENTITIES, - POST_WRITING_ENTITIES, -} = PRIORITY_NAMES; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -export const getCommandHelpOutput = async command => { - const program = await buildJHipster(); - const cmd = command ? program.commands.find(cmd => cmd.name() === command) : program; - if (!cmd) { - throw new Error(`Command ${command} not found.`); - } - if (command) { - await cmd._lazyBuildCommandCallBack(); - } - return cmd.configureOutput({ getOutHelpWidth: () => 1000, getErrHelpWidth: () => 1000 }).helpInformation(); -}; - -export const testOptions = data => { - const { generatorPath, customOptions, contextBuilder = () => helpers.create(generatorPath) } = data; - let runResult; - before(async () => { - runResult = await contextBuilder() - .withOptions({ ...customOptions }) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should write options to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customOptions }); - }); -}; - -const skipWritingPriorities = ['writing', 'writingEntities', 'postWriting', 'postWritingEntities']; - -export const basicTests = data => { - const { - generatorPath, - customPrompts, - requiredConfig, - defaultConfig, - getTemplateData = generator => generator.sharedData.getApplication(), - contextBuilder = () => helpers.create(generatorPath), - } = data; - describe('with default options', () => { - let runResult; - before(async () => { - runResult = await contextBuilder() - .withOptions({ - skipPrompts: true, - configure: true, - skipPriorities: skipWritingPriorities, - }) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should write default config to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); - }); - it('should load default config into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining(defaultConfig)); - }); - }); - describe('with defaults option', () => { - let runResult; - before(async () => { - runResult = await contextBuilder().withOptions({ defaults: true, skipPriorities: skipWritingPriorities }).run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should write default config to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); - }); - it('should load default config into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining(requiredConfig)); - }); - }); - describe('with custom prompt values', () => { - let runResult; - describe('and default options', () => { - before(async () => { - runResult = await contextBuilder() - .withOptions({ configure: true, skipPriorities: skipWritingPriorities }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should show prompts and write prompt values to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customPrompts }); - }); - it('should load default config with prompt values into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...customPrompts })); - }); - }); - describe('and defaults option', () => { - before(async () => { - runResult = await contextBuilder() - .withOptions({ defaults: true, skipPriorities: skipWritingPriorities }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should not show prompts and write default config to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); - }); - it('should load default config into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig })); - }); - }); - describe('and skipPrompts option', () => { - let runResult; - before(async () => { - runResult = await contextBuilder() - .withOptions({ skipPrompts: true, skipPriorities: skipWritingPriorities }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should not show prompts and write required config to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: requiredConfig }); - }); - it('should load default config and required config into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig })); - }); - }); - describe('and existing config', () => { - let runResult; - const existing = { baseName: 'existing' }; - before(async () => { - runResult = await contextBuilder() - .withJHipsterConfig(existing) - .withOptions({ skipPriorities: skipWritingPriorities }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should not show prompts and write required config to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { ...requiredConfig, ...existing } }); - }); - it('should load default config and required config into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...requiredConfig, ...existing })); - }); - }); - describe('and askAnswered option on an existing project', () => { - let runResult; - before(async () => { - runResult = await contextBuilder() - .withJHipsterConfig({ baseName: 'existing' }) - .withOptions({ - askAnswered: true, - skipPriorities: ['writing', 'writingEntities', 'postWriting', 'postWritingEntities'], - }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should show prompts and write prompt values to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: customPrompts }); - }); - it('should load default config and prompt values into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual(expect.objectContaining({ ...defaultConfig, ...customPrompts })); - }); - }); - describe('and add option on an existing project', () => { - let runResult; - const existingConfig = { baseName: 'existing' }; - before(async () => { - runResult = await contextBuilder() - .withJHipsterConfig(existingConfig) - .withOptions({ - add: true, - skipPriorities: ['writing', 'writingEntities', 'postWriting', 'postWritingEntities'], - }) - .withAnswers(customPrompts) - .run(); - }); - after(() => { - runResult.cleanup(); - }); - it('should show prompts and write prompt values to .yo-rc.json', () => { - runResult.assertJsonFileContent('.yo-rc.json', { [GENERATOR_JHIPSTER]: { ...customPrompts, ...existingConfig } }); - }); - it('should load default config and prompt values into the context', () => { - expect(getTemplateData(runResult.generator)).toEqual( - expect.objectContaining({ ...defaultConfig, ...customPrompts, ...existingConfig }), - ); - }); - }); - }); -}; - -export const testBlueprintSupport = (generatorName, options = {}) => { - if (typeof options === 'boolean') { - options = { skipSbsBlueprint: options }; - } - const { skipSbsBlueprint = false, entity = false } = options; - - let generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.cjs`); - if (!existsSync(generatorPath)) { - generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.js`); - } - if (!existsSync(generatorPath)) { - generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.mts`); - } - if (!existsSync(generatorPath)) { - generatorPath = path.join(__dirname, `../../generators/${generatorName}/index.mjs`); - } - const addSpies = generator => { - const { taskPrefix = '' } = generator.features; - const apiPrefix = taskPrefix ? '' : '_'; - const prioritiesSpy = sinon.spy(); - const prioritiesTasks = []; - let prioritiesCount = 0; - [...PRIORITY_NAMES_LIST, ...workspacesPriorityList].forEach(priority => { - let callback; - if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(generator), `${taskPrefix}${priority}`)) { - prioritiesCount++; - callback = prioritiesSpy; - } else { - callback = () => { - throw new Error(`${apiPrefix}${priority} should not be called`); - }; - } - const property = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(generator), `${apiPrefix}${priority}`); - if (property) { - const task = sinon.spy(); - prioritiesTasks[priority] = task; - if (property.value && typeof property.value === 'function') { - generator[`${apiPrefix}${priority}`] = () => { - callback(); - return { task }; - }; - } else if (property.get) { - Object.defineProperty(generator, `${apiPrefix}${priority}`, { - get() { - callback(); - return { task }; - }, - enumerable: true, - configurable: true, - }); - } - } - }); - return { prioritiesSpy, prioritiesCount, prioritiesTasks }; - }; - describe('with blueprint', () => { - let result; - let spy; - before(async () => { - result = await helpers - .run(generatorPath) - .withMockedGenerators([ - `jhipster-foo:${generatorName}`, - // Mock every generator except the generator been tested - ...Object.values(GeneratorList) - .filter(gen => gen !== generatorName) - .map(gen => `jhipster:${gen}`), - ]) - .withJHipsterConfig() - .withOptions({ blueprint: 'foo' }) - .onGenerator(generator => { - spy = addSpies(generator); - }); - }); - after(() => { - result.cleanup(); - }); - it(`should compose with jhipster-foo:${generatorName} blueprint once`, () => { - expect(result.mockedGenerators[`jhipster-foo:${generatorName}`].callCount).toBe(1); - }); - it('should not call any priority', () => { - expect(spy.prioritiesSpy.callCount).toBe(0); - }); - }); - describe('with sbs blueprint', () => { - let result; - let spy; - before(async function () { - if (skipSbsBlueprint) { - this.skip(); - } - const context = helpers - .run(generatorPath) - .withMockedGenerators([ - `jhipster-foo-sbs:${generatorName}`, - // Mock every generator except the generator been tested - ...Object.values(GeneratorList) - .filter(gen => gen !== generatorName) - .map(gen => `jhipster:${gen}`), - ]) - .withJHipsterConfig( - {}, - entity - ? [ - { - name: 'One', - fields: [{ fieldName: 'id', fieldType: 'Long' }], - relationships: [{ relationshipName: 'relationship', otherEntityName: 'Two', relationshipType: 'many-to-one' }], - }, - { - name: 'Two', - fields: [ - { fieldName: 'id', fieldType: 'Long' }, - { fieldName: 'name', fieldType: 'String' }, - ], - relationships: [ - { relationshipName: 'relationship1', otherEntityName: 'One', relationshipType: 'many-to-one' }, - { relationshipName: 'relationship2', otherEntityName: 'Two', relationshipType: 'many-to-one' }, - ], - }, - ] - : undefined, - ) - .commitFiles() - .withOptions({ blueprint: 'foo-sbs' }) - .onGenerator(generator => { - spy = addSpies(generator); - }); - - // simulate a sbs blueprint - Object.defineProperty(context.mockedGenerators[`jhipster-foo-sbs:${generatorName}`].prototype, 'sbsBlueprint', { - get() { - return true; - }, - enumerable: true, - configurable: true, - }); - - result = await context; - }); - after(() => { - result.cleanup(); - }); - it(`should compose with jhipster-foo:${generatorName} blueprint once`, () => { - expect(result.mockedGenerators[`jhipster-foo-sbs:${generatorName}`].callCount).toBe(1); - }); - it('should call every priority', () => { - expect(spy.prioritiesSpy.callCount).toBe(spy.prioritiesCount); - }); - [...PRIORITY_NAMES_LIST, ...workspacesPriorityList] - .filter(priority => !Object.values(ENTITY_PRIORITY_NAMES).includes(priority)) - .forEach(priority => { - it(`should call ${priority} tasks if implemented`, function () { - if (!spy.prioritiesTasks[priority]) { - this.skip(); - return; - } - expect(spy.prioritiesTasks[priority].callCount).toBe(1); - }); - }); - if (entity) { - [LOADING_ENTITIES, WRITING_ENTITIES, POST_WRITING_ENTITIES].forEach(priority => { - it(`should call ${priority} tasks once`, function () { - if (!spy.prioritiesTasks[priority]) { - this.skip(); - return; - } - expect(spy.prioritiesTasks[priority].callCount).toBe(1); - }); - }); - [CONFIGURING_EACH_ENTITY, PREPARING_EACH_ENTITY, POST_PREPARING_EACH_ENTITY].forEach(priority => { - it(`should call ${priority} tasks twice`, function () { - if (!spy.prioritiesTasks[priority]) { - this.skip(); - return; - } - expect(spy.prioritiesTasks[priority].callCount).toBe(2); - }); - }); - it(`should call ${PREPARING_EACH_ENTITY_FIELD} tasks 3 times`, function () { - if (!spy.prioritiesTasks[PREPARING_EACH_ENTITY_FIELD]) { - this.skip(); - return; - } - expect(spy.prioritiesTasks[PREPARING_EACH_ENTITY_FIELD].callCount).toBe(3); - }); - it(`should call ${PREPARING_EACH_ENTITY_RELATIONSHIP} tasks 3 times`, function () { - if (!spy.prioritiesTasks[PREPARING_EACH_ENTITY_RELATIONSHIP]) { - this.skip(); - return; - } - expect(spy.prioritiesTasks[PREPARING_EACH_ENTITY_RELATIONSHIP].callCount).toBe(3); - }); - } - }); -}; - -export const shouldSupportFeatures = Generator => { - it('should support features parameter', () => { - const instance = new Generator( - [], - { help: true, namespace: 'foo', resolved: 'bar', env: { cwd: 'foo' }, sharedData: {} }, - { unique: 'bar' }, - ); - expect(instance.features.unique).toBe('bar'); - }); -}; diff --git a/testing/get-generator.mts b/testing/get-generator.mts deleted file mode 100644 index 2b0be6d31e35..000000000000 --- a/testing/get-generator.mts +++ /dev/null @@ -1,21 +0,0 @@ -import { fileURLToPath } from 'url'; -import { dirname, resolve } from 'path'; -import { existsSync } from 'fs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -export const getGeneratorFolder = (generatorName: string) => { - return resolve(__dirname, '../generators', generatorName); -}; - -const getGenerator = (generatorName: string) => { - const generatorFolder = getGeneratorFolder(generatorName); - const resolved = resolve(generatorFolder, 'index.mts'); - if (existsSync(resolved)) { - return resolved; - } - return resolve(generatorFolder, 'index.mjs'); -}; - -export default getGenerator; diff --git a/testing/get-generator.ts b/testing/get-generator.ts new file mode 100644 index 000000000000..3c576f7b3fad --- /dev/null +++ b/testing/get-generator.ts @@ -0,0 +1,21 @@ +import { fileURLToPath } from 'url'; +import { dirname, resolve } from 'path'; +import { existsSync } from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const getGeneratorFolder = (generatorName: string) => { + return resolve(__dirname, '../generators', generatorName); +}; + +const getGenerator = (generatorName: string) => { + const generatorFolder = getGeneratorFolder(generatorName); + const resolved = resolve(generatorFolder, 'index.ts'); + if (existsSync(resolved)) { + return resolved; + } + return resolve(generatorFolder, 'index.js'); +}; + +export default getGenerator; diff --git a/testing/helpers.mts b/testing/helpers.mts deleted file mode 100644 index 80139d330ca9..000000000000 --- a/testing/helpers.mts +++ /dev/null @@ -1,369 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import type { BaseEnvironmentOptions, GetGeneratorConstructor, BaseGenerator as YeomanGenerator } from '@yeoman/types'; -import { YeomanTest, RunContext, RunContextSettings, RunResult, result } from 'yeoman-test'; -import * as _ from 'lodash-es'; - -import { basename, join } from 'path'; -import EnvironmentBuilder from '../cli/environment-builder.mjs'; -import { JHIPSTER_CONFIG_DIR } from '../generators/generator-constants.mjs'; -import { GENERATOR_WORKSPACES } from '../generators/generator-list.mjs'; -import getGenerator from './get-generator.mjs'; -import { createJHipsterLogger, normalizePathEnd, parseCreationTimestamp } from '../generators/base/support/index.mjs'; -import BaseGenerator from '../generators/base/index.mjs'; -import type { JHipsterGeneratorOptions } from '../generators/base/api.mjs'; -import { getPackageRoot, isDistFolder } from '../lib/index.mjs'; - -type BaseEntity = any; -type GeneratorTestType = YeomanGenerator; -type GeneratorTestOptions = JHipsterGeneratorOptions; - -const { set } = _; - -type JHipsterRunResult = RunResult & { - /** - * First argument of mocked source calls. - */ - sourceCallsArg: Record; -}; - -const runResult = result as JHipsterRunResult; - -export { runResult, runResult as result }; - -const DEFAULT_TEST_SETTINGS = { forwardCwd: true }; -const DEFAULT_TEST_OPTIONS = { skipInstall: true }; -const DEFAULT_TEST_ENV_OPTIONS = { skipInstall: true, dryRun: false }; - -let defaultMockFactory; - -export const defineDefaults = async ({ mockFactory }: { mockFactory?: any } = {}) => { - if (mockFactory) { - defaultMockFactory = mockFactory; - } else if (!defaultMockFactory) { - try { - const { esmocha } = await import('esmocha'); - defaultMockFactory = esmocha.fn; - } catch { - throw new Error('loadMockFactory should be called before using mock'); - } - } -}; - -const createFiles = (workspaceFolder: string, configuration: Record, entities?: BaseEntity[]) => { - if (!configuration.baseName) { - throw new Error('baseName is required'); - } - workspaceFolder = workspaceFolder ? normalizePathEnd(workspaceFolder) : workspaceFolder; - const entityFiles = entities - ? Object.fromEntries(entities?.map(entity => [`${workspaceFolder}${JHIPSTER_CONFIG_DIR}/${entity.name}.json`, entity])) - : {}; - configuration = { entities: entities?.map(e => e.name), ...configuration }; - return { - [`${workspaceFolder}.yo-rc.json`]: { 'generator-jhipster': configuration }, - ...entityFiles, - }; -}; - -export type FakeBlueprintOptions = { - packageJson?: any; - generator?: string | string[]; - generatorContent?: string; - files?: Record; -}; - -export const createBlueprintFiles = ( - blueprintPackage: string, - { packageJson, generator = 'test-blueprint', generatorContent, files = {} }: FakeBlueprintOptions = {}, -) => { - generatorContent = - generatorContent ?? - `export const createGenerator = async env => { - const BaseGenerator = await env.requireGenerator('jhipster:base'); - return class extends BaseGenerator { - get [BaseGenerator.INITIALIZING]() { - return {}; - } - }; - }; - `; - const generators = Array.isArray(generator) ? generator : [generator]; - return { - [`node_modules/${blueprintPackage}/package.json`]: { - name: blueprintPackage, - version: '9.9.9', - type: 'module', - ...packageJson, - }, - ...Object.fromEntries( - generators.map(generator => [`node_modules/${blueprintPackage}/generators/${generator}/index.js`, generatorContent]), - ), - ...Object.fromEntries(Object.entries(files).map(([file, content]) => [`node_modules/${blueprintPackage}/${file}`, content])), - }; -}; - -class JHipsterRunContext extends RunContext { - public sharedSource!: Record; - private sharedData!: Record; - private sharedApplication!: Record; - private sharedControl!: Record; - private workspaceApplications: string[] = []; - private commonWorkspacesConfig!: Record; - private generateApplicationsSet = false; - - withJHipsterConfig(configuration?: Record, entities?: BaseEntity[]): this { - return this.withFiles( - createFiles('', { baseName: 'jhipster', creationTimestamp: parseCreationTimestamp('2020-01-01'), ...configuration }, entities), - ); - } - - withSkipWritingPriorities(): this { - return this.withOptions({ skipPriorities: ['writing', 'postWriting', 'writingEntities', 'postWritingEntities'] }); - } - - withWorkspacesCommonConfig(commonWorkspacesConfig: Record): this { - if (this.workspaceApplications.length > 0) { - throw new Error('Cannot be called after withWorkspaceApplication'); - } - this.commonWorkspacesConfig = { ...this.commonWorkspacesConfig, ...commonWorkspacesConfig }; - return this; - } - - withWorkspaceApplicationAtFolder(workspaceFolder: string, configuration: Record, entities?: BaseEntity[]): this { - if (this.generateApplicationsSet) { - throw new Error('Cannot be called after withWorkspaceApplication'); - } - this.workspaceApplications.push(workspaceFolder); - return this.withFiles(createFiles(workspaceFolder, { ...configuration, ...this.commonWorkspacesConfig }, entities)); - } - - withWorkspaceApplication(configuration: Record, entities?: BaseEntity[]): this { - return this.withWorkspaceApplicationAtFolder(configuration.baseName as string, configuration, entities); - } - - withWorkspacesSamples(...appNames: string[]): this { - return this.onBeforePrepare(async () => { - try { - const { default: deploymentTestSamples } = await import('../test/support/deployment-samples.mjs'); - for (const appName of appNames) { - const application = deploymentTestSamples[appName]; - if (!application) { - throw new Error(`Application ${appName} not found`); - } - this.withWorkspaceApplicationAtFolder(appName, deploymentTestSamples[appName]); - } - } catch { - throw new Error('Samples are currently not available to blueprint testing.'); - } - }); - } - - withGenerateWorkspaceApplications(generateWorkspaces: boolean = false): this { - return this.onBeforePrepare(() => { - this.generateApplicationsSet = true; - this.withOptions({ generateApplications: true, workspacesFolders: this.workspaceApplications, workspaces: generateWorkspaces }); - }); - } - - withJHipsterLookup(): this { - return this.withLookups([{ packagePaths: [getPackageRoot()], lookups: [`${isDistFolder() ? 'dist/' : ''}generators`] }] as any); - } - - /** - * Lookup generators at generator-jhipster's parent at a npm repository - * @param lookups generators relative folder - * @returns - */ - withParentBlueprintLookup(lookups = ['generators']): this { - const packageRootParent = join(getPackageRoot(), '..'); - if (basename(packageRootParent) === 'node_modules') { - this.withLookups([{ packagePaths: [join(packageRootParent, '..')], lookups }] as any); - } - return this; - } - - withFakeTestBlueprint(blueprintPackage: string, { packageJson, generator = 'test-blueprint' }: FakeBlueprintOptions = {}): this { - return this.withFiles(createBlueprintFiles(blueprintPackage, { packageJson, generator })) - .withLookups({ localOnly: true }) - .commitFiles(); - } - - withMockedSource(): this { - this.sharedSource = new Proxy( - {}, - { - get(target, name) { - if (!target[name]) { - target[name] = defaultMockFactory(); - } - return target[name]; - }, - set() { - return true; - }, - }, - ); - - return this.onBeforePrepare(() => defineDefaults()).withSharedData({ sharedSource: this.sharedSource }); - } - - withControl(sharedControl: Record): this { - this.sharedControl = this.sharedControl ?? {}; - Object.assign(this.sharedControl, sharedControl); - return this.withSharedData({ control: this.sharedControl }); - } - - withSharedApplication(sharedApplication: Record): this { - this.sharedApplication = this.sharedApplication ?? {}; - Object.assign(this.sharedApplication, sharedApplication); - return this.withSharedData({ sharedApplication: this.sharedApplication }); - } - - private withSharedData(sharedData: Record): this { - if (!this.sharedData) { - const applicationId = 'test-application'; - this.sharedData = { ...sharedData }; - set((this as any).envOptions, `sharedOptions.sharedData.applications.${applicationId}`, this.sharedData); - return this.withOptions({ - applicationId, - }); - } - Object.assign(this.sharedData, sharedData); - return this; - } - - async run(): Promise> { - const runResult = await super.run(); - if (this.sharedSource) { - const sourceCallsArg = Object.fromEntries( - Object.entries(this.sharedSource).map(([name, fn]) => [name, fn.mock.calls.map(args => args[0])]), - ); - if (sourceCallsArg.addEntitiesToClient) { - sourceCallsArg.addEntitiesToClient = (sourceCallsArg.addEntitiesToClient as any).map(({ application, entities }) => ({ - application: `Application[${application.baseName}]`, - entities: entities.map(entity => `Entity[${entity.name}]`), - })); - } - if (sourceCallsArg.addEntityToCache) { - sourceCallsArg.addEntityToCache = (sourceCallsArg.addEntityToCache as any).map(({ relationships, ...fields }) => ({ - ...fields, - relationships: relationships.map(rel => `Relationship[${rel.relationshipName}]`), - })); - } - const jhipsterRunResult = runResult as unknown as JHipsterRunResult; - jhipsterRunResult.sourceCallsArg = sourceCallsArg; - } - return runResult; - } -} - -class JHipsterTest extends YeomanTest { - constructor() { - super(); - - this.adapterOptions = { log: createJHipsterLogger() }; - } - - // @ts-expect-error testing types should be improved - run = YeomanGenerator>( - GeneratorOrNamespace: string | GetGeneratorConstructor, - settings?: RunContextSettings | undefined, - envOptions?: BaseEnvironmentOptions | undefined, - ): JHipsterRunContext { - return super.run(GeneratorOrNamespace, settings, envOptions).withAdapterOptions({ log: createJHipsterLogger() }) as any; - } - - runJHipster( - jhipsterGenerator: string, - settings?: RunContextSettings | undefined, - envOptions?: BaseEnvironmentOptions | undefined, - ): JHipsterRunContext { - return this.run(getGenerator(jhipsterGenerator), settings, envOptions); - } - - runTestBlueprintGenerator() { - const blueprintNS = 'jhipster:test-blueprint'; - class BlueprintedGenerator extends BaseGenerator { - async beforeQueue() { - if (!this.fromBlueprint) { - await this.composeWithBlueprints('test-blueprint'); - } - } - - rootGeneratorName(): string { - // Force fromBlueprint to be false. - return 'generator-jhipster'; - } - - get [BaseGenerator.INITIALIZING]() { - return {}; - } - } - return this.run(blueprintNS).withGenerators([[BlueprintedGenerator, { namespace: blueprintNS }]]); - } - - // @ts-expect-error testing types should be improved - create = YeomanGenerator>( - GeneratorOrNamespace: string | GetGeneratorConstructor, - settings?: RunContextSettings | undefined, - envOptions?: BaseEnvironmentOptions | undefined, - ): JHipsterRunContext { - return super.create(GeneratorOrNamespace, settings, envOptions) as any; - } - - createJHipster( - jhipsterGenerator: string, - settings?: RunContextSettings | undefined, - envOptions?: BaseEnvironmentOptions | undefined, - ): JHipsterRunContext { - return this.create(getGenerator(jhipsterGenerator), settings, envOptions); - } - - generateDeploymentWorkspaces(commonConfig?: Record) { - return this.runJHipster(GENERATOR_WORKSPACES) - .withWorkspacesCommonConfig(commonConfig ?? {}) - .withOptions({ - generateWorkspaces: true, - generateWith: 'docker', - skipPriorities: ['prompting'], - }); - } -} - -export function createTestHelpers(options: any = {}) { - const { environmentOptions = {} } = options; - const sharedOptions = { - ...DEFAULT_TEST_OPTIONS, - ...environmentOptions.sharedOptions, - }; - const helper = new JHipsterTest(); - helper.settings = { ...DEFAULT_TEST_SETTINGS, ...options.settings }; - helper.environmentOptions = { ...DEFAULT_TEST_ENV_OPTIONS, ...environmentOptions, sharedOptions }; - helper.generatorOptions = { ...DEFAULT_TEST_OPTIONS, ...options.generatorOptions }; - helper.createEnv = (...args) => EnvironmentBuilder.createEnv(...args) as any; - // @ts-expect-error testing types should be improved - helper.getRunContextType = () => JHipsterRunContext; - return helper; -} - -const commonTestOptions = { - reproducible: true, - skipChecks: true, - reproducibleTests: true, - noInsight: true, - useVersionPlaceholders: true, - fakeKeytool: true, -}; - -export const basicHelpers = createTestHelpers({ generatorOptions: { ...commonTestOptions } }); - -export const defaultHelpers = createTestHelpers({ - generatorOptions: { skipPrettier: true, ...commonTestOptions }, - environmentOptions: { dryRun: true }, -}); - -export const skipPrettierHelpers = createTestHelpers({ generatorOptions: { skipPrettier: true, ...commonTestOptions } }); - -export const dryRunHelpers = createTestHelpers({ - generatorOptions: { ...commonTestOptions }, - environmentOptions: { dryRun: true }, -}); diff --git a/testing/helpers.ts b/testing/helpers.ts new file mode 100644 index 000000000000..d57887f00f87 --- /dev/null +++ b/testing/helpers.ts @@ -0,0 +1,369 @@ +/* eslint-disable max-classes-per-file */ +import type { BaseEnvironmentOptions, GetGeneratorConstructor, BaseGenerator as YeomanGenerator } from '@yeoman/types'; +import { YeomanTest, RunContext, RunContextSettings, RunResult, result } from 'yeoman-test'; +import * as _ from 'lodash-es'; + +import { basename, join } from 'path'; +import EnvironmentBuilder from '../cli/environment-builder.mjs'; +import { JHIPSTER_CONFIG_DIR } from '../generators/generator-constants.js'; +import { GENERATOR_WORKSPACES } from '../generators/generator-list.js'; +import getGenerator from './get-generator.js'; +import { createJHipsterLogger, normalizePathEnd, parseCreationTimestamp } from '../generators/base/support/index.js'; +import BaseGenerator from '../generators/base/index.js'; +import type { JHipsterGeneratorOptions } from '../generators/base/api.js'; +import { getPackageRoot, isDistFolder } from '../lib/index.js'; + +type BaseEntity = any; +type GeneratorTestType = YeomanGenerator; +type GeneratorTestOptions = JHipsterGeneratorOptions; + +const { set } = _; + +type JHipsterRunResult = RunResult & { + /** + * First argument of mocked source calls. + */ + sourceCallsArg: Record; +}; + +const runResult = result as JHipsterRunResult; + +export { runResult, runResult as result }; + +const DEFAULT_TEST_SETTINGS = { forwardCwd: true }; +const DEFAULT_TEST_OPTIONS = { skipInstall: true }; +const DEFAULT_TEST_ENV_OPTIONS = { skipInstall: true, dryRun: false }; + +let defaultMockFactory; + +export const defineDefaults = async ({ mockFactory }: { mockFactory?: any } = {}) => { + if (mockFactory) { + defaultMockFactory = mockFactory; + } else if (!defaultMockFactory) { + try { + const { esmocha } = await import('esmocha'); + defaultMockFactory = esmocha.fn; + } catch { + throw new Error('loadMockFactory should be called before using mock'); + } + } +}; + +const createFiles = (workspaceFolder: string, configuration: Record, entities?: BaseEntity[]) => { + if (!configuration.baseName) { + throw new Error('baseName is required'); + } + workspaceFolder = workspaceFolder ? normalizePathEnd(workspaceFolder) : workspaceFolder; + const entityFiles = entities + ? Object.fromEntries(entities?.map(entity => [`${workspaceFolder}${JHIPSTER_CONFIG_DIR}/${entity.name}.json`, entity])) + : {}; + configuration = { entities: entities?.map(e => e.name), ...configuration }; + return { + [`${workspaceFolder}.yo-rc.json`]: { 'generator-jhipster': configuration }, + ...entityFiles, + }; +}; + +export type FakeBlueprintOptions = { + packageJson?: any; + generator?: string | string[]; + generatorContent?: string; + files?: Record; +}; + +export const createBlueprintFiles = ( + blueprintPackage: string, + { packageJson, generator = 'test-blueprint', generatorContent, files = {} }: FakeBlueprintOptions = {}, +) => { + generatorContent = + generatorContent ?? + `export const createGenerator = async env => { + const BaseGenerator = await env.requireGenerator('jhipster:base'); + return class extends BaseGenerator { + get [BaseGenerator.INITIALIZING]() { + return {}; + } + }; + }; + `; + const generators = Array.isArray(generator) ? generator : [generator]; + return { + [`node_modules/${blueprintPackage}/package.json`]: { + name: blueprintPackage, + version: '9.9.9', + type: 'module', + ...packageJson, + }, + ...Object.fromEntries( + generators.map(generator => [`node_modules/${blueprintPackage}/generators/${generator}/index.js`, generatorContent]), + ), + ...Object.fromEntries(Object.entries(files).map(([file, content]) => [`node_modules/${blueprintPackage}/${file}`, content])), + }; +}; + +class JHipsterRunContext extends RunContext { + public sharedSource!: Record; + private sharedData!: Record; + private sharedApplication!: Record; + private sharedControl!: Record; + private workspaceApplications: string[] = []; + private commonWorkspacesConfig!: Record; + private generateApplicationsSet = false; + + withJHipsterConfig(configuration?: Record, entities?: BaseEntity[]): this { + return this.withFiles( + createFiles('', { baseName: 'jhipster', creationTimestamp: parseCreationTimestamp('2020-01-01'), ...configuration }, entities), + ); + } + + withSkipWritingPriorities(): this { + return this.withOptions({ skipPriorities: ['writing', 'postWriting', 'writingEntities', 'postWritingEntities'] }); + } + + withWorkspacesCommonConfig(commonWorkspacesConfig: Record): this { + if (this.workspaceApplications.length > 0) { + throw new Error('Cannot be called after withWorkspaceApplication'); + } + this.commonWorkspacesConfig = { ...this.commonWorkspacesConfig, ...commonWorkspacesConfig }; + return this; + } + + withWorkspaceApplicationAtFolder(workspaceFolder: string, configuration: Record, entities?: BaseEntity[]): this { + if (this.generateApplicationsSet) { + throw new Error('Cannot be called after withWorkspaceApplication'); + } + this.workspaceApplications.push(workspaceFolder); + return this.withFiles(createFiles(workspaceFolder, { ...configuration, ...this.commonWorkspacesConfig }, entities)); + } + + withWorkspaceApplication(configuration: Record, entities?: BaseEntity[]): this { + return this.withWorkspaceApplicationAtFolder(configuration.baseName as string, configuration, entities); + } + + withWorkspacesSamples(...appNames: string[]): this { + return this.onBeforePrepare(async () => { + try { + const { default: deploymentTestSamples } = await import('../test/support/deployment-samples.js'); + for (const appName of appNames) { + const application = deploymentTestSamples[appName]; + if (!application) { + throw new Error(`Application ${appName} not found`); + } + this.withWorkspaceApplicationAtFolder(appName, deploymentTestSamples[appName]); + } + } catch { + throw new Error('Samples are currently not available to blueprint testing.'); + } + }); + } + + withGenerateWorkspaceApplications(generateWorkspaces: boolean = false): this { + return this.onBeforePrepare(() => { + this.generateApplicationsSet = true; + this.withOptions({ generateApplications: true, workspacesFolders: this.workspaceApplications, workspaces: generateWorkspaces }); + }); + } + + withJHipsterLookup(): this { + return this.withLookups([{ packagePaths: [getPackageRoot()], lookups: [`${isDistFolder() ? 'dist/' : ''}generators`] }] as any); + } + + /** + * Lookup generators at generator-jhipster's parent at a npm repository + * @param lookups generators relative folder + * @returns + */ + withParentBlueprintLookup(lookups = ['generators']): this { + const packageRootParent = join(getPackageRoot(), '..'); + if (basename(packageRootParent) === 'node_modules') { + this.withLookups([{ packagePaths: [join(packageRootParent, '..')], lookups }] as any); + } + return this; + } + + withFakeTestBlueprint(blueprintPackage: string, { packageJson, generator = 'test-blueprint' }: FakeBlueprintOptions = {}): this { + return this.withFiles(createBlueprintFiles(blueprintPackage, { packageJson, generator })) + .withLookups({ localOnly: true }) + .commitFiles(); + } + + withMockedSource(): this { + this.sharedSource = new Proxy( + {}, + { + get(target, name) { + if (!target[name]) { + target[name] = defaultMockFactory(); + } + return target[name]; + }, + set() { + return true; + }, + }, + ); + + return this.onBeforePrepare(() => defineDefaults()).withSharedData({ sharedSource: this.sharedSource }); + } + + withControl(sharedControl: Record): this { + this.sharedControl = this.sharedControl ?? {}; + Object.assign(this.sharedControl, sharedControl); + return this.withSharedData({ control: this.sharedControl }); + } + + withSharedApplication(sharedApplication: Record): this { + this.sharedApplication = this.sharedApplication ?? {}; + Object.assign(this.sharedApplication, sharedApplication); + return this.withSharedData({ sharedApplication: this.sharedApplication }); + } + + private withSharedData(sharedData: Record): this { + if (!this.sharedData) { + const applicationId = 'test-application'; + this.sharedData = { ...sharedData }; + set((this as any).envOptions, `sharedOptions.sharedData.applications.${applicationId}`, this.sharedData); + return this.withOptions({ + applicationId, + }); + } + Object.assign(this.sharedData, sharedData); + return this; + } + + async run(): Promise> { + const runResult = await super.run(); + if (this.sharedSource) { + const sourceCallsArg = Object.fromEntries( + Object.entries(this.sharedSource).map(([name, fn]) => [name, fn.mock.calls.map(args => args[0])]), + ); + if (sourceCallsArg.addEntitiesToClient) { + sourceCallsArg.addEntitiesToClient = (sourceCallsArg.addEntitiesToClient as any).map(({ application, entities }) => ({ + application: `Application[${application.baseName}]`, + entities: entities.map(entity => `Entity[${entity.name}]`), + })); + } + if (sourceCallsArg.addEntityToCache) { + sourceCallsArg.addEntityToCache = (sourceCallsArg.addEntityToCache as any).map(({ relationships, ...fields }) => ({ + ...fields, + relationships: relationships.map(rel => `Relationship[${rel.relationshipName}]`), + })); + } + const jhipsterRunResult = runResult as unknown as JHipsterRunResult; + jhipsterRunResult.sourceCallsArg = sourceCallsArg; + } + return runResult; + } +} + +class JHipsterTest extends YeomanTest { + constructor() { + super(); + + this.adapterOptions = { log: createJHipsterLogger() }; + } + + // @ts-expect-error testing types should be improved + run = YeomanGenerator>( + GeneratorOrNamespace: string | GetGeneratorConstructor, + settings?: RunContextSettings | undefined, + envOptions?: BaseEnvironmentOptions | undefined, + ): JHipsterRunContext { + return super.run(GeneratorOrNamespace, settings, envOptions).withAdapterOptions({ log: createJHipsterLogger() }) as any; + } + + runJHipster( + jhipsterGenerator: string, + settings?: RunContextSettings | undefined, + envOptions?: BaseEnvironmentOptions | undefined, + ): JHipsterRunContext { + return this.run(getGenerator(jhipsterGenerator), settings, envOptions); + } + + runTestBlueprintGenerator() { + const blueprintNS = 'jhipster:test-blueprint'; + class BlueprintedGenerator extends BaseGenerator { + async beforeQueue() { + if (!this.fromBlueprint) { + await this.composeWithBlueprints('test-blueprint'); + } + } + + rootGeneratorName(): string { + // Force fromBlueprint to be false. + return 'generator-jhipster'; + } + + get [BaseGenerator.INITIALIZING]() { + return {}; + } + } + return this.run(blueprintNS).withGenerators([[BlueprintedGenerator, { namespace: blueprintNS }]]); + } + + // @ts-expect-error testing types should be improved + create = YeomanGenerator>( + GeneratorOrNamespace: string | GetGeneratorConstructor, + settings?: RunContextSettings | undefined, + envOptions?: BaseEnvironmentOptions | undefined, + ): JHipsterRunContext { + return super.create(GeneratorOrNamespace, settings, envOptions) as any; + } + + createJHipster( + jhipsterGenerator: string, + settings?: RunContextSettings | undefined, + envOptions?: BaseEnvironmentOptions | undefined, + ): JHipsterRunContext { + return this.create(getGenerator(jhipsterGenerator), settings, envOptions); + } + + generateDeploymentWorkspaces(commonConfig?: Record) { + return this.runJHipster(GENERATOR_WORKSPACES) + .withWorkspacesCommonConfig(commonConfig ?? {}) + .withOptions({ + generateWorkspaces: true, + generateWith: 'docker', + skipPriorities: ['prompting'], + }); + } +} + +export function createTestHelpers(options: any = {}) { + const { environmentOptions = {} } = options; + const sharedOptions = { + ...DEFAULT_TEST_OPTIONS, + ...environmentOptions.sharedOptions, + }; + const helper = new JHipsterTest(); + helper.settings = { ...DEFAULT_TEST_SETTINGS, ...options.settings }; + helper.environmentOptions = { ...DEFAULT_TEST_ENV_OPTIONS, ...environmentOptions, sharedOptions }; + helper.generatorOptions = { ...DEFAULT_TEST_OPTIONS, ...options.generatorOptions }; + helper.createEnv = (...args) => EnvironmentBuilder.createEnv(...args) as any; + // @ts-expect-error testing types should be improved + helper.getRunContextType = () => JHipsterRunContext; + return helper; +} + +const commonTestOptions = { + reproducible: true, + skipChecks: true, + reproducibleTests: true, + noInsight: true, + useVersionPlaceholders: true, + fakeKeytool: true, +}; + +export const basicHelpers = createTestHelpers({ generatorOptions: { ...commonTestOptions } }); + +export const defaultHelpers = createTestHelpers({ + generatorOptions: { skipPrettier: true, ...commonTestOptions }, + environmentOptions: { dryRun: true }, +}); + +export const skipPrettierHelpers = createTestHelpers({ generatorOptions: { skipPrettier: true, ...commonTestOptions } }); + +export const dryRunHelpers = createTestHelpers({ + generatorOptions: { ...commonTestOptions }, + environmentOptions: { dryRun: true }, +}); diff --git a/testing/index.mts b/testing/index.mts deleted file mode 100644 index ddfc9adcabe5..000000000000 --- a/testing/index.mts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as getGenerator } from './get-generator.mjs'; -export * from './helpers.mjs'; diff --git a/testing/index.ts b/testing/index.ts new file mode 100644 index 000000000000..958f88ed57c2 --- /dev/null +++ b/testing/index.ts @@ -0,0 +1,2 @@ +export { default as getGenerator } from './get-generator.js'; +export * from './helpers.js';