From f40766a60ea8edf28502d61a98a33b036f9fb15f Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Mon, 23 Oct 2023 21:52:18 -0600 Subject: [PATCH 01/45] Update Heroku generator for JHipster 8 --- .../heroku/__snapshots__/heroku.spec.mts.snap | 1277 ----------------- generators/heroku/generator.mjs | 239 +-- generators/heroku/heroku.spec.mts | 88 +- .../templates/provision-okta-addon.sh.ejs | 217 --- 4 files changed, 83 insertions(+), 1738 deletions(-) delete mode 100644 generators/heroku/__snapshots__/heroku.spec.mts.snap delete mode 100644 generators/heroku/templates/provision-okta-addon.sh.ejs diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap deleted file mode 100644 index 407698558301..000000000000 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ /dev/null @@ -1,1277 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`generator - Heroku microservice application with JAR deployment should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "applicationType": "microservice", - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "jar", - "herokuJavaVersion": "17" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=17 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application in the EU should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "jar", - "herokuJavaVersion": "11" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application in the US should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "jar", - "herokuJavaVersion": "11" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application with Git deployment should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "git", - "herokuJavaVersion": "11" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application with PostgreSQL should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "jar", - "herokuJavaVersion": "11" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application with an unavailable app name should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-new-name", - "herokuDeployType": "jar", - "herokuJavaVersion": "11" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-new-name.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application with elasticsearch should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-test", - "herokuDeployType": "jar", - "herokuJavaVersion": "11", - "searchEngine": "elasticsearch" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-test.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 - elasticsearch: - uris: \${BONSAI_URL} -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=11 ", - "stateCleared": "modified", - }, -} -`; - -exports[`generator - Heroku monolith application with existing app should match files snapshot 1`] = ` -{ - ".yo-rc.json": { - "contents": "{ - "generator-jhipster": { - "baseName": "jhipster", - "herokuAppName": "jhipster-existing", - "herokuDeployType": "git", - "herokuJavaVersion": "17" - } -} -", - "stateCleared": "modified", - }, - "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku -", - "stateCleared": "modified", - }, - "pom.xml": { - "contents": " - - 4.0.0 - - - - heroku - - - - 7.10.2 - - - - - org.elasticsearch.client - elasticsearch-rest-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - \${bonsai.elasticsearch.version} - - - org.elasticsearch - elasticsearch - \${bonsai.elasticsearch.version} - - - org.elasticsearch.plugin - transport-netty4-client - \${bonsai.elasticsearch.version} - - - - - - - org.liquibase - liquibase-maven-plugin - - src/main/resources/config/liquibase/master.xml - src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml - - \${env.JDBC_DATABASE_URL} - - \${env.JDBC_DATABASE_USERNAME} - \${env.JDBC_DATABASE_PASSWORD} - hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - false - - - - maven-clean-plugin - \${maven-clean-plugin.version} - - - clean-artifacts - install - - clean - - - true - - - target - - *.jar - - false - - - node_modules - false - - - - - - - - - - - -", - "stateCleared": "modified", - }, - "src/main/resources/config/application-heroku.yml": { - "contents": "# =================================================================== -# Spring Boot configuration for the "heroku" profile. -# -# This configuration overrides the application.yml file. -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -eureka: - instance: - hostname: jhipster-existing.herokuapp.com - non-secure-port: 80 - prefer-ip-address: false - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: \${JDBC_DATABASE_URL} - username: \${JDBC_DATABASE_USERNAME} - password: \${JDBC_DATABASE_PASSWORD} - hikari: - maximumPoolSize: 8 -server: - port: \${PORT:8080} -", - "stateCleared": "modified", - }, - "src/main/resources/config/bootstrap-heroku.yml": { - "contents": "# =================================================================== -# Spring Cloud Config bootstrap configuration for the "heroku" profile -# =================================================================== - -", - "stateCleared": "modified", - }, - "system.properties": { - "contents": "java.runtime.version=17 ", - "stateCleared": "modified", - }, -} -`; diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 3e472fc72f0f..71f6aa51e545 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -19,26 +19,16 @@ /* eslint-disable consistent-return */ import crypto from 'crypto'; import fs from 'fs'; -import ChildProcess from 'child_process'; -import util from 'util'; import * as _ from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; -import runAsync from 'run-async'; import BaseGenerator from '../base/index.mjs'; import statistics from '../statistics.mjs'; import { CLIENT_MAIN_SRC_DIR, JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; import { GENERATOR_HEROKU } from '../generator-list.mjs'; -import { - authenticationTypes, - buildToolTypes, - cacheTypes, - databaseTypes, - searchEngineTypes, - serviceDiscoveryTypes, -} from '../../jdl/jhipster/index.mjs'; +import { buildToolTypes, cacheTypes, databaseTypes, searchEngineTypes, serviceDiscoveryTypes } from '../../jdl/jhipster/index.mjs'; import { mavenProfileContent } from './templates.mjs'; import { createPomStorage } from '../maven/support/pom-store.mjs'; import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.mjs'; @@ -49,14 +39,12 @@ import { loadLanguagesConfig } from '../languages/support/index.mjs'; const cacheProviderOptions = cacheTypes; const { MEMCACHED, REDIS } = cacheTypes; -const { OAUTH2 } = authenticationTypes; const { GRADLE, MAVEN } = buildToolTypes; const { ELASTICSEARCH } = searchEngineTypes; const { MARIADB, MYSQL, POSTGRESQL } = databaseTypes; const { EUREKA } = serviceDiscoveryTypes; const NO_CACHE_PROVIDER = cacheProviderOptions.NO; -const execCmd = util.promisify(ChildProcess.exec); export default class HerokuGenerator extends BaseGenerator { constructor(args, options, features) { @@ -113,9 +101,6 @@ export default class HerokuGenerator extends BaseGenerator { this.dynoSize = 'Free'; this.herokuDeployType = configuration.get('herokuDeployType'); this.herokuJavaVersion = configuration.get('herokuJavaVersion'); - this.useOkta = configuration.get('useOkta'); - this.oktaAdminLogin = configuration.get('oktaAdminLogin'); - this.oktaAdminPassword = configuration.get('oktaAdminPassword'); }, }; } @@ -126,11 +111,9 @@ export default class HerokuGenerator extends BaseGenerator { get prompting() { return { - askForApp: runAsync(function () { - const done = this.async(); - + async askForApp(done) { if (this.herokuAppName) { - ChildProcess.exec(`heroku apps:info --json ${this.herokuAppName}`, (err, stdout) => { + this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, (err, stdout) => { if (err) { this.abort = true; this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`); @@ -175,7 +158,7 @@ export default class HerokuGenerator extends BaseGenerator { done(); }); } - }), + }, askForHerokuDeployType() { if (this.abort) return null; @@ -221,50 +204,6 @@ export default class HerokuGenerator extends BaseGenerator { this.herokuJavaVersion = props.herokuJavaVersion; }); }, - askForOkta() { - if (this.abort) return null; - if (this.authenticationType !== OAUTH2) return null; - if (this.useOkta) return null; - const prompts = [ - { - type: 'list', - name: 'useOkta', - 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.', - choices: [ - { - value: true, - name: 'Yes, provision the Okta add-on', - }, - { - value: false, - name: 'No, I want to configure my identity provider manually', - }, - ], - default: 1, - }, - { - when: answers => answers.useOkta, - type: 'input', - name: 'oktaAdminLogin', - message: 'Login (valid email) for the JHipster Admin user:', - validate: input => { - if (!input) { - return 'You must enter a login for the JHipster admin'; - } - return true; - }, - }, - ]; - - return this.prompt(prompts).then(props => { - this.useOkta = props.useOkta; - if (this.useOkta) { - this.oktaAdminLogin = props.oktaAdminLogin; - this.oktaAdminPassword = this.randomPassword; - } - }); - }, }; } @@ -274,26 +213,23 @@ export default class HerokuGenerator extends BaseGenerator { get configuring() { return { - checkInstallation: runAsync(function () { + async checkInstallation(done) { if (this.abort) return; - const done = this.async(); - ChildProcess.exec('heroku --version', err => { + this.spawnCommand('heroku --version', err => { if (err) { this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); this.abort = true; } done(); }); - }), + }, saveConfig() { this.config.set({ herokuAppName: this.herokuAppName, herokuDeployType: this.herokuDeployType, herokuJavaVersion: this.herokuJavaVersion, - useOkta: this.useOkta, - oktaAdminLogin: this.oktaAdminLogin, }); }, }; @@ -309,9 +245,8 @@ export default class HerokuGenerator extends BaseGenerator { statistics.sendSubGenEvent('generator', GENERATOR_HEROKU); }, - gitInit: runAsync(function () { + async gitInit(done) { if (this.abort) return; - const done = this.async(); try { fs.lstatSync('.git'); @@ -320,27 +255,26 @@ export default class HerokuGenerator extends BaseGenerator { } catch (e) { // An exception is thrown if the folder doesn't exist this.log.log(chalk.bold('\nInitializing Git repository')); - const child = ChildProcess.exec('git init', () => { + const child = this.spawnCommand('git init', () => { done(); }); child.stdout.on('data', data => { this.log.verboseInfo(data.toString()); }); } - }), + }, - installHerokuDeployPlugin: runAsync(function () { + async installHerokuDeployPlugin(done) { if (this.abort) return; - const done = this.async(); const cliPlugin = 'heroku-cli-deploy'; - ChildProcess.exec('heroku plugins', (err, stdout) => { + this.spawnCommand('heroku plugins', (err, stdout) => { if (_.includes(stdout, cliPlugin)) { this.log.log('\nHeroku CLI deployment plugin already installed'); done(); } else { this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); - const child = ChildProcess.exec(`heroku plugins:install ${cliPlugin}`, err => { + const child = this.spawnCommand(`heroku plugins:install ${cliPlugin}`, err => { if (err) { this.abort = true; this.log.error(err); @@ -354,16 +288,15 @@ export default class HerokuGenerator extends BaseGenerator { }); } }); - }), + }, - herokuCreate: runAsync(function () { + async herokuCreate(done) { if (this.abort || this.herokuAppExists) return; - const done = this.async(); const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; this.log.log(chalk.bold('\nCreating Heroku application and setting up node environment')); - const child = ChildProcess.exec(`heroku create ${this.herokuAppName}${regionParams}`, { timeout: 6000 }, (err, stdout, stderr) => { + const child = this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { timeout: 6000 }, (err, stdout, stderr) => { if (err) { if (stderr.includes('is already taken')) { const prompts = [ @@ -388,7 +321,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(''); this.prompt(prompts).then(props => { if (props.herokuForceName === 'Yes') { - ChildProcess.exec(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => { + this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => { if (err) { this.abort = true; this.log.error(err); @@ -402,7 +335,7 @@ export default class HerokuGenerator extends BaseGenerator { done(); }); } else { - ChildProcess.exec(`heroku create ${regionParams}`, (err, stdout) => { + this.spawnCommand(`heroku create ${regionParams}`, (err, stdout) => { if (err) { this.abort = true; this.log.error(err); @@ -412,7 +345,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(stdout.trim()); // ensure that the git remote is the same as the appName - ChildProcess.exec(`heroku git:remote --app ${this.herokuAppName}`, err => { + this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, err => { if (err) { this.abort = true; this.log.error(err); @@ -452,11 +385,10 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(output.trim()); } }); - }), + }, - herokuAddonsCreate: runAsync(function () { + async herokuAddonsCreate(done) { if (this.abort) return; - const done = this.async(); const addonCreateCallback = (addon, err) => { if (err) { @@ -476,18 +408,11 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nProvisioning addons')); if (this.searchEngine === ELASTICSEARCH) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - ChildProcess.exec(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, (err, stdout, stderr) => { + this.spawnCommand(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, (err, stdout, stderr) => { addonCreateCallback.bind('Elasticsearch', err, stdout, stderr); }); } - if (this.useOkta) { - this.log.log(chalk.bold('\nProvisioning okta addon')); - ChildProcess.exec(`heroku addons:create okta --app ${this.herokuAppName}`, (err, stdout, stderr) => { - addonCreateCallback('Okta', err, stdout, stderr); - }); - } - let dbAddOn; if (this.prodDatabaseType === POSTGRESQL) { dbAddOn = 'heroku-postgresql --as DATABASE'; @@ -499,7 +424,7 @@ export default class HerokuGenerator extends BaseGenerator { if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - ChildProcess.exec(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { + this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { addonCreateCallback('Database', err, stdout, stderr); }); } else { @@ -515,7 +440,7 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon ${cacheAddOn}`)); - ChildProcess.exec(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { + this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { addonCreateCallback('Cache', err, stdout, stderr); }); } else { @@ -523,7 +448,7 @@ export default class HerokuGenerator extends BaseGenerator { } done(); - }), + }, configureJHipsterRegistry() { if (this.abort || this.herokuAppExists) return undefined; @@ -557,7 +482,7 @@ export default class HerokuGenerator extends BaseGenerator { props.herokuJHipsterRegistryPassword = encodeURIComponent(props.herokuJHipsterRegistryPassword); const herokuJHipsterRegistry = `https://${props.herokuJHipsterRegistryUsername}:${props.herokuJHipsterRegistryPassword}@${props.herokuJHipsterRegistryApp}.herokuapp.com`; const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; - const child = ChildProcess.exec(configSetCmd, err => { + const child = this.spawnCommand(configSetCmd, err => { if (err) { this.abort = true; this.log.error(err); @@ -592,14 +517,6 @@ export default class HerokuGenerator extends BaseGenerator { if (this.buildTool === GRADLE) { this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle'); } - if (this.useOkta) { - this.writeFile('provision-okta-addon.sh.ejs', 'provision-okta-addon.sh'); - fs.appendFile('.gitignore', 'provision-okta-addon.sh', 'utf8', err => { - if (err) { - this.log.warn(`${chalk.yellow.bold('WARNING!')} Failed to add 'provision-okta-addon.sh' to .gitignore.'`); - } - }); - } }, addHerokuBuildPlugin() { @@ -629,20 +546,6 @@ export default class HerokuGenerator extends BaseGenerator { get end() { return this.asEndTaskGroup({ - makeScriptExecutable() { - if (this.abort) return; - if (this.useOkta) { - try { - fs.chmodSync('provision-okta-addon.sh', '755'); - } catch (err) { - 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'`, - ); - } - } - }, async productionBuild() { if (this.abort) return; @@ -671,7 +574,7 @@ export default class HerokuGenerator extends BaseGenerator { const gitAddCmd = 'git add .'; this.log.log(chalk.cyan(gitAddCmd)); - const gitAdd = execCmd(gitAddCmd); + const gitAdd = this.spawnCommand(gitAddCmd); gitAdd.child.stdout.on('data', data => { this.log.verboseInfo(data); }); @@ -684,7 +587,7 @@ export default class HerokuGenerator extends BaseGenerator { const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty'; this.log.log(chalk.cyan(gitCommitCmd)); - const gitCommit = execCmd(gitCommitCmd); + const gitCommit = this.spawnCommand(gitCommitCmd); gitCommit.child.stdout.on('data', data => { this.log.verboseInfo(data); }); @@ -702,14 +605,14 @@ export default class HerokuGenerator extends BaseGenerator { } this.log.log(chalk.bold('\nConfiguring Heroku')); - await execCmd(`heroku config:set ${configVars}--app ${this.herokuAppName}`); - const { stdout: data } = await execCmd(`heroku buildpacks:add ${buildpack} --app ${this.herokuAppName}`); + await this.spawnCommand(`heroku config:set ${configVars}--app ${this.herokuAppName}`); + const { stdout: data } = await this.spawnCommand(`heroku buildpacks:add ${buildpack} --app ${this.herokuAppName}`); if (data) { this.logger.info(data); // remote: ! The following add-ons were automatically provisioned: . These add-ons may incur additional cost, // which is prorated to the second. Run `heroku addons` for more info. if (data.includes('Run `heroku addons` for more info.')) { - await execCmd('heroku addons'); + await this.spawnCommand('heroku addons'); } this.log(''); @@ -748,7 +651,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nDeploying application')); - const herokuPush = execCmd('git push heroku HEAD:main', { maxBuffer: 1024 * 10000 }); + const herokuPush = this.spawnCommand('git push heroku HEAD:main', { maxBuffer: 1024 * 10000 }); herokuPush.child.stdout.on('data', data => { this.log.verboseInfo(data); @@ -763,43 +666,6 @@ export default class HerokuGenerator extends BaseGenerator { 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')}`)); - - if (this.useOkta) { - let curlAvailable = false; - let jqAvailable = false; - try { - await execCmd('curl --help'); - curlAvailable = true; - } catch (err) { - this.log.log( - chalk.red('cURL is not available but required. See https://curl.haxx.se/download.html for installation guidance.'), - ); - this.log.log(chalk.yellow('After you have installed curl execute ./provision-okta-addon.sh manually.')); - } - try { - await execCmd('jq --help'); - jqAvailable = true; - } catch (err) { - this.log.log( - chalk.red('jq is not available but required. See https://stedolan.github.io/jq/download/ for installation guidance.'), - ); - this.log.log(chalk.yellow('After you have installed jq execute ./provision-okta-addon.sh manually.')); - } - if (curlAvailable && jqAvailable) { - this.log.log(chalk.green('Running ./provision-okta-addon.sh to create all required roles and users for JHipster.')); - try { - await execCmd('./provision-okta-addon.sh'); - this.log.log(chalk.bold('\nOkta configured successfully!')); - this.log.log(chalk.green(`\nUse ${chalk.bold(`${this.oktaAdminLogin}/${this.oktaAdminPassword}`)} to login.\n`)); - } catch (err) { - this.log.log( - chalk.red( - 'Failed to execute ./provision-okta-addon.sh. Make sure to setup okta according to https://www.jhipster.tech/heroku/.', - ), - ); - } - } - } } catch (err) { this.log.error(err); } @@ -821,8 +687,8 @@ export default class HerokuGenerator extends BaseGenerator { ), ); try { - await execCmd(herokuSetBuildpackCommand); - const herokuDeploy = execCmd(herokuDeployCommand); + await this.spawnCommand(herokuSetBuildpackCommand); + const herokuDeploy = this.spawnCommand(herokuDeployCommand); herokuDeploy.child.stdout.on('data', data => { this.log.verboseInfo(data); }); @@ -834,43 +700,6 @@ export default class HerokuGenerator extends BaseGenerator { 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')}`)); - - if (this.useOkta) { - let curlAvailable = false; - let jqAvailable = false; - try { - await execCmd('curl --help'); - curlAvailable = true; - } catch (err) { - this.log.log( - chalk.red('cURL is not available but required. See https://curl.haxx.se/download.html for installation guidance.'), - ); - this.log.log(chalk.yellow('After you have installed curl execute ./provision-okta-addon.sh manually.')); - } - try { - await execCmd('jq --help'); - jqAvailable = true; - } catch (err) { - this.log.log( - chalk.red('jq is not available but required. See https://stedolan.github.io/jq/download/ for installation guidance.'), - ); - this.log.log(chalk.yellow('After you have installed jq execute ./provision-okta-addon.sh manually.')); - } - if (curlAvailable && jqAvailable) { - this.log.log(chalk.green('Running ./provision-okta-addon.sh to create all required roles and users for JHipster.')); - try { - await execCmd('./provision-okta-addon.sh'); - this.log.log(chalk.bold('\nOkta configured successfully!')); - this.log.log(chalk.green(`\nUse ${chalk.bold(`${this.oktaAdminLogin}/${this.oktaAdminPassword}`)} to login.`)); - } catch (err) { - 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/.', - ), - ); - } - } - } } catch (err) { this.log.error(err); } diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 72811508643e..36644a8020ef 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -1,4 +1,3 @@ -import ChildProcess from 'child_process'; import sinon from 'sinon'; import { expect } from 'esmocha'; @@ -15,24 +14,26 @@ describe('generator - Heroku', () => { let stub; beforeEach(() => { - stub = sinon.stub(ChildProcess, 'exec'); - stub.withArgs('heroku --version').yields(false); - stub.withArgs('heroku plugins').yields(false, 'heroku-cli-deploy'); - stub.withArgs('git init').yields([false, '', '']); + stub = sinon.stub(); + stub.withArgs('spawnCommand', 'heroku --version').yields(false); + stub.withArgs('spawnCommand', 'heroku plugins').yields(false, 'heroku-cli-deploy'); + stub.withArgs('spawnCommand', 'git init').yields([false, '', '']); }); afterEach(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (ChildProcess.exec as any).restore(); + stub.resetHistory(); }); describe('microservice application', () => { describe('with JAR deployment', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); stub - .withArgs(`heroku config:set JHIPSTER_REGISTRY_URL=https://admin:changeme@sushi.herokuapp.com --app ${herokuAppName}`) + .withArgs( + 'spawnCommand', + `heroku config:set JHIPSTER_REGISTRY_URL=https://admin:changeme@sushi.herokuapp.com --app ${herokuAppName}`, + ) .yields(false, '', '') .returns({ stdout: { @@ -52,8 +53,8 @@ describe('generator - Heroku', () => { herokuJHipsterRegistryUsername: 'admin', herokuJHipsterRegistryPassword: 'changeme', herokuJavaVersion: '17', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -71,7 +72,7 @@ describe('generator - Heroku', () => { let runResult; beforeEach(async () => { stub - .withArgs(`heroku create ${herokuAppName}`) + .withArgs('spawnCommand', `heroku create ${herokuAppName}`) .yields(true, '', `Name ${herokuAppName} is already taken`) .returns({ stdout: { @@ -79,9 +80,13 @@ describe('generator - Heroku', () => { on: () => {}, }, }); - stub.withArgs('heroku create ').yields(false, `https://${autogeneratedAppName}.herokuapp.com`); - stub.withArgs(`heroku git:remote --app ${autogeneratedAppName}`).yields(false, `https://${autogeneratedAppName}.herokuapp.com`); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${autogeneratedAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', 'heroku create ').yields(false, `https://${autogeneratedAppName}.herokuapp.com`); + stub + .withArgs('spawnCommand', `heroku git:remote --app ${autogeneratedAppName}`) + .yields(false, `https://${autogeneratedAppName}.herokuapp.com`); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${autogeneratedAppName}`) + .yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -92,8 +97,8 @@ describe('generator - Heroku', () => { herokuDeployType: 'jar', herokuForceName: 'No', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -108,13 +113,15 @@ describe('generator - Heroku', () => { describe('with Git deployment', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('git add .').yields(false, '', ''); - stub.withArgs('git commit -m "Deploy to Heroku" --allow-empty').yields(false, '', ''); - stub.withArgs(`heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku buildpacks:add heroku/java --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('git push heroku HEAD:master').yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', 'git add .').yields(false, '', ''); + stub.withArgs('spawnCommand', 'git commit -m "Deploy to Heroku" --allow-empty').yields(false, '', ''); + stub + .withArgs('spawnCommand', `heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app ${herokuAppName}`) + .yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku buildpacks:add heroku/java --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', 'git push heroku HEAD:master').yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -123,8 +130,8 @@ describe('generator - Heroku', () => { herokuRegion: 'us', herokuDeployType: 'git', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -139,8 +146,8 @@ describe('generator - Heroku', () => { describe('in the US', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -150,8 +157,8 @@ describe('generator - Heroku', () => { herokuRegion: 'us', herokuDeployType: 'jar', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -168,8 +175,8 @@ describe('generator - Heroku', () => { describe('in the EU', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName} --region eu`).yields(false, '', ''); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -179,8 +186,8 @@ describe('generator - Heroku', () => { herokuRegion: 'eu', herokuDeployType: 'jar', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -194,8 +201,8 @@ describe('generator - Heroku', () => { describe('with PostgreSQL', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName} --region eu`).yields(false, '', ''); - stub.withArgs(`heroku addons:create heroku-postgresql --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create heroku-postgresql --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -205,8 +212,8 @@ describe('generator - Heroku', () => { herokuRegion: 'eu', herokuDeployType: 'jar', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -224,13 +231,16 @@ describe('generator - Heroku', () => { let runResult; beforeEach(async () => { stub - .withArgs(`heroku apps:info --json ${existingHerokuAppName}`) + .withArgs('spawnCommand', `heroku apps:info --json ${existingHerokuAppName}`) .yields(false, `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}`); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${existingHerokuAppName}`).yields(false, '', ''); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${existingHerokuAppName}`) + .yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' }) .withOptions({ skipBuild: true }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { @@ -245,9 +255,9 @@ describe('generator - Heroku', () => { describe('with elasticsearch', () => { let runResult; beforeEach(async () => { - stub.withArgs(`heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs(`heroku addons:create bonsai --as BONSAI --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku addons:create bonsai --as BONSAI --app ${herokuAppName}`).yields(false, '', ''); runResult = await helpers .createJHipster(GENERATOR_HEROKU) @@ -258,8 +268,8 @@ describe('generator - Heroku', () => { herokuRegion: 'us', herokuDeployType: 'jar', herokuJavaVersion: '11', - useOkta: false, }) + .withSpawnMock(stub) .run(); }); it('should match files snapshot', function () { diff --git a/generators/heroku/templates/provision-okta-addon.sh.ejs b/generators/heroku/templates/provision-okta-addon.sh.ejs deleted file mode 100644 index afc6fe9d8375..000000000000 --- a/generators/heroku/templates/provision-okta-addon.sh.ejs +++ /dev/null @@ -1,217 +0,0 @@ -#! /usr/bin/env bash -# abort on nonzero exitstatus -set -o errexit -# abort on unbound variable -set -o nounset -# don't hide errors within pipes -set -o pipefail - -get_app_url() { - heroku info -j | jq '.app.web_url' -} - -get_apps() { - curl -s --location --request GET "${1}/api/v1/apps" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" >apps.json - - jq '.[0]' apps.json >app1.json - jq '.[1]' apps.json >app2.json -} - -add_redirect_url() { - REDIRECT_URL="${1//\"/}login/oauth2/code/oidc" - jq '.settings.oauthClient.redirect_uris += ["'"${REDIRECT_URL}"'"]' app1.json >app1-mod.json - jq '.settings.oauthClient.redirect_uris += ["'"${REDIRECT_URL}"'"]' app2.json >app2-mod.json - - jq '.settings.oauthClient.post_logout_redirect_uris += ["'"${1//\"/}"'"]' app1-mod.json >app1.json - jq '.settings.oauthClient.post_logout_redirect_uris += ["'"${1//\"/}"'"]' app2-mod.json >app2.json -} - -update_apps() { - data=$(jq '.' app1.json) - id=$(jq '.id' app1.json) - - curl -s --location --request PUT "${1}/api/v1/apps/${id//\"/}" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw "${data}" - - data=$(jq '.' app2.json) - id=$(jq '.id' app2.json) - - curl -s --location --request PUT "${1}/api/v1/apps/${id//\"/}" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw "${data}" -} - -add_groups() { - curl -s --location --request POST "${1}/api/v1/groups" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '{"profile": - { - "name": "ROLE_ADMIN", - "description": "JHipster Admin Role" - } - }' - - curl -s --location --request POST "${1}/api/v1/groups" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '{"profile": - { - "name": "ROLE_USER", - "description": "JHipster User Role" - } - }' -} - -add_admin_to_group() { - ADMIN_EMAIL=$(heroku config:get OKTA_ADMIN_EMAIL) - - GROUP_ID=$(curl -s --location --request GET "${1}/api/v1/groups?q=ROLE_ADMIN" \ - --header "Authorization: SSWS ${2}" | jq '.[0].id') - - USER_ID=$(curl -s --location --request GET "${1}/api/v1/users?q=${ADMIN_EMAIL}" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" | jq '.[0].id') - - curl -s --location --request PUT "${1}/api/v1/groups/${GROUP_ID//\"/}/users/${USER_ID//\"/}" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '' -} - -add_groups_claim() { - curl -s --location --request POST "${1}/api/v1/authorizationServers/default/claims" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '{ - "name": "groups", - "status": "ACTIVE", - "claimType": "IDENTITY", - "valueType": "GROUPS", - "value": ".*", - "conditions": { - "scopes": [] - }, - "system": false, - "alwaysIncludeInToken": true, - "group_filter_type": "REGEX" - }' -} - -add_admin_user() { - - GROUP_ID=$(curl -s --location --request GET "${1}/api/v1/groups?q=ROLE_ADMIN" \ - --header "Authorization: SSWS ${2}" | jq '.[0].id') - - # Create user with provided initial password - USER_ID=$(curl -s --location --request POST "${1}/api/v1/users?activate=true" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw ' - { - "profile": { - "firstName": "JHipster", - "lastName": "Admin", - "email": "<%= oktaAdminLogin %>", - "login": "<%= oktaAdminLogin %>" - }, - "credentials": { - "password" : { "value": "<%= oktaAdminPassword %>" } - } - }' | jq '.id') - - curl -s --location --request PUT "${1}/api/v1/groups/${GROUP_ID//\"/}/users/${USER_ID//\"/}" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '' - - # directly expire the password such that the user is forced to select a new password - curl -s --location --request POST "${1}/api/v1/users/${USER_ID//\"/}/lifecycle/expire_password" \ - --header "Content-Type: application/json" \ - --header "Accept: application/json" \ - --header "Authorization: SSWS ${2}" \ - --data-raw '' -} - -already_done() { - LENGTH=$(curl -s --location --request GET "${1}/api/v1/users?q=<%= oktaAdminLogin %>" \ - --header "Accept: application/json" \ - --header "Content-Type: application/json" \ - --header "Authorization: SSWS ${2}" | jq '. | length') - - if [ $LENGTH -gt 0 ] - then - echo "true" - else - echo "false" - fi -} - -check_required_dependencies() { - if hash curl 2>/dev/null; - then - echo -e "\U2714 cURL is available." - else - echo "\U1F6D1cURL is not available but required. See https://curl.haxx.se/download.html for installation guidance." - return 0; - fi - - if hash jq 2>/dev/null; - then - echo -e "\U2611 jq is available.️" - else - echo -e "\U1F6D1jq is not available but required. See https://stedolan.github.io/jq/download/ for installation guidance." - return 0; - fi -} - -main() { - check_required_dependencies - - OKTA_URL=$(heroku config:get OKTA_CLIENT_ORGURL) - OKTA_TOKEN=$(heroku config:get OKTA_CLIENT_TOKEN) - - APP_URL=$(get_app_url) - - DONE=$(already_done ${OKTA_URL} ${OKTA_TOKEN}) - - if [ ${DONE} == "true" ] - then - echo "User already created, doing nothing." - return 0 - fi - - # First add the correct redirect url to each application - get_apps "${OKTA_URL}" "${OKTA_TOKEN}" - add_redirect_url "${APP_URL}" - update_apps "${OKTA_URL}" "${OKTA_TOKEN}" - - # Create ROLE_ADMIN and ROLE_USER groups - add_groups "${OKTA_URL}" "${OKTA_TOKEN}" - - # Add the automatically provisioned HEROKU ADMIN to the ROLE_ADMIN group - add_admin_to_group "${OKTA_URL}" "${OKTA_TOKEN}" - - # Add the groups claim, see https://www.jhipster.tech/security/#okta - add_groups_claim "${OKTA_URL}" "${OKTA_TOKEN}" - - add_admin_user "${OKTA_URL}" "${OKTA_TOKEN}" - - # Delete all temporary files created during this script - rm apps.json - rm app1.json - rm app2.json - rm app1-mod.json - rm app2-mod.json -} - -main From b78bc671ab473e045f4b0cb64d669ebe5b0777ad Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Mon, 23 Oct 2023 22:39:40 -0600 Subject: [PATCH 02/45] Apply suggestions from code review Co-authored-by: Marcelo Shima --- generators/heroku/generator.mjs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 71f6aa51e545..11f8f278c6ee 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -111,11 +111,11 @@ export default class HerokuGenerator extends BaseGenerator { get prompting() { return { - async askForApp(done) { + async askForApp() { if (this.herokuAppName) { - this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, (err, stdout) => { - if (err) { - this.abort = true; + const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, { reject: false, stdio: 'pipe' }); + if (exitCode !== 0) { + this.cancelCancellableTasks(); this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`); this.log.error('Run the generator again to create a new application.'); this.herokuAppName = null; @@ -155,7 +155,6 @@ export default class HerokuGenerator extends BaseGenerator { this.herokuAppName = _.kebabCase(props.herokuAppName); this.herokuRegion = props.herokuRegion; this.herokuAppExists = false; - done(); }); } }, From 7cb70623f5f7dd0b7059cde8d1ff43a6add56f02 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Mon, 23 Oct 2023 22:44:48 -0600 Subject: [PATCH 03/45] Fix error handling --- generators/heroku/generator.mjs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 11f8f278c6ee..d1266f693bf4 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -212,16 +212,12 @@ export default class HerokuGenerator extends BaseGenerator { get configuring() { return { - async checkInstallation(done) { - if (this.abort) return; - - this.spawnCommand('heroku --version', err => { - if (err) { - this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); - this.abort = true; - } - done(); - }); + async checkInstallation() { + const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); + if (exitCode !== 0) { + this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); + this.cancelCancellableTasks(); + } }, saveConfig() { @@ -289,7 +285,7 @@ export default class HerokuGenerator extends BaseGenerator { }); }, - async herokuCreate(done) { + async herokuCreate() { if (this.abort || this.herokuAppExists) return; const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; From 7733d407985ce82c52b4d83dc4153ae18ced3aa1 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Mon, 23 Oct 2023 23:06:22 -0600 Subject: [PATCH 04/45] Remove done() --- generators/heroku/generator.mjs | 256 +++++++++++++++----------------- 1 file changed, 118 insertions(+), 138 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index d1266f693bf4..1d363d8715d0 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -113,27 +113,28 @@ export default class HerokuGenerator extends BaseGenerator { return { async askForApp() { if (this.herokuAppName) { - const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, { reject: false, stdio: 'pipe' }); + const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, { + reject: false, + stdio: 'pipe', + }); if (exitCode !== 0) { - this.cancelCancellableTasks(); - this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`); - this.log.error('Run the generator again to create a new application.'); - this.herokuAppName = null; - } 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.herokuAppExists = true; - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); + this.cancelCancellableTasks(); + this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`); + this.log.error('Run the generator again to create a new application.'); + this.herokuAppName = null; + } else { + const json = JSON.parse(stdout); + this.herokuAppName = json.app.name; + if (json.dynos.length > 0) { + this.dynoSize = json.dynos[0].size; } - done(); - }); + this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`); + this.herokuAppExists = true; + this.config.set({ + herokuAppName: this.herokuAppName, + herokuDeployType: this.herokuDeployType, + }); + } } else { const prompts = [ { @@ -151,16 +152,14 @@ export default class HerokuGenerator extends BaseGenerator { }, ]; - this.prompt(prompts).then(props => { - this.herokuAppName = _.kebabCase(props.herokuAppName); - this.herokuRegion = props.herokuRegion; - this.herokuAppExists = false; - }); + const props = await this.prompt(prompts); + this.herokuAppName = _.kebabCase(props.herokuAppName); + this.herokuRegion = props.herokuRegion; + this.herokuAppExists = false; } }, askForHerokuDeployType() { - if (this.abort) return null; if (this.herokuDeployType) return null; const prompts = [ { @@ -187,7 +186,6 @@ export default class HerokuGenerator extends BaseGenerator { }, askForHerokuJavaVersion() { - if (this.abort) return null; if (this.herokuJavaVersion) return null; const prompts = [ { @@ -240,151 +238,137 @@ export default class HerokuGenerator extends BaseGenerator { statistics.sendSubGenEvent('generator', GENERATOR_HEROKU); }, - async gitInit(done) { - if (this.abort) return; - + async gitInit() { try { fs.lstatSync('.git'); this.log.log(chalk.bold('\nUsing existing Git repository')); - done(); } catch (e) { // An exception is thrown if the folder doesn't exist this.log.log(chalk.bold('\nInitializing Git repository')); - const child = this.spawnCommand('git init', () => { - done(); - }); - child.stdout.on('data', data => { + const { stdout } = await this.spawnCommand('git init', { reject: false, stdio: 'pipe' }); + stdout.on('data', data => { this.log.verboseInfo(data.toString()); }); } }, - async installHerokuDeployPlugin(done) { - if (this.abort) return; + async installHerokuDeployPlugin() { const cliPlugin = 'heroku-cli-deploy'; - this.spawnCommand('heroku plugins', (err, stdout) => { + const { stdout, stderr, exitCode } = await this.spawnCommand('heroku plugins', { reject: false, stdio: 'pipe' }); + if (exitCode !== 0) { if (_.includes(stdout, cliPlugin)) { this.log.log('\nHeroku CLI deployment plugin already installed'); - done(); + this.cancelCancellableTasks(); } else { this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); - const child = this.spawnCommand(`heroku plugins:install ${cliPlugin}`, err => { - if (err) { - this.abort = true; - this.log.error(err); - } - - done(); - }); - - child.stdout.on('data', data => { + const { stdout, exitCode } = await this.spawnCommand(`heroku plugins:install ${cliPlugin}`, { reject: false, stdio: 'pipe' }); + if (exitCode !== 0) { + this.log.error(stderr); + this.cancelCancellableTasks(); + } + stdout.on('data', data => { this.log.verboseInfo(data.toString()); }); } - }); + } }, async herokuCreate() { - if (this.abort || this.herokuAppExists) return; + if (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 child = this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { timeout: 6000 }, (err, stdout, stderr) => { - if (err) { - 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, - }, - ]; + const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { + reject: false, + stdio: 'pipe', + }); - this.log.verboseInfo(''); - this.prompt(prompts).then(props => { - if (props.herokuForceName === 'Yes') { - this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => { + 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.verboseInfo(''); + const props = await this.prompt(prompts); + if (props.herokuForceName === 'Yes') { + this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => { + if (err) { + this.abort = true; + this.log.error(err); + } else { + this.log.verboseInfo(stdout.trim()); + this.config.set({ + herokuAppName: this.herokuAppName, + herokuDeployType: this.herokuDeployType, + }); + } + }); + } else { + this.spawnCommand(`heroku create ${regionParams}`, (err, stdout) => { + if (err) { + this.abort = true; + this.log.error(err); + } else { + // Extract from "Created random-app-name-1234... done" + this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); + this.log.verboseInfo(stdout.trim()); + + // ensure that the git remote is the same as the appName + this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, err => { if (err) { this.abort = true; this.log.error(err); } else { - this.log.verboseInfo(stdout.trim()); this.config.set({ herokuAppName: this.herokuAppName, herokuDeployType: this.herokuDeployType, }); } - done(); - }); - } else { - this.spawnCommand(`heroku create ${regionParams}`, (err, stdout) => { - if (err) { - this.abort = true; - this.log.error(err); - } else { - // Extract from "Created random-app-name-1234... done" - this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); - this.log.verboseInfo(stdout.trim()); - - // ensure that the git remote is the same as the appName - this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, err => { - if (err) { - this.abort = true; - this.log.error(err); - } else { - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); - } - done(); - }); - } }); } }); - } else { - this.abort = true; - this.herokuAppName = null; - if (stderr.includes('Invalid credentials')) { - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); - } else { - this.log.error(err); - } } } else { - done(); + this.cancelCancellableTasks(); + this.herokuAppName = null; + if (stderr.includes('Invalid credentials')) { + this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + } else { + this.log.error(stderr); + } } - }); + } - child.stdout.on('data', data => { + stdout.on('data', data => { const output = data.toString(); if (data.search('Heroku credentials') >= 0) { - this.abort = true; + this.cancelCancellableTasks(); this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); - done(); } else { this.log.verboseInfo(output.trim()); } }); }, - async herokuAddonsCreate(done) { - if (this.abort) return; - + async herokuAddonsCreate() { const addonCreateCallback = (addon, err) => { if (err) { const verifyAccountUrl = 'https://heroku.com/verify'; @@ -403,9 +387,11 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nProvisioning addons')); if (this.searchEngine === ELASTICSEARCH) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - this.spawnCommand(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, (err, stdout, stderr) => { - addonCreateCallback.bind('Elasticsearch', err, stdout, stderr); - }); + const { stderr, stdout, exitCode } = await this.spawnCommand( + `heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, + { reject: false, stdio: 'pipe' }, + ); + addonCreateCallback.bind('Elasticsearch', exitCode, stdout, stderr); } let dbAddOn; @@ -419,9 +405,11 @@ export default class HerokuGenerator extends BaseGenerator { if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { - addonCreateCallback('Database', err, stdout, stderr); + const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, { + reject: false, + stdio: 'pipe', }); + addonCreateCallback('Database', exitCode, stdout, stderr); } else { this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); } @@ -435,18 +423,19 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon ${cacheAddOn}`)); - this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, (err, stdout, stderr) => { - addonCreateCallback('Cache', err, stdout, stderr); + + const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, { + reject: false, + stdio: 'pipe', }); + addonCreateCallback('Cache', exitCode, stdout, stderr); } else { this.log.log(chalk.bold(`\nNo suitable cache addon for cacheprovider ${this.cacheProvider} available.`)); } - - done(); }, configureJHipsterRegistry() { - if (this.abort || this.herokuAppExists) return undefined; + if (this.herokuAppExists) return undefined; if (this.serviceDiscoveryType === EUREKA) { const prompts = [ @@ -501,10 +490,7 @@ export default class HerokuGenerator extends BaseGenerator { get writing() { return this.asWritingTaskGroup({ copyHerokuFiles() { - if (this.abort) return; - this.log.log(chalk.bold('\nCreating Heroku deployment files')); - this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`); this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`); this.writeFile('Procfile.ejs', 'Procfile'); @@ -515,7 +501,6 @@ export default class HerokuGenerator extends BaseGenerator { }, addHerokuBuildPlugin() { - if (this.abort) return; if (this.buildTool !== GRADLE) return; // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator this.editFile( @@ -527,7 +512,6 @@ export default class HerokuGenerator extends BaseGenerator { }, addHerokuMavenProfile() { - if (this.abort) return; if (this.buildTool === MAVEN) { this.addMavenProfile('heroku', mavenProfileContent(this)); } @@ -542,8 +526,6 @@ export default class HerokuGenerator extends BaseGenerator { get end() { return this.asEndTaskGroup({ async productionBuild() { - if (this.abort) return; - if (this.herokuSkipBuild || this.herokuDeployType === 'git') { this.log.log(chalk.bold('\nSkipping build')); return; @@ -556,8 +538,6 @@ export default class HerokuGenerator extends BaseGenerator { }, async productionDeploy() { - if (this.abort) return; - if (this.herokuSkipDeploy) { this.log.log(chalk.bold('\nSkipping deployment')); return; @@ -582,7 +562,7 @@ export default class HerokuGenerator extends BaseGenerator { const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty'; this.log.log(chalk.cyan(gitCommitCmd)); - const gitCommit = this.spawnCommand(gitCommitCmd); + const gitCommit = await this.spawnCommand(gitCommitCmd); gitCommit.child.stdout.on('data', data => { this.log.verboseInfo(data); }); From 9b7a7451bb62a8c83604bb8d4780d336cd7b6b53 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Tue, 24 Oct 2023 23:38:27 -0300 Subject: [PATCH 05/45] Update generator.mjs --- generators/heroku/generator.mjs | 42 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 1d363d8715d0..7c4a05c65acf 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -78,7 +78,7 @@ export default class HerokuGenerator extends BaseGenerator { } get initializing() { - return { + return this.asInitializingTaskGroup({ loadCommonConfig() { loadAppConfig({ config: this.jhipsterConfigWithDefaults, application: this, useVersionPlaceholders: this.useVersionPlaceholders }); loadServerConfig({ config: this.jhipsterConfigWithDefaults, application: this }); @@ -102,7 +102,7 @@ export default class HerokuGenerator extends BaseGenerator { this.herokuDeployType = configuration.get('herokuDeployType'); this.herokuJavaVersion = configuration.get('herokuJavaVersion'); }, - }; + }); } get [BaseGenerator.INITIALIZING]() { @@ -110,7 +110,7 @@ export default class HerokuGenerator extends BaseGenerator { } get prompting() { - return { + return this.asPromptingTaskGroup({ async askForApp() { if (this.herokuAppName) { const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, { @@ -201,7 +201,7 @@ export default class HerokuGenerator extends BaseGenerator { this.herokuJavaVersion = props.herokuJavaVersion; }); }, - }; + }); } get [BaseGenerator.PROMPTING]() { @@ -209,7 +209,7 @@ export default class HerokuGenerator extends BaseGenerator { } get configuring() { - return { + return this.asConfiguringTaskGroup({ async checkInstallation() { const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); if (exitCode !== 0) { @@ -225,7 +225,7 @@ export default class HerokuGenerator extends BaseGenerator { herokuJavaVersion: this.herokuJavaVersion, }); }, - }; + }); } get [BaseGenerator.CONFIGURING]() { @@ -233,7 +233,7 @@ export default class HerokuGenerator extends BaseGenerator { } get default() { - return { + return this.asDefaultTaskGroup({ insight() { statistics.sendSubGenEvent('generator', GENERATOR_HEROKU); }, @@ -245,10 +245,12 @@ export default class HerokuGenerator extends BaseGenerator { } catch (e) { // An exception is thrown if the folder doesn't exist this.log.log(chalk.bold('\nInitializing Git repository')); - const { stdout } = await this.spawnCommand('git init', { reject: false, stdio: 'pipe' }); - stdout.on('data', data => { + const gitInit = this.spawnCommand('git init', { stdio: 'pipe' }); + gitInit.stdout.on('data', data => { + this.log.verboseInfo(stdout); this.log.verboseInfo(data.toString()); }); + await gitInit; } }, @@ -267,9 +269,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.error(stderr); this.cancelCancellableTasks(); } - stdout.on('data', data => { - this.log.verboseInfo(data.toString()); - }); + this.log.verboseInfo(stdout); } } }, @@ -460,27 +460,25 @@ export default class HerokuGenerator extends BaseGenerator { ]; this.log.verboseInfo(''); - return this.prompt(prompts).then(props => { + return this.prompt(prompts).then(async props => { // Encode username/password to avoid errors caused by spaces props.herokuJHipsterRegistryUsername = encodeURIComponent(props.herokuJHipsterRegistryUsername); props.herokuJHipsterRegistryPassword = encodeURIComponent(props.herokuJHipsterRegistryPassword); const herokuJHipsterRegistry = `https://${props.herokuJHipsterRegistryUsername}:${props.herokuJHipsterRegistryPassword}@${props.herokuJHipsterRegistryApp}.herokuapp.com`; const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; - const child = this.spawnCommand(configSetCmd, err => { - if (err) { - this.abort = true; - this.log.error(err); - } + const child = this.spawnCommand(configSetCmd, { + stdio: 'pipe', }); child.stdout.on('data', data => { this.log.verboseInfo(data.toString()); }); + await child; }); } return undefined; }, - }; + }); } get [BaseGenerator.DEFAULT]() { @@ -550,11 +548,11 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.cyan(gitAddCmd)); const gitAdd = this.spawnCommand(gitAddCmd); - gitAdd.child.stdout.on('data', data => { + gitAdd.stdout.on('data', data => { this.log.verboseInfo(data); }); - gitAdd.child.stderr.on('data', data => { + gitAdd.stderr.on('data', data => { this.log.verboseInfo(data); }); await gitAdd; @@ -562,7 +560,7 @@ export default class HerokuGenerator extends BaseGenerator { const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty'; this.log.log(chalk.cyan(gitCommitCmd)); - const gitCommit = await this.spawnCommand(gitCommitCmd); + const gitCommit = this.spawnCommand(gitCommitCmd); gitCommit.child.stdout.on('data', data => { this.log.verboseInfo(data); }); From a2cbbfde2b1a4c0eea8f463e3b0bebe3d3425d79 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Tue, 24 Oct 2023 23:50:03 -0600 Subject: [PATCH 06/45] Prettier --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 7c4a05c65acf..b7b5311bcb2d 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -247,7 +247,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nInitializing Git repository')); const gitInit = this.spawnCommand('git init', { stdio: 'pipe' }); gitInit.stdout.on('data', data => { - this.log.verboseInfo(stdout); + this.log.verboseInfo(stdout); this.log.verboseInfo(data.toString()); }); await gitInit; From 32b8139bcc56db1248c6eb451ef3251eac02a1bd Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Wed, 25 Oct 2023 00:07:57 -0600 Subject: [PATCH 07/45] Attempt to fix tests --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index b7b5311bcb2d..fe88aacc273d 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -247,7 +247,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nInitializing Git repository')); const gitInit = this.spawnCommand('git init', { stdio: 'pipe' }); gitInit.stdout.on('data', data => { - this.log.verboseInfo(stdout); + this.log.verboseInfo(gitInit.stdout); this.log.verboseInfo(data.toString()); }); await gitInit; From 3540000ac8d4e57ace1b83bada74e6f7c2c70bf5 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 13:19:34 -0300 Subject: [PATCH 08/45] bump yeoman-test to 8.1.1 --- package-lock.json | 10 +++++----- package.json | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14418b2819bd..72725878c181 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,7 +103,7 @@ "sort-keys": "5.0.0", "typescript": "5.2.2", "yeoman-assert": "3.1.1", - "yeoman-test": "8.1.0" + "yeoman-test": "^8.1.1" }, "engines": { "node": "^18.13.0 || >= 20.6.1", @@ -114,7 +114,7 @@ "url": "https://opencollective.com/generator-jhipster" }, "peerDependencies": { - "yeoman-test": "8.1.0" + "yeoman-test": "^8.1.1" }, "peerDependenciesMeta": { "yeoman-test": { @@ -15987,9 +15987,9 @@ } }, "node_modules/yeoman-test": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yeoman-test/-/yeoman-test-8.1.0.tgz", - "integrity": "sha512-MLCxHSiiX5LCm3aseyG2y/sPdk/QcS7zUqvmjoqAGRVutcEpC7xfOz2bxKoBa9ZrRfNGpv/NjhJrV2Hs6qpNAA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/yeoman-test/-/yeoman-test-8.1.1.tgz", + "integrity": "sha512-85T+YDxROICLj90CMk5B0kTJxwjH3+Ki9Ge7ju/wpTkWM5WSZG9XIAJB++U1+TxzItBpddCTtCEqWHxFYo+xlg==", "dev": true, "dependencies": { "@yeoman/adapter": "^1.4.0", diff --git a/package.json b/package.json index e2f633b4e6e8..a7354ae30256 100644 --- a/package.json +++ b/package.json @@ -194,10 +194,10 @@ "sort-keys": "5.0.0", "typescript": "5.2.2", "yeoman-assert": "3.1.1", - "yeoman-test": "8.1.0" + "yeoman-test": "^8.1.1" }, "peerDependencies": { - "yeoman-test": "8.1.0" + "yeoman-test": "^8.1.1" }, "peerDependenciesMeta": { "yeoman-test": { From 447a2e63e698065e0fb930a09d702bf43928e55e Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 13:19:08 -0300 Subject: [PATCH 09/45] adjusts to heroku mocking --- generators/heroku/generator.mjs | 23 +++--- generators/heroku/heroku.spec.mts | 123 ++++++++++++++++-------------- 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index fe88aacc273d..3c94858f0971 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -280,11 +280,22 @@ export default class HerokuGenerator extends BaseGenerator { const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; this.log.log(chalk.bold('\nCreating Heroku application and setting up node environment')); - const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { + const createCommand = this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { reject: false, stdio: 'pipe', }); + createCommand.stdout.on('data', data => { + const output = data.toString(); + if (data.search('Heroku credentials') >= 0) { + this.cancelCancellableTasks(); + this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + } else { + this.log.verboseInfo(output.trim()); + } + }); + + const { stderr, exitCode } = await createCommand; if (exitCode !== 0) { if (stderr.includes('is already taken')) { const prompts = [ @@ -356,16 +367,6 @@ export default class HerokuGenerator extends BaseGenerator { } } } - - stdout.on('data', data => { - const output = data.toString(); - if (data.search('Heroku credentials') >= 0) { - this.cancelCancellableTasks(); - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); - } else { - this.log.verboseInfo(output.trim()); - } - }); }, async herokuAddonsCreate() { diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 36644a8020ef..b79810de5317 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -2,22 +2,37 @@ import sinon from 'sinon'; import { expect } from 'esmocha'; import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { defaultHelpers as helpers } from '../../test/support/index.mjs'; +import { defaultHelpers as helpers, runResult } from '../../test/support/index.mjs'; import { GENERATOR_HEROKU } from '../generator-list.mjs'; const expectedFiles = { monolith: ['Procfile', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`], }; +const createSpawnCommandReturn = (resolvedValue?, data?) => + Object.assign( + Promise.resolve({ + exitCode: 0, + stdout: '', + stderr: '', + ...resolvedValue, + }), + { + ...data, + stdout: { on: () => {} }, + stderr: { on: () => {} }, + }, + ); + describe('generator - Heroku', () => { const herokuAppName = 'jhipster-test'; let stub; beforeEach(() => { stub = sinon.stub(); - stub.withArgs('spawnCommand', 'heroku --version').yields(false); - stub.withArgs('spawnCommand', 'heroku plugins').yields(false, 'heroku-cli-deploy'); - stub.withArgs('spawnCommand', 'git init').yields([false, '', '']); + stub.withArgs('spawnCommand', 'heroku --version').returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); + stub.withArgs('spawnCommand', 'git init').returns(createSpawnCommandReturn()); }); afterEach(() => { stub.resetHistory(); @@ -25,23 +40,18 @@ describe('generator - Heroku', () => { describe('microservice application', () => { describe('with JAR deployment', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); stub .withArgs( 'spawnCommand', `heroku config:set JHIPSTER_REGISTRY_URL=https://admin:changeme@sushi.herokuapp.com --app ${herokuAppName}`, ) - .yields(false, '', '') - .returns({ - stdout: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - on: () => {}, - }, - }); - runResult = await helpers + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ applicationType: 'microservice' }) .withOptions({ skipBuild: true }) @@ -69,25 +79,20 @@ describe('generator - Heroku', () => { describe('monolith application', () => { describe('with an unavailable app name', () => { const autogeneratedAppName = 'jhipster-new-name'; - let runResult; beforeEach(async () => { stub .withArgs('spawnCommand', `heroku create ${herokuAppName}`) - .yields(true, '', `Name ${herokuAppName} is already taken`) - .returns({ - stdout: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - on: () => {}, - }, - }); - stub.withArgs('spawnCommand', 'heroku create ').yields(false, `https://${autogeneratedAppName}.herokuapp.com`); + .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); + stub + .withArgs('spawnCommand', 'heroku create ') + .yields(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); stub .withArgs('spawnCommand', `heroku git:remote --app ${autogeneratedAppName}`) - .yields(false, `https://${autogeneratedAppName}.herokuapp.com`); + .returns(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); stub .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${autogeneratedAppName}`) - .yields(false, '', ''); - runResult = await helpers + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() .withOptions({ skipBuild: true }) @@ -111,18 +116,19 @@ describe('generator - Heroku', () => { }); describe('with Git deployment', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', 'git add .').yields(false, '', ''); - stub.withArgs('spawnCommand', 'git commit -m "Deploy to Heroku" --allow-empty').yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', 'git add .').returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', 'git commit -m "Deploy to Heroku" --allow-empty').returns(createSpawnCommandReturn()); stub .withArgs('spawnCommand', `heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app ${herokuAppName}`) - .yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku buildpacks:add heroku/java --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', 'git push heroku HEAD:master').yields(false, '', ''); - runResult = await helpers + .returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', `heroku buildpacks:add heroku/java --app ${herokuAppName}`).returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', 'git push heroku HEAD:master').returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() .withAnswers({ @@ -144,11 +150,12 @@ describe('generator - Heroku', () => { }); describe('in the US', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - runResult = await helpers + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() .withOptions({ skipBuild: true }) @@ -173,11 +180,12 @@ describe('generator - Heroku', () => { }); describe('in the EU', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - runResult = await helpers + stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() .withOptions({ skipBuild: true }) @@ -199,11 +207,12 @@ describe('generator - Heroku', () => { }); describe('with PostgreSQL', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create heroku-postgresql --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - runResult = await helpers + stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create heroku-postgresql --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() .withOptions({ skipBuild: true }) @@ -228,15 +237,14 @@ describe('generator - Heroku', () => { describe('with existing app', () => { const existingHerokuAppName = 'jhipster-existing'; - let runResult; beforeEach(async () => { stub .withArgs('spawnCommand', `heroku apps:info --json ${existingHerokuAppName}`) - .yields(false, `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}`); + .returns(createSpawnCommandReturn({ stdout: `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}` })); stub .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${existingHerokuAppName}`) - .yields(false, '', ''); - runResult = await helpers + .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' }) .withOptions({ skipBuild: true }) @@ -253,13 +261,14 @@ describe('generator - Heroku', () => { }); describe('with elasticsearch', () => { - let runResult; beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`).yields(false, '', ''); - stub.withArgs('spawnCommand', `heroku addons:create bonsai --as BONSAI --app ${herokuAppName}`).yields(false, '', ''); + stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); + stub + .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) + .returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', `heroku addons:create bonsai --as BONSAI --app ${herokuAppName}`).returns(createSpawnCommandReturn()); - runResult = await helpers + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ searchEngine: 'elasticsearch' }) .withOptions({ skipBuild: true }) From 78916fa6e0e624e68881482e64e8f5553dc1da06 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 13:36:07 -0300 Subject: [PATCH 10/45] drop useOkta leftover --- .../heroku/templates/application-heroku.yml.ejs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/generators/heroku/templates/application-heroku.yml.ejs b/generators/heroku/templates/application-heroku.yml.ejs index 9389d89bbf07..aab47e9acf3f 100644 --- a/generators/heroku/templates/application-heroku.yml.ejs +++ b/generators/heroku/templates/application-heroku.yml.ejs @@ -78,17 +78,5 @@ spring: elasticsearch: uris: ${BONSAI_URL} <%_ } _%> -<%_ if (useOkta) { _%> - security: - oauth2: - client: - provider: - oidc: - issuer-uri: ${OKTA_OAUTH2_ISSUER} - registration: - oidc: - client-id: ${OKTA_OAUTH2_CLIENT_ID_WEB} - client-secret: ${OKTA_OAUTH2_CLIENT_SECRET_WEB} -<%_ } _%> server: port: ${PORT:8080} From 10cc682282a50f3bc986b8825b72fc6948f82a85 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 13:36:59 -0300 Subject: [PATCH 11/45] update snapshot --- .../heroku/__snapshots__/heroku.spec.mts.snap | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 generators/heroku/__snapshots__/heroku.spec.mts.snap diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap new file mode 100644 index 000000000000..186fc56008e7 --- /dev/null +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generator - Heroku monolith application with PostgreSQL should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "jar", + "herokuJavaVersion": "11" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; From 83117657f26a7c4a7e6f293ab295d29290f29dac Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 13:41:16 -0300 Subject: [PATCH 12/45] remove more callbacks. --- generators/heroku/generator.mjs | 58 ++++++++++----------------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 3c94858f0971..fd8382400d5e 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -320,51 +320,29 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(''); const props = await this.prompt(prompts); if (props.herokuForceName === 'Yes') { - this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, (err, stdout) => { - if (err) { - this.abort = true; - this.log.error(err); - } else { - this.log.verboseInfo(stdout.trim()); - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); - } + const { stdout } = await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); + this.log.verboseInfo(stdout); + this.config.set({ + herokuAppName: this.herokuAppName, + herokuDeployType: this.herokuDeployType, }); } else { - this.spawnCommand(`heroku create ${regionParams}`, (err, stdout) => { - if (err) { - this.abort = true; - this.log.error(err); - } else { - // Extract from "Created random-app-name-1234... done" - this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); - this.log.verboseInfo(stdout.trim()); - - // ensure that the git remote is the same as the appName - this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`, err => { - if (err) { - this.abort = true; - this.log.error(err); - } else { - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); - } - }); - } + const { stdout } = await this.spawnCommand(`heroku create ${regionParams}`); + // Extract from "Created random-app-name-1234... done" + this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); + this.log.verboseInfo(stdout.trim()); + + // ensure that the git remote is the same as the appName + await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); + this.config.set({ + herokuAppName: this.herokuAppName, + herokuDeployType: this.herokuDeployType, }); } + } else if (stderr.includes('Invalid credentials')) { + throw new Error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); } else { - this.cancelCancellableTasks(); - this.herokuAppName = null; - if (stderr.includes('Invalid credentials')) { - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); - } else { - this.log.error(stderr); - } + throw new Error(stderr); } } }, From cdd492fea328fa2b1aa3f03a37814123dea82425 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 20:20:22 -0300 Subject: [PATCH 13/45] simplify heroku generator --- .../heroku/__snapshots__/heroku.spec.mts.snap | 1463 ++++++++++++++++- generators/heroku/generator.mjs | 263 ++- generators/heroku/heroku.spec.mts | 33 +- 3 files changed, 1600 insertions(+), 159 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 186fc56008e7..c202fe16db06 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -1,6 +1,210 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`generator - Heroku monolith application with PostgreSQL should match files snapshot 1`] = ` +exports[`generator - Heroku microservice application with JAR deployment should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "applicationType": "microservice", + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "jar", + "herokuJavaVersion": "17" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=17 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application in the EU calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test --region eu", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application in the EU should match files snapshot 1`] = ` { ".yo-rc.json": { "contents": "{ @@ -158,3 +362,1260 @@ server: }, } `; + +exports[`generator - Heroku monolith application in the US calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application in the US should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "jar", + "herokuJavaVersion": "11" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application with Git deployment calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git add .", + ], + [ + "spawnCommand", + "git commit -m "Deploy to Heroku" --allow-empty", + ], + [ + "spawnCommand", + "heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app jhipster-test", + ], + [ + "spawnCommand", + "heroku buildpacks:add heroku/java --app jhipster-test", + ], + [ + "spawnCommand", + "git push heroku HEAD:main", + ], +] +`; + +exports[`generator - Heroku monolith application with Git deployment should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "git", + "herokuJavaVersion": "11" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application with PostgreSQL calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test --region eu", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application with PostgreSQL should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "jar", + "herokuJavaVersion": "11" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application with an unavailable app name calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create ", + ], + [ + "spawnCommand", + "heroku git:remote --app ", + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app ", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application with an unavailable app name should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "", + "herokuDeployType": "jar", + "herokuJavaVersion": "11" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: .herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application with elasticsearch calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku create jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create bonsai:sandbox-6 --as BONSAI --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application with elasticsearch should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-test", + "herokuDeployType": "jar", + "herokuJavaVersion": "11", + "searchEngine": "elasticsearch" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-test.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 + elasticsearch: + uris: \${BONSAI_URL} +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=11 ", + "stateCleared": "modified", + }, +} +`; + +exports[`generator - Heroku monolith application with existing app calls should match snapshot 1`] = ` +[ + [ + "spawnCommand", + "heroku --version", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku apps:info --json jhipster-existing", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "git init", + { + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku plugins", + { + "reject": false, + "stdio": "pipe", + }, + ], + [ + "spawnCommand", + "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-existing", + { + "reject": false, + "stdio": "pipe", + }, + ], +] +`; + +exports[`generator - Heroku monolith application with existing app should match files snapshot 1`] = ` +{ + ".yo-rc.json": { + "contents": "{ + "generator-jhipster": { + "baseName": "jhipster", + "herokuAppName": "jhipster-existing", + "herokuDeployType": "git", + "herokuJavaVersion": "17" + } +} +", + "stateCleared": "modified", + }, + "Procfile": { + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku +", + "stateCleared": "modified", + }, + "pom.xml": { + "contents": " + + 4.0.0 + + + + heroku + + + + 7.10.2 + + + + + org.elasticsearch.client + elasticsearch-rest-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + \${bonsai.elasticsearch.version} + + + org.elasticsearch + elasticsearch + \${bonsai.elasticsearch.version} + + + org.elasticsearch.plugin + transport-netty4-client + \${bonsai.elasticsearch.version} + + + + + + + org.liquibase + liquibase-maven-plugin + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/\${maven.build.timestamp}_changelog.xml + + \${env.JDBC_DATABASE_URL} + + \${env.JDBC_DATABASE_USERNAME} + \${env.JDBC_DATABASE_PASSWORD} + hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + false + + + + maven-clean-plugin + \${maven-clean-plugin.version} + + + clean-artifacts + install + + clean + + + true + + + target + + *.jar + + false + + + node_modules + false + + + + + + + + + + + +", + "stateCleared": "modified", + }, + "src/main/resources/config/application-heroku.yml": { + "contents": "# =================================================================== +# Spring Boot configuration for the "heroku" profile. +# +# This configuration overrides the application.yml file. +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +eureka: + instance: + hostname: jhipster-existing.herokuapp.com + non-secure-port: 80 + prefer-ip-address: false + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: \${JDBC_DATABASE_URL} + username: \${JDBC_DATABASE_USERNAME} + password: \${JDBC_DATABASE_PASSWORD} + hikari: + maximumPoolSize: 8 +server: + port: \${PORT:8080} +", + "stateCleared": "modified", + }, + "src/main/resources/config/bootstrap-heroku.yml": { + "contents": "# =================================================================== +# Spring Cloud Config bootstrap configuration for the "heroku" profile +# =================================================================== + +", + "stateCleared": "modified", + }, + "system.properties": { + "contents": "java.runtime.version=17 ", + "stateCleared": "modified", + }, +} +`; diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index fd8382400d5e..955d7b158452 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -47,6 +47,14 @@ const { EUREKA } = serviceDiscoveryTypes; const NO_CACHE_PROVIDER = cacheProviderOptions.NO; export default class HerokuGenerator extends BaseGenerator { + herokuAppName; + herokuDeployType; + herokuJavaVersion; + herokuRegion; + herokuAppExists; + herokuSkipDeploy; + herokuSkipBuild; + constructor(args, options, features) { super(args, options, features); @@ -79,6 +87,13 @@ export default class HerokuGenerator extends BaseGenerator { get initializing() { return this.asInitializingTaskGroup({ + async checkInstallation() { + const { exitCode } = await this.printChildOutput(this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' })); + if (exitCode !== 0) { + throw new Error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); + } + }, + loadCommonConfig() { loadAppConfig({ config: this.jhipsterConfigWithDefaults, application: this, useVersionPlaceholders: this.useVersionPlaceholders }); loadServerConfig({ config: this.jhipsterConfigWithDefaults, application: this }); @@ -97,10 +112,7 @@ export default class HerokuGenerator extends BaseGenerator { this.cacheProvider = this.cacheProvider || NO_CACHE_PROVIDER; this.enableHibernateCache = this.enableHibernateCache && ![NO_CACHE_PROVIDER, MEMCACHED].includes(this.cacheProvider); this.frontendAppName = getFrontendAppName({ baseName: this.jhipsterConfig.baseName }); - this.herokuAppName = configuration.get('herokuAppName'); this.dynoSize = 'Free'; - this.herokuDeployType = configuration.get('herokuDeployType'); - this.herokuJavaVersion = configuration.get('herokuJavaVersion'); }, }); } @@ -112,16 +124,14 @@ export default class HerokuGenerator extends BaseGenerator { get prompting() { return this.asPromptingTaskGroup({ async askForApp() { - if (this.herokuAppName) { - const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.herokuAppName}`, { + if (this.jhipsterConfig.herokuAppName) { + const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.jhipsterConfig.herokuAppName}`, { reject: false, stdio: 'pipe', }); if (exitCode !== 0) { - this.cancelCancellableTasks(); - this.log.error(`Could not find application: ${chalk.cyan(this.herokuAppName)}`); - this.log.error('Run the generator again to create a new application.'); - this.herokuAppName = null; + this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); + throw new Error('Run the generator again to create a new application.'); } else { const json = JSON.parse(stdout); this.herokuAppName = json.app.name; @@ -132,74 +142,66 @@ export default class HerokuGenerator extends BaseGenerator { this.herokuAppExists = true; this.config.set({ herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, }); } } else { - const prompts = [ - { - type: 'input', - name: 'herokuAppName', - message: 'Name to deploy as:', - default: this.baseName, - }, - { - type: 'list', - name: 'herokuRegion', - message: 'On which region do you want to deploy ?', - choices: ['us', 'eu'], - default: 0, - }, - ]; - - const props = await this.prompt(prompts); - this.herokuAppName = _.kebabCase(props.herokuAppName); - this.herokuRegion = props.herokuRegion; this.herokuAppExists = false; - } - }, - - askForHerokuDeployType() { - if (this.herokuDeployType) return null; - const prompts = [ - { - type: 'list', - name: 'herokuDeployType', - message: 'Which type of deployment do you want ?', - choices: [ - { - value: 'git', - name: 'Git (compile on Heroku)', - }, + await this.prompt( + [ { - value: 'jar', - name: 'JAR (compile locally)', + type: 'input', + name: 'herokuAppName', + message: 'Name to deploy as:', + default: this.baseName, }, ], - default: 0, - }, - ]; - - return this.prompt(prompts).then(props => { - this.herokuDeployType = props.herokuDeployType; - }); + this.config, + ); + } }, - - askForHerokuJavaVersion() { - if (this.herokuJavaVersion) return null; - const prompts = [ + async askForRegion() { + const answers = 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, + 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, + ); + }, - return this.prompt(prompts).then(props => { - this.herokuJavaVersion = props.herokuJavaVersion; - }); + 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, + ); }, }); } @@ -208,28 +210,18 @@ export default class HerokuGenerator extends BaseGenerator { return this.delegateTasksToBlueprint(() => this.prompting); } - get configuring() { + get loading() { return this.asConfiguringTaskGroup({ - async checkInstallation() { - const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); - if (exitCode !== 0) { - this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); - this.cancelCancellableTasks(); - } - }, - saveConfig() { - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - herokuJavaVersion: this.herokuJavaVersion, - }); + this.herokuAppName = _.kebabCase(this.jhipsterConfig.herokuAppName); + this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion; + this.herokuDeployType = this.jhipsterConfig.herokuDeployType; }, }); } - get [BaseGenerator.CONFIGURING]() { - return this.delegateTasksToBlueprint(() => this.configuring); + get [BaseGenerator.LOADING]() { + return this.delegateTasksToBlueprint(() => this.loading); } get default() { @@ -245,12 +237,7 @@ export default class HerokuGenerator extends BaseGenerator { } catch (e) { // An exception is thrown if the folder doesn't exist this.log.log(chalk.bold('\nInitializing Git repository')); - const gitInit = this.spawnCommand('git init', { stdio: 'pipe' }); - gitInit.stdout.on('data', data => { - this.log.verboseInfo(gitInit.stdout); - this.log.verboseInfo(data.toString()); - }); - await gitInit; + await this.printChildOutput(this.spawnCommand('git init', { stdio: 'pipe' })); } }, @@ -261,13 +248,11 @@ export default class HerokuGenerator extends BaseGenerator { if (exitCode !== 0) { if (_.includes(stdout, cliPlugin)) { this.log.log('\nHeroku CLI deployment plugin already installed'); - this.cancelCancellableTasks(); } else { this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); const { stdout, exitCode } = await this.spawnCommand(`heroku plugins:install ${cliPlugin}`, { reject: false, stdio: 'pipe' }); if (exitCode !== 0) { - this.log.error(stderr); - this.cancelCancellableTasks(); + throw new Error(stderr); } this.log.verboseInfo(stdout); } @@ -280,22 +265,18 @@ export default class HerokuGenerator extends BaseGenerator { const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; this.log.log(chalk.bold('\nCreating Heroku application and setting up node environment')); - const createCommand = this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { - reject: false, - stdio: 'pipe', - }); - - createCommand.stdout.on('data', data => { - const output = data.toString(); - if (data.search('Heroku credentials') >= 0) { - this.cancelCancellableTasks(); - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); - } else { - this.log.verboseInfo(output.trim()); - } - }); + const { stdout, stderr, exitCode } = await this.printChildOutput( + this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { + reject: false, + stdio: 'pipe', + }), + ); + + if (stdout.search('Heroku credentials') >= 0) { + this.cancelCancellableTasks(); + this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + } - const { stderr, exitCode } = await createCommand; if (exitCode !== 0) { if (stderr.includes('is already taken')) { const prompts = [ @@ -445,14 +426,11 @@ export default class HerokuGenerator extends BaseGenerator { props.herokuJHipsterRegistryPassword = encodeURIComponent(props.herokuJHipsterRegistryPassword); const herokuJHipsterRegistry = `https://${props.herokuJHipsterRegistryUsername}:${props.herokuJHipsterRegistryPassword}@${props.herokuJHipsterRegistryApp}.herokuapp.com`; const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; - const child = this.spawnCommand(configSetCmd, { - stdio: 'pipe', - }); - - child.stdout.on('data', data => { - this.log.verboseInfo(data.toString()); - }); - await child; + await this.printChildOutput( + this.spawnCommand(configSetCmd, { + stdio: 'pipe', + }), + ); }); } return undefined; @@ -526,28 +504,12 @@ export default class HerokuGenerator extends BaseGenerator { const gitAddCmd = 'git add .'; this.log.log(chalk.cyan(gitAddCmd)); - const gitAdd = this.spawnCommand(gitAddCmd); - gitAdd.stdout.on('data', data => { - this.log.verboseInfo(data); - }); - - gitAdd.stderr.on('data', data => { - this.log.verboseInfo(data); - }); - await gitAdd; + await this.printChildOutput(this.spawnCommand(gitAddCmd)); const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty'; this.log.log(chalk.cyan(gitCommitCmd)); - const gitCommit = this.spawnCommand(gitCommitCmd); - gitCommit.child.stdout.on('data', data => { - this.log.verboseInfo(data); - }); - - gitCommit.child.stderr.on('data', data => { - this.log.verboseInfo(data); - }); - await gitCommit; + await this.printChildOutput(this.spawnCommand(gitCommitCmd)); let buildpack = 'heroku/java'; let configVars = 'MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" '; @@ -603,17 +565,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nDeploying application')); - const herokuPush = this.spawnCommand('git push heroku HEAD:main', { maxBuffer: 1024 * 10000 }); - - herokuPush.child.stdout.on('data', data => { - this.log.verboseInfo(data); - }); - - herokuPush.child.stderr.on('data', data => { - this.log.verboseInfo(data); - }); - - await herokuPush; + await this.printChildOutput(this.spawnCommand('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')}`)); @@ -639,16 +591,8 @@ export default class HerokuGenerator extends BaseGenerator { ), ); try { - await this.spawnCommand(herokuSetBuildpackCommand); - const herokuDeploy = this.spawnCommand(herokuDeployCommand); - herokuDeploy.child.stdout.on('data', data => { - this.log.verboseInfo(data); - }); - - herokuDeploy.child.stderr.on('data', data => { - this.log.verboseInfo(data); - }); - await herokuDeploy; + await this.printChildOutput(this.spawnCommand(herokuSetBuildpackCommand, { stdio: 'pipe' })); + await this.printChildOutput(this.spawnCommand(herokuDeployCommand, { 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')}`)); @@ -675,4 +619,19 @@ export default class HerokuGenerator extends BaseGenerator { addMavenProfile(profileId, other) { createPomStorage(this).addProfile({ id: profileId, content: other }); } + + /** + * @param {ReturnType} child + * @param {(chunk: any) => void} child + * @returns {ReturnType} + */ + printChildOutput(child, log = data => this.log.verboseInfo(data)) { + child.stdout.on('data', data => { + data.toString().split(/\r?\n/).forEach(log); + }); + child.stderr.on('data', data => { + data.toString().split(/\r?\n/).forEach(log); + }); + return child; + } } diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index b79810de5317..162d68f1d445 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -1,4 +1,4 @@ -import sinon from 'sinon'; +import sinon, { SinonStub } from 'sinon'; import { expect } from 'esmocha'; import { SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; @@ -26,10 +26,13 @@ const createSpawnCommandReturn = (resolvedValue?, data?) => describe('generator - Heroku', () => { const herokuAppName = 'jhipster-test'; - let stub; + let stub: SinonStub; beforeEach(() => { stub = sinon.stub(); + // Add catch all + stub.returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand', 'heroku --version').returns(createSpawnCommandReturn()); stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); stub.withArgs('spawnCommand', 'git init').returns(createSpawnCommandReturn()); @@ -78,14 +81,11 @@ describe('generator - Heroku', () => { describe('monolith application', () => { describe('with an unavailable app name', () => { - const autogeneratedAppName = 'jhipster-new-name'; + const autogeneratedAppName = ''; beforeEach(async () => { stub .withArgs('spawnCommand', `heroku create ${herokuAppName}`) .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); - stub - .withArgs('spawnCommand', 'heroku create ') - .yields(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); stub .withArgs('spawnCommand', `heroku git:remote --app ${autogeneratedAppName}`) .returns(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); @@ -113,6 +113,9 @@ describe('generator - Heroku', () => { runResult.assertFile(expectedFiles.monolith); runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${autogeneratedAppName}"`); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('with Git deployment', () => { @@ -147,6 +150,9 @@ describe('generator - Heroku', () => { runResult.assertFile(expectedFiles.monolith); runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "git"'); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('in the US', () => { @@ -177,6 +183,9 @@ describe('generator - Heroku', () => { runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('in the EU', () => { @@ -204,6 +213,9 @@ describe('generator - Heroku', () => { it('creates expected monolith files', () => { runResult.assertFile(expectedFiles.monolith); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('with PostgreSQL', () => { @@ -233,6 +245,9 @@ describe('generator - Heroku', () => { runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('with existing app', () => { @@ -258,6 +273,9 @@ describe('generator - Heroku', () => { runResult.assertFile(expectedFiles.monolith); runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${existingHerokuAppName}"`); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); describe('with elasticsearch', () => { @@ -290,6 +308,9 @@ describe('generator - Heroku', () => { runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:'); runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:'); }); + it('calls should match snapshot', () => { + expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); + }); }); }); }); From a4889489ad7dd0449c31b6aa378567898a96255c Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 21:11:09 -0300 Subject: [PATCH 14/45] switch heroku to base-application --- .../heroku/__snapshots__/heroku.spec.mts.snap | 57 +----- generators/heroku/generator.mjs | 174 +++++++----------- 2 files changed, 78 insertions(+), 153 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index c202fe16db06..0c638452b75f 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -7,6 +7,7 @@ exports[`generator - Heroku microservice application with JAR deployment should "generator-jhipster": { "applicationType": "microservice", "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "jar", "herokuJavaVersion": "17" @@ -170,13 +171,6 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -210,6 +204,7 @@ exports[`generator - Heroku monolith application in the EU should match files sn "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "jar", "herokuJavaVersion": "11" @@ -373,13 +368,6 @@ exports[`generator - Heroku monolith application in the US calls should match sn "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -413,6 +401,7 @@ exports[`generator - Heroku monolith application in the US should match files sn "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "jar", "herokuJavaVersion": "11" @@ -576,13 +565,6 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -636,6 +618,7 @@ exports[`generator - Heroku monolith application with Git deployment should matc "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "git", "herokuJavaVersion": "11" @@ -799,13 +782,6 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -839,6 +815,7 @@ exports[`generator - Heroku monolith application with PostgreSQL should match fi "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "jar", "herokuJavaVersion": "11" @@ -1002,13 +979,6 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -1050,6 +1020,7 @@ exports[`generator - Heroku monolith application with an unavailable app name sh "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "", "herokuDeployType": "jar", "herokuJavaVersion": "11" @@ -1213,13 +1184,6 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -1261,6 +1225,7 @@ exports[`generator - Heroku monolith application with elasticsearch should match "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-test", "herokuDeployType": "jar", "herokuJavaVersion": "11", @@ -1435,13 +1400,6 @@ exports[`generator - Heroku monolith application with existing app calls should "stdio": "pipe", }, ], - [ - "spawnCommand", - "git init", - { - "stdio": "pipe", - }, - ], [ "spawnCommand", "heroku plugins", @@ -1467,6 +1425,7 @@ exports[`generator - Heroku monolith application with existing app should match "contents": "{ "generator-jhipster": { "baseName": "jhipster", + "entities": [], "herokuAppName": "jhipster-existing", "herokuDeployType": "git", "herokuJavaVersion": "17" diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 955d7b158452..8df588d9c4ab 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -18,33 +18,18 @@ */ /* eslint-disable consistent-return */ import crypto from 'crypto'; -import fs from 'fs'; import * as _ from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; -import BaseGenerator from '../base/index.mjs'; +import BaseGenerator from '../base-application/index.mjs'; import statistics from '../statistics.mjs'; -import { CLIENT_MAIN_SRC_DIR, JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; -import { GENERATOR_HEROKU } from '../generator-list.mjs'; -import { buildToolTypes, cacheTypes, databaseTypes, searchEngineTypes, serviceDiscoveryTypes } from '../../jdl/jhipster/index.mjs'; +import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, SERVER_MAIN_RES_DIR } from '../generator-constants.mjs'; +import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_HEROKU } from '../generator-list.mjs'; import { mavenProfileContent } from './templates.mjs'; import { createPomStorage } from '../maven/support/pom-store.mjs'; import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.mjs'; -import { getFrontendAppName } from '../base/support/index.mjs'; -import { loadAppConfig, loadDerivedAppConfig } from '../app/support/index.mjs'; -import { loadDerivedPlatformConfig, loadDerivedServerConfig, loadPlatformConfig, loadServerConfig } from '../server/support/index.mjs'; -import { loadLanguagesConfig } from '../languages/support/index.mjs'; - -const cacheProviderOptions = cacheTypes; -const { MEMCACHED, REDIS } = cacheTypes; -const { GRADLE, MAVEN } = buildToolTypes; -const { ELASTICSEARCH } = searchEngineTypes; -const { MARIADB, MYSQL, POSTGRESQL } = databaseTypes; -const { EUREKA } = serviceDiscoveryTypes; - -const NO_CACHE_PROVIDER = cacheProviderOptions.NO; export default class HerokuGenerator extends BaseGenerator { herokuAppName; @@ -54,6 +39,7 @@ export default class HerokuGenerator extends BaseGenerator { herokuAppExists; herokuSkipDeploy; herokuSkipBuild; + dynoSize; constructor(args, options, features) { super(args, options, features); @@ -83,6 +69,9 @@ export default class HerokuGenerator extends BaseGenerator { if (!this.fromBlueprint) { await this.composeWithBlueprints(GENERATOR_HEROKU); } + if (!this.delegateToBlueprint) { + await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION); + } } get initializing() { @@ -94,24 +83,8 @@ export default class HerokuGenerator extends BaseGenerator { } }, - loadCommonConfig() { - loadAppConfig({ config: this.jhipsterConfigWithDefaults, application: this, useVersionPlaceholders: this.useVersionPlaceholders }); - loadServerConfig({ config: this.jhipsterConfigWithDefaults, application: this }); - loadLanguagesConfig({ application: this, config: this.jhipsterConfigWithDefaults }); - loadPlatformConfig({ config: this.jhipsterConfigWithDefaults, application: this }); - - loadDerivedAppConfig({ application: this }); - loadDerivedPlatformConfig({ application: this }); - loadDerivedServerConfig({ application: this }); - }, - initializing() { this.log.log(chalk.bold('Heroku configuration is starting')); - const configuration = this.config; - this.env.options.appPath = configuration.get('appPath') || CLIENT_MAIN_SRC_DIR; - this.cacheProvider = this.cacheProvider || NO_CACHE_PROVIDER; - this.enableHibernateCache = this.enableHibernateCache && ![NO_CACHE_PROVIDER, MEMCACHED].includes(this.cacheProvider); - this.frontendAppName = getFrontendAppName({ baseName: this.jhipsterConfig.baseName }); this.dynoSize = 'Free'; }, }); @@ -157,20 +130,19 @@ export default class HerokuGenerator extends BaseGenerator { ], 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 askForRegion() { - 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( [ @@ -231,13 +203,12 @@ export default class HerokuGenerator extends BaseGenerator { }, async gitInit() { - try { - fs.lstatSync('.git'); + const git = this.createGit(); + if (await git.checkIsRepo()) { this.log.log(chalk.bold('\nUsing existing Git repository')); - } catch (e) { - // An exception is thrown if the folder doesn't exist + } else { this.log.log(chalk.bold('\nInitializing Git repository')); - await this.printChildOutput(this.spawnCommand('git init', { stdio: 'pipe' })); + await git.init(); } }, @@ -246,7 +217,7 @@ export default class HerokuGenerator extends BaseGenerator { const { stdout, stderr, exitCode } = await this.spawnCommand('heroku plugins', { reject: false, stdio: 'pipe' }); if (exitCode !== 0) { - if (_.includes(stdout, cliPlugin)) { + if (stdout.includes(cliPlugin)) { this.log.log('\nHeroku CLI deployment plugin already installed'); } else { this.log.log(chalk.bold('\nInstalling Heroku CLI deployment plugin')); @@ -272,9 +243,8 @@ export default class HerokuGenerator extends BaseGenerator { }), ); - if (stdout.search('Heroku credentials') >= 0) { - this.cancelCancellableTasks(); - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + if (stdout.includes('Heroku credentials')) { + throw new Error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); } if (exitCode !== 0) { @@ -303,22 +273,15 @@ export default class HerokuGenerator extends BaseGenerator { if (props.herokuForceName === 'Yes') { const { stdout } = await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); this.log.verboseInfo(stdout); - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); } else { const { stdout } = await this.spawnCommand(`heroku create ${regionParams}`); // Extract from "Created random-app-name-1234... done" this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); - this.log.verboseInfo(stdout.trim()); + this.log.verboseInfo(stdout); // ensure that the git remote is the same as the appName await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); - this.config.set({ - herokuAppName: this.herokuAppName, - herokuDeployType: this.herokuDeployType, - }); + this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { throw new Error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); @@ -328,76 +291,75 @@ export default class HerokuGenerator extends BaseGenerator { } }, - async herokuAddonsCreate() { + async herokuAddonsCreate({ application }) { const addonCreateCallback = (addon, err) => { if (err) { const verifyAccountUrl = 'https://heroku.com/verify'; - if (_.includes(err, verifyAccountUrl)) { - this.abort = true; + if (err.includes(verifyAccountUrl)) { this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`); - this.log.error(err); + throw new Error(err); } else { this.log.verboseInfo(`No new ${addon} addon created`); } } else { - this.log.verboseInfo(`Created ${addon} addon`); + this.log.ok(`Created ${addon} addon`); } }; this.log.log(chalk.bold('\nProvisioning addons')); - if (this.searchEngine === ELASTICSEARCH) { + if (application.searchEngineElasticsearch) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - const { stderr, stdout, exitCode } = await this.spawnCommand( - `heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, - { reject: false, stdio: 'pipe' }, - ); - addonCreateCallback.bind('Elasticsearch', exitCode, stdout, stderr); + const { stderr } = await this.spawnCommand(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, { + reject: false, + stdio: 'pipe', + }); + addonCreateCallback('Elasticsearch', stderr); } let dbAddOn; - if (this.prodDatabaseType === POSTGRESQL) { + if (application.prodDatabaseTypePostgresql) { dbAddOn = 'heroku-postgresql --as DATABASE'; - } else if (this.prodDatabaseType === MYSQL) { + } else if (application.prodDatabaseTypeMysql) { dbAddOn = 'jawsdb:kitefin --as DATABASE'; - } else if (this.prodDatabaseType === MARIADB) { + } else if (application.prodDatabaseTypeMariadb) { dbAddOn = 'jawsdb-maria:kitefin --as DATABASE'; } if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, { + const { stderr } = await this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, { reject: false, stdio: 'pipe', }); - addonCreateCallback('Database', exitCode, stdout, stderr); + addonCreateCallback('Database', stderr); } else { this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); } let cacheAddOn; - if (this.cacheProvider === MEMCACHED) { + if (application.cacheProviderMemcached) { cacheAddOn = 'memcachier:dev --as MEMCACHIER'; - } else if (this.cacheProvider === REDIS) { + } else if (application.cacheProviderRedis) { cacheAddOn = 'heroku-redis:hobby-dev --as REDIS'; } if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon ${cacheAddOn}`)); - const { stderr, stdout, exitCode } = await this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, { + const { stderr } = await this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, { reject: false, stdio: 'pipe', }); - addonCreateCallback('Cache', exitCode, stdout, stderr); + addonCreateCallback('Cache', stderr); } else { this.log.log(chalk.bold(`\nNo suitable cache addon for cacheprovider ${this.cacheProvider} available.`)); } }, - configureJHipsterRegistry() { + configureJHipsterRegistry({ application }) { if (this.herokuAppExists) return undefined; - if (this.serviceDiscoveryType === EUREKA) { + if (application.serviceDiscoveryEureka) { const prompts = [ { type: 'input', @@ -444,19 +406,25 @@ export default class HerokuGenerator extends BaseGenerator { get writing() { return this.asWritingTaskGroup({ - copyHerokuFiles() { + copyHerokuFiles({ application }) { this.log.log(chalk.bold('\nCreating Heroku deployment files')); - this.writeFile('bootstrap-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`); - this.writeFile('application-heroku.yml.ejs', `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`); - this.writeFile('Procfile.ejs', 'Procfile'); - this.writeFile('system.properties.ejs', 'system.properties'); - if (this.buildTool === GRADLE) { - this.writeFile('heroku.gradle.ejs', 'gradle/heroku.gradle'); + const context = { + ...application, + herokuAppName: this.herokuAppName, + dynoSize: this.dynoSize, + herokuJavaVersion: this.herokuJavaVersion, + }; + 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() { - if (this.buildTool !== GRADLE) return; + addHerokuBuildPlugin({ application }) { + if (!application.buildToolGradle) return; // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator this.editFile( 'build.gradle', @@ -466,9 +434,9 @@ export default class HerokuGenerator extends BaseGenerator { this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); }, - addHerokuMavenProfile() { - if (this.buildTool === MAVEN) { - this.addMavenProfile('heroku', mavenProfileContent(this)); + addHerokuMavenProfile({ application }) { + if (application.buildToolMaven) { + this.addMavenProfile('heroku', mavenProfileContent(application)); } }, }); @@ -492,7 +460,7 @@ export default class HerokuGenerator extends BaseGenerator { await this.spawnCommand('npm run java:jar:prod', { stdio: 'inherit' }); }, - async productionDeploy() { + async productionDeploy({ application }) { if (this.herokuSkipDeploy) { this.log.log(chalk.bold('\nSkipping deployment')); return; @@ -513,7 +481,7 @@ export default class HerokuGenerator extends BaseGenerator { let buildpack = 'heroku/java'; let configVars = 'MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" '; - if (this.buildTool === GRADLE) { + if (application.buildToolGradle) { buildpack = 'heroku/gradle'; configVars = 'GRADLE_TASK="stage -Pprod -PnodeInstall" '; } @@ -554,10 +522,8 @@ export default class HerokuGenerator extends BaseGenerator { if (props.userDeployDecision === 'Yes') { this.log.info(chalk.bold('Continuing deployment...')); } else { - this.log(this.logger); this.log.info(chalk.bold('You aborted deployment!')); - this.abort = true; - this.herokuAppName = null; + this.cancelCancellableTasks(); return; } this.log(''); @@ -576,7 +542,7 @@ export default class HerokuGenerator extends BaseGenerator { } else { this.log.log(chalk.bold('\nDeploying application')); let jarFileWildcard = 'target/*.jar'; - if (this.buildTool === GRADLE) { + if (application.buildToolGradle) { jarFileWildcard = 'build/libs/*.jar'; } From 92b93bacd58bd11f5c9bc10dcaaf0a2ad5775543 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Oct 2023 21:31:31 -0300 Subject: [PATCH 15/45] randomPassword is not used --- generators/heroku/generator.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 8df588d9c4ab..79845242d2de 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -17,8 +17,7 @@ * limitations under the License. */ /* eslint-disable consistent-return */ -import crypto from 'crypto'; -import * as _ from 'lodash-es'; +import { kebabCase } from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; @@ -60,7 +59,6 @@ export default class HerokuGenerator extends BaseGenerator { return; } - this.randomPassword = crypto.randomBytes(20).toString('hex'); this.herokuSkipBuild = this.options.skipBuild; this.herokuSkipDeploy = this.options.skipDeploy || this.options.skipBuild; } @@ -185,7 +183,7 @@ export default class HerokuGenerator extends BaseGenerator { get loading() { return this.asConfiguringTaskGroup({ saveConfig() { - this.herokuAppName = _.kebabCase(this.jhipsterConfig.herokuAppName); + this.herokuAppName = kebabCase(this.jhipsterConfig.herokuAppName); this.herokuJavaVersion = this.jhipsterConfig.herokuJavaVersion; this.herokuDeployType = this.jhipsterConfig.herokuDeployType; }, From 0e2c0dde200cbd99da6b86929ff9100cd736de13 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Thu, 26 Oct 2023 08:26:10 -0300 Subject: [PATCH 16/45] adjusts --- .../heroku/__snapshots__/heroku.spec.mts.snap | 12 -- generators/heroku/generator.mjs | 150 +++++++++--------- generators/heroku/heroku.spec.mts | 4 - 3 files changed, 75 insertions(+), 91 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 0c638452b75f..0581a91b3fa9 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -589,14 +589,6 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], - [ - "spawnCommand", - "git add .", - ], - [ - "spawnCommand", - "git commit -m "Deploy to Heroku" --allow-empty", - ], [ "spawnCommand", "heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app jhipster-test", @@ -605,10 +597,6 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "spawnCommand", "heroku buildpacks:add heroku/java --app jhipster-test", ], - [ - "spawnCommand", - "git push heroku HEAD:main", - ], ] `; diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 79845242d2de..a25d21cb8d1a 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -31,6 +31,8 @@ import { createPomStorage } from '../maven/support/pom-store.mjs'; import { addGradlePluginCallback, applyFromGradleCallback } from '../gradle/internal/needles.mjs'; export default class HerokuGenerator extends BaseGenerator { + hasHerokuCli; + herokuAppName; herokuDeployType; herokuJavaVersion; @@ -76,14 +78,22 @@ export default class HerokuGenerator extends BaseGenerator { return this.asInitializingTaskGroup({ async checkInstallation() { const { exitCode } = await this.printChildOutput(this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' })); - if (exitCode !== 0) { - throw new Error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"); + this.hasHerokuCli = exitCode === 0; + if (!this.hasHerokuCli) { + const error = "You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"; + if (this.skipChecks) { + this.log.warn(error); + this.log.warn('Generation will continue with limited support'); + } else { + throw new Error(error); + } } }, initializing() { this.log.log(chalk.bold('Heroku configuration is starting')); this.dynoSize = 'Free'; + this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName); }, }); } @@ -95,7 +105,7 @@ export default class HerokuGenerator extends BaseGenerator { get prompting() { return this.asPromptingTaskGroup({ async askForApp() { - if (this.jhipsterConfig.herokuAppName) { + if (this.hasHerokuCli && this.herokuAppExists) { const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.jhipsterConfig.herokuAppName}`, { reject: false, stdio: 'pipe', @@ -110,13 +120,11 @@ export default class HerokuGenerator extends BaseGenerator { this.dynoSize = json.dynos[0].size; } this.log.verboseInfo(`Deploying as existing application: ${chalk.bold(this.herokuAppName)}`); - this.herokuAppExists = true; this.config.set({ herokuAppName: this.herokuAppName, }); } } else { - this.herokuAppExists = false; await this.prompt( [ { @@ -201,6 +209,8 @@ export default class HerokuGenerator extends BaseGenerator { }, async gitInit() { + if (!this.herokuDeployType === 'git') return; + const git = this.createGit(); if (await git.checkIsRepo()) { this.log.log(chalk.bold('\nUsing existing Git repository')); @@ -211,6 +221,8 @@ export default class HerokuGenerator extends BaseGenerator { }, async installHerokuDeployPlugin() { + if (!this.hasHerokuCli) return; + const cliPlugin = 'heroku-cli-deploy'; const { stdout, stderr, exitCode } = await this.spawnCommand('heroku plugins', { reject: false, stdio: 'pipe' }); @@ -229,7 +241,7 @@ export default class HerokuGenerator extends BaseGenerator { }, async herokuCreate() { - if (this.herokuAppExists) return; + if (!this.hasHerokuCli || this.herokuAppExists) return; const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; @@ -282,7 +294,7 @@ export default class HerokuGenerator extends BaseGenerator { this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { - throw new Error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); } else { throw new Error(stderr); } @@ -290,19 +302,7 @@ export default class HerokuGenerator extends BaseGenerator { }, async herokuAddonsCreate({ application }) { - const addonCreateCallback = (addon, err) => { - if (err) { - const verifyAccountUrl = 'https://heroku.com/verify'; - if (err.includes(verifyAccountUrl)) { - this.log.error(`Account must be verified to use addons. Please go to: ${verifyAccountUrl}`); - throw new Error(err); - } else { - this.log.verboseInfo(`No new ${addon} addon created`); - } - } else { - this.log.ok(`Created ${addon} addon`); - } - }; + if (!this.hasHerokuCli) return; this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { @@ -311,7 +311,7 @@ export default class HerokuGenerator extends BaseGenerator { reject: false, stdio: 'pipe', }); - addonCreateCallback('Elasticsearch', stderr); + this.checkAddOnReturn({ addOn: 'Elasticsearch', stderr }); } let dbAddOn; @@ -329,7 +329,7 @@ export default class HerokuGenerator extends BaseGenerator { reject: false, stdio: 'pipe', }); - addonCreateCallback('Database', stderr); + this.checkAddOnReturn({ addOn: 'Database', stderr }); } else { this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); } @@ -342,58 +342,49 @@ export default class HerokuGenerator extends BaseGenerator { } if (cacheAddOn) { - this.log.log(chalk.bold(`\nProvisioning cache addon ${cacheAddOn}`)); + this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); const { stderr } = await this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, { reject: false, stdio: 'pipe', }); - addonCreateCallback('Cache', stderr); + this.checkAddOnReturn({ addOn: 'Cache', stderr }); } else { this.log.log(chalk.bold(`\nNo suitable cache addon for cacheprovider ${this.cacheProvider} available.`)); } }, - configureJHipsterRegistry({ application }) { - if (this.herokuAppExists) return undefined; - - if (application.serviceDiscoveryEureka) { - const prompts = [ - { - 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', - }, - ]; - - this.log.verboseInfo(''); - return this.prompt(prompts).then(async props => { - // Encode username/password to avoid errors caused by spaces - props.herokuJHipsterRegistryUsername = encodeURIComponent(props.herokuJHipsterRegistryUsername); - props.herokuJHipsterRegistryPassword = encodeURIComponent(props.herokuJHipsterRegistryPassword); - const herokuJHipsterRegistry = `https://${props.herokuJHipsterRegistryUsername}:${props.herokuJHipsterRegistryPassword}@${props.herokuJHipsterRegistryApp}.herokuapp.com`; - const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; - await this.printChildOutput( - this.spawnCommand(configSetCmd, { - stdio: 'pipe', - }), - ); - }); - } - return undefined; + async configureJHipsterRegistry({ application }) { + if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka) return undefined; + + this.log.verboseInfo(''); + 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', + }, + ]); + + // Encode username/password to avoid errors caused by spaces + const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername); + const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword); + const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`; + const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; + await this.printChildOutput(this.spawnCommand(configSetCmd, { stdio: 'pipe' })); }, }); } @@ -411,7 +402,9 @@ export default class HerokuGenerator extends BaseGenerator { 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); @@ -459,7 +452,7 @@ export default class HerokuGenerator extends BaseGenerator { }, async productionDeploy({ application }) { - if (this.herokuSkipDeploy) { + if (this.herokuSkipDeploy || !this.hasHerokuCli) { this.log.log(chalk.bold('\nSkipping deployment')); return; } @@ -467,15 +460,8 @@ export default class HerokuGenerator extends BaseGenerator { if (this.herokuDeployType === 'git') { try { this.log.log(chalk.bold('\nUpdating Git repository')); - const gitAddCmd = 'git add .'; - this.log.log(chalk.cyan(gitAddCmd)); - - await this.printChildOutput(this.spawnCommand(gitAddCmd)); - - const gitCommitCmd = 'git commit -m "Deploy to Heroku" --allow-empty'; - this.log.log(chalk.cyan(gitCommitCmd)); - - await this.printChildOutput(this.spawnCommand(gitCommitCmd)); + const git = this.createGit(); + await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null }); let buildpack = 'heroku/java'; let configVars = 'MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" '; @@ -529,7 +515,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nDeploying application')); - await this.printChildOutput(this.spawnCommand('git push heroku HEAD:main')); + 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')}`)); @@ -598,4 +584,18 @@ export default class HerokuGenerator extends BaseGenerator { }); return child; } + + checkAddOnReturn({ addon, stderr }) { + 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} addon created`); + } + } else { + this.log.ok(`Created ${addon} addon`); + } + } } diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 162d68f1d445..449f386c8fc2 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -35,7 +35,6 @@ describe('generator - Heroku', () => { stub.withArgs('spawnCommand', 'heroku --version').returns(createSpawnCommandReturn()); stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); - stub.withArgs('spawnCommand', 'git init').returns(createSpawnCommandReturn()); }); afterEach(() => { stub.resetHistory(); @@ -124,13 +123,10 @@ describe('generator - Heroku', () => { stub .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) .returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', 'git add .').returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', 'git commit -m "Deploy to Heroku" --allow-empty').returns(createSpawnCommandReturn()); stub .withArgs('spawnCommand', `heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app ${herokuAppName}`) .returns(createSpawnCommandReturn()); stub.withArgs('spawnCommand', `heroku buildpacks:add heroku/java --app ${herokuAppName}`).returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', 'git push heroku HEAD:master').returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() From f8936f441bfcaa884e852c76def257fda6d944f5 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Thu, 26 Oct 2023 08:30:13 -0300 Subject: [PATCH 17/45] run 'heroku login' --- .../heroku/__snapshots__/heroku.spec.mts.snap | 49 +++++++++++++++++++ generators/heroku/generator.mjs | 2 + 2 files changed, 51 insertions(+) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 0581a91b3fa9..5fc567dbced0 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -171,6 +171,13 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -368,6 +375,13 @@ exports[`generator - Heroku monolith application in the US calls should match sn "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -565,6 +579,13 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -770,6 +791,13 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -967,6 +995,13 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -1172,6 +1207,13 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku plugins", @@ -1380,6 +1422,13 @@ exports[`generator - Heroku monolith application with existing app calls should "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku login", + { + "stdio": "inherit", + }, + ], [ "spawnCommand", "heroku apps:info --json jhipster-existing", diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index a25d21cb8d1a..5faa1c7bc541 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -87,6 +87,8 @@ export default class HerokuGenerator extends BaseGenerator { } else { throw new Error(error); } + } else { + await this.spawnCommand('heroku login', { stdio: 'inherit' }); } }, From 53e8190152d6f41ed2e70856b3c7dce54ba01e40 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Thu, 26 Oct 2023 08:36:48 -0300 Subject: [PATCH 18/45] add --skip-checks to error --- generators/heroku/generator.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 5faa1c7bc541..4f09df97321c 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -80,12 +80,12 @@ export default class HerokuGenerator extends BaseGenerator { const { exitCode } = await this.printChildOutput(this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' })); this.hasHerokuCli = exitCode === 0; if (!this.hasHerokuCli) { - const error = "You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/"; + const error = "You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/."; if (this.skipChecks) { this.log.warn(error); this.log.warn('Generation will continue with limited support'); } else { - throw new Error(error); + throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`); } } else { await this.spawnCommand('heroku login', { stdio: 'inherit' }); From 8d7d014927f445506944e7d12e3b87ad2b255d12 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Thu, 26 Oct 2023 22:44:16 -0600 Subject: [PATCH 19/45] Fix Gradle plugin coordinates --- generators/heroku/generator.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 4f09df97321c..052e2bd16bd0 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -421,7 +421,7 @@ export default class HerokuGenerator extends BaseGenerator { // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator this.editFile( 'build.gradle', - addGradlePluginCallback({ groupId: 'gradle.plugin.com.heroku.sdk', artifactId: 'heroku-gradle', version: '1.0.4' }), + addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' }), ); // TODO applyFromGradleCallback is an internal api, switch to source api when converted to BaseApplicationGenerator this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); @@ -473,6 +473,7 @@ export default class HerokuGenerator extends BaseGenerator { } this.log.log(chalk.bold('\nConfiguring Heroku')); + this.log.log(`heroku config:set ${configVars}--app ${this.herokuAppName}`) await this.spawnCommand(`heroku config:set ${configVars}--app ${this.herokuAppName}`); const { stdout: data } = await this.spawnCommand(`heroku buildpacks:add ${buildpack} --app ${this.herokuAppName}`); if (data) { From ca6ad41fd59ed6928f847835a4f6f79988d0bd5b Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Thu, 26 Oct 2023 22:49:24 -0600 Subject: [PATCH 20/45] Fix URL to Heroku CLI --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 052e2bd16bd0..40ced898c9e1 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -80,7 +80,7 @@ export default class HerokuGenerator extends BaseGenerator { const { exitCode } = await this.printChildOutput(this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' })); this.hasHerokuCli = exitCode === 0; if (!this.hasHerokuCli) { - const error = "You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/."; + 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'); From aab4165a7fd67d69b4fe5aaf7668dacbd5d8d90b Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Thu, 26 Oct 2023 22:51:23 -0600 Subject: [PATCH 21/45] Polishing --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 40ced898c9e1..8d8983533180 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -247,7 +247,7 @@ export default class HerokuGenerator extends BaseGenerator { const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; - this.log.log(chalk.bold('\nCreating Heroku application and setting up node environment')); + this.log.log(chalk.bold('\nCreating Heroku application and setting up Node environment')); const { stdout, stderr, exitCode } = await this.printChildOutput( this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { reject: false, From 530f483e1c29ffd1992258af4d2ecbbbb7900c9c Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Fri, 27 Oct 2023 02:08:22 -0600 Subject: [PATCH 22/45] Fix spawnCommand and add-ons --- generators/heroku/generator.mjs | 61 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 8d8983533180..3adab1bccced 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -77,7 +77,7 @@ export default class HerokuGenerator extends BaseGenerator { get initializing() { return this.asInitializingTaskGroup({ async checkInstallation() { - const { exitCode } = await this.printChildOutput(this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' })); + const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); 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."; @@ -87,8 +87,6 @@ export default class HerokuGenerator extends BaseGenerator { } else { throw new Error(`${error} To ignore this error run 'jhipster heroku --skip-checks'`); } - } else { - await this.spawnCommand('heroku login', { stdio: 'inherit' }); } }, @@ -309,50 +307,48 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - const { stderr } = await this.spawnCommand(`heroku addons:create bonsai:sandbox-6 --as BONSAI --app ${this.herokuAppName}`, { + const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', 'bonsai:sandbox-6', '--as', 'BONSAI', '--app', this.herokuAppName], { reject: false, stdio: 'pipe', }); - this.checkAddOnReturn({ addOn: 'Elasticsearch', stderr }); + this.checkAddOnReturn({addOn: 'Elasticsearch', stdout, stderr}); } let dbAddOn; if (application.prodDatabaseTypePostgresql) { - dbAddOn = 'heroku-postgresql --as DATABASE'; + dbAddOn = 'heroku-postgresql'; } else if (application.prodDatabaseTypeMysql) { - dbAddOn = 'jawsdb:kitefin --as DATABASE'; + dbAddOn = 'jawsdb:kitefin'; } else if (application.prodDatabaseTypeMariadb) { - dbAddOn = 'jawsdb-maria:kitefin --as DATABASE'; + dbAddOn = 'jawsdb-maria:kitefin'; } if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - const { stderr } = await this.spawnCommand(`heroku addons:create ${dbAddOn} --app ${this.herokuAppName}`, { + const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', dbAddOn, '--as', 'DATABASE', '--app', this.herokuAppName], { reject: false, stdio: 'pipe', }); - this.checkAddOnReturn({ addOn: 'Database', stderr }); + 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'; + cacheAddOn = ['memcachier:dev', '--as', 'MEMCACHIER']; } else if (application.cacheProviderRedis) { - cacheAddOn = 'heroku-redis:hobby-dev --as REDIS'; + cacheAddOn = ['heroku-redis:hobby-dev', '--as', 'REDIS']; } if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - const { stderr } = await this.spawnCommand(`heroku addons:create ${cacheAddOn} --app ${this.herokuAppName}`, { + const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', cacheAddOn[0], cacheAddOn[1], cacheAddOn[2], '--app', this.herokuAppName], { reject: false, stdio: 'pipe', }); - this.checkAddOnReturn({ addOn: 'Cache', stderr }); - } else { - this.log.log(chalk.bold(`\nNo suitable cache addon for cacheprovider ${this.cacheProvider} available.`)); + this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); } }, @@ -385,8 +381,8 @@ export default class HerokuGenerator extends BaseGenerator { const herokuJHipsterRegistryUsername = encodeURIComponent(answers.herokuJHipsterRegistryUsername); const herokuJHipsterRegistryPassword = encodeURIComponent(answers.herokuJHipsterRegistryPassword); const herokuJHipsterRegistry = `https://${herokuJHipsterRegistryUsername}:${herokuJHipsterRegistryPassword}@${answers.herokuJHipsterRegistryApp}.herokuapp.com`; - const configSetCmd = `heroku config:set JHIPSTER_REGISTRY_URL=${herokuJHipsterRegistry} --app ${this.herokuAppName}`; - await this.printChildOutput(this.spawnCommand(configSetCmd, { stdio: 'pipe' })); + const configSetCmd = ['config:set', 'JHIPSTER_REGISTRY_URL', herokuJHipsterRegistry, '--app', this.herokuAppName]; + await this.printChildOutput(this.spawnCommand('heroku', configSetCmd, { stdio: 'pipe' })); }, }); } @@ -466,20 +462,22 @@ export default class HerokuGenerator extends BaseGenerator { await git.add('.').commit('Deploy to Heroku', { '--allow-empty': null }); let buildpack = 'heroku/java'; - let configVars = 'MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" '; + let configName = 'MAVEN_CUSTOM_OPTS'; + let configValues = '-Pprod,heroku -DskipTests'; if (application.buildToolGradle) { buildpack = 'heroku/gradle'; - configVars = 'GRADLE_TASK="stage -Pprod -PnodeInstall" '; + configName = 'GRADLE_TASK'; + configValues = 'stage -Pprod -PnodeInstall'; } this.log.log(chalk.bold('\nConfiguring Heroku')); - this.log.log(`heroku config:set ${configVars}--app ${this.herokuAppName}`) - await this.spawnCommand(`heroku config:set ${configVars}--app ${this.herokuAppName}`); - const { stdout: data } = await this.spawnCommand(`heroku buildpacks:add ${buildpack} --app ${this.herokuAppName}`); + // todo: check if config already exists + await this.spawnCommand('heroku', ['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); + + // todo: check if buildpack already exists + const { stdout: data } = await this.spawnCommand('heroku', ['buildpacks:add', buildpack, '--app', this.herokuAppName]); if (data) { this.logger.info(data); - // remote: ! The following add-ons were automatically provisioned: . These add-ons may incur additional cost, - // which is prorated to the second. Run `heroku addons` for more info. if (data.includes('Run `heroku addons` for more info.')) { await this.spawnCommand('heroku addons'); } @@ -516,7 +514,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log(''); } - this.log.log(chalk.bold('\nDeploying application')); + this.log.log(chalk.bold('\nDeploying application...')); await git.push('heroku', 'HEAD:main'); @@ -588,17 +586,18 @@ export default class HerokuGenerator extends BaseGenerator { return child; } - checkAddOnReturn({ addon, stderr }) { - if (stderr) { + 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} addon created`); + this.log.verboseInfo(`No new ${addOn.valueOf()} add-on created`); } - } else { - this.log.ok(`Created ${addon} addon`); } } } From 25b88d8ed3fc28a75d6ac18aa0d0e690332c82f3 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Fri, 27 Oct 2023 02:10:20 -0600 Subject: [PATCH 23/45] Prettier --- generators/heroku/generator.mjs | 50 ++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 3adab1bccced..8f655b40fbfd 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -80,7 +80,8 @@ export default class HerokuGenerator extends BaseGenerator { const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); 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."; + 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'); @@ -307,11 +308,15 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', 'bonsai:sandbox-6', '--as', 'BONSAI', '--app', this.herokuAppName], { - reject: false, - stdio: 'pipe', - }); - this.checkAddOnReturn({addOn: 'Elasticsearch', stdout, stderr}); + const { stdout, stderr } = await this.spawnCommand( + 'heroku', + ['addons:create', 'bonsai:sandbox-6', '--as', 'BONSAI', '--app', this.herokuAppName], + { + reject: false, + stdio: 'pipe', + }, + ); + this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr }); } let dbAddOn; @@ -325,11 +330,15 @@ export default class HerokuGenerator extends BaseGenerator { if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', dbAddOn, '--as', 'DATABASE', '--app', this.herokuAppName], { - reject: false, - stdio: 'pipe', - }); - this.checkAddOnReturn({addOn: 'Database', stdout, stderr}); + const { stdout, stderr } = await this.spawnCommand( + 'heroku', + ['addons:create', dbAddOn, '--as', 'DATABASE', '--app', this.herokuAppName], + { + reject: false, + stdio: 'pipe', + }, + ); + this.checkAddOnReturn({ addOn: 'Database', stdout, stderr }); } else { this.log.log(chalk.bold(`\nNo suitable database addon for database ${this.prodDatabaseType} available.`)); } @@ -344,10 +353,14 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - const { stdout, stderr } = await this.spawnCommand('heroku', ['addons:create', cacheAddOn[0], cacheAddOn[1], cacheAddOn[2], '--app', this.herokuAppName], { - reject: false, - stdio: 'pipe', - }); + const { stdout, stderr } = await this.spawnCommand( + 'heroku', + ['addons:create', cacheAddOn[0], cacheAddOn[1], cacheAddOn[2], '--app', this.herokuAppName], + { + reject: false, + stdio: 'pipe', + }, + ); this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); } }, @@ -415,10 +428,7 @@ export default class HerokuGenerator extends BaseGenerator { addHerokuBuildPlugin({ application }) { if (!application.buildToolGradle) return; // TODO addGradlePluginCallback is an internal api, switch to source api when converted to BaseApplicationGenerator - this.editFile( - 'build.gradle', - addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' }), - ); + this.editFile('build.gradle', addGradlePluginCallback({ id: 'com.heroku.sdk.heroku-gradle', version: '1.0.4' })); // TODO applyFromGradleCallback is an internal api, switch to source api when converted to BaseApplicationGenerator this.editFile('build.gradle', applyFromGradleCallback({ script: 'gradle/heroku.gradle' })); }, @@ -586,7 +596,7 @@ export default class HerokuGenerator extends BaseGenerator { return child; } - checkAddOnReturn({addOn, stdout, stderr}) { + checkAddOnReturn({ addOn, stdout, stderr }) { if (stdout) { this.log.ok(`Created ${addOn.valueOf()} add-on`); this.log.ok(stdout); From a14d7a873f1e5052827d9174f91a610dc55ffd91 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Fri, 27 Oct 2023 02:26:02 -0600 Subject: [PATCH 24/45] Update snapshot --- .../heroku/__snapshots__/heroku.spec.mts.snap | 145 +++++++++++------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 5fc567dbced0..afe773bd7e01 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -171,13 +171,6 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -196,7 +189,15 @@ exports[`generator - Heroku monolith application in the EU calls should match sn ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -375,13 +376,6 @@ exports[`generator - Heroku monolith application in the US calls should match sn "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -400,7 +394,15 @@ exports[`generator - Heroku monolith application in the US calls should match sn ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -579,13 +581,6 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -604,7 +599,15 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -612,11 +615,23 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul ], [ "spawnCommand", - "heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app jhipster-test", + "heroku", + [ + "config:set", + "MAVEN_CUSTOM_OPTS=-Pprod,heroku -DskipTests", + "--app", + "jhipster-test", + ], ], [ "spawnCommand", - "heroku buildpacks:add heroku/java --app jhipster-test", + "heroku", + [ + "buildpacks:add", + "heroku/java", + "--app", + "jhipster-test", + ], ], ] `; @@ -791,13 +806,6 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -816,7 +824,15 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -995,13 +1011,6 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -1028,7 +1037,15 @@ exports[`generator - Heroku monolith application with an unavailable app name ca ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app ", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "", + ], { "reject": false, "stdio": "pipe", @@ -1207,13 +1224,6 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku plugins", @@ -1232,7 +1242,15 @@ exports[`generator - Heroku monolith application with elasticsearch calls should ], [ "spawnCommand", - "heroku addons:create bonsai:sandbox-6 --as BONSAI --app jhipster-test", + "heroku", + [ + "addons:create", + "bonsai:sandbox-6", + "--as", + "BONSAI", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -1240,7 +1258,15 @@ exports[`generator - Heroku monolith application with elasticsearch calls should ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-test", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", @@ -1422,13 +1448,6 @@ exports[`generator - Heroku monolith application with existing app calls should "stdio": "pipe", }, ], - [ - "spawnCommand", - "heroku login", - { - "stdio": "inherit", - }, - ], [ "spawnCommand", "heroku apps:info --json jhipster-existing", @@ -1447,7 +1466,15 @@ exports[`generator - Heroku monolith application with existing app calls should ], [ "spawnCommand", - "heroku addons:create heroku-postgresql --as DATABASE --app jhipster-existing", + "heroku", + [ + "addons:create", + "heroku-postgresql", + "--as", + "DATABASE", + "--app", + "jhipster-existing", + ], { "reject": false, "stdio": "pipe", From 56d7646b127f38ccc8512eef6a7573c600611576 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 06:49:50 -0300 Subject: [PATCH 25/45] add outputHandler to git --- generators/heroku/generator.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 8f655b40fbfd..8eacf927aaf4 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -212,7 +212,7 @@ export default class HerokuGenerator extends BaseGenerator { async gitInit() { if (!this.herokuDeployType === 'git') return; - const git = this.createGit(); + const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr })); if (await git.checkIsRepo()) { this.log.log(chalk.bold('\nUsing existing Git repository')); } else { @@ -468,7 +468,7 @@ export default class HerokuGenerator extends BaseGenerator { if (this.herokuDeployType === 'git') { try { this.log.log(chalk.bold('\nUpdating Git repository')); - const git = this.createGit(); + 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'; @@ -586,11 +586,11 @@ export default class HerokuGenerator extends BaseGenerator { * @param {(chunk: any) => void} child * @returns {ReturnType} */ - printChildOutput(child, log = data => this.log.verboseInfo(data)) { - child.stdout.on('data', data => { + printChildOutput({ stdout, stderr }, log = data => this.log.verboseInfo(data)) { + stdout.on('data', data => { data.toString().split(/\r?\n/).forEach(log); }); - child.stderr.on('data', data => { + stderr.on('data', data => { data.toString().split(/\r?\n/).forEach(log); }); return child; From fd33532f19fa9ea84d187393e6fb6a4034649f74 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 08:10:47 -0300 Subject: [PATCH 26/45] use spawn instead of spawnCommnad for non CLI safe arguments --- .../heroku/__snapshots__/heroku.spec.mts.snap | 97 +++++++++++++------ generators/heroku/generator.mjs | 27 +++--- generators/heroku/heroku.spec.mts | 53 ++-------- 3 files changed, 89 insertions(+), 88 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index afe773bd7e01..0da4ad5773b1 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -180,15 +180,21 @@ exports[`generator - Heroku monolith application in the EU calls should match sn }, ], [ - "spawnCommand", - "heroku create jhipster-test --region eu", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + "--region", + "eu", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -385,15 +391,19 @@ exports[`generator - Heroku monolith application in the US calls should match sn }, ], [ - "spawnCommand", - "heroku create jhipster-test", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -590,15 +600,19 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul }, ], [ - "spawnCommand", - "heroku create jhipster-test", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -614,7 +628,7 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul }, ], [ - "spawnCommand", + "spawn", "heroku", [ "config:set", @@ -624,7 +638,7 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul ], ], [ - "spawnCommand", + "spawn", "heroku", [ "buildpacks:add", @@ -815,15 +829,21 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma }, ], [ - "spawnCommand", - "heroku create jhipster-test --region eu", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + "--region", + "eu", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -1020,23 +1040,35 @@ exports[`generator - Heroku monolith application with an unavailable app name ca }, ], [ - "spawnCommand", - "heroku create jhipster-test", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", - "heroku create ", + "spawn", + "heroku", + [ + "create", + ], ], [ - "spawnCommand", - "heroku git:remote --app ", + "spawn", + "heroku", + [ + "git:remote", + "--app", + "", + ], ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -1233,15 +1265,19 @@ exports[`generator - Heroku monolith application with elasticsearch calls should }, ], [ - "spawnCommand", - "heroku create jhipster-test", + "spawn", + "heroku", + [ + "create", + "jhipster-test", + ], { "reject": false, "stdio": "pipe", }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -1257,7 +1293,7 @@ exports[`generator - Heroku monolith application with elasticsearch calls should }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", @@ -1449,8 +1485,13 @@ exports[`generator - Heroku monolith application with existing app calls should }, ], [ - "spawnCommand", - "heroku apps:info --json jhipster-existing", + "spawn", + "heroku", + [ + "apps:info", + "--json", + "jhipster-existing", + ], { "reject": false, "stdio": "pipe", @@ -1465,7 +1506,7 @@ exports[`generator - Heroku monolith application with existing app calls should }, ], [ - "spawnCommand", + "spawn", "heroku", [ "addons:create", diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 8eacf927aaf4..90aa02895cc6 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -107,7 +107,7 @@ export default class HerokuGenerator extends BaseGenerator { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { - const { stdout, exitCode } = await this.spawnCommand(`heroku apps:info --json ${this.jhipsterConfig.herokuAppName}`, { + const { stdout, exitCode } = await this.spawn('heroku', ['apps:info', '--json', this.jhipsterConfig.herokuAppName], { reject: false, stdio: 'pipe', }); @@ -244,11 +244,11 @@ export default class HerokuGenerator extends BaseGenerator { async herokuCreate() { if (!this.hasHerokuCli || this.herokuAppExists) return; - const regionParams = this.herokuRegion !== 'us' ? ` --region ${this.herokuRegion}` : ''; + 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.printChildOutput( - this.spawnCommand(`heroku create ${this.herokuAppName}${regionParams}`, { + this.spawn('heroku', ['create', this.herokuAppName, ...regionParams], { reject: false, stdio: 'pipe', }), @@ -282,16 +282,16 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(''); const props = await this.prompt(prompts); if (props.herokuForceName === 'Yes') { - const { stdout } = await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); + const { stdout } = await this.spawn('heroku', ['git:remote', '--app', this.herokuAppName]); this.log.verboseInfo(stdout); } else { - const { stdout } = await this.spawnCommand(`heroku create ${regionParams}`); + const { stdout } = await this.spawn('heroku', ['create', ...regionParams]); // Extract from "Created random-app-name-1234... done" this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); this.log.verboseInfo(stdout); // ensure that the git remote is the same as the appName - await this.spawnCommand(`heroku git:remote --app ${this.herokuAppName}`); + await this.spawn('heroku', ['git:remote', '--app', this.herokuAppName]); this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { @@ -308,7 +308,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { this.log.log(chalk.bold('\nProvisioning bonsai elasticsearch addon')); - const { stdout, stderr } = await this.spawnCommand( + const { stdout, stderr } = await this.spawn( 'heroku', ['addons:create', 'bonsai:sandbox-6', '--as', 'BONSAI', '--app', this.herokuAppName], { @@ -330,7 +330,7 @@ export default class HerokuGenerator extends BaseGenerator { if (dbAddOn) { this.log.log(chalk.bold(`\nProvisioning database addon ${dbAddOn}`)); - const { stdout, stderr } = await this.spawnCommand( + const { stdout, stderr } = await this.spawn( 'heroku', ['addons:create', dbAddOn, '--as', 'DATABASE', '--app', this.herokuAppName], { @@ -353,7 +353,7 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - const { stdout, stderr } = await this.spawnCommand( + const { stdout, stderr } = await this.spawn( 'heroku', ['addons:create', cacheAddOn[0], cacheAddOn[1], cacheAddOn[2], '--app', this.herokuAppName], { @@ -395,7 +395,7 @@ export default class HerokuGenerator extends BaseGenerator { 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.printChildOutput(this.spawnCommand('heroku', configSetCmd, { stdio: 'pipe' })); + await this.printChildOutput(this.spawn('heroku', configSetCmd, { stdio: 'pipe' })); }, }); } @@ -482,10 +482,10 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nConfiguring Heroku')); // todo: check if config already exists - await this.spawnCommand('heroku', ['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); + await this.spawn('heroku', ['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); // todo: check if buildpack already exists - const { stdout: data } = await this.spawnCommand('heroku', ['buildpacks:add', buildpack, '--app', this.herokuAppName]); + const { stdout: data } = await this.spawn('heroku', ['buildpacks:add', buildpack, '--app', this.herokuAppName]); if (data) { this.logger.info(data); if (data.includes('Run `heroku addons` for more info.')) { @@ -586,7 +586,8 @@ export default class HerokuGenerator extends BaseGenerator { * @param {(chunk: any) => void} child * @returns {ReturnType} */ - printChildOutput({ stdout, stderr }, log = data => this.log.verboseInfo(data)) { + printChildOutput(child, log = data => this.log.verboseInfo(data)) { + const { stdout, stderr } = child; stdout.on('data', data => { data.toString().split(/\r?\n/).forEach(log); }); diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 449f386c8fc2..01ef9b2a3384 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -31,9 +31,9 @@ describe('generator - Heroku', () => { beforeEach(() => { stub = sinon.stub(); // Add catch all - stub.returns(createSpawnCommandReturn()); + stub.withArgs('spawnCommand').returns(createSpawnCommandReturn()); + stub.withArgs('spawn').returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', 'heroku --version').returns(createSpawnCommandReturn()); stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' })); }); afterEach(() => { @@ -43,16 +43,6 @@ describe('generator - Heroku', () => { describe('microservice application', () => { describe('with JAR deployment', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); - stub - .withArgs( - 'spawnCommand', - `heroku config:set JHIPSTER_REGISTRY_URL=https://admin:changeme@sushi.herokuapp.com --app ${herokuAppName}`, - ) - .returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ applicationType: 'microservice' }) @@ -83,14 +73,12 @@ describe('generator - Heroku', () => { const autogeneratedAppName = ''; beforeEach(async () => { stub - .withArgs('spawnCommand', `heroku create ${herokuAppName}`) + .withArgs('spawn', 'heroku', ['create', herokuAppName]) .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); stub - .withArgs('spawnCommand', `heroku git:remote --app ${autogeneratedAppName}`) + .withArgs('spawn', 'heroku', ['git:remote', '--app', autogeneratedAppName]) .returns(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${autogeneratedAppName}`) - .returns(createSpawnCommandReturn()); + await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -119,14 +107,6 @@ describe('generator - Heroku', () => { describe('with Git deployment', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku config:set MAVEN_CUSTOM_OPTS="-Pprod,heroku -DskipTests" --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', `heroku buildpacks:add heroku/java --app ${herokuAppName}`).returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -153,10 +133,6 @@ describe('generator - Heroku', () => { describe('in the US', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -186,10 +162,6 @@ describe('generator - Heroku', () => { describe('in the EU', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -216,10 +188,6 @@ describe('generator - Heroku', () => { describe('with PostgreSQL', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName} --region eu`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create heroku-postgresql --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig() @@ -250,11 +218,8 @@ describe('generator - Heroku', () => { const existingHerokuAppName = 'jhipster-existing'; beforeEach(async () => { stub - .withArgs('spawnCommand', `heroku apps:info --json ${existingHerokuAppName}`) + .withArgs('spawn', 'heroku', sinon.match(['apps:info', '--json', existingHerokuAppName])) .returns(createSpawnCommandReturn({ stdout: `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}` })); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${existingHerokuAppName}`) - .returns(createSpawnCommandReturn()); await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' }) @@ -276,12 +241,6 @@ describe('generator - Heroku', () => { describe('with elasticsearch', () => { beforeEach(async () => { - stub.withArgs('spawnCommand', `heroku create ${herokuAppName}`).returns(createSpawnCommandReturn()); - stub - .withArgs('spawnCommand', `heroku addons:create jawsdb:kitefin --as DATABASE --app ${herokuAppName}`) - .returns(createSpawnCommandReturn()); - stub.withArgs('spawnCommand', `heroku addons:create bonsai --as BONSAI --app ${herokuAppName}`).returns(createSpawnCommandReturn()); - await helpers .createJHipster(GENERATOR_HEROKU) .withJHipsterConfig({ searchEngine: 'elasticsearch' }) From fbeeea065b204377b3a2d0b40ea2c7c17968f9fb Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 09:05:29 -0300 Subject: [PATCH 27/45] add spawnHerokuCommand and spawnHeroku utilities --- .../heroku/__snapshots__/heroku.spec.mts.snap | 3 + generators/heroku/generator.mjs | 65 ++++++++++++------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 0da4ad5773b1..cd3fae5e5162 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -646,6 +646,9 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "--app", "jhipster-test", ], + { + "reject": false, + }, ], ] `; diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 90aa02895cc6..d22fd56fee54 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -77,7 +77,7 @@ export default class HerokuGenerator extends BaseGenerator { get initializing() { return this.asInitializingTaskGroup({ async checkInstallation() { - const { exitCode } = await this.spawnCommand('heroku --version', { reject: false, stdio: 'pipe' }); + const { exitCode } = await this.spawnHerokuCommand('--version', { reject: false, stdio: 'pipe' }); this.hasHerokuCli = exitCode === 0; if (!this.hasHerokuCli) { const error = @@ -107,7 +107,7 @@ export default class HerokuGenerator extends BaseGenerator { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { - const { stdout, exitCode } = await this.spawn('heroku', ['apps:info', '--json', this.jhipsterConfig.herokuAppName], { + const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { reject: false, stdio: 'pipe', }); @@ -226,13 +226,13 @@ export default class HerokuGenerator extends BaseGenerator { const cliPlugin = 'heroku-cli-deploy'; - const { stdout, stderr, exitCode } = await this.spawnCommand('heroku plugins', { reject: false, stdio: 'pipe' }); + const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { reject: false, 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 { stdout, exitCode } = await this.spawnCommand(`heroku plugins:install ${cliPlugin}`, { reject: false, stdio: 'pipe' }); + const { stdout, exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`, { reject: false, stdio: 'pipe' }); if (exitCode !== 0) { throw new Error(stderr); } @@ -247,12 +247,10 @@ export default class HerokuGenerator extends BaseGenerator { 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.printChildOutput( - this.spawn('heroku', ['create', this.herokuAppName, ...regionParams], { - reject: false, - stdio: 'pipe', - }), - ); + const { stdout, stderr, exitCode } = await this.spawnHeroku(['create', this.herokuAppName, ...regionParams], { + reject: false, + stdio: 'pipe', + }); if (stdout.includes('Heroku credentials')) { throw new Error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); @@ -282,16 +280,16 @@ export default class HerokuGenerator extends BaseGenerator { this.log.verboseInfo(''); const props = await this.prompt(prompts); if (props.herokuForceName === 'Yes') { - const { stdout } = await this.spawn('heroku', ['git:remote', '--app', this.herokuAppName]); + const { stdout } = await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); this.log.verboseInfo(stdout); } else { - const { stdout } = await this.spawn('heroku', ['create', ...regionParams]); + const { stdout } = await this.spawnHeroku(['create', ...regionParams]); // Extract from "Created random-app-name-1234... done" this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); this.log.verboseInfo(stdout); // ensure that the git remote is the same as the appName - await this.spawn('heroku', ['git:remote', '--app', this.herokuAppName]); + await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { @@ -395,7 +393,7 @@ export default class HerokuGenerator extends BaseGenerator { 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.printChildOutput(this.spawn('heroku', configSetCmd, { stdio: 'pipe' })); + await this.spawnHeroku(configSetCmd, { stdio: 'pipe' }); }, }); } @@ -482,14 +480,14 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nConfiguring Heroku')); // todo: check if config already exists - await this.spawn('heroku', ['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); + await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); // todo: check if buildpack already exists - const { stdout: data } = await this.spawn('heroku', ['buildpacks:add', buildpack, '--app', this.herokuAppName]); + const { stdout: data } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName], { reject: false }); if (data) { - this.logger.info(data); + this.log.info(data); if (data.includes('Run `heroku addons` for more info.')) { - await this.spawnCommand('heroku addons'); + await this.spawnHerokuCommand('addons'); } this.log(''); @@ -543,8 +541,6 @@ export default class HerokuGenerator extends BaseGenerator { const files = glob.sync(jarFileWildcard, {}); const jarFile = files[0]; - const herokuDeployCommand = `heroku deploy:jar ${jarFile} --app ${this.herokuAppName}`; - const herokuSetBuildpackCommand = 'heroku buildpacks:set heroku/jvm'; this.log.log( chalk.bold( @@ -552,8 +548,8 @@ export default class HerokuGenerator extends BaseGenerator { ), ); try { - await this.printChildOutput(this.spawnCommand(herokuSetBuildpackCommand, { stdio: 'pipe' })); - await this.printChildOutput(this.spawnCommand(herokuDeployCommand, { stdio: 'pipe' })); + 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')}`)); @@ -582,10 +578,31 @@ export default class HerokuGenerator extends BaseGenerator { } /** - * @param {ReturnType} child - * @param {(chunk: any) => void} child + * @param {string} command + * @param {import('execa').Options} opt * @returns {ReturnType} */ + spawnHerokuCommand(command, opt = {}) { + const varargs = opt ? [opt] : []; + return this.printChildOutput(this.spawnCommand(`heroku ${command}`, ...varargs)); + } + + /** + * @param {string[]} args + * @param {import('execa').Options} opt + * @returns {ReturnType} + */ + spawnHeroku(args, opt) { + const varargs = opt ? [opt] : []; + return this.printChildOutput(this.spawn('heroku', args, ...varargs)); + } + + /** + * @template {{stdout: any; stderr: any} = ReturnType} T + * @param {T} child + * @param {(chunk: any) => void} child + * @returns {T} + */ printChildOutput(child, log = data => this.log.verboseInfo(data)) { const { stdout, stderr } = child; stdout.on('data', data => { From dc64514702938bf15bb43f4001b89d6f6379eb60 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 09:28:50 -0300 Subject: [PATCH 28/45] utilities adjusts and add login --- generators/heroku/generator.mjs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index d22fd56fee54..70ffeac0723f 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -90,6 +90,15 @@ export default class HerokuGenerator extends BaseGenerator { } } }, + async heroku() { + if (!this.hasHerokuCli) return; + + const { exitCode } = await this.spawnHerokuCommand('whoami', { reject: false }); + if (exitCode !== 0) { + this.log.log(chalk.bold('Log in heroku to continue.')); + await this.spawnHerokuCommand('login --interactive', { stdio: 'inherit' }); + } + }, initializing() { this.log.log(chalk.bold('Heroku configuration is starting')); @@ -285,8 +294,8 @@ export default class HerokuGenerator extends BaseGenerator { } else { const { stdout } = await this.spawnHeroku(['create', ...regionParams]); // Extract from "Created random-app-name-1234... done" - this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); this.log.verboseInfo(stdout); + this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); // ensure that the git remote is the same as the appName await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); @@ -582,9 +591,13 @@ export default class HerokuGenerator extends BaseGenerator { * @param {import('execa').Options} opt * @returns {ReturnType} */ - spawnHerokuCommand(command, opt = {}) { + spawnHerokuCommand(command, opt) { const varargs = opt ? [opt] : []; - return this.printChildOutput(this.spawnCommand(`heroku ${command}`, ...varargs)); + const child = this.spawnCommand(`heroku ${command}`, ...varargs); + if (opt?.stdio !== 'pipe') { + return child; + } + return this.printChildOutput(child); } /** @@ -594,11 +607,15 @@ export default class HerokuGenerator extends BaseGenerator { */ spawnHeroku(args, opt) { const varargs = opt ? [opt] : []; - return this.printChildOutput(this.spawn('heroku', args, ...varargs)); + const child = this.spawn('heroku', args, ...varargs); + if (opt?.stdio !== 'pipe') { + return child; + } + return this.printChildOutput(); } /** - * @template {{stdout: any; stderr: any} = ReturnType} T + * @template {{stdout: any; stderr: any}} T * @param {T} child * @param {(chunk: any) => void} child * @returns {T} From 41ea9723bc8104f76af6468ac6b36ea24d2d55d9 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 09:30:29 -0300 Subject: [PATCH 29/45] fix spawnHerokuCommand --- .../heroku/__snapshots__/heroku.spec.mts.snap | 49 +++++++++++++++++++ generators/heroku/generator.mjs | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index cd3fae5e5162..1e31779b0a98 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -171,6 +171,13 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -382,6 +389,13 @@ exports[`generator - Heroku monolith application in the US calls should match sn "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -591,6 +605,13 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -823,6 +844,13 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -1034,6 +1062,13 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -1259,6 +1294,13 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawnCommand", "heroku plugins", @@ -1487,6 +1529,13 @@ exports[`generator - Heroku monolith application with existing app calls should "stdio": "pipe", }, ], + [ + "spawnCommand", + "heroku whoami", + { + "reject": false, + }, + ], [ "spawn", "heroku", diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 70ffeac0723f..e8b9146051e1 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -611,7 +611,7 @@ export default class HerokuGenerator extends BaseGenerator { if (opt?.stdio !== 'pipe') { return child; } - return this.printChildOutput(); + return this.printChildOutput(child); } /** From dc9f2117e9cf00fa3f8caee438d26fd8fa8edcaf Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 09:33:16 -0300 Subject: [PATCH 30/45] use spread operator --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index e8b9146051e1..f327d2a89ccf 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -362,7 +362,7 @@ export default class HerokuGenerator extends BaseGenerator { const { stdout, stderr } = await this.spawn( 'heroku', - ['addons:create', cacheAddOn[0], cacheAddOn[1], cacheAddOn[2], '--app', this.herokuAppName], + ['addons:create', ...cacheAddOn, '--app', this.herokuAppName], { reject: false, stdio: 'pipe', From 5c3f2f53df83ba943839ba02ec626cab1f7fab40 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 10:10:07 -0300 Subject: [PATCH 31/45] fix prettier --- generators/heroku/generator.mjs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index f327d2a89ccf..019c8c324ab2 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -90,7 +90,7 @@ export default class HerokuGenerator extends BaseGenerator { } } }, - async heroku() { + async herokuLogin() { if (!this.hasHerokuCli) return; const { exitCode } = await this.spawnHerokuCommand('whoami', { reject: false }); @@ -360,14 +360,10 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - const { stdout, stderr } = await this.spawn( - 'heroku', - ['addons:create', ...cacheAddOn, '--app', this.herokuAppName], - { - reject: false, - stdio: 'pipe', - }, - ); + const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName], { + reject: false, + stdio: 'pipe', + }); this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); } }, @@ -463,7 +459,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nBuilding application')); // Use npm script so blueprints just need to override it. - await this.spawnCommand('npm run java:jar:prod', { stdio: 'inherit' }); + await this.printChildOutput(this.spawnCommand('npm run java:jar:prod')); }, async productionDeploy({ application }) { From 9a45e58b9df04ce1ed301e4dab9151f280b6be1c Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 15:45:02 -0300 Subject: [PATCH 32/45] set spawn defaults for heroku commnads --- .../heroku/__snapshots__/heroku.spec.mts.snap | 52 +++++------- generators/heroku/generator.mjs | 82 ++++++++----------- 2 files changed, 56 insertions(+), 78 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index 1e31779b0a98..d6236d35d8b1 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -176,6 +176,7 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -211,10 +212,6 @@ exports[`generator - Heroku monolith application in the EU calls should match sn "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; @@ -394,6 +391,7 @@ exports[`generator - Heroku monolith application in the US calls should match sn "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -427,10 +425,6 @@ exports[`generator - Heroku monolith application in the US calls should match sn "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; @@ -610,6 +604,7 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -643,10 +638,6 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], [ "spawn", @@ -657,6 +648,10 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "--app", "jhipster-test", ], + { + "reject": false, + "stdio": "pipe", + }, ], [ "spawn", @@ -669,6 +664,7 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul ], { "reject": false, + "stdio": "pipe", }, ], ] @@ -849,6 +845,7 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -884,10 +881,6 @@ exports[`generator - Heroku monolith application with PostgreSQL calls should ma "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; @@ -1067,6 +1060,7 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -1095,6 +1089,10 @@ exports[`generator - Heroku monolith application with an unavailable app name ca [ "create", ], + { + "reject": false, + "stdio": "pipe", + }, ], [ "spawn", @@ -1104,6 +1102,10 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "--app", "", ], + { + "reject": false, + "stdio": "pipe", + }, ], [ "spawn", @@ -1116,10 +1118,6 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "--app", "", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; @@ -1299,6 +1297,7 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -1332,10 +1331,6 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], [ "spawn", @@ -1348,10 +1343,6 @@ exports[`generator - Heroku monolith application with elasticsearch calls should "--app", "jhipster-test", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; @@ -1534,6 +1525,7 @@ exports[`generator - Heroku monolith application with existing app calls should "heroku whoami", { "reject": false, + "stdio": "pipe", }, ], [ @@ -1568,10 +1560,6 @@ exports[`generator - Heroku monolith application with existing app calls should "--app", "jhipster-existing", ], - { - "reject": false, - "stdio": "pipe", - }, ], ] `; diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 019c8c324ab2..2f2d6f6d024c 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -77,7 +77,7 @@ export default class HerokuGenerator extends BaseGenerator { get initializing() { return this.asInitializingTaskGroup({ async checkInstallation() { - const { exitCode } = await this.spawnHerokuCommand('--version', { reject: false, stdio: 'pipe' }); + const { exitCode } = await this.spawnHerokuCommand('--version', { verboseInfo: false }); this.hasHerokuCli = exitCode === 0; if (!this.hasHerokuCli) { const error = @@ -93,10 +93,10 @@ export default class HerokuGenerator extends BaseGenerator { async herokuLogin() { if (!this.hasHerokuCli) return; - const { exitCode } = await this.spawnHerokuCommand('whoami', { reject: false }); + const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false }); if (exitCode !== 0) { this.log.log(chalk.bold('Log in heroku to continue.')); - await this.spawnHerokuCommand('login --interactive', { stdio: 'inherit' }); + await this.spawnHerokuCommand('login --interactive', { reject: true, stdio: 'inherit' }); } }, @@ -116,10 +116,7 @@ export default class HerokuGenerator extends BaseGenerator { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { - const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { - reject: false, - stdio: 'pipe', - }); + const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName]); if (exitCode !== 0) { this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); throw new Error('Run the generator again to create a new application.'); @@ -235,17 +232,16 @@ export default class HerokuGenerator extends BaseGenerator { const cliPlugin = 'heroku-cli-deploy'; - const { stdout, stderr, exitCode } = await this.spawnHerokuCommand('plugins', { reject: false, stdio: 'pipe' }); + 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 { stdout, exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`, { reject: false, stdio: 'pipe' }); + const { exitCode } = await this.spawnHerokuCommand(`plugins:install ${cliPlugin}`); if (exitCode !== 0) { throw new Error(stderr); } - this.log.verboseInfo(stdout); } } }, @@ -256,10 +252,7 @@ export default class HerokuGenerator extends BaseGenerator { 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], { - reject: false, - stdio: 'pipe', - }); + 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 login to your heroku account and try again."); @@ -286,15 +279,13 @@ export default class HerokuGenerator extends BaseGenerator { }, ]; - this.log.verboseInfo(''); + this.log.log(''); const props = await this.prompt(prompts); if (props.herokuForceName === 'Yes') { - const { stdout } = await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); - this.log.verboseInfo(stdout); + await this.spawnHeroku(['git:remote', '--app', this.herokuAppName], { reject: true }); } else { const { stdout } = await this.spawnHeroku(['create', ...regionParams]); // Extract from "Created random-app-name-1234... done" - this.log.verboseInfo(stdout); this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); // ensure that the git remote is the same as the appName @@ -315,14 +306,14 @@ export default class HerokuGenerator extends BaseGenerator { 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], - { - reject: false, - stdio: 'pipe', - }, - ); + const { stdout, stderr } = await this.spawn('heroku', [ + 'addons:create', + 'bonsai:sandbox-6', + '--as', + 'BONSAI', + '--app', + this.herokuAppName, + ]); this.checkAddOnReturn({ addOn: 'Elasticsearch', stdout, stderr }); } @@ -337,14 +328,14 @@ export default class HerokuGenerator extends BaseGenerator { 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], - { - reject: false, - stdio: 'pipe', - }, - ); + 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.`)); @@ -360,10 +351,7 @@ export default class HerokuGenerator extends BaseGenerator { if (cacheAddOn) { this.log.log(chalk.bold(`\nProvisioning cache addon '${cacheAddOn}'`)); - const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName], { - reject: false, - stdio: 'pipe', - }); + const { stdout, stderr } = await this.spawn('heroku', ['addons:create', ...cacheAddOn, '--app', this.herokuAppName]); this.checkAddOnReturn({ addOn: 'Cache', stdout, stderr }); } }, @@ -371,7 +359,7 @@ export default class HerokuGenerator extends BaseGenerator { async configureJHipsterRegistry({ application }) { if (!this.hasHerokuCli || this.herokuAppExists || !application.serviceDiscoveryEureka) return undefined; - this.log.verboseInfo(''); + this.log.log(''); const answers = await this.prompt([ { type: 'input', @@ -488,7 +476,7 @@ export default class HerokuGenerator extends BaseGenerator { await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); // todo: check if buildpack already exists - const { stdout: data } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName], { reject: false }); + const { stdout: data } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); if (data) { this.log.info(data); if (data.includes('Run `heroku addons` for more info.')) { @@ -588,9 +576,10 @@ export default class HerokuGenerator extends BaseGenerator { * @returns {ReturnType} */ spawnHerokuCommand(command, opt) { - const varargs = opt ? [opt] : []; - const child = this.spawnCommand(`heroku ${command}`, ...varargs); - if (opt?.stdio !== 'pipe') { + 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); @@ -602,9 +591,10 @@ export default class HerokuGenerator extends BaseGenerator { * @returns {ReturnType} */ spawnHeroku(args, opt) { - const varargs = opt ? [opt] : []; - const child = this.spawn('heroku', args, ...varargs); - if (opt?.stdio !== 'pipe') { + 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); From 6eb01662abca78ff38b2b8acc387d5b0a46f6fbd Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 18:27:01 -0300 Subject: [PATCH 33/45] adjust test for unavailable app name --- generators/heroku/__snapshots__/heroku.spec.mts.snap | 8 ++++---- generators/heroku/heroku.spec.mts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index d6236d35d8b1..ff5c9add50ea 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -1100,7 +1100,7 @@ exports[`generator - Heroku monolith application with an unavailable app name ca [ "git:remote", "--app", - "", + "random-app-name", ], { "reject": false, @@ -1116,7 +1116,7 @@ exports[`generator - Heroku monolith application with an unavailable app name ca "--as", "DATABASE", "--app", - "", + "random-app-name", ], ], ] @@ -1129,7 +1129,7 @@ exports[`generator - Heroku monolith application with an unavailable app name sh "generator-jhipster": { "baseName": "jhipster", "entities": [], - "herokuAppName": "", + "herokuAppName": "random-app-name", "herokuDeployType": "jar", "herokuJavaVersion": "11" } @@ -1250,7 +1250,7 @@ exports[`generator - Heroku monolith application with an unavailable app name sh eureka: instance: - hostname: .herokuapp.com + hostname: random-app-name.herokuapp.com non-secure-port: 80 prefer-ip-address: false diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 01ef9b2a3384..8f8356c0dde1 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -70,13 +70,13 @@ describe('generator - Heroku', () => { describe('monolith application', () => { describe('with an unavailable app name', () => { - const autogeneratedAppName = ''; + const autogeneratedAppName = 'random-app-name'; beforeEach(async () => { stub - .withArgs('spawn', 'heroku', ['create', herokuAppName]) + .withArgs('spawn', 'heroku', sinon.match(['create', herokuAppName])) .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); stub - .withArgs('spawn', 'heroku', ['git:remote', '--app', autogeneratedAppName]) + .withArgs('spawn', 'heroku', sinon.match(['create'])) .returns(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); await helpers @@ -98,7 +98,7 @@ describe('generator - Heroku', () => { }); it('creates expected monolith files', () => { runResult.assertFile(expectedFiles.monolith); - runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${autogeneratedAppName}"`); + runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { herokuAppName: autogeneratedAppName } }); }); it('calls should match snapshot', () => { expect(runResult.getSpawnArgsUsingDefaultImplementation()).toMatchSnapshot(); From 2b67302983ff44074d134547b446ce3c8f7c9a8e Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Fri, 27 Oct 2023 20:03:09 -0300 Subject: [PATCH 34/45] wait for the application to be provisioned --- generators/heroku/generator.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 2f2d6f6d024c..ac506da923ed 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -17,6 +17,7 @@ * limitations under the License. */ /* eslint-disable consistent-return */ +import { setTimeout } from 'node:timers/promises'; import { kebabCase } from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; @@ -291,6 +292,11 @@ export default class HerokuGenerator extends BaseGenerator { // ensure that the git remote is the same as the appName await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); this.jhipsterConfig.herokuAppName = this.herokuAppName; + + if (!this.skipChecks) { + this.log.info('Waiting a few seconds for the application to be provisioned'); + await setTimeout(3000); + } } } else if (stderr.includes('Invalid credentials')) { this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); From 19ef8abf28be4d233f254e9607afb9eb048c1208 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Fri, 27 Oct 2023 23:45:07 -0600 Subject: [PATCH 35/45] Dyno size is Basic by default, not Free --- generators/heroku/templates/Procfile.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/templates/Procfile.ejs b/generators/heroku/templates/Procfile.ejs index 24ae0b46e23b..113dafba817d 100644 --- a/generators/heroku/templates/Procfile.ejs +++ b/generators/heroku/templates/Procfile.ejs @@ -16,4 +16,4 @@ See the License for the specific language governing permissions and limitations under the License. -%> -web: java $JAVA_OPTS <% if (applicationTypeGateway || dynoSize === 'Free') { %>-Xmx256m<% } %> -jar <% if (buildToolMaven) { %>target<% } %><% if (buildToolGradle) { %>build/libs<% } %>/*.jar --spring.profiles.active=prod,heroku<% if (databaseTypeMongodb) { %> --spring.data.mongodb.database=$(echo "$MONGODB_URI" | sed "s/^.*:[0-9]*\///g")<% } %> +web: java $JAVA_OPTS <% if (applicationTypeGateway || dynoSize === 'Basic') { %>-Xmx256m<% } %> -jar <% if (buildToolMaven) { %>target<% } %><% if (buildToolGradle) { %>build/libs<% } %>/*.jar --spring.profiles.active=prod,heroku<% if (databaseTypeMongodb) { %> --spring.data.mongodb.database=$(echo "$MONGODB_URI" | sed "s/^.*:[0-9]*\///g")<% } %> From 95247b7809d4c75d6404cc65d5175216b996190f Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Fri, 27 Oct 2023 23:54:34 -0600 Subject: [PATCH 36/45] Fix random app name + check for config and buildpacks before setting --- .../heroku/__snapshots__/heroku.spec.mts.snap | 55 +++++++++----- generators/heroku/generator.mjs | 74 +++++-------------- generators/heroku/heroku.spec.mts | 2 +- 3 files changed, 54 insertions(+), 77 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index ff5c9add50ea..faf0b47e7ba6 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -17,7 +17,7 @@ exports[`generator - Heroku microservice application with JAR deployment should "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -232,7 +232,7 @@ exports[`generator - Heroku monolith application in the EU should match files sn "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -445,7 +445,7 @@ exports[`generator - Heroku monolith application in the US should match files sn "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -639,6 +639,20 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "jhipster-test", ], ], + [ + "spawn", + "heroku", + [ + "config:get", + "MAVEN_CUSTOM_OPTS", + "--app", + "jhipster-test", + ], + { + "reject": false, + "stdio": "pipe", + }, + ], [ "spawn", "heroku", @@ -653,6 +667,19 @@ exports[`generator - Heroku monolith application with Git deployment calls shoul "stdio": "pipe", }, ], + [ + "spawn", + "heroku", + [ + "buildpacks", + "--app", + "jhipster-test", + ], + { + "reject": false, + "stdio": "pipe", + }, + ], [ "spawn", "heroku", @@ -686,7 +713,7 @@ exports[`generator - Heroku monolith application with Git deployment should matc "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -901,7 +928,7 @@ exports[`generator - Heroku monolith application with PostgreSQL should match fi "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1138,7 +1165,7 @@ exports[`generator - Heroku monolith application with an unavailable app name sh "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1364,7 +1391,7 @@ exports[`generator - Heroku monolith application with elasticsearch should match "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1549,18 +1576,6 @@ exports[`generator - Heroku monolith application with existing app calls should "stdio": "pipe", }, ], - [ - "spawn", - "heroku", - [ - "addons:create", - "heroku-postgresql", - "--as", - "DATABASE", - "--app", - "jhipster-existing", - ], - ], ] `; @@ -1580,7 +1595,7 @@ exports[`generator - Heroku monolith application with existing app should match "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index ac506da923ed..f7212918e2d8 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -17,7 +17,6 @@ * limitations under the License. */ /* eslint-disable consistent-return */ -import { setTimeout } from 'node:timers/promises'; import { kebabCase } from 'lodash-es'; import chalk from 'chalk'; import { glob } from 'glob'; @@ -96,8 +95,8 @@ export default class HerokuGenerator extends BaseGenerator { const { exitCode } = await this.spawnHerokuCommand('whoami', { verboseInfo: false }); if (exitCode !== 0) { - this.log.log(chalk.bold('Log in heroku to continue.')); - await this.spawnHerokuCommand('login --interactive', { reject: true, stdio: 'inherit' }); + this.log.log(chalk.bold('Log in to Heroku to continue.')); + await this.spawnHerokuCommand('login', { reject: true, stdio: 'inherit' }); } }, @@ -117,9 +116,11 @@ export default class HerokuGenerator extends BaseGenerator { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { + // todo: suppress output so JSON isn't printed to console const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName]); 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); @@ -149,7 +150,7 @@ export default class HerokuGenerator extends BaseGenerator { { type: 'list', name: 'herokuRegion', - message: 'On which region do you want to deploy ?', + message: 'On which region do you want to deploy?', choices: ['us', 'eu'], default: 0, }, @@ -163,7 +164,7 @@ export default class HerokuGenerator extends BaseGenerator { { type: 'list', name: 'herokuDeployType', - message: 'Which type of deployment do you want ?', + message: 'Which type of deployment do you want?', choices: [ { value: 'git', name: 'Git (compile on Heroku)' }, { value: 'jar', name: 'JAR (compile locally)' }, @@ -181,7 +182,7 @@ export default class HerokuGenerator extends BaseGenerator { { type: 'list', name: 'herokuJavaVersion', - message: 'Which Java version would you like to use to build and run your app ?', + 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, }, @@ -256,7 +257,7 @@ export default class HerokuGenerator extends BaseGenerator { 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 login to your heroku account and try again."); + throw new Error("Error: Not authenticated. Run 'heroku login' to login to your Heroku account and try again."); } if (exitCode !== 0) { @@ -287,16 +288,10 @@ export default class HerokuGenerator extends BaseGenerator { } else { const { stdout } = await this.spawnHeroku(['create', ...regionParams]); // Extract from "Created random-app-name-1234... done" - this.herokuAppName = stdout.substring(stdout.indexOf('https://') + 8, stdout.indexOf('.herokuapp')); - + this.herokuAppName = stdout.substring(stdout.lastIndexOf('/') + 1, stdout.indexOf('.git')); // ensure that the git remote is the same as the appName await this.spawnHeroku(['git:remote', '--app', this.herokuAppName]); this.jhipsterConfig.herokuAppName = this.herokuAppName; - - if (!this.skipChecks) { - this.log.info('Waiting a few seconds for the application to be provisioned'); - await setTimeout(3000); - } } } else if (stderr.includes('Invalid credentials')) { this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); @@ -307,7 +302,7 @@ export default class HerokuGenerator extends BaseGenerator { }, async herokuAddonsCreate({ application }) { - if (!this.hasHerokuCli) return; + if (!this.hasHerokuCli || this.herokuAppExists) return; this.log.log(chalk.bold('\nProvisioning addons')); if (application.searchEngineElasticsearch) { @@ -478,47 +473,14 @@ export default class HerokuGenerator extends BaseGenerator { } this.log.log(chalk.bold('\nConfiguring Heroku')); - // todo: check if config already exists - await this.spawnHeroku(['config:set', `${configName}=${configValues}`, '--app', this.herokuAppName]); - - // todo: check if buildpack already exists - const { stdout: data } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); - if (data) { - this.log.info(data); - if (data.includes('Run `heroku addons` for more info.')) { - await this.spawnHerokuCommand('addons'); - } - - this.log(''); - const prompts = [ - { - type: 'list', - name: 'userDeployDecision', - message: 'Continue to deploy?', - choices: [ - { - value: 'Yes', - name: 'Yes, I confirm', - }, - { - value: 'No', - name: 'No, abort (Recommended)', - }, - ], - default: 0, - }, - ]; - - this.log(''); - const props = await this.prompt(prompts); - if (props.userDeployDecision === 'Yes') { - this.log.info(chalk.bold('Continuing deployment...')); - } else { - this.log.info(chalk.bold('You aborted deployment!')); - this.cancelCancellableTasks(); - return; - } - this.log(''); + 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)) { + const { stdout: data, stderr: error } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); } this.log.log(chalk.bold('\nDeploying application...')); diff --git a/generators/heroku/heroku.spec.mts b/generators/heroku/heroku.spec.mts index 8f8356c0dde1..a9fe1e688928 100644 --- a/generators/heroku/heroku.spec.mts +++ b/generators/heroku/heroku.spec.mts @@ -77,7 +77,7 @@ describe('generator - Heroku', () => { .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` })); stub .withArgs('spawn', 'heroku', sinon.match(['create'])) - .returns(createSpawnCommandReturn({ stdout: `https://${autogeneratedAppName}.herokuapp.com` })); + .returns(createSpawnCommandReturn({ stdout: `https://git.heroku.com/${autogeneratedAppName}.git` })); await helpers .createJHipster(GENERATOR_HEROKU) From 32804d4ccb910b8c18083e73d1a5073ac781f95c Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 00:05:41 -0600 Subject: [PATCH 37/45] Remove unused variables --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index f7212918e2d8..672205871e11 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -480,7 +480,7 @@ export default class HerokuGenerator extends BaseGenerator { const { stdout: buildpackData } = await this.spawnHeroku(['buildpacks', '--app', this.herokuAppName]); if (!buildpackData.includes(buildpack)) { - const { stdout: data, stderr: error } = await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); + await this.spawnHeroku(['buildpacks:add', buildpack, '--app', this.herokuAppName]); } this.log.log(chalk.bold('\nDeploying application...')); From c368971aeb7e731c1cd1d9e2051710fbf370b654 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 00:13:23 -0600 Subject: [PATCH 38/45] Default to Basic dyno and fix spelling --- .../heroku/__snapshots__/heroku.spec.mts.snap | 16 ++++++++-------- generators/heroku/generator.mjs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/generators/heroku/__snapshots__/heroku.spec.mts.snap b/generators/heroku/__snapshots__/heroku.spec.mts.snap index faf0b47e7ba6..da6a3bdcc3ea 100644 --- a/generators/heroku/__snapshots__/heroku.spec.mts.snap +++ b/generators/heroku/__snapshots__/heroku.spec.mts.snap @@ -17,7 +17,7 @@ exports[`generator - Heroku microservice application with JAR deployment should "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -232,7 +232,7 @@ exports[`generator - Heroku monolith application in the EU should match files sn "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -445,7 +445,7 @@ exports[`generator - Heroku monolith application in the US should match files sn "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -713,7 +713,7 @@ exports[`generator - Heroku monolith application with Git deployment should matc "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -928,7 +928,7 @@ exports[`generator - Heroku monolith application with PostgreSQL should match fi "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1165,7 +1165,7 @@ exports[`generator - Heroku monolith application with an unavailable app name sh "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1391,7 +1391,7 @@ exports[`generator - Heroku monolith application with elasticsearch should match "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, @@ -1595,7 +1595,7 @@ exports[`generator - Heroku monolith application with existing app should match "stateCleared": "modified", }, "Procfile": { - "contents": "web: java $JAVA_OPTS -jar target/*.jar --spring.profiles.active=prod,heroku + "contents": "web: java $JAVA_OPTS -Xmx256m -jar target/*.jar --spring.profiles.active=prod,heroku ", "stateCleared": "modified", }, diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 672205871e11..e1b132550e5e 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -102,7 +102,7 @@ export default class HerokuGenerator extends BaseGenerator { initializing() { this.log.log(chalk.bold('Heroku configuration is starting')); - this.dynoSize = 'Free'; + this.dynoSize = 'Basic'; this.herokuAppExists = Boolean(this.jhipsterConfig.herokuAppName); }, }); @@ -257,7 +257,7 @@ export default class HerokuGenerator extends BaseGenerator { 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 login to your Heroku account and try again."); + throw new Error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); } if (exitCode !== 0) { @@ -294,7 +294,7 @@ export default class HerokuGenerator extends BaseGenerator { this.jhipsterConfig.herokuAppName = this.herokuAppName; } } else if (stderr.includes('Invalid credentials')) { - this.log.error("Error: Not authenticated. Run 'heroku login' to login to your heroku account and try again."); + this.log.error("Error: Not authenticated. Run 'heroku login' to log in to your Heroku account and try again."); } else { throw new Error(stderr); } From e5a090c93dfb62e46751e41e702d082eb3476470 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Sat, 28 Oct 2023 07:14:50 -0300 Subject: [PATCH 39/45] drop outputHandler for git init --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index e1b132550e5e..b9d3a4efe418 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -220,7 +220,7 @@ export default class HerokuGenerator extends BaseGenerator { async gitInit() { if (!this.herokuDeployType === 'git') return; - const git = this.createGit().outputHandler((_command, stdout, stderr) => this.printChildOutput({ stdout, stderr })); + const git = this.createGit(); if (await git.checkIsRepo()) { this.log.log(chalk.bold('\nUsing existing Git repository')); } else { From 5646530e441492fc572c1c1fa7bb766f8567df1c Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Sat, 28 Oct 2023 10:03:38 -0300 Subject: [PATCH 40/45] Update generators/heroku/generator.mjs --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index b9d3a4efe418..e34e10a32bcd 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -117,7 +117,7 @@ export default class HerokuGenerator extends BaseGenerator { async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { // todo: suppress output so JSON isn't printed to console - const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName]); + 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; From 2a7e9c37eaa8e1a714e0a685d0c74419587c4f9f Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Sat, 28 Oct 2023 13:57:27 -0300 Subject: [PATCH 41/45] Update generator.mjs --- generators/heroku/generator.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index e34e10a32bcd..3955d8f92a90 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -117,7 +117,9 @@ export default class HerokuGenerator extends BaseGenerator { async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { // todo: suppress output so JSON isn't printed to console - const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { verboseInfo:false }); + 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; From 03f47d67ce23696f5a2a180b5db4de4143ff6fc7 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 13:47:26 -0600 Subject: [PATCH 42/45] Ignore blank lines Co-authored-by: Marcelo Shima --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 3955d8f92a90..0c4236ffe3c5 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -579,7 +579,7 @@ export default class HerokuGenerator extends BaseGenerator { printChildOutput(child, log = data => this.log.verboseInfo(data)) { const { stdout, stderr } = child; stdout.on('data', data => { - data.toString().split(/\r?\n/).forEach(log); + data.toString().split(/\r?\n/).filter(Boolean).forEach(log); }); stderr.on('data', data => { data.toString().split(/\r?\n/).forEach(log); From 681e40170a3f0bc14f430607ca63739ebdb14525 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 13:52:17 -0600 Subject: [PATCH 43/45] Ignore blank lines Co-authored-by: Marcelo Shima --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 0c4236ffe3c5..53f8b566b6cf 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -582,7 +582,7 @@ export default class HerokuGenerator extends BaseGenerator { data.toString().split(/\r?\n/).filter(Boolean).forEach(log); }); stderr.on('data', data => { - data.toString().split(/\r?\n/).forEach(log); + data.toString().split(/\r?\n/).filter(Boolean).forEach(log); }); return child; } From ebcf3cced43d4f7871b3278058200309ce57238e Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 15:13:31 -0600 Subject: [PATCH 44/45] Prettier --- generators/heroku/generator.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 53f8b566b6cf..297a9d3d3576 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -116,9 +116,8 @@ export default class HerokuGenerator extends BaseGenerator { return this.asPromptingTaskGroup({ async askForApp() { if (this.hasHerokuCli && this.herokuAppExists) { - // todo: suppress output so JSON isn't printed to console const { stdout, exitCode } = await this.spawnHeroku(['apps:info', '--json', this.jhipsterConfig.herokuAppName], { - verboseInfo:false, + verboseInfo: false, }); if (exitCode !== 0) { this.log.error(`Could not find application: ${chalk.cyan(this.jhipsterConfig.herokuAppName)}`); From 601ebf3205357b54ee9d728cf5d5af9c6a8fd8df Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 28 Oct 2023 15:40:16 -0600 Subject: [PATCH 45/45] Fix JAR deploy --- generators/heroku/generator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/heroku/generator.mjs b/generators/heroku/generator.mjs index 297a9d3d3576..168bf3c8e7e2 100644 --- a/generators/heroku/generator.mjs +++ b/generators/heroku/generator.mjs @@ -449,7 +449,7 @@ export default class HerokuGenerator extends BaseGenerator { this.log.log(chalk.bold('\nBuilding application')); // Use npm script so blueprints just need to override it. - await this.printChildOutput(this.spawnCommand('npm run java:jar:prod')); + await this.printChildOutput(this.spawnCommand('npm run java:jar:prod', { stdio: 'pipe' })); }, async productionDeploy({ application }) {