generator-jhipster 8.0.0-rc.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +10 -1
  2. package/dist/cli/environment-builder.mjs +3 -3
  3. package/dist/cli/jhipster-command.mjs +7 -3
  4. package/dist/cli/program.mjs +1 -1
  5. package/dist/generators/angular/generator.mjs +8 -7
  6. package/dist/generators/angular/resources/package.json +10 -9
  7. package/dist/generators/angular/templates/package.json.ejs +4 -8
  8. package/dist/generators/angular/templates/src/main/webapp/app/entities/_entityFolder_/list/_entityFile_.component.html.ejs +6 -10
  9. package/dist/generators/angular/templates/src/main/webapp/app/entities/_entityFolder_/service/_entityFile_.service.ts.ejs +1 -1
  10. package/dist/generators/angular/templates/src/main/webapp/app/layouts/navbar/navbar.component.scss.ejs +1 -1
  11. package/dist/generators/angular/templates/webpack/webpack.microfrontend.js.ejs +48 -126
  12. package/dist/generators/app/support/config.mjs +3 -3
  13. package/dist/generators/base/generator.mjs +0 -3
  14. package/dist/generators/base/support/config.mjs +6 -6
  15. package/dist/generators/base/support/jhipster7-context.mjs +3 -3
  16. package/dist/generators/base-application/generator.mjs +2 -2
  17. package/dist/generators/base-application/support/enum.mjs +1 -1
  18. package/dist/generators/base-application/support/prepare-entity.mjs +14 -10
  19. package/dist/generators/base-application/support/prepare-field.mjs +11 -7
  20. package/dist/generators/base-application/support/prepare-relationship.mjs +12 -14
  21. package/dist/generators/base-workspaces/internal/docker-prompts.mjs +13 -12
  22. package/dist/generators/bootstrap/generator.mjs +64 -43
  23. package/dist/generators/bootstrap/support/auto-crlf-transform.mjs +34 -21
  24. package/dist/generators/bootstrap/support/eslint-transform.mjs +2 -5
  25. package/dist/generators/bootstrap/support/java-unused-imports-transform.mjs +2 -2
  26. package/dist/generators/bootstrap/support/multi-step-transform/index.mjs +17 -28
  27. package/dist/generators/bootstrap/support/multi-step-transform/template-file-fs.mjs +12 -6
  28. package/dist/generators/bootstrap/support/multi-step-transform/template-file.mjs +1 -0
  29. package/dist/generators/bootstrap/support/prettier-support.mjs +2 -2
  30. package/dist/generators/bootstrap-application/generator.mjs +10 -3
  31. package/dist/generators/bootstrap-application-base/generator.mjs +9 -6
  32. package/dist/generators/bootstrap-application-server/generator.mjs +2 -2
  33. package/dist/generators/client/resources/package.json +3 -3
  34. package/dist/generators/client/templates/src/main/webapp/content/css/loading.css.ejs +1 -1
  35. package/dist/generators/client/templates/webpack/webpack.microfrontend.js.jhi.ejs +14 -1
  36. package/dist/generators/common/files.mjs +1 -1
  37. package/dist/generators/common/generator.mjs +29 -7
  38. package/dist/generators/common/resources/package.json +3 -3
  39. package/dist/generators/common/templates/.lintstagedrc.cjs.ejs +21 -0
  40. package/dist/generators/common/templates/.prettierrc.ejs +1 -1
  41. package/dist/generators/common/templates/README.md.jhi.ejs +2 -0
  42. package/dist/generators/cypress/templates/src/test/javascript/cypress/e2e/account/settings-page.cy.ts.ejs +3 -0
  43. package/dist/generators/entity/prompts.mjs +123 -306
  44. package/dist/generators/generator-constants.mjs +5 -5
  45. package/dist/generators/heroku/generator.mjs +318 -595
  46. package/dist/generators/heroku/templates/Procfile.ejs +1 -1
  47. package/dist/generators/heroku/templates/application-heroku.yml.ejs +0 -12
  48. package/dist/generators/info/generator.mjs +6 -4
  49. package/dist/generators/init/generator.mjs +2 -2
  50. package/dist/generators/java/entity-files.mjs +1 -1
  51. package/dist/generators/java/generator.mjs +12 -13
  52. package/dist/generators/java/support/package-info-transform.mjs +20 -6
  53. package/dist/generators/java/templates/src/main/java/_package_/_entityPackage_/domain/_persistClass_.java.jhi.ejs +24 -28
  54. package/dist/generators/java/templates/src/main/java/_package_/_entityPackage_/domain/enumeration/_enumName_.java.ejs +3 -3
  55. package/dist/generators/java/templates/src/test/java/_package_/_entityPackage_/domain/_persistClass_Test.java.ejs +86 -8
  56. package/dist/generators/java/templates/src/test/java/_package_/_entityPackage_/domain/_persistClass_TestSamples.java.ejs +85 -0
  57. package/dist/generators/jdl/generator.mjs +14 -5
  58. package/dist/generators/kubernetes/templates/ingress.yml.ejs +0 -1
  59. package/dist/generators/languages/command.mjs +5 -0
  60. package/dist/generators/languages/generator.mjs +20 -13
  61. package/dist/generators/languages/translation-data.mjs +8 -13
  62. package/dist/generators/liquibase/generator.mjs +7 -3
  63. package/dist/generators/project-name/generator.mjs +1 -1
  64. package/dist/generators/react/generator.mjs +18 -10
  65. package/dist/generators/react/resources/package.json +19 -18
  66. package/dist/generators/react/templates/package.json.ejs +4 -5
  67. package/dist/generators/react/templates/src/main/webapp/app/config/icon-loader.ts.ejs +35 -33
  68. package/dist/generators/react/templates/src/main/webapp/app/entities/_entityFolder_/_entityFile_-update.tsx.ejs +7 -2
  69. package/dist/generators/react/templates/src/main/webapp/app/entities/_entityFolder_/_entityFile_.reducer.ts.ejs +1 -1
  70. package/dist/generators/react/templates/src/main/webapp/app/entities/_entityFolder_/_entityFile_.tsx.ejs +1 -1
  71. package/dist/generators/react/templates/src/main/webapp/app/modules/home/home.tsx.ejs +1 -1
  72. package/dist/generators/react/templates/src/main/webapp/app/routes.tsx.ejs +9 -2
  73. package/dist/generators/react/templates/src/main/webapp/app/shared/layout/header/header-components.tsx.ejs +1 -1
  74. package/dist/generators/react/templates/src/main/webapp/app/shared/layout/header/header.scss.ejs +5 -4
  75. package/dist/generators/react/templates/src/main/webapp/app/shared/layout/menus/entities.tsx.ejs +10 -1
  76. package/dist/generators/react/templates/tsconfig.test.json.ejs +1 -1
  77. package/dist/generators/react/templates/webpack/webpack.microfrontend.js.jhi.react.ejs +13 -58
  78. package/dist/generators/server/generator.mjs +13 -4
  79. package/dist/generators/server/resources/Dockerfile +7 -7
  80. package/dist/generators/server/resources/gradle/libs.versions.toml +1 -1
  81. package/dist/generators/server/resources/pom.xml +11 -11
  82. package/dist/generators/server/support/config.mjs +4 -4
  83. package/dist/generators/server/support/prepare-entity.mjs +5 -5
  84. package/dist/generators/server/support/prepare-field.mjs +20 -0
  85. package/dist/generators/server/support/relationship.mjs +2 -1
  86. package/dist/generators/server/support/templates/field-values.mjs +5 -2
  87. package/dist/generators/server/templates/README.md.jhi.spring-boot.ejs +0 -1
  88. package/dist/generators/server/templates/package.json.ejs +0 -5
  89. package/dist/generators/server/templates/pom.xml.ejs +1 -8
  90. package/dist/generators/server/templates/src/main/java/_package_/_entityPackage_/_partials_entity_/get_all_template.ejs +3 -3
  91. package/dist/generators/server/templates/src/main/java/_package_/_entityPackage_/service/dto/_dtoClass_.java.ejs +2 -2
  92. package/dist/generators/server/templates/src/main/java/_package_/_entityPackage_/web/rest/_entityClass_Resource.java.ejs +11 -11
  93. package/dist/generators/server/templates/src/main/java/_package_/config/JacksonConfiguration.java.ejs +2 -1
  94. package/dist/generators/server/templates/src/main/java/_package_/config/SecurityConfiguration_imperative.java.ejs +1 -1
  95. package/dist/generators/server/templates/src/main/java/_package_/config/SecurityConfiguration_reactive.java.ejs +2 -0
  96. package/dist/generators/server/templates/src/main/java/_package_/service/MailService.java.ejs +10 -4
  97. package/dist/generators/server/templates/src/main/java/_package_/web/rest/PublicUserResource.java.ejs +3 -4
  98. package/dist/generators/server/templates/src/main/java/_package_/web/rest/UserResource.java.ejs +0 -2
  99. package/dist/generators/server/templates/src/main/java/_package_/web/rest/errors/ExceptionTranslator.java.ejs +4 -7
  100. package/dist/generators/server/templates/src/test/java/_package_/_entityPackage_/web/rest/_entityClass_ResourceIT.java.ejs +3 -3
  101. package/dist/generators/server/templates/src/test/java/_package_/web/rest/AccountResourceIT.java.ejs +4 -4
  102. package/dist/generators/spring-data-elasticsearch/templates/src/main/java/_package_/_entityPackage_/repository/search/_entityClass_SearchRepository.java.ejs +0 -2
  103. package/dist/generators/spring-data-neo4j/templates/src/main/java/_package_/_entityPackage_/domain/_persistClass_.java.jhi.spring_data_neo4j.ejs +3 -37
  104. package/dist/generators/vue/generator.mjs +25 -16
  105. package/dist/generators/vue/resources/package.json +17 -16
  106. package/dist/generators/vue/templates/package.json.ejs +3 -5
  107. package/dist/generators/vue/templates/src/main/webapp/app/core/jhi-navbar/jhi-navbar.component.ts.ejs +10 -3
  108. package/dist/generators/vue/templates/src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue.ejs +3 -4
  109. package/dist/generators/vue/templates/src/main/webapp/app/entities/_entityFolder_/_entityFile_.service.ts.ejs +1 -1
  110. package/dist/generators/vue/templates/src/main/webapp/app/entities/_entityFolder_/_entityFile_.vue.ejs +12 -0
  111. package/dist/generators/vue/templates/src/main/webapp/app/router/index.ts.ejs +8 -1
  112. package/dist/generators/vue/templates/webpack/webpack.microfrontend.js.jhi.vue.ejs +19 -34
  113. package/dist/jdl/converters/jdl-to-json/jdl-to-json-option-converter.js +2 -2
  114. package/dist/jdl/converters/jdl-to-json/jdl-to-json-relationship-converter.js +1 -1
  115. package/dist/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.js +1 -1
  116. package/dist/jdl/converters/parsed-jdl-to-jdl-object/application-converter.js +1 -17
  117. package/dist/jdl/exporters/applications/jhipster-application-formatter.js +17 -1
  118. package/dist/jdl/exporters/config.js +11 -0
  119. package/dist/jdl/exporters/export-utils.js +2 -13
  120. package/dist/jdl/index.js +1 -0
  121. package/dist/jdl/jdl-importer.js +6 -2
  122. package/dist/jdl/models/jdl-application-configuration-factory.js +34 -2
  123. package/dist/jdl/models/jdl-application-configuration.js +6 -3
  124. package/dist/jdl/models/jdl-application-factory.js +2 -2
  125. package/dist/jdl/models/jdl-application.js +12 -3
  126. package/dist/jdl/parsing/jdl-ast-builder-visitor.js +63 -7
  127. package/dist/jdl/parsing/jdl-parser.js +52 -6
  128. package/dist/jdl/parsing/lexer/application-tokens.js +1 -1
  129. package/dist/jdl/validators/entity-validator.js +4 -2
  130. package/dist/jdl/validators/enum-validator.js +4 -2
  131. package/dist/jdl/validators/jdl-with-application-validator.js +18 -8
  132. package/dist/jdl/validators/jdl-without-application-validator.js +30 -29
  133. package/dist/jdl/validators/validator.js +1 -1
  134. package/dist/types/generators/base/support/config.d.mts +3 -3
  135. package/dist/types/generators/base-application/support/prepare-entity.d.mts +6 -1
  136. package/dist/types/generators/bootstrap/support/auto-crlf-transform.d.mts +3 -2
  137. package/dist/types/generators/bootstrap/support/multi-step-transform/index.d.mts +3 -20
  138. package/dist/types/generators/bootstrap/support/multi-step-transform/template-file-fs.d.mts +15 -9
  139. package/dist/types/generators/bootstrap/support/multi-step-transform/template-file.d.mts +1 -0
  140. package/dist/types/generators/generator-constants.d.mts +3 -3
  141. package/dist/types/generators/java/support/package-info-transform.d.mts +1 -4
  142. package/dist/types/generators/server/support/templates/field-values.d.mts +1 -1
  143. package/dist/types/jdl/exporters/config.d.ts +1 -0
  144. package/dist/types/jdl/index.d.ts +1 -0
  145. package/dist/types/jdl/jdl-importer.d.ts +1 -0
  146. package/dist/types/jdl/models/jdl-application-configuration-factory.d.ts +1 -0
  147. package/dist/types/jdl/models/jdl-application-configuration-option.d.ts +2 -2
  148. package/dist/types/jdl/models/jdl-application-configuration.d.ts +8 -6
  149. package/dist/types/jdl/models/jdl-application-factory.d.ts +1 -1
  150. package/dist/types/jdl/models/jdl-application.d.ts +8 -4
  151. package/dist/types/jdl/models/jdl-object.d.ts +1 -1
  152. package/dist/types/jdl/models/list-jdl-application-configuration-option.d.ts +1 -1
  153. package/dist/types/jdl/parsing/jdl-ast-builder-visitor.d.ts +10 -0
  154. package/dist/types/jdl/parsing/jdl-parser.d.ts +3 -0
  155. package/dist/types/jdl/parsing/lexer/application-tokens.d.ts +1 -0
  156. package/dist/types/jdl/validators/entity-validator.d.ts +2 -2
  157. package/dist/types/jdl/validators/enum-validator.d.ts +2 -2
  158. package/dist/types/jdl/validators/validator.d.ts +4 -1
  159. package/package.json +31 -31
