gen-jhipster
Version:
Spring Boot + Angular/React/Vue in one handy generator
456 lines (454 loc) • 25.1 kB
JavaScript
/**
* Copyright 2013-2024 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import os from 'os';
import { lowerFirst } from 'lodash-es';
import chalk from 'chalk';
import { passthrough } from '@yeoman/transform';
import { isFileStateModified } from 'mem-fs-editor/state';
import BaseApplicationGenerator from '../base-application/index.js';
import { addFakerToEntity, loadEntitiesAnnotations, loadEntitiesOtherSide, prepareEntity as prepareEntityForTemplates, prepareField as prepareFieldForTemplates, prepareRelationship, stringifyApplicationData, } from '../base-application/support/index.js';
import { JAVA_DOCKER_DIR } from '../generator-constants.js';
import { GENERATOR_BOOTSTRAP, GENERATOR_COMMON, GENERATOR_PROJECT_NAME } from '../generator-list.js';
import { packageJson } from '../../lib/index.js';
import { loadLanguagesConfig } from '../languages/support/index.js';
import { loadAppConfig, loadDerivedAppConfig, loadStoredAppOptions } from '../app/support/index.js';
import { lookupCommandsConfigs } from '../../lib/command/lookup-commands-configs.js';
import { loadCommandConfigsIntoApplication, loadCommandConfigsKeysIntoTemplatesContext } from '../../lib/command/load.js';
import { getConfigWithDefaults } from '../../lib/jhipster/default-application-options.js';
import { removeFieldsWithNullishValues } from '../base/support/index.js';
import { convertFieldBlobType, getBlobContentType, isFieldBinaryType, isFieldBlobType } from '../../lib/application/field-types.js';
import { createAuthorityEntity, createUserEntity, createUserManagementEntity } from './utils.js';
import { exportJDLTransform, importJDLTransform } from './support/index.js';
const isWin32 = os.platform() === 'win32';
export default class BootstrapApplicationBase extends BaseApplicationGenerator {
constructor(args, options, features) {
super(args, options, { jhipsterBootstrap: false, ...features });
if (this.options.help)
return;
loadStoredAppOptions.call(this);
}
async beforeQueue() {
if (!this.fromBlueprint) {
await this.composeWithBlueprints();
}
if (this.delegateToBlueprint) {
throw new Error('Only sbs blueprint is supported');
}
const projectNameGenerator = (await this.dependsOnJHipster(GENERATOR_PROJECT_NAME));
projectNameGenerator.javaApplication = true;
await this.composeWithJHipster(GENERATOR_BOOTSTRAP);
}
get initializing() {
return this.asInitializingTaskGroup({
displayLogo() {
this.printDestinationInfo();
},
async jdlStore() {
if (this.jhipsterConfig.jdlStore) {
this.logger.warn('Storing configuration inside a JDL file is experimental');
this.logger.info(`Using JDL store ${this.jhipsterConfig.jdlStore}`);
const destinationPath = this.destinationPath();
const jdlStorePath = this.destinationPath(this.jhipsterConfig.jdlStore);
const { jdlDefinition } = this.options;
this.features.commitTransformFactory = () => exportJDLTransform({ destinationPath, jdlStorePath, jdlDefinition: jdlDefinition });
await this.pipeline({ refresh: true, pendingFiles: false }, importJDLTransform({ destinationPath, jdlStorePath, jdlDefinition: jdlDefinition }));
}
},
});
}
get [BaseApplicationGenerator.INITIALIZING]() {
return this.initializing;
}
get configuring() {
return this.asConfiguringTaskGroup({
configuring() {
if (this.jhipsterConfig.baseName === undefined) {
this.jhipsterConfig.baseName = 'jhipster';
}
},
});
}
get [BaseApplicationGenerator.CONFIGURING]() {
return this.configuring;
}
get loading() {
return this.asLoadingTaskGroup({
loadApplication({ application, control, applicationDefaults }) {
loadAppConfig({
config: this.jhipsterConfigWithDefaults,
application,
useVersionPlaceholders: this.useVersionPlaceholders,
});
loadLanguagesConfig({ application, config: this.jhipsterConfigWithDefaults, control });
applicationDefaults({
backendType: this.jhipsterConfig.backendType ?? 'Java',
syncUserWithIdp: this.jhipsterConfig.syncUserWithIdp,
packageJsonScripts: {},
clientPackageJsonScripts: {},
});
},
loadNodeDependencies({ application }) {
this.loadNodeDependencies(application.nodeDependencies, {
prettier: packageJson.dependencies.prettier,
'prettier-plugin-java': packageJson.dependencies['prettier-plugin-java'],
'prettier-plugin-packagejson': packageJson.dependencies['prettier-plugin-packagejson'],
});
this.loadNodeDependenciesFromPackageJson(application.nodeDependencies, this.fetchFromInstalledJHipster(GENERATOR_COMMON, 'resources', 'package.json'));
},
loadPackageJson({ application }) {
application.jhipsterPackageJson = packageJson;
},
});
}
get [BaseApplicationGenerator.LOADING]() {
return this.loading;
}
get preparing() {
return this.asPreparingTaskGroup({
/**
* Avoid having undefined keys in the application object when redering ejs templates
*/
async loadApplicationKeys({ application }) {
const { applyDefaults = getConfigWithDefaults, commandsConfigs = await lookupCommandsConfigs() } = this.options;
loadCommandConfigsIntoApplication({
source: applyDefaults(removeFieldsWithNullishValues(this.config.getAll())),
application,
commandsConfigs,
});
},
prepareApplication({ application, applicationDefaults }) {
loadDerivedAppConfig({ application });
applicationDefaults({
__override__: false,
nodePackageManager: 'npm',
dockerServicesDir: JAVA_DOCKER_DIR,
// TODO drop clientPackageManager
clientPackageManager: ({ nodePackageManager }) => nodePackageManager,
hipsterName: 'Java Hipster',
hipsterProductName: 'JHipster',
hipsterHomePageProductName: 'JHipster',
hipsterStackOverflowProductName: 'JHipster',
hipsterBugTrackerProductName: 'JHipster',
hipsterChatProductName: 'JHipster',
hipsterTwitterUsername: '@jhipster',
hipsterDocumentationLink: 'https://www.jhipster.tech/',
hipsterTwitterLink: 'https://twitter.com/jhipster',
hipsterProjectLink: 'https://github.com/jhipster/gen-jhipster',
hipsterStackoverflowLink: 'https://stackoverflow.com/tags/jhipster/info',
hipsterBugTrackerLink: 'https://github.com/jhipster/gen-jhipster/issues?state=open',
hipsterChatLink: 'https://gitter.im/jhipster/gen-jhipster',
backendTypeSpringBoot: ({ backendType }) => backendType === 'Java',
backendTypeJavaAny: ({ backendTypeSpringBoot }) => backendTypeSpringBoot,
clientFrameworkBuiltIn: ({ clientFramework }) => ['angular', 'vue', 'react'].includes(clientFramework),
});
},
userRelationship({ applicationDefaults }) {
applicationDefaults({
__override__: false,
anyEntityHasRelationshipWithUser: this.getExistingEntities().some(entity => (entity.definition.relationships ?? []).some(relationship => relationship.otherEntityName.toLowerCase() === 'user')),
});
},
syncUserWithIdp({ application, applicationDefaults }) {
if (!application.backendTypeSpringBoot)
return;
if (application.syncUserWithIdp === undefined && application.authenticationType === 'oauth2') {
applicationDefaults({
__override__: false,
syncUserWithIdp: data => data.databaseType !== 'no' && (data.applicationType === 'gateway' || data.anyEntityHasRelationshipWithUser),
});
}
else if (application.syncUserWithIdp && application.authenticationType !== 'oauth2') {
throw new Error('syncUserWithIdp is only supported with oauth2 authenticationType');
}
},
userManagement({ applicationDefaults }) {
applicationDefaults({
generateBuiltInUserEntity: ({ generateUserManagement, syncUserWithIdp }) => generateUserManagement || syncUserWithIdp,
generateBuiltInAuthorityEntity: ({ generateBuiltInUserEntity, databaseType }) => generateBuiltInUserEntity && databaseType !== 'cassandra',
});
},
});
}
get [BaseApplicationGenerator.PREPARING]() {
return this.preparing;
}
get configuringEachEntity() {
return this.asConfiguringEachEntityTaskGroup({
configureEntity({ entityStorage, entityConfig }) {
entityStorage.defaults({ fields: [], relationships: [], annotations: {} });
for (const field of entityConfig.fields.filter(field => field.fieldType === 'byte[]')) {
convertFieldBlobType(field);
entityStorage.save();
}
if (entityConfig.changelogDate) {
entityConfig.annotations.changelogDate = entityConfig.changelogDate;
delete entityConfig.changelogDate;
}
if (!entityConfig.annotations.changelogDate) {
entityConfig.annotations.changelogDate = this.dateFormatForLiquibase();
entityStorage.save();
}
},
configureRelationships({ entityName, entityStorage, entityConfig }) {
// Validate entity json relationship content
entityConfig.relationships.forEach(relationship => {
const { otherEntityName, relationshipType } = relationship;
assert(otherEntityName, `otherEntityName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`);
assert(relationshipType, `relationshipType is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`);
if (!relationship.relationshipSide) {
// Try to create relationshipSide based on best bet.
// @ts-ignore deprecated property
if (relationship.ownerSide !== undefined) {
// @ts-ignore deprecated property
relationship.relationshipSide = relationship.ownerSide ? 'left' : 'right';
}
else {
// Missing ownerSide (one-to-many/many-to-one relationships) depends on the otherSide existence.
const unidirectionalRelationship = !relationship.otherEntityRelationshipName;
const bidirectionalOneToManySide = !unidirectionalRelationship && relationship.relationshipType === 'one-to-many';
relationship.relationshipSide = unidirectionalRelationship || bidirectionalOneToManySide ? 'left' : 'right';
}
}
relationship.otherEntityName = lowerFirst(otherEntityName);
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`);
}
});
entityStorage.save();
},
});
}
get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() {
return this.configuringEachEntity;
}
get loadingEntities() {
return this.asLoadingEntitiesTaskGroup({
loadUser({ application, entitiesToLoad }) {
if (application.generateBuiltInUserEntity) {
const User = 'User';
const customUser = entitiesToLoad.find(entityToLoad => entityToLoad.entityName === User);
const bootstrap = customUser?.entityBootstrap;
if (!bootstrap) {
throw new Error('User entity should already be passed.');
}
const customUserData = customUser?.entityStorage.getAll() ?? {};
Object.assign(bootstrap, createUserEntity.call(this, { ...customUserData, ...customUserData.annotations }, application));
application.user = bootstrap;
}
},
loadUserManagement({ application, entitiesToLoad }) {
if (application.generateBuiltInUserEntity && application.generateUserManagement) {
const UserManagement = 'UserManagement';
const customUserManagement = entitiesToLoad.find(entityToLoad => entityToLoad.entityName === UserManagement);
const bootstrap = customUserManagement?.entityBootstrap;
if (!bootstrap) {
throw new Error('UserManagement entity should already be passed.');
}
const customUserManagementData = customUserManagement?.entityStorage.getAll() ?? {};
Object.assign(bootstrap, createUserManagementEntity.call(this, { ...customUserManagementData, ...customUserManagementData.annotations }, application));
application.userManagement = bootstrap;
}
},
loadAuthority({ application, entitiesToLoad }) {
if (application.generateBuiltInAuthorityEntity) {
const authority = 'Authority';
const customEntity = entitiesToLoad.find(entityToLoad => entityToLoad.entityName === authority);
const bootstrap = customEntity?.entityBootstrap;
if (!bootstrap) {
throw new Error('Authority entity should already be passed.');
}
const customEntityData = customEntity?.entityStorage.getAll() ?? {};
Object.assign(bootstrap, createAuthorityEntity.call(this, { ...customEntityData, ...customEntityData.annotations }, application));
application.authority = bootstrap;
}
},
loadingEntities({ application, entitiesToLoad }) {
for (const { entityName, entityBootstrap, entityStorage } of entitiesToLoad) {
if (!entityBootstrap.builtIn) {
let entity = entityStorage.getAll();
entity.name = entity.name ?? entityName;
entity = { ...entity, ...entity.annotations };
Object.assign(entityBootstrap, entity);
}
}
const entities = this.sharedData.getEntities().map(({ entity }) => entity);
loadEntitiesAnnotations(entities);
this.validateResult(loadEntitiesOtherSide(entities, { application }));
for (const entity of entities) {
if (!entity.builtIn) {
const invalidRelationship = entity.relationships.find(({ otherEntity }) => !otherEntity.builtIn && entity.microserviceName !== otherEntity.microserviceName);
if (invalidRelationship) {
throw new Error(`Microservice entities cannot have relationships with entities from other microservice: '${entity.name}.${invalidRelationship.relationshipName}'`);
}
}
for (const field of entity.fields) {
if (isFieldBinaryType(field)) {
if (isFieldBlobType(field)) {
field.fieldTypeBlobContent ??= getBlobContentType(field.fieldType);
}
if (application.databaseTypeCassandra || entity.databaseType === 'cassandra') {
// @ts-expect-error set another type
field.fieldType = 'ByteBuffer';
}
else if (isFieldBlobType(field)) {
field.fieldType = 'byte[]';
}
}
}
for (const relationship of entity.relationships) {
if (relationship.ownerSide === undefined) {
// ownerSide backward compatibility
relationship.ownerSide =
relationship.relationshipType === 'many-to-one' ||
(relationship.relationshipType !== 'one-to-many' && relationship.relationshipSide === 'left');
}
}
}
},
});
}
get [BaseApplicationGenerator.LOADING_ENTITIES]() {
return this.loadingEntities;
}
get preparingEachEntity() {
return this.asPreparingEachEntityTaskGroup({
async preparingEachEntity({ application, entity }) {
await addFakerToEntity(entity, application.nativeLanguage);
prepareEntityForTemplates(entity, this, application);
},
});
}
get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() {
return this.preparingEachEntity;
}
get preparingEachEntityField() {
return this.asPreparingEachEntityFieldTaskGroup({
prepareFieldsForTemplates({ entity, field }) {
prepareFieldForTemplates(entity, field, this);
},
});
}
get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_FIELD]() {
return this.preparingEachEntityField;
}
get preparingEachEntityRelationship() {
return this.asPreparingEachEntityRelationshipTaskGroup({
prepareRelationshipsForTemplates({ entity, relationship }) {
prepareRelationship(entity, relationship, this);
},
});
}
get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() {
return this.preparingEachEntityRelationship;
}
get postPreparingEachEntity() {
return this.asPostPreparingEachEntityTaskGroup({
hasRequiredRelationship({ entity }) {
entity.anyRelationshipIsRequired = entity.relationships.some(rel => rel.relationshipRequired || rel.id);
},
checkForCircularRelationships({ entity }) {
const detectCyclicRequiredRelationship = (entity, relatedEntities) => {
if (relatedEntities.has(entity))
return true;
relatedEntities.add(entity);
return entity.relationships
?.filter(rel => rel.relationshipRequired || rel.id)
.some(rel => detectCyclicRequiredRelationship(rel.otherEntity, new Set([...relatedEntities])));
};
entity.hasCyclicRequiredRelationship = detectCyclicRequiredRelationship(entity, new Set());
},
});
}
get [BaseApplicationGenerator.POST_PREPARING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity);
}
get default() {
return this.asDefaultTaskGroup({
/**
* Avoid having undefined keys in the application object when redering ejs templates
*/
async loadApplicationKeys({ application }) {
if (this.options.commandsConfigs) {
// Load keys passed from cli
loadCommandConfigsKeysIntoTemplatesContext({
templatesContext: application,
commandsConfigs: this.options.commandsConfigs,
});
}
// Load keys from main generators
loadCommandConfigsKeysIntoTemplatesContext({
templatesContext: application,
commandsConfigs: await lookupCommandsConfigs(),
});
},
task({ application }) {
const packageJsonFiles = [this.destinationPath('package.json')];
if (application.clientRootDir) {
packageJsonFiles.push(this.destinationPath(`${application.clientRootDir}package.json`));
}
const isPackageJson = file => packageJsonFiles.includes(file.path);
const populateNullValues = dependencies => {
if (!dependencies)
return;
for (const key of Object.keys(dependencies)) {
if (dependencies[key] === null && application.nodeDependencies[key]) {
dependencies[key] = application.nodeDependencies[key];
}
}
};
this.queueTransformStream({
name: 'updating package.json dependencies versions',
filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isPackageJson(file),
refresh: false,
}, passthrough(file => {
const contents = file.contents.toString();
if (contents.includes('null')) {
const content = JSON.parse(file.contents.toString());
populateNullValues(content.dependencies);
populateNullValues(content.devDependencies);
populateNullValues(content.peerDependencies);
file.contents = Buffer.from(`${JSON.stringify(content, null, 2)}\n`);
}
}));
},
});
}
get [BaseApplicationGenerator.DEFAULT]() {
return this.default;
}
/**
* Return the user home
*/
getUserHome() {
return process.env[isWin32 ? 'USERPROFILE' : 'HOME'];
}
printDestinationInfo(cwd = this.destinationPath()) {
this.log.log(chalk.green(' _______________________________________________________________________________________________________________\n'));
this.log.log(chalk.white(` Documentation for creating an application is at ${chalk.yellow('https://www.jhipster.tech/creating-an-app/')}
Application files will be generated in folder: ${chalk.yellow(cwd)}`));
if (process.cwd() === this.getUserHome()) {
this.log.log(chalk.red.bold('\n️⚠️ WARNING ⚠️ You are in your HOME folder!'));
this.log.log(chalk.red('This can cause problems, you should always create a new directory and run the jhipster command from here.'));
this.log.log(chalk.white(`See the troubleshooting section at ${chalk.yellow('https://www.jhipster.tech/installation/')}`));
}
this.log.log(chalk.green(' _______________________________________________________________________________________________________________\n'));
}
}