UNPKG

gen-jhipster

Version:

VHipster - Spring Boot + Angular/React/Vue in one handy generator

399 lines (398 loc) 20 kB
/** * Copyright 2013-2026 the original author or authors from the JHipster project. * * This file is part of the JHipster project, see https://www.jhipster.tech/ * for more information. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES 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 'node:assert'; import chalk from 'chalk'; import { isFileStateModified } from 'mem-fs-editor/state'; import { clientFrameworkTypes, fieldTypes } from "../../lib/jhipster/index.js"; import { createNeedleCallback } from "../base-core/support/index.js"; import { ClientApplicationGenerator } from "../client/generator.js"; import { generateEntityClientEnumImports as getClientEnumImportsFormat, generateEntityClientFields as getHydratedEntityClientFields, generateEntityClientImports as formatEntityClientImports, } from "../client/support/index.js"; import { JAVA_WEBAPP_SOURCES_DIR } from "../index.js"; import { writeEslintClientRootConfigFile } from "../javascript-simple-application/generators/eslint/support/tasks.js"; import cleanupOldFilesTask from "./cleanup.js"; import { cleanupEntitiesFiles, postWriteEntityFiles, writeEntityFiles } from "./entity-files-vue.js"; import { writeEntitiesFiles, writeFiles } from "./files-vue.js"; import { convertTranslationsSupport, isTranslatedVueFile, translateVueFilesTransform } from "./support/index.js"; const { CommonDBTypes } = fieldTypes; const { VUE } = clientFrameworkTypes; const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN; export default class VueGenerator extends ClientApplicationGenerator { async beforeQueue() { if (!this.fromBlueprint) { await this.composeWithBlueprints(); } await this.dependsOnBootstrap('vue'); if (!this.delegateToBlueprint) { await this.dependsOnJHipster('jhipster:client:i18n'); await this.dependsOnJHipster('client'); await this.dependsOnJHipster('languages'); } } get configuring() { return this.asConfiguringTaskGroup({ configureDevServerPort({ control }) { if (this.jhipsterConfig.devServerPort === undefined) return; if (control.isJhipsterVersionLessThan('8.7.4')) { // Migrate old devServerPort with new one const { applicationIndex = 0 } = this.jhipsterConfigWithDefaults; this.jhipsterConfig.devServerPort = 9000 + applicationIndex; } }, }); } get [ClientApplicationGenerator.CONFIGURING]() { return this.delegateTasksToBlueprint(() => this.configuring); } get composing() { return this.asComposingTaskGroup({ async composing() { await this.composeWithJHipster('jhipster:client:common'); if (this.jhipsterConfigWithDefaults.websocket === 'spring-websocket') { await this.composeWithJHipster('jhipster:client:encode-csrf-token'); } }, }); } get [ClientApplicationGenerator.COMPOSING]() { return this.delegateTasksToBlueprint(() => this.composing); } get preparing() { return this.asPreparingTaskGroup({ loadPackageJson({ application }) { this.loadNodeDependenciesFromPackageJson(application.nodeDependencies, this.fetchFromInstalledJHipster('vue', 'resources', 'package.json')); }, applicationDefaults({ application, applicationDefaults }) { applicationDefaults({ __override__: true, webappEnumerationsDir: app => `${app.clientSrcDir}app/shared/model/enumerations/`, }); if (application.clientBundlerWebpack) { application.prettierFolders.push('webpack/'); } if (!application.backendTypeJavaAny && application.clientSrcDir !== JAVA_WEBAPP_SOURCES_DIR) { // When we have a java backend, 'src/**' is already added by java:bootstrap application.prettierFolders.push(`${application.clientSrcDir}**/`); } }, async javaNodeBuildPaths({ application }) { const { clientBundlerVite, clientBundlerWebpack, microfrontend, javaNodeBuildPaths } = application; javaNodeBuildPaths?.push('.postcssrc.js', 'tsconfig.json', 'tsconfig.app.json'); if (microfrontend) { javaNodeBuildPaths?.push('module-federation.config.cjs'); } if (clientBundlerWebpack) { javaNodeBuildPaths?.push('webpack/'); } else if (clientBundlerVite) { javaNodeBuildPaths?.push('vite.config.mts'); } }, prepareForTemplates({ application, source }) { application.prettierExtensions.push('html', 'vue', 'css', 'scss'); source.addWebpackConfig = args => { if (!application.clientBundlerWebpack) { throw new Error('This application is not webpack based'); } const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; const ignoreNonExisting = this.ignoreNeedlesError && 'Webpack configuration file not found'; this.editFile(webpackPath, { ignoreNonExisting }, createNeedleCallback({ needle: 'jhipster-needle-add-webpack-config', contentToAdd: `,${args.config}`, })); }; source.addEntitiesToClient = ({ application, entities }) => { const { enableTranslation } = application; for (const entity of entities) { const { entityInstance, entityFolderName, entityFileName, entityNameHumanized, entityPage, entityTranslationKeyMenuPath, entityAngularName, readOnly, } = entity; this.editFile(`${application.clientSrcDir}/app/router/entities.ts`, createNeedleCallback({ needle: 'jhipster-needle-add-entity-to-router-import', contentToAdd: `const ${entityAngularName} = () => import('@/entities/${entityFolderName}/${entityFileName}.vue'); const ${entityAngularName}Details = () => import('@/entities/${entityFolderName}/${entityFileName}-details.vue');${readOnly ? '' : ` const ${entityAngularName}Update = () => import('@/entities/${entityFolderName}/${entityFileName}-update.vue');`}`, contentToCheck: `import('@/entities/${entityFolderName}/${entityFileName}.vue');`, })); this.editFile(`${application.clientSrcDir}/app/router/entities.ts`, createNeedleCallback({ needle: 'jhipster-needle-add-entity-to-router', contentToAdd: `{ path: '${entityPage}', name: '${entityAngularName}', component: ${entityAngularName}, meta: { authorities: [Authority.USER] } }, { path: '${entityPage}/:${entityInstance}Id/view', name: '${entityAngularName}View', component: ${entityAngularName}Details, meta: { authorities: [Authority.USER] } },${readOnly ? '' : ` { path: '${entityPage}/new', name: '${entityAngularName}Create', component: ${entityAngularName}Update, meta: { authorities: [Authority.USER] } }, { path: '${entityPage}/:${entityInstance}Id/edit', name: '${entityAngularName}Edit', component: ${entityAngularName}Update, meta: { authorities: [Authority.USER] } },`}`, contentToCheck: `path: '${entityFileName}'`, })); this.editFile(`${application.clientSrcDir}/app/entities/entities.component.ts`, createNeedleCallback({ needle: 'jhipster-needle-add-entity-service-to-entities-component-import', contentToAdd: `import ${entityAngularName}Service from './${entityFolderName}/${entityFileName}.service';`, }), createNeedleCallback({ needle: 'add-entity-service-to-entities-component', contentToAdd: `provide('${entityInstance}Service', () => new ${entityAngularName}Service());`, })); this.editFile(`${application.clientSrcDir}/app/entities/entities-menu.vue`, createNeedleCallback({ needle: 'add-entity-to-menu', contentToAdd: `<b-dropdown-item to="/${entityPage}"> <font-awesome-icon icon="asterisk" /> <span>${enableTranslation ? `{{ $t('${entityTranslationKeyMenuPath}') }}` : entityNameHumanized}</span> </b-dropdown-item>`, contentToCheck: `<b-dropdown-item to="/${entityPage}">`, })); } }; }, }); } get [ClientApplicationGenerator.PREPARING]() { return this.delegateTasksToBlueprint(() => this.preparing); } get default() { return this.asDefaultTaskGroup({ async queueTranslateTransform({ application }) { const { clientI18nDir, enableTranslation, getWebappTranslation } = application; assert.ok(getWebappTranslation, 'getWebappTranslation is required'); 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({ clientI18nDir }); this.queueTransformStream({ name: 'converting vue translations', filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslationFile(file), refresh: false, }, transform); } }, }); } get [ClientApplicationGenerator.DEFAULT]() { return this.delegateTasksToBlueprint(() => this.default); } get writing() { return this.asWritingTaskGroup({ async cleanup({ control, application }) { await control.cleanupFiles({ '8.6.1': ['.eslintrc.json', '.eslintignore'], '8.7.4': [ [ application.microfrontend, `${application.srcMainWebapp}microfrontends/entities-menu-test.vue`, `${application.srcMainWebapp}microfrontends/entities-menu-component-test.ts`, `${application.srcMainWebapp}microfrontends/entities-router-test.ts`, ], ], '9.0.0-alpha.0': [ '.postcssrc.js', // Try to remove possibles old eslint config files 'eslint.config.js', 'eslint.config.mjs', ], '9.0.0-beta.1': [ `${application.clientSrcDir}app/config/error.constants.ts`, `${application.clientSrcDir}app/shared/security/authority.ts`, ], }); }, cleanupOldFilesTask, writeEslintClientRootConfigFile, writeFiles, }); } get [ClientApplicationGenerator.WRITING]() { return this.delegateTasksToBlueprint(() => this.writing); } get writingEntities() { return this.asWritingEntitiesTaskGroup({ cleanupEntitiesFiles, writeEntitiesFiles, writeEntityFiles, }); } get [ClientApplicationGenerator.WRITING_ENTITIES]() { return this.delegateTasksToBlueprint(() => this.writingEntities); } get postWriting() { return this.asPostWritingTaskGroup({ addPackageJsonScripts({ application, source }) { const { clientBundlerVite, clientBundlerWebpack, nodePackageManager } = application; if (clientBundlerVite) { source.mergeClientPackageJson({ scripts: { 'webapp:build:dev': `${nodePackageManager} run vite-build`, 'webapp:build:prod': `${nodePackageManager} run vite-build`, 'webapp:dev': `${nodePackageManager} run vite-serve`, 'webapp:serve': `${nodePackageManager} run vite-serve`, 'vite-serve': 'vite', 'vite-build': 'vite build', }, }); } else if (clientBundlerWebpack) { source.mergeClientPackageJson({ scripts: { 'webapp:build:dev': `${nodePackageManager} run webpack -- --mode development --env stats=minimal`, 'webapp:build:prod': `${nodePackageManager} run webpack -- --mode production --env stats=minimal`, 'webapp:dev': `${nodePackageManager} run webpack-dev-server -- --mode development --env stats=normal`, 'webpack-dev-server': 'webpack serve --config webpack/webpack.common.js', webpack: 'webpack --config webpack/webpack.common.js', }, }); } }, addMicrofrontendDependencies({ application, source }) { const { clientBundlerVite, clientBundlerWebpack, enableTranslation, microfrontend } = application; if (!microfrontend) return; if (clientBundlerVite) { source.mergeClientPackageJson({ devDependencies: { '@originjs/vite-plugin-federation': '1.3.6', }, }); } else if (clientBundlerWebpack) { source.mergeClientPackageJson({ devDependencies: { '@module-federation/enhanced': null, 'browser-sync-webpack-plugin': null, 'copy-webpack-plugin': null, 'css-loader': null, 'css-minimizer-webpack-plugin': null, 'html-webpack-plugin': null, 'mini-css-extract-plugin': null, 'postcss-loader': null, 'sass-loader': null, 'terser-webpack-plugin': null, 'ts-loader': null, 'vue-loader': null, 'vue-style-loader': null, webpack: null, 'webpack-bundle-analyzer': null, 'webpack-cli': null, 'webpack-dev-server': null, 'webpack-merge': null, 'workbox-webpack-plugin': null, ...(enableTranslation ? { 'folder-hash': null, 'merge-jsons-webpack-plugin': null, } : {}), }, }); } }, addIndexAsset({ source, application }) { if (!application.clientBundlerVite) return; source.addExternalResourceToRoot({ resource: '<script>const global = globalThis;</script>', comment: 'Workaround https://github.com/axios/axios/issues/5622', }); source.addExternalResourceToRoot({ resource: `<script type="module" src="./app/${application.microfrontend ? 'index.ts' : 'main.ts'}"></script>`, comment: 'Load vue main', }); }, sonar({ application, source }) { const { clientI18nDir, clientDistDir, clientSrcDir, temporaryDir } = application; source.addSonarProperties?.([ { key: 'sonar.test.inclusions', value: `${clientSrcDir}app/**/*.spec.ts`, valueSep: ', ' }, { key: 'sonar.testExecutionReportPaths', value: `${temporaryDir}test-results/jest/TESTS-results-sonar.xml` }, { key: 'sonar.javascript.lcov.reportPaths', value: `${temporaryDir}test-results/lcov.info` }, { key: 'sonar.exclusions', value: `${clientSrcDir}content/**/*.*, ${clientI18nDir}*.ts, ${clientDistDir}**/*.*`, valueSep: ', ', }, ]); }, }); } get [ClientApplicationGenerator.POST_WRITING]() { return this.delegateTasksToBlueprint(() => this.postWriting); } get postWritingEntities() { return this.asPostWritingEntitiesTaskGroup({ postWriteEntityFiles, }); } get [ClientApplicationGenerator.POST_WRITING_ENTITIES]() { return this.delegateTasksToBlueprint(() => this.postWritingEntities); } get end() { return this.asEndTaskGroup({ end({ application }) { this.log.ok(`Vue ${application.nodeDependencies.vue} application generated successfully.`); this.log.log(chalk.green(` Start your Webpack development server with: ${chalk.yellow.bold(`${application.nodePackageManager} start`)} `)); }, }); } get [ClientApplicationGenerator.END]() { return this.delegateTasksToBlueprint(() => this.end); } /** * @private * Generate Entity Client Field Default Values */ generateEntityClientFieldDefaultValues(fields) { const defaultVariablesValues = {}; fields.forEach(field => { const { fieldType, fieldName } = field; if (fieldType === TYPE_BOOLEAN) { defaultVariablesValues[fieldName] = `this.${fieldName} = this.${fieldName} ?? false;`; } }); return defaultVariablesValues; } 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); } }