UNPKG

generator-pyhipster

Version:

Python (Flask) + Angular/React/Vue in one handy generator

946 lines (869 loc) 28.7 kB
/** * Copyright 2013-2022 the original author or authors from the JHipster project. * * This file is part of the JHipster project, see https://www.jhipster.tech/ * for more information. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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 no-console */ const path = require('path'); const shelljs = require('shelljs'); const ejs = require('ejs'); const _ = require('lodash'); const fs = require('fs'); const crypto = require('crypto'); const os = require('os'); const constants = require('./generator-constants'); const FileUtils = require('../jdl/utils/file-utils'); const LANGUAGES_MAIN_SRC_DIR = `${__dirname}/languages/templates/${constants.CLIENT_MAIN_SRC_DIR}`; module.exports = { rewrite, rewriteFile, replaceContent, classify, rewriteJSONFile, copyWebResource, renderContent, deepFind, escapeRegExp, getJavadoc, buildEnumInfo, getEnumInfo, copyObjectProps, decodeBase64, getDBTypeFromDBValue, getBase64Secret, getRandomHex, checkStringInFile, checkRegexInFile, loadYoRc, packageNameToNamespace, stringHashCode, gitExec, isGitInstalled, vueReplaceTranslation, vueAddPageToRouterImport, vueAddPageToRouter, vueAddPageServiceToMainImport, vueAddPageServiceToMain, vueAddPageProtractorConf, languageSnakeCase, languageToJavaLanguage, addSectionsCondition, mergeSections, }; const databaseTypes = require('../jdl/jhipster/database-types'); const SQL = databaseTypes.SQL; /** * Rewrite file with passed arguments * @param {object} args argument object (containing path, file, haystack, etc properties) * @param {object} generator reference to the generator */ function rewriteFile(args, generator) { let fullPath; if (args.path) { fullPath = generator.destinationPath(path.join(args.path, args.file)); } else { fullPath = generator.destinationPath(args.file); } if (!generator.env.sharedFs.existsInMemory(fullPath) && generator.env.sharedFs.existsInMemory(`${fullPath}.jhi`)) { fullPath = `${fullPath}.jhi`; } args.haystack = generator.fs.read(fullPath); const body = rewrite(args); generator.fs.write(fullPath, body); return args.haystack !== body; } /** * Replace content * @param {object} args argument object * @param {object} generator reference to the generator */ function replaceContent(args, generator) { let fullPath = generator.destinationPath(args.file); if (!generator.env.sharedFs.existsInMemory(fullPath) && generator.env.sharedFs.existsInMemory(`${fullPath}.jhi`)) { fullPath = `${fullPath}.jhi`; } const re = args.regex ? new RegExp(args.pattern, 'g') : args.pattern; const currentBody = generator.fs.read(fullPath); const newBody = currentBody.replace(re, args.content); generator.fs.write(fullPath, newBody); return newBody !== currentBody; } /** * Escape regular expressions. * * @param {string} str string * @returns {string} string with regular expressions escaped */ function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // eslint-disable-line } /** * Normalize line endings. * If in Windows is Git autocrlf used then need to replace \r\n with \n * to achieve consistent comparison result when comparing strings read from file. * * @param {string} str string * @returns {string} string where CRLF is replaced with LF in Windows */ function normalizeLineEndings(str) { const isWin32 = os.platform() === 'win32'; return isWin32 ? str.replace(/\r\n/g, '\n') : str; } /** * Change spaces sequences and '>' to allow any number of spaces or new line prefix * * @param {string} str string * @returns {string} string where CRLF is replaced with LF in Windows */ function convertToPrettierExpressions(str) { return str.replace(/\s+/g, '([\\s\n]*)').replace(/>+/g, '(\n?[\\s]*)>'); } /** * Rewrite using the passed argument object. * * @param {object} args arguments object (containing splicable, haystack, needle properties) to be used * @param {string[]} args.splicable - content to be added. * @param {boolean} [args.prettierAware] - apply prettier aware expressions before looking for applied needles. * @param {string|RegExp} [args.regexp] - use another content for looking for applied needles. * @returns {*} re-written file */ function rewrite(args) { // check if splicable is already in the body text let re; if (args.regexp) { re = args.regexp; if (!re.test) { re = escapeRegExp(re); } } else { re = args.splicable.map(line => `\\s*${escapeRegExp(normalizeLineEndings(line))}`).join('\n'); } if (!re.test) { if (args.prettierAware) { re = convertToPrettierExpressions(re); } re = new RegExp(re); } if (re.test(normalizeLineEndings(args.haystack))) { return args.haystack; } const lines = args.haystack.split('\n'); let otherwiseLineIndex = -1; lines.forEach((line, i) => { if (line.includes(args.needle)) { otherwiseLineIndex = i; } }); if (otherwiseLineIndex === -1) { console.warn(`Needle ${args.needle} not found at file ${args.file}`); return args.haystack; } let spaces = 0; while (lines[otherwiseLineIndex].charAt(spaces) === ' ') { spaces += 1; } let spaceStr = ''; // eslint-disable-next-line no-cond-assign while ((spaces -= 1) >= 0) { spaceStr += ' '; } lines.splice(otherwiseLineIndex, 0, args.splicable.map(line => spaceStr + line).join('\n')); return lines.join('\n'); } /** * Convenient function to convert string into valid java class name * Note: _.classify uses _.titleize which lowercase the string, * so if the user chooses a proper ClassName it will not rename properly * * @param string string to 'class'-ify * @returns {string} 'class'-ified string */ function classify(string) { string = string.replace(/[\W_](\w)/g, match => ` ${match[1].toUpperCase()}`).replace(/\s/g, ''); return string.charAt(0).toUpperCase() + string.slice(1); } /** * Rewrite JSON file * * @param {string} filePath file path * @param {function} rewriteFile rewriteFile function * @param {object} generator reference to the generator */ function rewriteJSONFile(filePath, rewriteFile, generator) { filePath = generator.destinationPath(filePath); const jsonObj = generator.fs.readJSON(filePath); rewriteFile(jsonObj, generator); generator.fs.writeJSON(filePath, jsonObj, null, 2); } /** * Copy web resources * * @param {string} source source * @param {string} dest destination * @param {regex} regex regex * @param {string} type type of resource (html, js, etc) * @param {object} generator reference to the generator * @param {object} opt options * @param {any} template template */ function copyWebResource(source, dest, regex, type, generator, opt = {}, template) { if (generator.enableTranslation) { generator.template(source, dest, generator, opt); } else { dest = generator.destinationPath(dest); if (!dest) { return; } renderContent(source, generator, generator, opt, body => { body = body.replace(regex, ''); switch (type) { case 'html': body = replacePlaceholders(body, generator); break; case 'js': body = replaceTitle(body, generator); if (dest.endsWith('error.route.ts')) { body = replaceErrorMessage(body, generator); } break; case 'jsx': body = replaceTranslation(body, generator); break; default: break; } generator.fs.write(dest, body); }); } } /** * Render content * * @param {string} source source * @param {object} generator reference to the generator * @param {any} context context * @param {object} options options * @param {function} [cb] callback function * @return {Promise<String>} Promise rendered content */ function renderContent(source, generator, context, options, cb) { options = { root: options.root || generator.jhipsterTemplatesFolders || generator.templatePath(), context: generator, ...options, }; if (context.entityClass) { const basename = path.basename(source); if (context.configOptions && context.configOptions.sharedEntities) { Object.values(context.configOptions.sharedEntities).forEach(entity => { entity.resetFakerSeed(`${context.entityClass}-${basename}`); }); } else if (context.resetFakerSeed) { context.resetFakerSeed(basename); } } const promise = ejs.renderFile(generator.templatePath(source), context, options); if (cb) { return promise .then(res => cb(res)) .catch(err => { generator.warning(`Copying template ${source} failed. [${err}]`); throw err; }); } return promise; } /** * * @param {string} body html body * @param {object} generator reference to the generator * @returns string with pageTitle replaced */ function replaceTitle(body, generator) { const regex = /pageTitle[\s]*:[\s]*['|"]([a-zA-Z0-9.\-_]+)['|"]/g; return replaceTranslationKeysWithText(body, generator, regex); } /** * * @param {string} body html body * @param {object} generator reference to the generator * @returns string with pageTitle replaced */ function replaceErrorMessage(body, generator) { const regex = /errorMessage[\s]*:[\s]*['|"]([a-zA-Z0-9.\-_]+)['|"]/g; return replaceTranslationKeysWithText(body, generator, regex); } /** * * @param {string} body html body * @param {object} generator reference to the generator * @param {object} regex regular expression to find keys * @returns string with pageTitle replaced */ function replaceTranslationKeysWithText(body, generator, regex) { let match; // eslint-disable-next-line no-cond-assign while ((match = regex.exec(body)) !== null) { // match is now the next match, in array form and our key is at index 1, index 1 is replace target. const key = match[1]; const target = key; const jsonData = geti18nJson(key, generator); const keyValue = jsonData !== undefined ? deepFind(jsonData, key) : undefined; body = body.replace(target, keyValue !== undefined ? keyValue : generator.baseName); } return body; } /** * * @param {string} body html body * @param {object} generator reference to the generator * @returns string with placeholders replaced */ function replacePlaceholders(body, generator) { const re = /placeholder=['|"]([{]{2}\s*['|"]([a-zA-Z0-9.\-_]+)['|"][\s][|][\s](translate)\s*[}]{2})['|"]/g; let match; // eslint-disable-next-line no-cond-assign while ((match = re.exec(body)) !== null) { // match is now the next match, in array form and our key is at index 2, index 1 is replace target. const key = match[2]; const target = match[1]; const jsonData = geti18nJson(key, generator); const keyValue = jsonData !== undefined ? deepFind(jsonData, key, true) : undefined; // dirty fix to get placeholder as it is not in proper json format, name has a dot in it. Assuming that all placeholders are in similar format body = body.replace(target, keyValue !== undefined ? keyValue : ''); } return body; } /** * * @param {string} body html body * @param {object} generator reference to the generator * @returns string with Translate components replaced */ function replaceTranslation(body, generator) { const replaceRegex = (re, defultReplaceText) => { let match; // eslint-disable-next-line no-cond-assign while ((match = re.exec(body)) !== null) { // match is now the next match, in array form and our key is at index 2, index 1 is replace target. const key = match[2]; const target = match[1]; const limit = match[4]; // string indicating validation limit (e.g. "{ max: 4 }") const jsonData = geti18nJson(key, generator); let keyValue = jsonData !== undefined ? deepFind(jsonData, key) : undefined; if (!keyValue) { keyValue = deepFind(jsonData, key, true); // dirty fix to get placeholder as it is not in proper json format, name has a dot in it. Assuming that all placeholders are in similar format } if (limit) { // Replace "{{ placeholder }}" with numeric limit keyValue = keyValue.replace(/{{.+}}/, /{.+:\s(.+)\s}/.exec(limit)[1]); } body = body.replace(target, keyValue !== undefined ? `"${keyValue}"` : defultReplaceText); } }; replaceRegex(/(\{translate\('([a-zA-Z0-9.\-_]+)'(, ?null, ?'.*')?\)\})/g, '""'); replaceRegex(/(translate\(\s*'([a-zA-Z0-9.\-_]+)'(,\s*(null|\{.*\}),?\s*('.*')?\s*)?\))/g, "''"); return body; } /** * * @param key i18n key * @param {object} generator reference to the generator * @returns parsed json file */ function geti18nJson(key, generator) { const i18nDirectory = `${LANGUAGES_MAIN_SRC_DIR}i18n/en/`; const names = []; let result; const namePrefix = _.kebabCase(key.split('.')[0]); if (['entity', 'error', 'footer'].includes(namePrefix)) { names.push('global'); if (namePrefix === 'error') { names.push('error'); } } else { names.push(namePrefix); } for (let i = 0; i < names.length; i++) { let filename = `${i18nDirectory + names[i]}.json`; let render; if (!shelljs.test('-f', filename)) { filename = `${filename}.ejs`; render = true; } try { let file = generator.fs.read(filename); file = render ? ejs.render(file, generator, {}) : file; file = JSON.parse(file); if (result === undefined) { result = file; } else { result = { ...result, ...file }; } } catch (err) { generator.log(err); generator.log(`Error in file: ${filename}`); } } return result; } /** * * @param obj object to find in * @param path path to traverse * @param placeholder placeholder */ function deepFind(obj, path, placeholder) { const paths = path.split('.'); let current = obj; if (placeholder) { // dirty fix for placeholders, the json files needs to be corrected paths[paths.length - 2] = `${paths[paths.length - 2]}.${paths[paths.length - 1]}`; paths.pop(); } for (let i = 0; i < paths.length; ++i) { if (current[paths[i]] === undefined) { return undefined; } current = current[paths[i]]; } return current; } /** * 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 */ function getJavadoc(text, indentSize = 0) { if (!text) { text = ''; } if (text.includes('"')) { text = text.replace(/"/g, '\\"'); } let javadoc = `${_.repeat(' ', indentSize)}/**`; const rows = text.split('\n'); for (let i = 0; i < rows.length; i++) { javadoc = `${javadoc}\n${_.repeat(' ', indentSize)} * ${rows[i]}`; } javadoc = `${javadoc}\n${_.repeat(' ', indentSize)} */`; return javadoc; } /** * Build an enum object * @param {Object} field - entity field * @param {String} clientRootFolder - the client's root folder * @return {Object} the enum info. */ function getEnumInfo(field, clientRootFolder) { const fieldType = field.fieldType; // Todo: check if the next line does a side-effect and refactor it. field.enumInstance = _.lowerFirst(fieldType); const enums = field.fieldValues.split(',').map(fieldValue => fieldValue.trim()); const customValuesState = getCustomValuesState(enums); return { enumName: fieldType, javadoc: field.fieldTypeJavadoc && getJavadoc(field.fieldTypeJavadoc), enumInstance: field.enumInstance, enums, ...customValuesState, enumValues: getEnums(enums, customValuesState, field.fieldValuesJavadocs), clientRootFolder: clientRootFolder ? `${clientRootFolder}-` : '', }; } /** * @Deprecated * Build an enum object, deprecated use getEnumInfoInstead * @param {any} field : entity field * @param {string} frontendAppName * @param {string} packageName * @param {string} clientRootFolder */ function buildEnumInfo(field, frontendAppName, packageName, clientRootFolder) { const fieldType = field.fieldType; field.enumInstance = _.lowerFirst(fieldType); const enums = field.fieldValues.replace(/\s/g, '').split(','); const enumsWithCustomValue = getEnumsWithCustomValue(enums); return { enumName: fieldType, enumValues: field.fieldValues.split(',').join(', '), enumInstance: field.enumInstance, enums, enumsWithCustomValue, frontendAppName, packageName, clientRootFolder: clientRootFolder ? `${clientRootFolder}-` : '', }; } /** * @deprecated * private function to remove for jhipster v7 * @param enums * @return {*} */ function getEnumsWithCustomValue(enums) { return enums.reduce((enumsWithCustomValueArray, currentEnumValue) => { if (doesTheEnumValueHaveACustomValue(currentEnumValue)) { const matches = /([A-Z\-_]+)(\((.+?)\))?/.exec(currentEnumValue); const enumValueName = matches[1]; const enumValueCustomValue = matches[3]; enumsWithCustomValueArray.push({ name: enumValueName, value: enumValueCustomValue }); } else { enumsWithCustomValueArray.push({ name: currentEnumValue, value: false }); } return enumsWithCustomValueArray; }, []); } function 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, }; } function getEnums(enums, customValuesState, comments) { if (customValuesState.withoutCustomValues) { return enums.map(enumValue => ({ name: enumValue, value: enumValue, comment: comments && comments[enumValue] && getJavadoc(comments[enumValue], 4), })); } return enums.map(enumValue => { if (!doesTheEnumValueHaveACustomValue(enumValue)) { return { name: enumValue.trim(), value: enumValue.trim(), comment: comments && comments[enumValue] && getJavadoc(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]] && getJavadoc(comments[matched[1]], 4), }; }); } function doesTheEnumValueHaveACustomValue(enumValue) { return enumValue.includes('('); } /** * Copy object props from source to destination * @param {*} toObj * @param {*} fromObj */ function copyObjectProps(toObj, fromObj) { // we use Object.assign instead of spread as we want to mutilate the object. Object.assign(toObj, fromObj); } /** * Decode the given string from base64 to said encoding. * @param string the base64 string to decode * @param encoding the encoding to decode into. default to 'utf-8' */ function decodeBase64(string, encoding = 'utf-8') { return Buffer.from(string, 'base64').toString(encoding); } function loadYoRc(filePath = '.yo-rc.json') { if (!FileUtils.doesFileExist(filePath)) { return undefined; } return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' })); } /** * Get DB type from DB value * @param {string} db - db */ function getDBTypeFromDBValue(db) { if (constants.SQL_DB_OPTIONS.map(db => db.value).includes(db)) { return SQL; } return db; } /** * Get a random hex string * @param {int} len the length to use, defaults to 50 */ function getRandomHex(len = 50) { return crypto.randomBytes(len).toString('hex'); } /** * Generates a base64 secret from given string or random hex * @param {string} value the value used to get base64 secret * @param {int} len the length to use for random hex, defaults to 50 */ function getBase64Secret(value = '', len = 50) { if (this && this.options && this.options.reproducibleTests) { if (value) { return `SECRET-${value}-${len}`; } return `SECRET--${len}`; } return Buffer.from(value || getRandomHex(len)).toString('base64'); } /** * Checks if string is already in file * @param {string} path file path * @param {string} search search string * @param {object} generator reference to generator * @returns {boolean} true if string is in file, false otherwise */ function checkStringInFile(path, search, generator) { const fileContent = generator.fs.read(generator.destinationPath(path)); return fileContent.includes(search); } /** * Checks if regex is found in file * @param {string} path file path * @param {regex} regex regular expression * @param {object} generator reference to generator * @returns {boolean} true if regex is matched in file, false otherwise */ function checkRegexInFile(path, regex, generator) { const fileContent = generator.fs.read(generator.destinationPath(path)); return fileContent.match(regex); } /** * Remove 'generator-' prefix from generators for compatibility with yeoman namespaces. * @param {string} packageName - name of the blueprint's package name * @returns {string} namespace of the blueprint */ function packageNameToNamespace(packageName) { return packageName.replace('generator-', ''); } /** * Calculate a hash code for a given string. * @param {string} str - any string * @returns {number} returns the calculated hash code. */ function stringHashCode(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const character = str.charCodeAt(i); hash = (hash << 5) - hash + character; // eslint-disable-line no-bitwise hash |= 0; // eslint-disable-line no-bitwise } if (hash < 0) { hash *= -1; } return hash; } /** * Executes a Git command using shellJS * gitExec(args [, options, callback]) * * @param {string|array} args - can be an array of arguments or a string command * @param {object} options[optional] - takes any of child process options * @param {function} callback[optional] - a callback function to be called once process complete, The call back will receive code, stdout and stderr * @return {object} when in synchronous mode, this returns a ShellString. Otherwise, this returns the child process object. */ function gitExec(args, options = {}, callback) { if (typeof options === 'function') { callback = options; options = {}; } if (options.async === undefined) options.async = callback !== undefined; if (options.silent === undefined) options.silent = true; if (options.trace === undefined) options.trace = true; if (!Array.isArray(args)) { args = [args]; } const command = `git ${args.join(' ')}`; if (options.trace) { console.info(command); } if (callback) { return shelljs.exec(command, options, callback); } return shelljs.exec(command, options); } /** * Checks if git is installed. * * @param {function} callback[optional] - function to be called after checking if git is installed. The callback will receive the code of the shell command executed. * * @return {boolean} true if installed; false otherwise.. */ function isGitInstalled(callback) { const code = gitExec('--version', { trace: false }).code; if (callback) callback(code); return code === 0; } /** * Replace translation for Vue application * @param {*} generator * @param {*} files */ function vueReplaceTranslation(generator, files) { for (let i = 0; i < files.length; i++) { const filePath = `${generator.CLIENT_MAIN_SRC_DIR}${files[i]}`; // Match the below attributes and the $t() method const regexp = ['v-text', 'v-bind:placeholder', 'v-html', 'v-bind:title', 'v-bind:label', 'v-bind:value', 'v-bind:html'] .map(s => `${s}="\\$t\\(.*?\\)"`) .join(')|('); replaceContent( { file: filePath, pattern: new RegExp(` ?(${regexp})`, 'g'), content: '', }, generator ); } } function vueAddPageToRouterImport(generator, pageName, pageFolderName, pageFilename = pageFolderName) { rewriteFile( { file: `${generator.CLIENT_MAIN_SRC_DIR}/app/router/pages.ts`, needle: 'jhipster-needle-add-entity-to-router-import', splicable: [ generator.stripMargin( // prettier-ignore `|// prettier-ignore |const ${pageName} = () => import('@/pages/${pageFolderName}/${pageFilename}.vue');` ), ], }, generator ); } function vueAddPageToRouter(generator, pageName, pageFilename) { rewriteFile( { file: `${generator.CLIENT_MAIN_SRC_DIR}/app/router/pages.ts`, needle: 'jhipster-needle-add-entity-to-router', splicable: [ generator.stripMargin( // prettier-ignore `|{ | path: '/pages/${pageFilename}', | name: '${pageName}', | component: ${pageName}, | meta: { authorities: [Authority.USER] } | },` ), ], }, generator ); } function vueAddPageServiceToMainImport(generator, pageName, pageFolderName, pageFilename = pageFolderName) { rewriteFile( { file: `${generator.CLIENT_MAIN_SRC_DIR}/app/main.ts`, needle: 'jhipster-needle-add-entity-service-to-main-import', splicable: [ generator.stripMargin( // prettier-ignore `|import ${pageName}Service from '@/pages/${pageFolderName}/${pageFilename}.service';` ), ], }, generator ); } function vueAddPageServiceToMain(generator, pageName, pageInstance) { rewriteFile( { file: `${generator.CLIENT_MAIN_SRC_DIR}/app/main.ts`, needle: 'jhipster-needle-add-entity-service-to-main', splicable: [ generator.stripMargin( // prettier-ignore `|${pageInstance}Service: () => new ${pageName}Service(),` ), ], }, generator ); } function vueAddPageProtractorConf(generator) { rewriteFile( { file: `${generator.CLIENT_TEST_SRC_DIR}/protractor.conf.js`, needle: 'jhipster-needle-add-protractor-tests', splicable: [generator.stripMargin("'./e2e/pages/**/*.spec.ts',")], }, generator ); } function languageSnakeCase(language) { // Template the message server side properties return language.replace(/-/g, '_'); } function languageToJavaLanguage(language) { // Template the message server side properties const langProp = languageSnakeCase(language); // Target file : change xx_yyyy_zz to xx_yyyy_ZZ to match java locales return langProp.replace(/_[a-z]+$/g, lang => lang.toUpperCase()); } /** * @private * Utility function add condition to every block in addition to the already existing condition. */ function addSectionsCondition(files, commonCondition) { return Object.fromEntries( Object.entries(files).map(([sectionName, sectionValue]) => { sectionValue = sectionValue.map(block => { const { condition } = block; let newCondition = commonCondition; if (typeof condition === 'function') { newCondition = (...args) => { return commonCondition(...args) && condition(...args); }; } else if (condition !== undefined) { newCondition = (...args) => commonCondition(...args) && condition; } block = { ...block, condition: newCondition, }; return block; }); return [sectionName, sectionValue]; }) ); } /** * @private * Utility function to merge sections (jhipster files structure) * Merging { foo: [blocks1], bar: [block2]} and { foo: [blocks3], bar: [block4]} * Results in { foo: [blocks1, block3], bar: [block2, block4]} */ function mergeSections(...allFiles) { const generated = {}; for (const files of allFiles) { for (const [sectionName, sectionValue] of Object.entries(files)) { generated[sectionName] = generated[sectionName] || []; generated[sectionName].push(...sectionValue); } } return generated; }