UNPKG

generator-pyhipster

Version:

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

531 lines (481 loc) 18.9 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. */ const chalk = require('chalk'); const fs = require('fs'); const _ = require('lodash'); const path = require('path'); const pluralize = require('pluralize'); const { fork: forkProcess } = require('child_process'); const EnvironmentBuilder = require('./environment-builder'); const { CLI_NAME, GENERATOR_NAME, logger, toString, printSuccess, getOptionAsArgs } = require('./utils'); const { createImporterFromContent, createImporterFromFiles } = require('../jdl/jdl-importer'); const packagejs = require('../package.json'); const statistics = require('../generators/statistics'); const { JHIPSTER_CONFIG_DIR } = require('../generators/generator-constants'); const jhipsterCli = require.resolve('./cli.js'); const { writeConfigFile } = require('../jdl/exporters/export-utils'); const { createFolderIfItDoesNotExist } = require('../jdl/utils/file-utils'); const getDeploymentType = deployment => deployment && deployment[GENERATOR_NAME] && deployment[GENERATOR_NAME].deploymentType; /** * Check if .yo-rc.json exists inside baseName folder. * @param {string} baseName * @return {boolean} */ const baseNameConfigExists = baseName => fs.existsSync(baseName === undefined ? '.yo-rc.json' : path.join(baseName, '.yo-rc.json')); const multiplesApplications = processor => { return Object.values(processor.importState.exportedApplicationsWithEntities).length > 1; }; /** * When importing multiples applications, we should import each of them at it's own baseName folder. * @param {JDLProcessor} processor * @return {boolean} */ const shouldRunInFolder = processor => { return multiplesApplications(processor); }; /** * Check if every application is new. * @param {JDLProcessor} processor * @return {boolean} */ const allNewApplications = processor => { const applications = Object.values(processor.importState.exportedApplicationsWithEntities); if (applications.length === 1) return !baseNameConfigExists(); return !applications.find(application => baseNameConfigExists(application.config.baseName)); }; /** * Check if the generation should be forced. * @param {JDLProcessor} processor * @return {boolean} */ const shouldForce = processor => { if (processor.options.force !== undefined) { return processor.options.force; } if (Object.values(processor.importState.exportedApplicationsWithEntities).length === 0) { return undefined; } return allNewApplications(processor) ? true : undefined; }; /** * JHipster will use fork by default only when generating new applications, otherwise it can conflict with incremental changelog. * @param {JDLProcessor} processor * @return {boolean} */ const shouldFork = processor => { if (processor.options.fork !== undefined) { return processor.options.fork; } if (Object.values(processor.importState.exportedApplicationsWithEntities).length > 1 && allNewApplications(processor)) { return true; } return undefined; }; /** * When regenerating applications we should run interactively, so prompts will be shown by default. * @param {JDLProcessor} processor * @return {boolean} true if generation should be executed interactively */ const shouldRunInteractively = processor => { if (processor.options.interactive !== undefined) { return processor.options.interactive; } return !shouldFork(processor); }; /** * Write entity config to disk. * @param {any} entity * @param {string} basePath */ function writeEntityConfig(entity, basePath) { const entitiesPath = path.join(basePath, JHIPSTER_CONFIG_DIR); createFolderIfItDoesNotExist(entitiesPath); const filePath = path.join(entitiesPath, `${_.upperFirst(entity.name)}.json`); fs.writeFileSync(filePath, JSON.stringify(entity, null, 2).concat('\n')); } /** * Write application config to disk. * @param {any} applicationWithEntities * @param {string} basePath */ function writeApplicationConfig(applicationWithEntities, basePath) { createFolderIfItDoesNotExist(basePath); writeConfigFile({ 'generator-jhipster': applicationWithEntities.config }, path.join(basePath, '.yo-rc.json')); applicationWithEntities.entities.forEach(entity => writeEntityConfig(entity, basePath)); } /** * Run the generator. * @param {string} command * @param {Object} options * @param {string} options.cwd * @param {boolean} options.fork * @param {Environment} options.env * @param {Object} generatorOptions * @param {Promise} */ function runGenerator(command, { cwd, fork, env }, generatorOptions = {}) { generatorOptions = { ...generatorOptions, // Remove jdl command exclusive options fork: undefined, interactive: undefined, jsonOnly: undefined, ignoreApplication: undefined, ignoreDeployments: undefined, inline: undefined, skipSampleRepository: undefined, workspaces: undefined, forceNoFiltering: undefined, unidirectionalRelationships: undefined, localConfigOnly: undefined, commandName: undefined, fromJdl: true, }; if (!generatorOptions.blueprints) { delete generatorOptions.blueprints; } if (!fork) { const oldCwd = process.cwd(); process.chdir(cwd); env = env || EnvironmentBuilder.createDefaultBuilder(undefined, { cwd }).getEnvironment(); return env .run(`${CLI_NAME}:${command}`, generatorOptions) .then( () => { logger.info(`Generator ${command} succeed`); }, error => { logger.error(`Error running generator ${command}: ${error}`, error); return Promise.reject(error); } ) .finally(() => { process.chdir(oldCwd); }); } logger.debug(`Child process will be triggered for ${command} with cwd: ${cwd}`); const args = [command, ...getOptionAsArgs(generatorOptions)]; const childProc = forkProcess(jhipsterCli, args, { cwd, }); return new Promise((resolve, reject) => { childProc.on('exit', code => { logger.debug(`Process ${args} exited with code ${code}`); logger.info(`Generator ${command} child process exited with code ${code}`); if (code !== 0) { process.exitCode = code; reject(new Error(`Error executing ${args.join(' ')}`)); return; } resolve(); }); }); } /** * Imports the Applications and Entities defined in JDL * The app .yo-rc.json files and entity json files are written to disk */ function importJDL(jdlImporter) { logger.info('The JDL is being parsed.'); try { const importState = jdlImporter.import(); logger.debug(`importState exportedEntities: ${importState.exportedEntities.length}`); logger.debug(`importState exportedApplications: ${importState.exportedApplications.length}`); logger.debug(`importState exportedDeployments: ${importState.exportedDeployments.length}`); if (importState.exportedEntities.length > 0) { const entityNames = _.uniq(importState.exportedEntities.map(exportedEntity => exportedEntity.name)).join(', '); logger.info(`Found entities: ${chalk.yellow(entityNames)}.`); } else { logger.info(chalk.yellow('No change in entity configurations, no entities were updated.')); } logger.info('The JDL has been successfully parsed'); return importState; } catch (error) { logger.debug('Error:', error); if (error) { const errorName = `${error.name}:` || ''; const errorMessage = error.message || ''; logger.log(chalk.red(`${errorName} ${errorMessage}`)); } logger.error(`Error while parsing applications and entities from the JDL ${error}`, error); throw error; } } /** * Check if application needs to be generated * @param {any} processor */ const shouldGenerateApplications = processor => !processor.options.ignoreApplication && !processor.options.jsonOnly && processor.importState.exportedApplications.length !== 0; /** * Check if deployments needs to be generated * @param {any} processor */ const shouldGenerateDeployments = processor => !processor.options.ignoreDeployments && !processor.options.jsonOnly && processor.importState.exportedDeployments.length !== 0; /** * Generate deployment source code for JDL deployments defined. * @param {any} config * @returns Promise */ const generateDeploymentFiles = ({ processor, deployment }) => { const deploymentType = getDeploymentType(deployment); logger.info(`Generating deployment ${deploymentType} in a new parallel process`); logger.debug(`Generating deployment: ${JSON.stringify(deployment[GENERATOR_NAME], null, 2)}`); const cwd = path.join(processor.pwd, deploymentType); logger.debug(`Child process will be triggered for ${jhipsterCli} with cwd: ${cwd}`); return runGenerator(deploymentType, { cwd, fork: false }, { force: true, ...processor.options, skipPrompts: true }); }; /** * Generate application source code for JDL apps defined. * @param {any} config * @returns Promise */ const generateApplicationFiles = ({ processor, applicationWithEntities }) => { logger.debug(`Generating application: ${JSON.stringify(applicationWithEntities.config, null, 2)}`); const { inFolder, fork, force, reproducible } = processor; const baseName = applicationWithEntities.config.baseName; const cwd = inFolder ? path.join(processor.pwd, baseName) : processor.pwd; if (processor.options.jsonOnly) { writeApplicationConfig(applicationWithEntities, cwd); return Promise.resolve(); } if (fork) { writeApplicationConfig(applicationWithEntities, cwd); } const withEntities = applicationWithEntities.entities.length > 0 ? true : undefined; const generatorOptions = { reproducible, force, withEntities, ...processor.options }; if (!fork) { generatorOptions.applicationWithEntities = applicationWithEntities; } return runGenerator('app', { cwd, fork }, generatorOptions); }; /** * Generate entities for the applications * @param {any} processor * @param {any} exportedEntities * @param {any} env * @return Promise */ const generateEntityFiles = (processor, exportedEntities, env) => { const { fork, inFolder, force } = processor; const generatorOptions = { force, ...processor.options, }; const callGenerator = baseName => { const cwd = inFolder && baseName ? path.join(processor.pwd, baseName) : processor.pwd; if (processor.options.jsonOnly || baseName) { exportedEntities .filter(entity => !baseName || entity.applications.includes(baseName)) .forEach(entity => writeEntityConfig(entity, cwd)); if (processor.options.jsonOnly) { logger.info('Entity JSON files created. Entity generation skipped.'); return Promise.resolve(); } } else { generatorOptions.entitiesToImport = exportedEntities; } logger.info(`Generating entities for application ${baseName} in a new parallel process`); logger.debug(`Child process will be triggered for ${jhipsterCli} with cwd: ${cwd}`); return runGenerator('entities', { cwd, env, fork }, generatorOptions); }; if (fork) { /* Generating entities inside multiple apps */ const baseNames = [...new Set(exportedEntities.flatMap(entity => entity.applications))]; if (processor.interactive) { return baseNames.reduce((promise, baseName) => { return promise.then(() => callGenerator(baseName)); }, Promise.resolve()); } return Promise.all(baseNames.map(baseName => callGenerator(baseName))); } return callGenerator(); }; class JDLProcessor { constructor(jdlFiles, jdlContent, options) { logger.debug( `JDLProcessor started with ${jdlContent ? `content: ${jdlContent}` : `files: ${jdlFiles}`} and options: ${toString(options)}` ); this.jdlFiles = jdlFiles; this.jdlContent = jdlContent; this.options = options; this.pwd = process.cwd(); } importJDL() { const configuration = { applicationName: this.options.baseName, databaseType: this.options.db, applicationType: this.options.applicationType, skipUserManagement: this.options.skipUserManagement, unidirectionalRelationships: this.options.unidirectionalRelationships, forceNoFiltering: this.options.forceNoFiltering, generatorVersion: packagejs.version, skipFileGeneration: true, }; let importer; if (this.jdlContent) { importer = createImporterFromContent(this.jdlContent, configuration); } else { importer = createImporterFromFiles(this.jdlFiles, configuration); } this.importState = importJDL.call(this, importer); } config() { this.interactive = shouldRunInteractively(this); this.fork = shouldFork(this); this.reproducible = allNewApplications(this); this.inFolder = shouldRunInFolder(this); this.force = shouldForce(this); return this; } sendInsight() { statistics.sendSubGenEvent('generator', 'import-jdl'); } generateWorkspaces(options) { if (!options.workspaces || !multiplesApplications(this)) { return Promise.resolve(); } return EnvironmentBuilder.createDefaultBuilder() .getEnvironment() .run('jhipster:workspaces', { workspaces: false, ...options, importState: this.importState }); } generateApplications() { if (this.options.ignoreApplication) { logger.debug('Applications not generated'); return Promise.resolve(); } const callGenerator = applicationWithEntities => { try { return generateApplicationFiles({ processor: this, applicationWithEntities, }); } catch (error) { logger.error(`Error while generating applications from the parsed JDL\n${error}`, error); throw error; } }; const applicationsWithEntities = Object.values(this.importState.exportedApplicationsWithEntities); logger.info(`Generating ${applicationsWithEntities.length} ${pluralize('application', applicationsWithEntities.length)}.`); if (applicationsWithEntities.length === 0) { return Promise.resolve(); } const allApplications = Object.fromEntries( applicationsWithEntities.map((applicationWithEntities, applicationIndex) => { applicationWithEntities.config.applicationIndex = applicationIndex; return [applicationWithEntities.config.baseName, applicationWithEntities.config]; }) ); applicationsWithEntities.forEach((applicationWithEntities, idx) => { const relatedApplications = Object.entries(allApplications).filter( ([baseName]) => applicationWithEntities.config.baseName !== baseName && applicationWithEntities.entities.find(entity => entity.microserviceName === baseName) ); const { serverPort: gatewayServerPort } = applicationWithEntities.config; if (relatedApplications.length > 0) { applicationWithEntities.config.applications = Object.fromEntries( relatedApplications.map(([baseName, config]) => { config.gatewayServerPort = gatewayServerPort; const { clientFramework, serverPort, applicationIndex, devServerPort } = config; return [baseName, { clientFramework, serverPort, applicationIndex, devServerPort }]; }) ); } }); if (this.interactive) { return applicationsWithEntities.reduce((promise, applicationWithEntities) => { return promise.then(() => callGenerator(applicationWithEntities)); }, Promise.resolve()); } return Promise.all(applicationsWithEntities.map(applicationWithEntities => callGenerator(applicationWithEntities))); } generateDeployments() { if (!shouldGenerateDeployments(this)) { logger.debug('Deployments not generated'); return Promise.resolve(); } logger.info( `Generating ${this.importState.exportedDeployments.length} ` + `${pluralize('deployment', this.importState.exportedDeployments.length)}.` ); const callDeploymentGenerator = () => { const callGenerator = deployment => { try { return generateDeploymentFiles({ processor: this, deployment, }); } catch (error) { logger.error(`Error while generating deployments from the parsed JDL\n${error}`, error); throw error; } }; // Queue callGenerator in chain return this.importState.exportedDeployments.reduce((promise, deployment) => { return promise.then(() => callGenerator(deployment)); }, Promise.resolve()); }; return callDeploymentGenerator(); } generateEntities(env) { if (this.importState.exportedEntities.length === 0 || shouldGenerateApplications(this)) { logger.debug('Entities not generated'); return Promise.resolve(); } try { logger.info( `Generating ${this.importState.exportedEntities.length} ${pluralize('entity', this.importState.exportedEntities.length)}.` ); return generateEntityFiles(this, this.importState.exportedEntities, env); } catch (error) { logger.error(`Error while generating entities from the parsed JDL\n${error}`, error); throw error; } } } /** * Import-JDL sub generator * @param {any} args arguments passed for import-jdl * @param {any} options options passed from CLI * @param {any} env the yeoman environment */ module.exports = (jdlFiles, options = {}, env) => { logger.info(chalk.yellow(`Executing import-jdl ${options.inline ? 'with inline content' : jdlFiles.join(' ')}`)); logger.debug(chalk.yellow(`Options: ${toString({ ...options, inline: options.inline ? 'inline content' : '' })}`)); try { const jdlImporter = new JDLProcessor(jdlFiles, options.inline, options); jdlImporter.importJDL(); jdlImporter.sendInsight(); jdlImporter.config(); return jdlImporter .generateWorkspaces(options) .then(() => jdlImporter.generateApplications()) .then(() => jdlImporter.generateEntities(env)) .then(() => jdlImporter.generateDeployments()) .then(() => { printSuccess(); return jdlFiles; }); } catch (e) { logger.error(`Error during import-jdl: ${e}`, e); return Promise.reject(new Error(`Error during import-jdl: ${e.message}`)); } };