UNPKG

generator-fedhipster

Version:

Spring Boot + Angular/React in one handy generator

456 lines (425 loc) 18.3 kB
/** * Copyright 2013-2019 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 * * http://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 _ = require('lodash'); const path = require('path'); const shelljs = require('shelljs'); const jhiCore = require('jhipster-core'); const pretty = require('js-object-pretty-print').pretty; const pluralize = require('pluralize'); const { fork } = require('child_process'); const waitUntil = require('./wait-until'); const { CLI_NAME, GENERATOR_NAME, logger, toString, getOptionsFromArgs, done, getOptionAsArgs } = require('./utils'); const jhipsterUtils = require('../generators/utils'); const packagejs = require('../package.json'); const statistics = require('../generators/statistics'); const runYeomanProcess = require.resolve('./run-yeoman-process.js'); // holds the state of generation for interactive mode const generationCompletionState = { exportedEntities: {}, exportedApplications: {}, exportedDeployments: {} }; const getBaseName = application => application && application[GENERATOR_NAME] && application[GENERATOR_NAME].baseName; const getDeploymentType = deployment => deployment && deployment[GENERATOR_NAME] && deployment[GENERATOR_NAME].deploymentType; /** * update the initial state */ const updateDeploymentState = importState => Object.entries(importState).forEach(([key, val]) => { val.forEach(it => { generationCompletionState[key][getBaseName(it) || getDeploymentType(it) || it.name] = false; }); }); /** * Imports the Applications and Entities defined in JDL * The app .yo-rc.json files and entity json files are written to disk */ function importJDL() { logger.info('The JDL is being parsed.'); const jdlImporter = new jhiCore.JDLImporter(this.jdlFiles, { databaseType: this.prodDatabaseType, applicationType: this.applicationType, applicationName: this.baseName, generatorVersion: packagejs.version, forceNoFiltering: this.options.force }); let importState = { exportedEntities: [], exportedApplications: [], exportedDeployments: [] }; try { importState = jdlImporter.import(); logger.debug(`importState exportedEntities: ${importState.exportedEntities.length}`); logger.debug(`importState exportedApplications: ${importState.exportedApplications.length}`); logger.debug(`importState exportedDeployments: ${importState.exportedDeployments.length}`); updateDeploymentState(importState); 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'); } 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); } return importState; } /** * Check if application needs to be generated * @param {any} generator */ const shouldGenerateApplications = generator => !generator.options['ignore-application'] && generator.importState.exportedApplications.length !== 0; /** * Check if deployments needs to be generated * @param {any} generator */ const shouldGenerateDeployments = generator => !generator.options['ignore-deployments'] && generator.importState.exportedDeployments.length !== 0; /** * Generate deployment source code for JDL deployments defined. * @param {any} config * @param {function} forkProcess * @returns {obj} Nodejs ChildProcess: https://nodejs.org/api/child_process.html#child_process_child_process */ const generateDeploymentFiles = ({ generator, deployment, inFolder }, forkProcess) => { const deploymentType = getDeploymentType(deployment); logger.info(`Generating deployment ${deploymentType} in a new parallel process`); logger.debug(`Generating deployment: ${pretty(deployment[GENERATOR_NAME])}`); const cwd = inFolder ? path.join(generator.pwd, deploymentType) : generator.pwd; logger.debug(`Child process will be triggered for ${runYeomanProcess} with cwd: ${cwd}`); const command = `${CLI_NAME}:${deploymentType}`; const childProc = forkProcess( runYeomanProcess, [command, '--skip-prompts', ...getOptionAsArgs(generator.options, false, !generator.options.interactive)], { cwd } ); childProc.on('exit', code => { logger.info(`Deployment: child process exited with code ${code}`); generationCompletionState.exportedDeployments[deploymentType] = true; }); }; /** * Generate application source code for JDL apps defined. * @param {any} config * @param {function} forkProcess * @returns {obj} Nodejs ChildProcess: https://nodejs.org/api/child_process.html#child_process_child_process */ const generateApplicationFiles = ({ generator, application, withEntities, inFolder }, forkProcess) => { const baseName = getBaseName(application); logger.info(`Generating application ${baseName} in a new parallel process`); logger.debug(`Generating application: ${pretty(application[GENERATOR_NAME])}`); const cwd = inFolder ? path.join(generator.pwd, baseName) : generator.pwd; logger.debug(`Child process will be triggered for ${runYeomanProcess} with cwd: ${cwd}`); const command = `${CLI_NAME}:app`; const childProc = forkProcess( runYeomanProcess, [command, ...getOptionAsArgs(generator.options, withEntities, !generator.options.interactive)], { cwd } ); childProc.on('exit', code => { logger.info(`App: child process exited with code ${code}`); generationCompletionState.exportedApplications[baseName] = true; }); }; /** * Generate entities for the applications * @param {any} generator * @param {any} entity * @param {boolean} inFolder * @param {any} env * @param {boolean} shouldTriggerInstall * @param {function} forkProcess */ const generateEntityFiles = (generator, entity, inFolder, env, shouldTriggerInstall, forkProcess) => { const options = { ...generator.options, regenerate: true, 'from-cli': true, 'skip-install': true, 'skip-client': entity.skipClient, 'skip-server': entity.skipServer, 'no-fluent-methods': entity.noFluentMethod, 'skip-user-management': entity.skipUserManagement, 'skip-ui-grouping': generator.options['skip-ui-grouping'] }; const command = `${CLI_NAME}:entity ${entity.name}`; if (inFolder) { /* Generating entities inside multiple apps */ let previousEntity; const callGenerator = baseName => { logger.info(`Generating entities for application ${baseName} in a new parallel process`); const cwd = path.join(generator.pwd, baseName); logger.debug(`Child process will be triggered for ${runYeomanProcess} with cwd: ${cwd}`); const childProc = forkProcess(runYeomanProcess, [command, ...getOptionAsArgs(options, false, !options.interactive)], { cwd }); childProc.on('exit', code => { logger.info(`Entity: child process exited with code ${code}`); generationCompletionState.exportedEntities[entity.name] = true; }); previousEntity = entity.name; }; const baseNames = entity.applications; baseNames.forEach(baseName => { if (generator.options.interactive) { waitUntil() .interval(500) .times(200) // approximate 2 minutes .condition(() => generationCompletionState.exportedEntities[previousEntity] || !previousEntity) .done(result => { logger.debug(`Result from waitUntil for application ${previousEntity} to finish: ${result}`); callGenerator(baseName); }); } else { callGenerator(baseName); } }); } else { /* Traditional entity only generation */ env.run( command, { ...options, force: !options.interactive, 'skip-install': !shouldTriggerInstall }, done ); } }; /** * Check if NPM/Yarn install needs to be triggered. This will be done for the last entity. * @param {any} generator * @param {number} index */ const shouldTriggerInstall = (generator, index) => index === generator.importState.exportedEntities.length - 1 && !generator.options['skip-install'] && !generator.skipClient && !generator.options['json-only'] && !shouldGenerateApplications(generator); const isAllCompleted = items => Object.values(items).every(it => it); class JDLProcessor { constructor(jdlFiles, options) { logger.debug(`JDLProcessor started with jdlFiles: ${jdlFiles} and options: ${toString(options)}`); this.jdlFiles = jdlFiles; this.options = options; this.pwd = process.cwd(); } validate() { if (this.jdlFiles) { this.jdlFiles.forEach(key => { if (!shelljs.test('-f', key)) { logger.error(chalk.red(`\nCould not find ${key}, make sure the path is correct.\n`)); } }); } } getConfig() { if (jhiCore.FileUtils.doesFileExist('.yo-rc.json')) { logger.info('Found .yo-rc.json on path. This is an existing app'); const configuration = jhipsterUtils.getAllJhipsterConfig(null, true); this.applicationType = configuration.applicationType; this.baseName = configuration.baseName; this.databaseType = configuration.databaseType || jhipsterUtils.getDBTypeFromDBValue(this.options.db); this.prodDatabaseType = configuration.prodDatabaseType || this.options.db; this.devDatabaseType = configuration.devDatabaseType || this.options.db; this.skipClient = configuration.skipClient; this.clientFramework = configuration.clientFramework; this.clientFramework = this.clientFramework || 'angularX'; this.styleLibrary = configuration.styleLibrary; this.clientPackageManager = configuration.clientPackageManager; if (!this.clientPackageManager) { if (this.useNpm) { this.clientPackageManager = 'npm'; } else { this.clientPackageManager = 'yarn'; } } } } importJDL() { this.importState = importJDL.call(this); } sendInsight() { statistics.sendSubGenEvent('generator', 'import-jdl'); } generateApplications(forkProcess) { if (!shouldGenerateApplications(this)) { logger.debug('Applications not generated'); return; } logger.info( `Generating ${this.importState.exportedApplications.length} ` + `${pluralize('application', this.importState.exportedApplications.length)}.` ); let previousApp; const callGenerator = application => { try { generateApplicationFiles( { generator: this, application, withEntities: this.importState.exportedEntities.length !== 0, inFolder: this.importState.exportedApplications.length > 1 }, forkProcess ); previousApp = getBaseName(application); } catch (error) { logger.error(`Error while generating applications from the parsed JDL\n${error}`, error); } }; this.importState.exportedApplications.forEach(application => { if (this.options.interactive) { waitUntil() .interval(500) .times(500) // approximate 5 minutes .condition(() => generationCompletionState.exportedApplications[previousApp] || !previousApp) .done(result => { logger.debug(`Result from waitUntil for application ${previousApp} to finish: ${result}`); callGenerator(application); }); } else { callGenerator(application); } }); } generateDeployments(forkProcess) { if (!shouldGenerateDeployments(this)) { logger.debug('Deployments not generated'); return; } logger.info( `Generating ${this.importState.exportedDeployments.length} ` + `${pluralize('deployment', this.importState.exportedDeployments.length)}.` ); const callDeploymentGenerator = () => { let previousDeployment; const callGenerator = deployment => { try { generateDeploymentFiles( { generator: this, deployment, inFolder: true }, forkProcess ); previousDeployment = getDeploymentType(deployment); } catch (error) { logger.error(`Error while generating deployments from the parsed JDL\n${error}`, error); } }; this.importState.exportedDeployments.forEach(deployment => { if (this.options.interactive) { waitUntil() .interval(500) .times(200) // approximate 2 minutes .condition(() => generationCompletionState.exportedDeployments[previousDeployment] || !previousDeployment) .done(result => { logger.debug(`Result from waitUntil for deployment ${previousDeployment} to finish: ${result}`); callGenerator(deployment); }); } else { callGenerator(deployment); } }); }; if (shouldGenerateApplications(this)) { waitUntil() .interval(500) .times(1000) // approximate 10 minutes .condition(() => isAllCompleted(generationCompletionState.exportedApplications)) .done(result => { logger.info('Done waiting for application generation'); logger.debug(`Result from waitUntil for all applications to finish: ${result}`); callDeploymentGenerator(); }); } else { callDeploymentGenerator(); } } generateEntities(env, forkProcess) { if (this.importState.exportedEntities.length === 0 || shouldGenerateApplications(this)) { logger.debug('Entities not generated'); return; } if (this.options['json-only']) { logger.info('Entity JSON files created. Entity generation skipped.'); return; } try { logger.info( `Generating ${this.importState.exportedEntities.length} ` + `${pluralize('entity', this.importState.exportedEntities.length)}.` ); this.importState.exportedEntities.forEach((exportedEntity, i) => { generateEntityFiles( this, exportedEntity, this.importState.exportedApplications.length > 1, env, shouldTriggerInstall(this, i), forkProcess ); }); } catch (error) { logger.error(`Error while generating entities from the parsed JDL\n${error}`, 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 * @param {function} forkProcess the method to use for process forking */ module.exports = (args, options, env, forkProcess = fork) => { logger.debug('cmd: import-jdl from ./import-jdl'); logger.debug(`args: ${toString(args)}`); const jdlFiles = getOptionsFromArgs(args); logger.info(chalk.yellow(`Executing import-jdl ${jdlFiles.join(' ')}`)); logger.info(chalk.yellow(`Options: ${toString(options)}`)); try { const jdlImporter = new JDLProcessor(jdlFiles, options); jdlImporter.validate(); jdlImporter.getConfig(); jdlImporter.importJDL(); jdlImporter.sendInsight(); jdlImporter.generateApplications(forkProcess); jdlImporter.generateEntities(env, forkProcess); jdlImporter.generateDeployments(forkProcess); } catch (e) { logger.error(`Error during import-jdl: ${e.message}`, e); } };