UNPKG

generator-begcode

Version:

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

493 lines (492 loc) 24.1 kB
import { kebabCase } from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; import BaseGenerator from '../base-application/index.js'; import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.js'; import { createPomStorage } from '../maven/support/pom-store.js'; import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.js'; import { mavenProfileContent } from './templates.js'; export default class HerokuGenerator extends BaseGenerator { hasHerokuCli; herokuAppName; herokuDeployType; herokuJavaVersion; herokuRegion; herokuAppExists; herokuSkipDeploy; herokuSkipBuild; dynoSize; constructor(args, options, features) { super(args, options, features); this.option('skip-build', { description: 'Skips building the application', type: Boolean, default: false, }); this.option('skip-deploy', { description: 'Skips deployment to Heroku', type: Boolean, default: false, }); if (this.options.help) { return; } this.herokuSkipBuild = this.options.skipBuild; this.herokuSkipDeploy = this.options.skipDeploy || this.options.skipBuild; } async beforeQueue() { if (!this.fromBlueprint) { await this.composeWithBlueprints(); } if (!this.delegateToBlueprint) { await this.dependsOnBootstrapApplication(); } } get initializing() { return this.asInitializingTaskGroup({ async checkInstallation() { const { exitCode } = await this.spawnHerokuCommand('--version', { verboseInfo: false }); this.hasHerokuCli = exitCode === 0; if (!this.hasHerokuCli) { const error = "You don't have the Heroku CLI installed. See https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli to learn how to install it."; if (this.skipChecks) { this.log.warn(error); this.log.warn('Generation will continue with limited support'); } else { throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`); } } }, async herokuLogin() { if (!this.hasHerokuCli) return; const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false }); if (exitCode !== 0) { this.log.log(chalk.bold('Log in to Heroku to continue.')); await this.spawnHerokuCommand('login', { reject: true, stdio: 'inherit' }); } }, initializing() { this.log.log(chalk.bold('Heroku configuration is starting')); this.dynoSize = 'Basic'; this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName); }, }); } get [BaseGenerator.INITIALIZING]() { return this.delegateTasksToBlueprint(() => this.initializing); } get prompting() { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { verboseInfo: false, }); if (exitCode !== 0) { this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); this.herokuAppName = null; throw new Error('Run the generator again to create a new application.'); } else { const json = JSON.parse(stdout); this.herokuAppName = json.app.name; if (json.dynos.length > 0) { this.dynoSize = json.dynos[0].size; } this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`); this.config.set({ herokuAppName: this.herokuAppName, }); } } else { await this.prompt([ { type: 'input', name: 'herokuAppName', message: 'Name to deploy as:', default: this.jhipsterConfigWithDefaults.baseName, }, ], this.config); const answers = await this.prompt([ { type: 'list', name: 'herokuRegion', message: 'On which region do you want to deploy?', choices: ['us', 'eu'], default: 0, }, ]); this.herokuRegion = answers.herokuRegion; } }, async askForHerokuDeployType() { await this.prompt([ { type: 'list', name: 'herokuDeployType', message: 'Which type of deployment do you want?', choices: [ { value: 'git', name: 'Git (compile on Heroku)' }, { value: 'jar', name: 'JAR (compile locally)' }, ], default: 0, }, ], this.config); }, async askForHerokuJavaVersion() { await this.prompt([ { type: 'list', name: 'herokuJavaVersion', message: 'Which Java version would you like to use to build and run your app?', choices: JAVA_COMPATIBLE_VERSIONS.map(version => ({ value: version })), default: JAVA_VERSION, }, ], this.config); }, }); } get [BaseGenerator.PROMPTING]() { return this.delegateTasksToBlueprint(() => this.prompting); } get loading() { return this.asConfiguringTaskGroup({ saveConfig() { this.herokuAppName = kebabCase(this.jhipsterConfig.herokuAppName); this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion; this.herokuDeployType = this.jhipsterConfig.herokuDeployType; }, }); } get [BaseGenerator.LOADING]() { return this.delegateTasksToBlueprint(() => this.loading); } get default() { return this.asDefaultTaskGroup({ async gitInit() { if (!this.herokuDeployType === 'git') return; const git = this.createGit(); if (await git.checkIsRepo()) { this.log.log(chalk.bold('\nUsing existing Git repository')); } else { this.log.log(chalk.bold('\nInitializing Git repository')); await git.init(); } }, async installHerokuDeployPlugin() { if (!this.hasHerokuCli) return; const cliPlugin = 'heroku-cli-deploy'; const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { stdio: 'pipe' }); if (exitCode !== 0) { if (stdout.includes(cliPlugin)) { this.log.log('\nHeroku CLI deployment plugin already installed'); } else { this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); const { exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`); if (exitCode !== 0) { throw new Error(stderr); } } } }, async herokuCreate() { if (!this.hasHerokuCli || this.herokuAppExists) return; const regionParams = this.herokuRegion !== 'us' ? ['--region', this.herokuRegion] : []; this.log.log(chalk.bold('\nCreating Heroku application and setting up Node environment')); const { stdout, stderr, exitCode } = await this.spawnHeroku(['create', this.herokuAppName, ...regionParams]); if (stdout.includes('Heroku credentials')) { throw new Error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); } if (exitCode !== 0) { if (stderr.includes('is already taken')) { const prompts = [ { type: 'list', name: 'herokuForceName', message: `The Heroku application "${chalk.cyan(this.herokuAppName)}" already exists! Use it anyways?`, choices: [ { value: 'Yes', name: 'Yes, I have access to it', }, { value: 'No', name: 'No, generate a random name', }, ], default: 0, }, ]; this.log.log(''); const props = await this.prompt(prompts); if (props.herokuForceName === 'Yes') { await this.spawnHeroku(['git:remote', '--app', this.herokuAppName], { reject: true }); } else { const { stdout } = await this.spawnHeroku(['create', ...regionParams]); this.herokuAppName = stdout.substring(stdout.lastIndexOf('/') + 1, stdout.indexOf('.git')); await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { this.log.error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); } else { throw new Error(stderr); } } }, async herokuAddonsCreate({ application }) { if (!this.hasHerokuCli || this.herokuAppExists) return; this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); const { stdout, stderr } = await this.spawn('heroku', [ 'addons:create', 'bonsai:sandbox-6', '--as', 'BONSAI', '--app', this.herokuAppName, ]); this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr }); } let dbAddOn; if (application.prodDatabaseTypePostgresql) { dbAddOn = 'heroku-postgresql'; } else if (application.prodDatabaseTypeMysql) { dbAddOn = 'jawsdb:kitefin'; } else if (application.prodDatabaseTypeMariadb) { dbAddOn = 'jawsdb-maria:kitefin'; } if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); const { stdout, stderr } = await this.spawn('heroku', [ 'addons:create', dbAddOn, '--as', 'DATABASE', '--app', this.herokuAppName, ]); this.checkAddOnReturn({ addOn: 'Database', stdout, stderr }); } else { this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); } let cacheAddOn; if (application.cacheProviderMemcached) { cacheAddOn = ['memcachier:dev', '--as', 'MEMCACHIER']; } else if (application.cacheProviderRedis) { cacheAddOn = ['heroku-redis:hobby-dev', '--as', 'REDIS']; } if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName]); this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); } }, async configureJHipsterRegistry({ application }) { if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka) return undefined; this.log.log(''); const answers = await this.prompt([ { type: 'input', name: 'herokuJHipsterRegistryApp', message: 'What is the name of your JHipster Registry Heroku application?', default: 'jhipster-registry', }, { type: 'input', name: 'herokuJHipsterRegistryUsername', message: 'What is your JHipster Registry username?', default: 'admin', }, { type: 'input', name: 'herokuJHipsterRegistryPassword', message: 'What is your JHipster Registry password?', default: 'password', }, ]); const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername); const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword); const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`; const configSetCmd = ['config:set', 'JHIPSTER_REGISTRY_URL', herokuJHipsterRegistry, '--app', this.herokuAppName]; await this.spawnHeroku(configSetCmd, { stdio: 'pipe' }); }, }); } get [BaseGenerator.DEFAULT]() { return this.delegateTasksToBlueprint(() => this.default); } get writing() { return this.asWritingTaskGroup({ copyHerokuFiles({ application }) { this.log.log(chalk.bold('\nCreating Heroku deployment files')); const context = { ...application, herokuAppName: this.herokuAppName, dynoSize: this.dynoSize, herokuJavaVersion: this.herokuJavaVersion, herokuDeployType: this.herokuDeployType, }; this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, context); this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, context); this.writeFile('Procfile.ejs', 'Procfile', context); this.writeFile('system.properties.ejs', 'system.properties', context); if (application.buildToolGradle) { this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle', context); } }, addHerokuBuildPlugin({ application }) { if (!application.buildToolGradle) return; this.editFile('build.gradle', addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' })); this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); }, addHerokuMavenProfile({ application }) { if (application.buildToolMaven) { this.addMavenProfile('heroku', mavenProfileContent(application)); } }, }); } get [BaseGenerator.WRITING]() { return this.delegateTasksToBlueprint(() => this.writing); } get end() { return this.asEndTaskGroup({ async productionBuild() { if (this.herokuSkipBuild || this.herokuDeployType === 'git') { this.log.log(chalk.bold('\nSkipping build')); return; } this.log.log(chalk.bold('\nBuilding application')); await this.printChildOutput(this.spawnCommand('npm run java:jar:prod', { stdio: 'pipe' })); }, async productionDeploy({ application }) { if (this.herokuSkipDeploy || !this.hasHerokuCli) { this.log.log(chalk.bold('\nSkipping deployment')); return; } if (this.herokuDeployType === 'git') { try { this.log.log(chalk.bold('\nUpdating Git repository')); const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr })); await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null }); let buildpack = 'heroku/java'; let configName = 'MAVEN_CUSTOM_OPTS'; let configValues = '-Pprod,heroku -DskipTests'; if (application.buildToolGradle) { buildpack = 'heroku/gradle'; configName = 'GRADLE_TASK'; configValues = 'stage -Pprod -PnodeInstall'; } this.log.log(chalk.bold('\nConfiguring Heroku')); const { stdout: configData } = await this.spawnHeroku(['config:get', configName, '--app', this.herokuAppName]); if (!configData) { await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); } const { stdout: buildpackData } = await this.spawnHeroku(['buildpacks', '--app', this.herokuAppName]); if (!buildpackData.includes(buildpack)) { await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); } this.log.log(chalk.bold('\nDeploying application...')); await git.push('heroku', 'HEAD:main'); this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); } catch (err) { this.log.error(err); } } else { this.log.log(chalk.bold('\nDeploying application')); let jarFileWildcard = 'target/*.jar'; if (application.buildToolGradle) { jarFileWildcard = 'build/libs/*.jar'; } const files = glob.sync(jarFileWildcard, {}); const jarFile = files[0]; this.log.log(chalk.bold(`\nUploading your application code.\nThis may take ${chalk.cyan('several minutes')} depending on your connection speed...`)); try { await this.spawnHeroku(['deploy:jar', jarFile, '--app', this.herokuAppName], { stdio: 'pipe' }); await this.spawnHerokuCommand('buildpacks:set heroku/jvm', { stdio: 'pipe' }); this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`)); this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`)); this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`)); } catch (err) { this.log.error(err); } } }, }); } get [BaseGenerator.END]() { return this.delegateTasksToBlueprint(() => this.end); } addMavenProfile(profileId, other) { createPomStorage(this, { sortFile: false }).addProfile({ id: profileId, content: other }); } spawnHerokuCommand(command, opt) { opt = { stdio: 'pipe', reject: false, ...opt }; const { verboseInfo, ...spawnOptions } = opt; const child = this.spawnCommand(`heroku ${command}`, spawnOptions); if (opt.stdio !== 'pipe' || verboseInfo === false) { return child; } return this.printChildOutput(child); } spawnHeroku(args, opt) { opt = { stdio: 'pipe', reject: false, ...opt }; const { verboseInfo, ...spawnOptions } = opt; const child = this.spawn('heroku', args, spawnOptions); if (spawnOptions.stdio !== 'pipe' || verboseInfo === false) { return child; } return this.printChildOutput(child); } printChildOutput(child, log = data => this.log.verboseInfo(data)) { const { stdout, stderr } = child; stdout.on('data', data => { data.toString().split(/\r?\n/).filter(Boolean).forEach(log); }); stderr.on('data', data => { data.toString().split(/\r?\n/).filter(Boolean).forEach(log); }); return child; } checkAddOnReturn({ addOn, stdout, stderr }) { if (stdout) { this.log.ok(`Created ${addOn.valueOf()} add-on`); this.log.ok(stdout); } else if (stderr) { const verifyAccountUrl = 'https://heroku.com/verify'; if (stderr.includes(verifyAccountUrl)) { this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`); throw new Error(stderr); } else { this.log.verboseInfo(`No new ${addOn.valueOf()} add-on created`); } } } }