UNPKG

gen-jhipster

Version:

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

873 lines (872 loc) 37.5 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 fs from 'node:fs'; import chalk from 'chalk'; import { isArray, lowerFirst, snakeCase, uniq, upperFirst } from 'lodash-es'; import { APPLICATION_TYPE_GATEWAY } from "../../lib/core/application-types.js"; import { clientFrameworkTypes, databaseTypes, entityOptions, fieldTypes, reservedKeywords, validations } from "../../lib/jhipster/index.js"; import { asPromptingTask } from "../base-application/support/task-type-inference.js"; import { inputIsNumber, inputIsSignedDecimalNumber, inputIsSignedNumber } from "./support/index.js"; const { isReservedPaginationWords, isReservedFieldName, isReservedTableName } = reservedKeywords; const { NO: NO_DATABASE, CASSANDRA, SQL } = databaseTypes; const { FilteringTypes, MapperTypes, ServiceTypes, PaginationTypes } = entityOptions; const { ANGULAR, REACT } = clientFrameworkTypes; const { NO: NO_FILTERING, JPA_METAMODEL } = FilteringTypes; const { NO: NO_PAGINATION, INFINITE_SCROLL, PAGINATION } = PaginationTypes; const { SERVICE_IMPL, SERVICE_CLASS } = ServiceTypes; const { MAPSTRUCT } = MapperTypes; const { CommonDBTypes, RelationalOnlyDBTypes, BlobTypes } = fieldTypes; const { BIG_DECIMAL, BOOLEAN, DOUBLE, DURATION, ENUM, FLOAT, INTEGER, INSTANT, LOCAL_DATE, LONG, STRING, UUID, ZONED_DATE_TIME, LOCAL_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 getFieldNameUndercored = (fields) => ['id'].concat(fields.map(field => snakeCase(field.fieldName))); export const askForMicroserviceJson = asPromptingTask(async function askForMicroserviceJson() { const context = this.entityData; if (this.jhipsterConfig.applicationType !== APPLICATION_TYPE_GATEWAY || context.configExisted) { return undefined; } const databaseType = this.jhipsterConfig.databaseType; const answers = await this.prompt([ { 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}/`; }, }, ]); 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; } }); export const askForUpdate = asPromptingTask(async 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 answers = await this.prompt([ { type: 'select', 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, }, ]); context.updateEntity = answers.updateEntity; if (context.updateEntity === 'none') { throw new Error(chalk.green('Aborting entity update, no changes were made.')); } }); export 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); } export 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; } return this.prompt([ { 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, }, ]).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.includes(field.fieldName)) { fields.splice(i, 1); } } this.entityConfig.fields = fields; } }); } export const askForRelationships = 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); }; export 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; } return this.prompt([ { 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, }, ]).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.includes(`${rel.relationshipName}:${rel.relationshipType}`)) { relationships.splice(i, 1); } } this.entityConfig.relationships = relationships; } }); } export 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; } return this.prompt([ { type: 'select', 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, }, ]).then(props => { this.entityConfig.jpaMetamodelFiltering = props.filtering === JPA_METAMODEL; }); } export function askForReadOnly() { const context = this.entityData; // don't prompt if data is imported from a file if (context.useConfigurationFile) { return undefined; } return this.prompt([ { type: 'confirm', name: 'readOnly', message: 'Is this entity read-only?', default: false, }, ]).then(props => { this.entityConfig.readOnly = props.readOnly; }); } export function askForDTO() { const context = this.entityData; // don't set 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; } // VHipster: Force DTO=mapstruct (bỏ prompt) this.entityConfig.dto = MAPSTRUCT; } export 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; } // VHipster: Force Service class (bỏ option "No") return this.prompt([ { type: 'select', name: 'service', message: 'Do you want to use separate service class for your business logic?', choices: [ { value: SERVICE_CLASS, name: 'Yes, generate a separate service class', }, { value: SERVICE_IMPL, name: 'Yes, generate a separate service interface and implementation', }, ], default: 0, }, ]).then(props => { this.entityConfig.service = props.service; }); } export 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; } return this.prompt([ { type: 'select', 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, }, ]).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 (!/^(\w*)$/.test(input)) { return 'Your field name cannot contain special characters'; } if (input === '') { return 'Your field name cannot be empty'; } if (input.startsWith(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: 'select', 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: LOCAL_TIME, name: 'LocalTime' }, ...(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 (!/^\w*$/.test(input)) { return 'Your enum name cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; } if (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 (const enumValue of enums) { if (/^\d.*/.test(enumValue)) { return `Enum value "${enumValue}" cannot start with a number`; } if (enumValue === '') { 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: 'select', 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) { return [ ...opts, { 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)) { return [ ...opts, { name: 'Minimum', value: MIN, }, { name: 'Maximum', value: MAX, }, ]; } return opts; }, default: [REQUIRED], }, { 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: (...args) => { // response param does not exist in newer versions of inquirer const [input, response] = args; 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: (...args) => { // response param does not exist in newer versions of inquirer const [input, response] = args; 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: 'select', name: 'otherEntityName', message: 'What is the other entity?', choices: () => [...this.getExistingEntityNames(), ...(application.generateBuiltInUserEntity ? ['User'] : [])], }, { type: 'input', name: 'relationshipName', validate: input => { if (!/^(\w*)$/.test(input)) { return 'Your relationship cannot contain special characters'; } if (input === '') { return 'Your relationship cannot be empty'; } if (input.startsWith(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: 'select', name: 'relationshipType', message: 'What is the type of the relationship?', choices: response => [ 'many-to-one', 'many-to-many', 'one-to-one', ...(application.generateBuiltInUserEntity && response.otherEntityName.toLowerCase() === 'user' ? [] : ['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 (application.generateBuiltInUserEntity && answers.otherEntityName.toLowerCase() === 'user)') { answers.bidirectional = false; return false; } if (!application.databaseTypeNeo4j && answers.relationshipType !== 'many-to-one') { // Relationships requires bidirectional. answers.bidirectional = true; return false; } return true; }, type: 'confirm', 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: [REQUIRED], }, // VHipster: VAuthz relationship prompts { when: () => application.vauthz, type: 'confirm', name: 'vauthzAddRelationship', message: 'Add this relationship to VAuthz policy?', default: true, }, { when: response => response.vauthzAddRelationship === true, type: 'input', name: 'vauthzRelationshipName', message: 'VAuthz relationship name (for policy):', default: response => response.relationshipName, }, { when: response => response.vauthzAddRelationship === true, type: 'confirm', name: 'vauthzIsSubject', message: 'Is this relationship to the subject (e.g. User/account)?', default: response => response.otherEntityName?.toLowerCase() === 'user', }, ]); const relationship = { relationshipSide: 'left', relationshipName: answers.relationshipName, otherEntityName: lowerFirst(answers.otherEntityName), relationshipType: answers.relationshipType, relationshipValidateRules: answers.relationshipValidateRules, otherEntityField: answers.otherEntityField, id: answers.id, otherEntityRelationshipName: answers.otherEntityRelationshipName, // VHipster: VAuthz relationship fields ...(application.vauthz && answers.vauthzAddRelationship && { vauthzRelationshipName: answers.vauthzRelationshipName, isSubject: answers.vauthzIsSubject, }), }; if (application.generateBuiltInUserEntity && answers.otherEntityName.toLowerCase() === 'user') { 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) { 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?.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(); } }