@@ -17,36 +17,26 @@
17
17
  * limitations under the License.
18
18
  */
19
19
  /* eslint-disable consistent-return */
20
- import crypto from 'crypto';
21
- import fs from 'fs';
22
- import ChildProcess from 'child_process';
23
- import util from 'util';
24
- import * as _ from 'lodash-es';
20
+ import { kebabCase } from 'lodash-es';
25
21
  import chalk from 'chalk';
26
22
  import { glob } from 'glob';
27
- import runAsync from 'run-async';
28
- import BaseGenerator from '../base/index.mjs';
23
+ import BaseGenerator from '../base-application/index.mjs';
29
24
  import statistics from '../statistics.mjs';
30
- import { CLIENT_MAIN_SRC_DIR, JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs';
31
- import { GENERATOR_HEROKU } from '../generator-list.mjs';
32
- import { authenticationTypes, buildToolTypes, cacheTypes, databaseTypes, searchEngineTypes, serviceDiscoveryTypes, } from '../../jdl/jhipster/index.mjs';
25
+ import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs';
26
+ import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_HEROKU } from '../generator-list.mjs';
33
27
  import { mavenProfileContent } from './templates.mjs';
34
28
  import { createPomStorage } from '../maven/support/pom-store.mjs';
35
29
  import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.mjs';
