gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
591 lines (590 loc) • 23.2 kB
JavaScript
/**
* 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 { upperFirst } from 'lodash-es';
import { getConfigWithDefaults } from "../../lib/jhipster/default-application-options.js";
import { mutateData } from "../../lib/utils/index.js";
import BaseGenerator from "../base-simple-application/index.js";
import { CONTEXT_DATA_APPLICATION_KEY, CONTEXT_DATA_SOURCE_KEY } from "../base-simple-application/support/index.js";
import { JHIPSTER_CONFIG_DIR } from "../generator-constants.js";
import { CUSTOM_PRIORITIES, PRIORITY_NAMES, QUEUES } from "./priorities.js";
import { CONTEXT_DATA_APPLICATION_ENTITIES_KEY, getEntitiesFromDir } from "./support/index.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;
const PRIORITY_WITH_ENTITIES_TO_LOAD = new Set([LOADING_ENTITIES]);
const PRIORITY_WITH_ENTITIES = new Set([DEFAULT]);
const PRIORITY_WITH_FILTERED_ENTITIES = new Set([WRITING_ENTITIES, POST_WRITING_ENTITIES]);
const PRIORITY_WITH_SOURCE = new Set([PREPARING, POST_PREPARING, POST_WRITING, POST_WRITING_ENTITIES]);
const PRIORITY_WITH_APPLICATION_DEFAULTS = new Set([PREPARING, LOADING]);
const PRIORITY_WITH_APPLICATION = new Set([
LOADING,
PREPARING,
POST_PREPARING,
DEFAULT,
WRITING,
POST_WRITING,
PRE_CONFLICTS,
INSTALL,
END,
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,
]);
const getFirstArgForPriority = (priorityName) => ({
source: PRIORITY_WITH_SOURCE.has(priorityName),
application: PRIORITY_WITH_APPLICATION.has(priorityName),
applicationDefaults: PRIORITY_WITH_APPLICATION_DEFAULTS.has(priorityName),
entitiesToLoad: PRIORITY_WITH_ENTITIES_TO_LOAD.has(priorityName),
entities: PRIORITY_WITH_ENTITIES.has(priorityName),
filteredEntities: PRIORITY_WITH_FILTERED_ENTITIES.has(priorityName),
});
/**
* This is the base class for a generator that generates entities.
*/
export default class BaseApplicationGenerator 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, options, features) {
super(args, options, { storeJHipsterVersion: true, storeBlueprintVersion: true, ...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();
});
this.on('before:render', (sourceBasename, context) => {
const seed = `${context.entityClass}-${sourceBasename}${context.fakerSeed ?? ''}`;
this.resetEntitiesFakeData(seed);
});
}
get #application() {
return this.getContextData(CONTEXT_DATA_APPLICATION_KEY, {
factory: () => ({}),
});
}
get #entities() {
return this.getContextData(CONTEXT_DATA_APPLICATION_ENTITIES_KEY, { factory: () => new Map() });
}
get #entitiesForTasks() {
return [...this.#entities.entries()].map(([key, value]) => ({
description: key,
entityName: key,
entity: value,
}));
}
get #source() {
return this.getContextData(CONTEXT_DATA_SOURCE_KEY, { factory: () => ({}) });
}
/**
* JHipster config with default values fallback
*/
get jhipsterConfigWithDefaults() {
return getConfigWithDefaults(super.jhipsterConfigWithDefaults);
}
/**
* @deprecated use dependsOnBootstrap('app')
*/
dependsOnBootstrapApplication(options) {
return this.dependsOnJHipster('jhipster:base-application:bootstrap', options);
}
/**
* @deprecated use dependsOnBootstrap('server')
*/
dependsOnBootstrapApplicationServer(options) {
return this.dependsOnJHipster('jhipster:server:bootstrap', options);
}
/**
* @deprecated use dependsOnBootstrap('client')
*/
dependsOnBootstrapApplicationClient(options) {
return this.dependsOnJHipster('jhipster:client:bootstrap', options);
}
/**
* 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) {
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, create = false) {
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() {
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() {
function isBefore(e1, e2) {
return (e1.definition.annotations?.changelogDate ?? 0) - (e2.definition.annotations?.changelogDate ?? 0);
}
const configDir = this.getEntitiesConfigPath();
const entities = [];
for (const entityName of new Set([...(this.jhipsterConfig.entities || []), ...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() {
return {};
}
get preparingEachEntity() {
return {};
}
/**
* Priority API stub for blueprints.
*/
get preparingEachEntityField() {
return {};
}
/**
* Priority API stub for blueprints.
*/
get preparingEachEntityRelationship() {
return {};
}
/**
* Priority API stub for blueprints.
*/
get postPreparingEachEntity() {
return {};
}
/**
* Priority API stub for blueprints.
*/
get writingEntities() {
return {};
}
/**
* Priority API stub for blueprints.
*/
get postWritingEntities() {
return {};
}
/**
* Utility method to get typed objects for autocomplete.
*/
asConfiguringEachEntityTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asLoadingEntitiesTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asPreparingEachEntityTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asPreparingEachEntityFieldTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asPreparingEachEntityRelationshipTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asPostPreparingEachEntityTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asWritingEntitiesTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Utility method to get typed objects for autocomplete.
*/
asPostWritingEntitiesTaskGroup(taskGroup) {
return taskGroup;
}
/**
* Reset entities fake data seed.
* @param {string} seed
*/
resetEntitiesFakeData(seed) {
seed = `${this.#application.baseName}-${seed}`;
this.log.debug(`Resetting entities seed with '${seed}'`);
for (const entity of this.#entities.values()) {
entity.resetFakerSeed?.(seed);
}
}
getArgsForPriority(priorityName) {
const args = super.getArgsForPriority(priorityName);
let firstArg = this.getTaskFirstArgForPriority(priorityName);
if (args.length > 0) {
firstArg = { ...args[0], ...firstArg };
}
return [firstArg];
}
/**
* @protected
*/
getTaskFirstArgForPriority(priorityName) {
const { source, application, applicationDefaults, entitiesToLoad, entities, filteredEntities } = getFirstArgForPriority(priorityName);
const args = {};
if (source) {
args.source = this.#source;
}
if (application) {
args.application = this.#application;
}
if (applicationDefaults) {
args.applicationDefaults = (...args) => mutateData(this.#application, ...args.map(data => ({ __override__: false, ...data })));
}
if (entitiesToLoad) {
args.entitiesToLoad = this.#getEntitiesDataToLoad();
}
if (entities) {
args.entities = [...this.#entities.values()];
}
if (filteredEntities) {
const { entities: entitiesToFilter = [] } = this.options;
if (entitiesToFilter.length === 0) {
args.entities = [...this.#entities.values()];
}
else {
args.entities = [...this.#entities.values()].filter(entity => entitiesToFilter.includes(entity.name));
}
}
return args;
}
/**
* @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() {
const application = this.#application;
const builtInEntities = [];
if (application.generateBuiltInUserEntity) {
// Reorder User entity to be the first one to be loaded
builtInEntities.push('User');
}
if (application.generateBuiltInUserEntity && application.generateUserManagement) {
// Reorder User entity to be the first one to be loaded
builtInEntities.push('UserManagement');
}
if (application.generateBuiltInAuthorityEntity) {
// Reorder User entity to be the first one to be loaded
builtInEntities.push('Authority');
}
const entitiesToLoad = [...new Set([...builtInEntities, ...this.getExistingEntityNames()])];
return entitiesToLoad.map(entityName => {
const generator = this;
if (!this.#entities.has(entityName)) {
this.#entities.set(entityName, { name: entityName, fields: [], relationships: [] });
}
const entityBootstrap = this.#entities.get(entityName);
return {
entityName,
get entityStorage() {
return generator.getEntityConfig(entityName, true);
},
get entityConfig() {
return generator.getEntityConfig(entityName, true).createProxy();
},
entityBootstrap,
};
});
}
/**
* @private
* Get entities to prepare.
* @returns {object[]}
*/
#getEntitiesDataToPrepare() {
return this.#entitiesForTasks;
}
/**
* @private
* Get entities and fields to prepare.
* @returns {object[]}
*/
#getEntitiesFieldsDataToPrepare() {
return this.#getEntitiesDataToPrepare().flatMap(({ entity, entityName, ...data }) => {
if (!entity.fields)
return [];
return entity.fields.map(field => ({
entity,
entityName,
...data,
field,
fieldName: field.fieldName,
description: `${entityName}#${field.fieldName}`,
}));
});
}
/**
* @private
* Get entities and relationships to prepare.
* @returns {object[]}
*/
#getEntitiesRelationshipsDataToPrepare() {
return this.#getEntitiesDataToPrepare().flatMap(({ entity, entityName, ...data }) => {
if (!entity.relationships)
return [];
return entity.relationships.map(relationship => ({
entity,
entityName,
...data,
relationship,
relationshipName: relationship.relationshipName,
description: `${entityName}#${relationship.relationshipName}`,
}));
});
}
/**
* @private
* Get entities to post prepare.
* @returns {object[]}
*/
#getEntitiesDataToPostPrepare() {
return this.#getEntitiesDataToPrepare();
}
/**
* @private
* Queue entity tasks.
*/
#queueEntityTasks() {
this.queueTask({
queueName: CONFIGURING_EACH_ENTITY_QUEUE,
taskName: 'queueConfiguringEachEntity',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(CONFIGURING_EACH_ENTITY))
return;
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 }],
});
});
});
},
});
this.queueTask({
queueName: LOADING_ENTITIES_QUEUE,
taskName: 'queueLoadingEntities',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(LOADING_ENTITIES))
return;
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,
});
});
},
});
this.queueTask({
queueName: PREPARING_EACH_ENTITY_QUEUE,
taskName: 'queuePreparingEachEntity',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY))
return;
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 }],
});
});
});
},
});
this.queueTask({
queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE,
taskName: 'queuePreparingEachEntityField',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY_FIELD))
return;
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 }],
});
});
});
},
});
this.queueTask({
queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE,
taskName: 'queuePreparingEachEntityRelationship',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY_RELATIONSHIP))
return;
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 }],
});
});
});
},
});
this.queueTask({
queueName: POST_PREPARING_EACH_ENTITY_QUEUE,
taskName: 'queuePostPreparingEachEntity',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(POST_PREPARING_EACH_ENTITY))
return;
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 }],
});
});
});
},
});
this.queueTask({
queueName: WRITING_ENTITIES_QUEUE,
taskName: 'queueWritingEachEntity',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(WRITING_ENTITIES))
return;
const tasks = this.extractTasksFromPriority(WRITING_ENTITIES, { skip: false });
const args = this.getArgsForPriority(WRITING_ENTITIES);
tasks.forEach(task => {
this.queueTask({
...task,
args,
});
});
},
});
this.queueTask({
queueName: POST_WRITING_ENTITIES_QUEUE,
taskName: 'queuePostWritingEachEntity',
cancellable: true,
method: () => {
if (this.options.skipPriorities?.includes(POST_WRITING_ENTITIES))
return;
const tasks = this.extractTasksFromPriority(POST_WRITING_ENTITIES, { skip: false });
const args = this.getArgsForPriority(POST_WRITING_ENTITIES);
tasks.forEach(task => {
this.queueTask({
...task,
args,
});
});
},
});
}
}