36
- import { getFrontendAppName } from '../base/support/index.mjs';
37
- import { loadAppConfig, loadDerivedAppConfig } from '../app/support/index.mjs';
38
- import { loadDerivedPlatformConfig, loadDerivedServerConfig, loadPlatformConfig, loadServerConfig } from '../server/support/index.mjs';
39
- import { loadLanguagesConfig } from '../languages/support/index.mjs';
40
- const cacheProviderOptions = cacheTypes;
41
- const { MEMCACHED, REDIS } = cacheTypes;
42
- const { OAUTH2 } = authenticationTypes;
43
- const { GRADLE, MAVEN } = buildToolTypes;
44
- const { ELASTICSEARCH } = searchEngineTypes;
45
- const { MARIADB, MYSQL, POSTGRESQL } = databaseTypes;
46
- const { EUREKA } = serviceDiscoveryTypes;
47
- const NO_CACHE_PROVIDER = cacheProviderOptions.NO;
48
- const execCmd = util.promisify(ChildProcess.exec);
49
30
  export default class HerokuGenerator extends BaseGenerator {
31
+ hasHerokuCli;
32
+ herokuAppName;
33
+ herokuDeployType;
34
+ herokuJavaVersion;
35
+ herokuRegion;
36
+ herokuAppExists;
37
+ herokuSkipDeploy;
38
+ herokuSkipBuild;
39
+ dynoSize;
50
40
  constructor(args, options, features) {
51
41
  super(args, options, features);
52
42
  this.option('skip-build', {
@@ -62,7 +52,6 @@ export default class HerokuGenerator extends BaseGenerator {
62
52
  if (this.options.help) {
63
53
  return;
64
54
  }
65
- this.randomPassword = crypto.randomBytes(20).toString('hex');
66
55
  this.herokuSkipBuild = this.options.skipBuild;
67
56
  this.herokuSkipDeploy = this.options.skipDeploy || this.options.skipBuild;
68
57
  }
@@ -70,520 +59,338 @@ export default class HerokuGenerator extends BaseGenerator {
70
59
  if (!this.fromBlueprint) {
71
60
  await this.composeWithBlueprints(GENERATOR_HEROKU);
72
61
  }
62
+ if (!this.delegateToBlueprint) {
63
+ await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION);
64
+ }
73
65
  }
74
66
  get initializing() {
75
- return {
76
- loadCommonConfig() {
77
- loadAppConfig({ config: this.jhipsterConfigWithDefaults, application: this, useVersionPlaceholders: this.useVersionPlaceholders });
78
- loadServerConfig({ config: this.jhipsterConfigWithDefaults, application: this });
79
- loadLanguagesConfig({ application: this, config: this.jhipsterConfigWithDefaults });
80
- loadPlatformConfig({ config: this.jhipsterConfigWithDefaults, application: this });
81
- loadDerivedAppConfig({ application: this });
82
- loadDerivedPlatformConfig({ application: this });
83
- loadDerivedServerConfig({ application: this });
67
+ return this.asInitializingTaskGroup({
68
+ async checkInstallation() {
69
+ const { exitCode } = await this.spawnHerokuCommand('--version', { verboseInfo: false });
70
+ this.hasHerokuCli = exitCode === 0;
71
+ if (!this.hasHerokuCli) {
72
+ 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.";
73
+ if (this.skipChecks) {
74
+ this.log.warn(error);
75
+ this.log.warn('Generation will continue with limited support');
76
+ }
77
+ else {
78
+ throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`);
79
+ }
80
+ }
81
+ },
82
+ async herokuLogin() {
83
+ if (!this.hasHerokuCli)
84
+ return;
85
+ const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false });
86
+ if (exitCode !== 0) {
87
+ this.log.log(chalk.bold('Log in to Heroku to continue.'));
88
+ await this.spawnHerokuCommand('login', { reject: true, stdio: 'inherit' });
89
+ }
84
90
  },
85
91
  initializing() {
86
92
  this.log.log(chalk.bold('Heroku configuration is starting'));
87
- const configuration = this.config;
88
- this.env.options.appPath = configuration.get('appPath') || CLIENT_MAIN_SRC_DIR;
89
- this.cacheProvider = this.cacheProvider || NO_CACHE_PROVIDER;
90
- this.enableHibernateCache = this.enableHibernateCache && ![NO_CACHE_PROVIDER, MEMCACHED].includes(this.cacheProvider);
91
- this.frontendAppName = getFrontendAppName({ baseName: this.jhipsterConfig.baseName });
92
- this.herokuAppName = configuration.get('herokuAppName');
93
- this.dynoSize = 'Free';
94
- this.herokuDeployType = configuration.get('herokuDeployType');
95
- this.herokuJavaVersion = configuration.get('herokuJavaVersion');
96
- this.useOkta = configuration.get('useOkta');
97
- this.oktaAdminLogin = configuration.get('oktaAdminLogin');
98
- this.oktaAdminPassword = configuration.get('oktaAdminPassword');
93
+ this.dynoSize = 'Basic';
94
+ this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName);
99
95
  },
100
- };
96
+ });
101
97
  }
102
98
  get [BaseGenerator.INITIALIZING]() {
103
99
  return this.delegateTasksToBlueprint(() => this.initializing);
104
100
  }
105
101
  get prompting() {
106
- return {
107
- askForApp: runAsync(function () {
108
- const done = this.async();
109
- if (this.herokuAppName) {
110
- ChildProcess.exec(`heroku apps:info --json ${this.herokuAppName}`, (err, stdout) => {
111
- if (err) {
112
- this.abort = true;
113
- this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`);
114
- this.log.error('Run the generator again to create a new application.');
115
- this.herokuAppName = null;
116
- }
117
- else {
118
- const json = JSON.parse(stdout);
119
- this.herokuAppName = json.app.name;
120
- if (json.dynos.length > 0) {
121
- this.dynoSize = json.dynos[0].size;
122
- }
123
- this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`);
124
- this.herokuAppExists = true;
125
- this.config.set({
126
- herokuAppName: this.herokuAppName,
127
- herokuDeployType: this.herokuDeployType,
128
- });
129
- }
130
- done();
102
+ return this.asPromptingTaskGroup({
103
+ async askForApp() {
104
+ if (this.hasHerokuCli && this.herokuAppExists) {
105
+ const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], {
106
+ verboseInfo: false,
131
107
  });
108
+ if (exitCode !== 0) {
109
+ this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`);
110
+ this.herokuAppName = null;
111
+ throw new Error('Run the generator again to create a new application.');
112
+ }
113
+ else {
114
+ const json = JSON.parse(stdout);
115
+ this.herokuAppName = json.app.name;
116
+ if (json.dynos.length > 0) {
117
+ this.dynoSize = json.dynos[0].size;
118
+ }
119
+ this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`);
120
+ this.config.set({
121
+ herokuAppName: this.herokuAppName,
122
+ });
123
+ }
132
124
  }
133
125
  else {
134
- const prompts = [
126
+ await this.prompt([
135
127
  {
136
128
  type: 'input',
137
129
  name: 'herokuAppName',
138
130
  message: 'Name to deploy as:',
139
131
  default: this.baseName,
140
132
  },
133
+ ], this.config);
134
+ const answers = await this.prompt([
141
135
  {
142
136
  type: 'list',
143
137
  name: 'herokuRegion',
144
- message: 'On which region do you want to deploy ?',
138
+ message: 'On which region do you want to deploy?',
145
139
  choices: ['us', 'eu'],
146
140
  default: 0,
147
141
  },
148
- ];
149
- this.prompt(prompts).then(props => {
150
- this.herokuAppName = _.kebabCase(props.herokuAppName);
151
- this.herokuRegion = props.herokuRegion;
152
- this.herokuAppExists = false;
153
- done();
154
- });
142
+ ]);
143
+ this.herokuRegion = answers.herokuRegion;
155
144
  }
156
- }),
157
- askForHerokuDeployType() {
158
- if (this.abort)
159
- return null;
160
- if (this.herokuDeployType)
161
- return null;
162
- const prompts = [
145
+ },
146
+ async askForHerokuDeployType() {
147
+ await this.prompt([
163
148
  {
164
149
  type: 'list',
165
150
  name: 'herokuDeployType',
166
- message: 'Which type of deployment do you want ?',
151
+ message: 'Which type of deployment do you want?',
167
152
  choices: [
168
- {
169
- value: 'git',
170
- name: 'Git (compile on Heroku)',
171
- },
172
- {
173
- value: 'jar',
174
- name: 'JAR (compile locally)',
175
- },
153
+ { value: 'git', name: 'Git (compile on Heroku)' },
154
+ { value: 'jar', name: 'JAR (compile locally)' },
176
155
  ],
177
156
  default: 0,
178
157
  },
179
- ];
180
- return this.prompt(prompts).then(props => {
181
- this.herokuDeployType = props.herokuDeployType;
182
- });
158
+ ], this.config);
183
159
  },
184
- askForHerokuJavaVersion() {
185
- if (this.abort)
186
- return null;
187
- if (this.herokuJavaVersion)
188
- return null;
189
- const prompts = [
160
+ async askForHerokuJavaVersion() {
161
+ await this.prompt([
190
162
  {
191
163
  type: 'list',
192
164
  name: 'herokuJavaVersion',
193
- message: 'Which Java version would you like to use to build and run your app ?',
165
+ message: 'Which Java version would you like to use to build and run your app?',
194
166
  choices: JAVA_COMPATIBLE_VERSIONS.map(version => ({ value: version })),
195
167
  default: JAVA_VERSION,
196
168
  },
197
- ];
198
- return this.prompt(prompts).then(props => {
199
- this.herokuJavaVersion = props.herokuJavaVersion;
200
- });
169
+ ], this.config);
201
170
  },
202
- askForOkta() {
203
- if (this.abort)
204
- return null;
205
- if (this.authenticationType !== OAUTH2)
206
- return null;
207
- if (this.useOkta)
208
- return null;
209
- const prompts = [
210
- {
211
- type: 'list',
212
- name: 'useOkta',
213
- message: 'You are using OAuth 2.0. Do you want to use Okta? When you choose Okta, the automated configuration of users and groups requires cURL and jq.',
214
- choices: [
215
- {
216
- value: true,
217
- name: 'Yes, provision the Okta add-on',
218
- },
219
- {
220
- value: false,
221
- name: 'No, I want to configure my identity provider manually',
222
- },
223
- ],
224
- default: 1,
225
- },
226
- {
227
- when: answers => answers.useOkta,
228
- type: 'input',
229
- name: 'oktaAdminLogin',
230
- message: 'Login (valid email) for the JHipster Admin user:',
231
- validate: input => {
232
- if (!input) {
233
- return 'You must enter a login for the JHipster admin';
234
- }
235
- return true;
236
- },
237
- },
238
- ];
239
- return this.prompt(prompts).then(props => {
240
- this.useOkta = props.useOkta;
241
- if (this.useOkta) {
242
- this.oktaAdminLogin = props.oktaAdminLogin;
243
- this.oktaAdminPassword = this.randomPassword;
244
- }
245
- });
246
- },
247
- };
171
+ });
248
172
  }
249
173
  get [BaseGenerator.PROMPTING]() {
250
174
  return this.delegateTasksToBlueprint(() => this.prompting);
251
175
  }
252
- get configuring() {
253
- return {
254
- checkInstallation: runAsync(function () {
255
- if (this.abort)
256
- return;
257
- const done = this.async();
258
- ChildProcess.exec('heroku --version', err => {
259
- if (err) {
260
- this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/");
261
- this.abort = true;
262
- }
263
- done();
264
- });
265
- }),
176
+ get loading() {
177
+ return this.asConfiguringTaskGroup({
266
178
  saveConfig() {
267
- this.config.set({
268
- herokuAppName: this.herokuAppName,
269
- herokuDeployType: this.herokuDeployType,
270
- herokuJavaVersion: this.herokuJavaVersion,
271
- useOkta: this.useOkta,
272
- oktaAdminLogin: this.oktaAdminLogin,
273
- });
179
+ this.herokuAppName = kebabCase(this.jhipsterConfig.herokuAppName);
180
+ this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion;
181
+ this.herokuDeployType = this.jhipsterConfig.herokuDeployType;
274
182
  },
275
- };
183
+ });
276
184
  }
277
- get [BaseGenerator.CONFIGURING]() {
278
- return this.delegateTasksToBlueprint(() => this.configuring);
185
+ get [BaseGenerator.LOADING]() {
186
+ return this.delegateTasksToBlueprint(() => this.loading);
279
187
  }
280
188
  get default() {
281
- return {
189
+ return this.asDefaultTaskGroup({
282
190
  insight() {
283
191
  statistics.sendSubGenEvent('generator', GENERATOR_HEROKU);
284
192
  },
285
- gitInit: runAsync(function () {
286
- if (this.abort)
193
+ async gitInit() {
194
+ if (!this.herokuDeployType === 'git')
287
195
  return;
288
- const done = this.async();
289
- try {
290
- fs.lstatSync('.git');
196
+ const git = this.createGit();
197
+ if (await git.checkIsRepo()) {
291
198
  this.log.log(chalk.bold('\nUsing existing Git repository'));
292
- done();
293
199
  }
294
- catch (e) {
295
- // An exception is thrown if the folder doesn't exist
200
+ else {
296
201
  this.log.log(chalk.bold('\nInitializing Git repository'));
297
- const child = ChildProcess.exec('git init', () => {
298
- done();
299
- });
300
- child.stdout.on('data', data => {
301
- this.log.verboseInfo(data.toString());
302
- });
202
+ await git.init();
303
203
  }
304
- }),
305
- installHerokuDeployPlugin: runAsync(function () {
306
- if (this.abort)
204
+ },
205
+ async installHerokuDeployPlugin() {
206
+ if (!this.hasHerokuCli)
307
207
  return;
308
- const done = this.async();
309
208
  const cliPlugin = 'heroku-cli-deploy';
310
- ChildProcess.exec('heroku plugins', (err, stdout) => {
311
- if (_.includes(stdout, cliPlugin)) {
209
+ const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { stdio: 'pipe' });
210
+ if (exitCode !== 0) {
211
+ if (stdout.includes(cliPlugin)) {
312
212
  this.log.log('\nHeroku CLI deployment plugin already installed');
313
- done();
314
213
  }
315
214
  else {
316
215
  this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin'));
317
- const child = ChildProcess.exec(`heroku plugins:install ${cliPlugin}`, err => {
318
- if (err) {
319
- this.abort = true;
320
- this.log.error(err);
321
- }
322
- done();
323
- });
324
- child.stdout.on('data', data => {
325
- this.log.verboseInfo(data.toString());
326
- });
216
+ const { exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`);
217
+ if (exitCode !== 0) {
218
+ throw new Error(stderr);
219
+ }
327
220
  }
328
- });
329
- }),
330
- herokuCreate: runAsync(function () {
331
- if (this.abort || this.herokuAppExists)
221
+ }
222
+ },
223
+ async herokuCreate() {
224
+ if (!this.hasHerokuCli || this.herokuAppExists)
332
225
  return;
333
- const done = this.async();
334
- const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : '';
335
- this.log.log(chalk.bold('\nCreating Heroku application and setting up node environment'));
336
- const child = ChildProcess.exec(`heroku create ${this.herokuAppName}${regionParams}`, { timeout: 6000 }, (err, stdout, stderr) => {
337
- if (err) {
338
- if (stderr.includes('is already taken')) {
339
- const prompts = [
340
- {
341
- type: 'list',
342
- name: 'herokuForceName',
343
- message: `The Heroku application "${chalk.cyan(this.herokuAppName)}" already exists! Use it anyways?`,
344
- choices: [
345
- {
346
- value: 'Yes',
347
- name: 'Yes, I have access to it',
348
- },
349
- {
350
- value: 'No',
351
- name: 'No, generate a random name',
352
- },
353
- ],
354
- default: 0,
355
- },
356
- ];
357
- this.log.verboseInfo('');
358
- this.prompt(prompts).then(props => {
359
- if (props.herokuForceName === 'Yes') {
360
- ChildProcess.exec(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => {
361
- if (err) {
362
- this.abort = true;
363
- this.log.error(err);
364
- }
365
- else {
366
- this.log.verboseInfo(stdout.trim());
367
- this.config.set({
368
- herokuAppName: this.herokuAppName,
369
- herokuDeployType: this.herokuDeployType,
370
- });
371
- }
372
- done();
373
- });
374
- }
375
- else {
376
- ChildProcess.exec(`heroku create ${regionParams}`, (err, stdout) => {
377
- if (err) {
378
- this.abort = true;
379
- this.log.error(err);
380
- }
381
- else {
382
- // Extract from "Created random-app-name-1234... done"
383
- this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp'));
384
- this.log.verboseInfo(stdout.trim());
385
- // ensure that the git remote is the same as the appName
386
- ChildProcess.exec(`heroku git:remote --app ${this.herokuAppName}`, err => {
387
- if (err) {
388
- this.abort = true;
389
- this.log.error(err);
390
- }
391
- else {
392
- this.config.set({
393
- herokuAppName: this.herokuAppName,
394
- herokuDeployType: this.herokuDeployType,
395
- });
396
- }
397
- done();
398
- });
399
- }
400
- });
401
- }
402
- });
226
+ const regionParams = this.herokuRegion !== 'us' ? ['--region', this.herokuRegion] : [];
227
+ this.log.log(chalk.bold('\nCreating Heroku application and setting up Node environment'));
228
+ const { stdout, stderr, exitCode } = await this.spawnHeroku(['create', this.herokuAppName, ...regionParams]);
229
+ if (stdout.includes('Heroku credentials')) {
230
+ throw new Error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again.");
231
+ }
232
+ if (exitCode !== 0) {
233
+ if (stderr.includes('is already taken')) {
234
+ const prompts = [
235
+ {
236
+ type: 'list',
237
+ name: 'herokuForceName',
238
+ message: `The Heroku application "${chalk.cyan(this.herokuAppName)}" already exists! Use it anyways?`,
239
+ choices: [
240
+ {
241
+ value: 'Yes',
242
+ name: 'Yes, I have access to it',
243
+ },
244
+ {
245
+ value: 'No',
246
+ name: 'No, generate a random name',
247
+ },
248
+ ],
249
+ default: 0,
250
+ },
251
+ ];
252
+ this.log.log('');
253
+ const props = await this.prompt(prompts);
254
+ if (props.herokuForceName === 'Yes') {
255
+ await this.spawnHeroku(['git:remote', '--app', this.herokuAppName], { reject: true });
403
256
  }
404
257
  else {
405
- this.abort = true;
406
- this.herokuAppName = null;
407
- if (stderr.includes('Invalid credentials')) {
408
- this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again.");
409
- }
410
- else {
411
- this.log.error(err);
412
- }
258
+ const { stdout } = await this.spawnHeroku(['create', ...regionParams]);
259
+ // Extract from "Created random-app-name-1234... done"
260
+ this.herokuAppName = stdout.substring(stdout.lastIndexOf('/') + 1, stdout.indexOf('.git'));
261
+ // ensure that the git remote is the same as the appName
262
+ await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]);
263
+ this.jhipsterConfig.herokuAppName = this.herokuAppName;
413
264
  }
414
265
  }
415
- else {
416
- done();
417
- }
418
- });
419
- child.stdout.on('data', data => {
420
- const output = data.toString();
421
- if (data.search('Heroku credentials') >= 0) {
422
- this.abort = true;
423
- this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again.");
424
- done();
266
+ else if (stderr.includes('Invalid credentials')) {
267
+ this.log.error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again.");
425
268
  }
426
269
  else {
427
- this.log.verboseInfo(output.trim());
270
+ throw new Error(stderr);
428
271
  }
429
- });
430
- }),
431
- herokuAddonsCreate: runAsync(function () {
432
- if (this.abort)
272
+ }
273
+ },
274
+ async herokuAddonsCreate({ application }) {
275
+ if (!this.hasHerokuCli || this.herokuAppExists)
433
276
  return;
434
- const done = this.async();
435
- const addonCreateCallback = (addon, err) => {
436
- if (err) {
437
- const verifyAccountUrl = 'https://heroku.com/verify';
438
- if (_.includes(err, verifyAccountUrl)) {
439
- this.abort = true;
440
- this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`);
441
- this.log.error(err);
442
- }
443
- else {
444
- this.log.verboseInfo(`No new ${addon} addon created`);
445
- }
446
- }
447
- else {
448
- this.log.verboseInfo(`Created ${addon} addon`);
449
- }
450
- };
451
277
  this.log.log(chalk.bold('\nProvisioning addons'));
452
- if (this.searchEngine === ELASTICSEARCH) {
278
+ if (application.searchEngineElasticsearch) {
453
279
  this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon'));
454
- ChildProcess.exec(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, (err, stdout, stderr) => {
455
- addonCreateCallback.bind('Elasticsearch', err, stdout, stderr);
456
- });
457
- }
458
- if (this.useOkta) {
459
- this.log.log(chalk.bold('\nProvisioning okta addon'));
460
- ChildProcess.exec(`heroku addons:create okta --app ${this.herokuAppName}`, (err, stdout, stderr) => {
461
- addonCreateCallback('Okta', err, stdout, stderr);
462
- });
280
+ const { stdout, stderr } = await this.spawn('heroku', [
281
+ 'addons:create',
282
+ 'bonsai:sandbox-6',
283
+ '--as',
284
+ 'BONSAI',
285
+ '--app',
286
+ this.herokuAppName,
287
+ ]);
288
+ this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr });
463
289
  }
464
290
  let dbAddOn;
465
- if (this.prodDatabaseType === POSTGRESQL) {
466
- dbAddOn = 'heroku-postgresql --as DATABASE';
291
+ if (application.prodDatabaseTypePostgresql) {
292
+ dbAddOn = 'heroku-postgresql';
467
293
  }
468
- else if (this.prodDatabaseType === MYSQL) {
469
- dbAddOn = 'jawsdb:kitefin --as DATABASE';
294
+ else if (application.prodDatabaseTypeMysql) {
295
+ dbAddOn = 'jawsdb:kitefin';
470
296
  }
471
- else if (this.prodDatabaseType === MARIADB) {
472
- dbAddOn = 'jawsdb-maria:kitefin --as DATABASE';
297
+ else if (application.prodDatabaseTypeMariadb) {
298
+ dbAddOn = 'jawsdb-maria:kitefin';
473
299
  }
474
300
  if (dbAddOn) {
475
301
  this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`));
476
- ChildProcess.exec(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => {
477
- addonCreateCallback('Database', err, stdout, stderr);
478
- });
302
+ const { stdout, stderr } = await this.spawn('heroku', [
303
+ 'addons:create',
304
+ dbAddOn,
305
+ '--as',
306
+ 'DATABASE',
307
+ '--app',
308
+ this.herokuAppName,
309
+ ]);
310
+ this.checkAddOnReturn({ addOn: 'Database', stdout, stderr });
479
311
  }
480
312
  else {
481
313
  this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`));
482
314
  }
483
315
  let cacheAddOn;
484
- if (this.cacheProvider === MEMCACHED) {
485
- cacheAddOn = 'memcachier:dev --as MEMCACHIER';
316
+ if (application.cacheProviderMemcached) {
317
+ cacheAddOn = ['memcachier:dev', '--as', 'MEMCACHIER'];
486
318
  }
487
- else if (this.cacheProvider === REDIS) {
488
- cacheAddOn = 'heroku-redis:hobby-dev --as REDIS';
319
+ else if (application.cacheProviderRedis) {
320
+ cacheAddOn = ['heroku-redis:hobby-dev', '--as', 'REDIS'];
489
321
  }
490
322
  if (cacheAddOn) {
491
- this.log.log(chalk.bold(`\nProvisioning cache addon ${cacheAddOn}`));
492
- ChildProcess.exec(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => {
493
- addonCreateCallback('Cache', err, stdout, stderr);
494
- });
323
+ this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`));
324
+ const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName]);
325
+ this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr });
495
326
  }
496
- else {
497
- this.log.log(chalk.bold(`\nNo suitable cache addon for cacheprovider ${this.cacheProvider} available.`));
498
- }
499
- done();
500
- }),
501
- configureJHipsterRegistry() {
502
- if (this.abort || this.herokuAppExists)
327
+ },
328
+ async configureJHipsterRegistry({ application }) {
329
+ if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka)
503
330
  return undefined;
504
- if (this.serviceDiscoveryType === EUREKA) {
505
- const prompts = [
506
- {
507
- type: 'input',
508
- name: 'herokuJHipsterRegistryApp',
509
- message: 'What is the name of your JHipster Registry Heroku application?',
510
- default: 'jhipster-registry',
511
- },
512
- {
513
- type: 'input',
514
- name: 'herokuJHipsterRegistryUsername',
515
- message: 'What is your JHipster Registry username?',
516
- default: 'admin',
517
- },
518
- {
519
- type: 'input',
520
- name: 'herokuJHipsterRegistryPassword',
521
- message: 'What is your JHipster Registry password?',
522
- default: 'password',
523
- },
524
- ];
525
- this.log.verboseInfo('');
526
- return this.prompt(prompts).then(props => {
527
- // Encode username/password to avoid errors caused by spaces
528
- props.herokuJHipsterRegistryUsername = encodeURIComponent(props.herokuJHipsterRegistryUsername);
529
- props.herokuJHipsterRegistryPassword = encodeURIComponent(props.herokuJHipsterRegistryPassword);
530
- const herokuJHipsterRegistry = `https://${props.herokuJHipsterRegistryUsername}:${props.herokuJHipsterRegistryPassword}@${props.herokuJHipsterRegistryApp}.herokuapp.com`;
531
- const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`;
532
- const child = ChildProcess.exec(configSetCmd, err => {
533
- if (err) {
534
- this.abort = true;
535
- this.log.error(err);
536
- }
537
- });
538
- child.stdout.on('data', data => {
539
- this.log.verboseInfo(data.toString());
540
- });
541
- });
542
- }
543
- return undefined;
331
+ this.log.log('');
332
+ const answers = await this.prompt([
333
+ {
334
+ type: 'input',
335
+ name: 'herokuJHipsterRegistryApp',
336
+ message: 'What is the name of your JHipster Registry Heroku application?',
337
+ default: 'jhipster-registry',
338
+ },
339
+ {
340
+ type: 'input',
341
+ name: 'herokuJHipsterRegistryUsername',
342
+ message: 'What is your JHipster Registry username?',
343
+ default: 'admin',
344
+ },
345
+ {
346
+ type: 'input',
347
+ name: 'herokuJHipsterRegistryPassword',
348
+ message: 'What is your JHipster Registry password?',
349
+ default: 'password',
350
+ },
351
+ ]);
352
+ // Encode username/password to avoid errors caused by spaces
353
+ const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername);
354
+ const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword);
355
+ const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`;
356
+ const configSetCmd = ['config:set', 'JHIPSTER_REGISTRY_URL', herokuJHipsterRegistry, '--app', this.herokuAppName];
357
+ await this.spawnHeroku(configSetCmd, { stdio: 'pipe' });
544
358
  },
545
- };
359
+ });
546
360
  }
547
361
  get [BaseGenerator.DEFAULT]() {
548
362
  return this.delegateTasksToBlueprint(() => this.default);
549
363
  }
550
364
  get writing() {
551
365
  return this.asWritingTaskGroup({
552
- copyHerokuFiles() {
553
- if (this.abort)
554
- return;
366
+ copyHerokuFiles({ application }) {
555
367
  this.log.log(chalk.bold('\nCreating Heroku deployment files'));
556
- this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`);
557
- this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`);
558
- this.writeFile('Procfile.ejs', 'Procfile');
559
- this.writeFile('system.properties.ejs', 'system.properties');
560
- if (this.buildTool === GRADLE) {
561
- this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle');
562
- }
563
- if (this.useOkta) {
564
- this.writeFile('provision-okta-addon.sh.ejs', 'provision-okta-addon.sh');
565
- fs.appendFile('.gitignore', 'provision-okta-addon.sh', 'utf8', err => {
566
- if (err) {
567
- this.log.warn(`${chalk.yellow.bold('WARNING!')} Failed to add 'provision-okta-addon.sh' to .gitignore.'`);
568
- }
569
- });
368
+ const context = {
369
+ ...application,
370
+ herokuAppName: this.herokuAppName,
371
+ dynoSize: this.dynoSize,
372
+ herokuJavaVersion: this.herokuJavaVersion,
373
+ herokuDeployType: this.herokuDeployType,
374
+ };
375
+ this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, context);
376
+ this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, context);
377
+ this.writeFile('Procfile.ejs', 'Procfile', context);
378
+ this.writeFile('system.properties.ejs', 'system.properties', context);
379
+ if (application.buildToolGradle) {
380
+ this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle', context);
570
381
  }
571
382
  },
572
- addHerokuBuildPlugin() {
573
- if (this.abort)
574
- return;
575
- if (this.buildTool !== GRADLE)
383
+ addHerokuBuildPlugin({ application }) {
384
+ if (!application.buildToolGradle)
576
385
  return;
577
386
  // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator
578
- this.editFile('build.gradle', addGradlePluginCallback({ groupId: 'gradle.plugin.com.heroku.sdk', artifactId: 'heroku-gradle', version: '1.0.4' }));
387
+ this.editFile('build.gradle', addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' }));
579
388
  // TODO applyFromGradleCallback is an internal api, switch to source api when converted to BaseApplicationGenerator
580
389
  this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' }));
581
390
  },
582
- addHerokuMavenProfile() {
583
- if (this.abort)
584
- return;
585
- if (this.buildTool === MAVEN) {
586
- this.addMavenProfile('heroku', mavenProfileContent(this));
391
+ addHerokuMavenProfile({ application }) {
392
+ if (application.buildToolMaven) {
393
+ this.addMavenProfile('heroku', mavenProfileContent(application));
587
394
  }
588
395
  },
589
396
  });
@@ -593,151 +400,47 @@ export default class HerokuGenerator extends BaseGenerator {
593
400
  }
594
401
  get end() {
595
402
  return this.asEndTaskGroup({
596
- makeScriptExecutable() {
597
- if (this.abort)
598
- return;
599
- if (this.useOkta) {
600
- try {
601
- fs.chmodSync('provision-okta-addon.sh', '755');
602
- }
603
- catch (err) {
604
- this.log.warn(`${chalk.yellow.bold('WARNING!')}Failed to make 'provision-okta-addon.sh' executable, you may need to run 'chmod +x provison-okta-addon.sh'`);
605
- }
606
- }
607
- },
608
403
  async productionBuild() {
609
- if (this.abort)
610
- return;
611
404
  if (this.herokuSkipBuild || this.herokuDeployType === 'git') {
612
405
  this.log.log(chalk.bold('\nSkipping build'));
613
406
  return;
614
407
  }
615
408
  this.log.log(chalk.bold('\nBuilding application'));
616
409
  // Use npm script so blueprints just need to override it.
617
- await this.spawnCommand('npm run java:jar:prod', { stdio: 'inherit' });
410
+ await this.printChildOutput(this.spawnCommand('npm run java:jar:prod', { stdio: 'pipe' }));
618
411
  },
619
- async productionDeploy() {
620
- if (this.abort)
621
- return;
622
- if (this.herokuSkipDeploy) {
412
+ async productionDeploy({ application }) {
413
+ if (this.herokuSkipDeploy || !this.hasHerokuCli) {
623
414
  this.log.log(chalk.bold('\nSkipping deployment'));
624
415
  return;
625
416
  }
626
417
  if (this.herokuDeployType === 'git') {
627
418
  try {
628
419
  this.log.log(chalk.bold('\nUpdating Git repository'));
629
- const gitAddCmd = 'git add .';
630
- this.log.log(chalk.cyan(gitAddCmd));
631
- const gitAdd = execCmd(gitAddCmd);
632
- gitAdd.child.stdout.on('data', data => {
633
- this.log.verboseInfo(data);
634
- });
635
- gitAdd.child.stderr.on('data', data => {
636
- this.log.verboseInfo(data);
637
- });
638
- await gitAdd;
639
- const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty';
640
- this.log.log(chalk.cyan(gitCommitCmd));
641
- const gitCommit = execCmd(gitCommitCmd);
642
- gitCommit.child.stdout.on('data', data => {
643
- this.log.verboseInfo(data);
644
- });
645
- gitCommit.child.stderr.on('data', data => {
646
- this.log.verboseInfo(data);
647
- });
648
- await gitCommit;
420
+ const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr }));
421
+ await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null });
649
422
  let buildpack = 'heroku/java';
650
- let configVars = 'MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" ';
651
- if (this.buildTool === GRADLE) {
423
+ let configName = 'MAVEN_CUSTOM_OPTS';
424
+ let configValues = '-Pprod,heroku -DskipTests';
425
+ if (application.buildToolGradle) {
652
426
  buildpack = 'heroku/gradle';
653
- configVars = 'GRADLE_TASK="stage -Pprod -PnodeInstall" ';
427
+ configName = 'GRADLE_TASK';
428
+ configValues = 'stage -Pprod -PnodeInstall';
654
429
  }
655
430
  this.log.log(chalk.bold('\nConfiguring Heroku'));
656
- await execCmd(`heroku config:set ${configVars}--app ${this.herokuAppName}`);
657
- const { stdout: data } = await execCmd(`heroku buildpacks:add ${buildpack} --app ${this.herokuAppName}`);
658
- if (data) {
659
- this.logger.info(data);
660
- // remote: ! The following add-ons were automatically provisioned: . These add-ons may incur additional cost,
661
- // which is prorated to the second. Run `heroku addons` for more info.
662
- if (data.includes('Run `heroku addons` for more info.')) {
663
- await execCmd('heroku addons');
664
- }
665
- this.log('');
666
- const prompts = [
667
- {
668
- type: 'list',
669
- name: 'userDeployDecision',
670
- message: 'Continue to deploy?',
671
- choices: [
672
- {
673
- value: 'Yes',
674
- name: 'Yes, I confirm',
675
- },
676
- {
677
- value: 'No',
678
- name: 'No, abort (Recommended)',
679
- },
680
- ],
681
- default: 0,
682
- },
683
- ];
684
- this.log('');
685
- const props = await this.prompt(prompts);
686
- if (props.userDeployDecision === 'Yes') {
687
- this.log.info(chalk.bold('Continuing deployment...'));
688
- }
689
- else {
690
- this.log(this.logger);
691
- this.log.info(chalk.bold('You aborted deployment!'));
692
- this.abort = true;
693
- this.herokuAppName = null;
694
- return;
695
- }
696
- this.log('');
431
+ const { stdout: configData } = await this.spawnHeroku(['config:get', configName, '--app', this.herokuAppName]);
432
+ if (!configData) {
433
+ await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]);
697
434
  }
698
- this.log.log(chalk.bold('\nDeploying application'));
699
- const herokuPush = execCmd('git push heroku HEAD:main', { maxBuffer: 1024 * 10000 });
700
- herokuPush.child.stdout.on('data', data => {
701
- this.log.verboseInfo(data);
702
- });
703
- herokuPush.child.stderr.on('data', data => {
704
- this.log.verboseInfo(data);
705
- });
706
- await herokuPush;
435
+ const { stdout: buildpackData } = await this.spawnHeroku(['buildpacks', '--app', this.herokuAppName]);
436
+ if (!buildpackData.includes(buildpack)) {
437
+ await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]);
438
+ }
439
+ this.log.log(chalk.bold('\nDeploying application...'));
440
+ await git.push('heroku', 'HEAD:main');
707
441
  this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`));
708
442
  this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`));
709
443
  this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`));
710
- if (this.useOkta) {
711
- let curlAvailable = false;
712
- let jqAvailable = false;
713
- try {
714
- await execCmd('curl --help');
715
- curlAvailable = true;
716
- }
717
- catch (err) {
718
- this.log.log(chalk.red('cURL is not available but required. See https://curl.haxx.se/download.html for installation guidance.'));
719
- this.log.log(chalk.yellow('After you have installed curl execute ./provision-okta-addon.sh manually.'));
720
- }
721
- try {
722
- await execCmd('jq --help');
723
- jqAvailable = true;
724
- }
725
- catch (err) {
726
- this.log.log(chalk.red('jq is not available but required. See https://stedolan.github.io/jq/download/ for installation guidance.'));
727
- this.log.log(chalk.yellow('After you have installed jq execute ./provision-okta-addon.sh manually.'));
728
- }
729
- if (curlAvailable && jqAvailable) {
730
- this.log.log(chalk.green('Running ./provision-okta-addon.sh to create all required roles and users for JHipster.'));
731
- try {
732
- await execCmd('./provision-okta-addon.sh');
733
- this.log.log(chalk.bold('\nOkta configured successfully!'));
734
- this.log.log(chalk.green(`\nUse ${chalk.bold(`${this.oktaAdminLogin}/${this.oktaAdminPassword}`)} to login.\n`));
735
- }
736
- catch (err) {
737
- this.log.log(chalk.red('Failed to execute ./provision-okta-addon.sh. Make sure to setup okta according to https://www.jhipster.tech/heroku/.'));
738
- }
739
- }
740
- }
741
444
  }
742
445
  catch (err) {
743
446
  this.log.error(err);
@@ -746,58 +449,18 @@ export default class HerokuGenerator extends BaseGenerator {
746
449
  else {
747
450
  this.log.log(chalk.bold('\nDeploying application'));
748
451
  let jarFileWildcard = 'target/*.jar';
749
- if (this.buildTool === GRADLE) {
452
+ if (application.buildToolGradle) {
750
453
  jarFileWildcard = 'build/libs/*.jar';
751
454
  }
752
455
  const files = glob.sync(jarFileWildcard, {});
753
456
  const jarFile = files[0];
754
- const herokuDeployCommand = `heroku deploy:jar ${jarFile} --app ${this.herokuAppName}`;
755
- const herokuSetBuildpackCommand = 'heroku buildpacks:set heroku/jvm';
756
457
  this.log.log(chalk.bold(`\nUploading your application code.\nThis may take ${chalk.cyan('several minutes')} depending on your connection speed...`));
757
458
  try {
758
- await execCmd(herokuSetBuildpackCommand);
759
- const herokuDeploy = execCmd(herokuDeployCommand);
760
- herokuDeploy.child.stdout.on('data', data => {
761
- this.log.verboseInfo(data);
762
- });
763
- herokuDeploy.child.stderr.on('data', data => {
764
- this.log.verboseInfo(data);
765
- });
766
- await herokuDeploy;
459
+ await this.spawnHeroku(['deploy:jar', jarFile, '--app', this.herokuAppName], { stdio: 'pipe' });
460
+ await this.spawnHerokuCommand('buildpacks:set heroku/jvm', { stdio: 'pipe' });
767
461
  this.log.log(chalk.green(`\nYour app should now be live. To view it run\n\t${chalk.bold('heroku open')}`));
768
462
  this.log.log(chalk.yellow(`And you can view the logs with this command\n\t${chalk.bold('heroku logs --tail')}`));
769
463
  this.log.log(chalk.yellow(`After application modification, redeploy it with\n\t${chalk.bold('jhipster heroku')}`));
770
- if (this.useOkta) {
771
- let curlAvailable = false;
772
- let jqAvailable = false;
773
- try {
774
- await execCmd('curl --help');
775
- curlAvailable = true;
776
- }
777
- catch (err) {
778
- this.log.log(chalk.red('cURL is not available but required. See https://curl.haxx.se/download.html for installation guidance.'));
779
- this.log.log(chalk.yellow('After you have installed curl execute ./provision-okta-addon.sh manually.'));
780
- }
781
- try {
782
- await execCmd('jq --help');
783
- jqAvailable = true;
784
- }
785
- catch (err) {
786
- this.log.log(chalk.red('jq is not available but required. See https://stedolan.github.io/jq/download/ for installation guidance.'));
787
- this.log.log(chalk.yellow('After you have installed jq execute ./provision-okta-addon.sh manually.'));
788
- }
789
- if (curlAvailable && jqAvailable) {
790
- this.log.log(chalk.green('Running ./provision-okta-addon.sh to create all required roles and users for JHipster.'));
791
- try {
792
- await execCmd('./provision-okta-addon.sh');
793
- this.log.log(chalk.bold('\nOkta configured successfully!'));
794
- this.log.log(chalk.green(`\nUse ${chalk.bold(`${this.oktaAdminLogin}/${this.oktaAdminPassword}`)} to login.`));
795
- }
796
- catch (err) {
797
- this.log.log(chalk.red('Failed to execute ./provision-okta-addon.sh. Make sure to set up Okta according to https://www.jhipster.tech/heroku/.'));
798
- }
799
- }
800
- }
801
464
  }
802
465
  catch (err) {
803
466
  this.log.error(err);
@@ -820,4 +483,64 @@ export default class HerokuGenerator extends BaseGenerator {
820
483
  addMavenProfile(profileId, other) {
821
484
  createPomStorage(this).addProfile({ id: profileId, content: other });
822
485
  }
486
+ /**
487
+ * @param {string} command
488
+ * @param {import('execa').Options} opt
489
+ * @returns {ReturnType<BaseGenerator['spawnCommand']>}
490
+ */
491
+ spawnHerokuCommand(command, opt) {
492
+ opt = { stdio: 'pipe', reject: false, ...opt };
493
+ const { verboseInfo, ...spawnOptions } = opt;
494
+ const child = this.spawnCommand(`heroku ${command}`, spawnOptions);
495
+ if (opt.stdio !== 'pipe' || verboseInfo === false) {
496
+ return child;
497
+ }
498
+ return this.printChildOutput(child);
499
+ }
500
+ /**
501
+ * @param {string[]} args
502
+ * @param {import('execa').Options} opt
503
+ * @returns {ReturnType<BaseGenerator['spawn']>}
504
+ */
505
+ spawnHeroku(args, opt) {
506
+ opt = { stdio: 'pipe', reject: false, ...opt };
507
+ const { verboseInfo, ...spawnOptions } = opt;
508
+ const child = this.spawn('heroku', args, spawnOptions);
509
+ if (spawnOptions.stdio !== 'pipe' || verboseInfo === false) {
510
+ return child;
511
+ }
512
+ return this.printChildOutput(child);
513
+ }
514
+ /**
515
+ * @template {{stdout: any; stderr: any}} T
516
+ * @param {T} child
517
+ * @param {(chunk: any) => void} child
518
+ * @returns {T}
519
+ */
520
+ printChildOutput(child, log = data => this.log.verboseInfo(data)) {
521
+ const { stdout, stderr } = child;
522
+ stdout.on('data', data => {
523
+ data.toString().split(/\r?\n/).filter(Boolean).forEach(log);
524
+ });
525
+ stderr.on('data', data => {
526
+ data.toString().split(/\r?\n/).filter(Boolean).forEach(log);
527
+ });
528
+ return child;
529
+ }
530
+ checkAddOnReturn({ addOn, stdout, stderr }) {
531
+ if (stdout) {
532
+ this.log.ok(`Created ${addOn.valueOf()} add-on`);
533
+ this.log.ok(stdout);
534
+ }
535
+ else if (stderr) {
536
+ const verifyAccountUrl = 'https://heroku.com/verify';
537
+ if (stderr.includes(verifyAccountUrl)) {
538
+ this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`);
539
+ throw new Error(stderr);
540
+ }
541
+ else {
542
+ this.log.verboseInfo(`No new ${addOn.valueOf()} add-on created`);
543
+ }
544
+ }
545
+ }
823
546
  }