potassium 6.0.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +103 -38
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.gitignore +2 -1
  5. data/.rubocop.yml +530 -0
  6. data/CHANGELOG.md +57 -0
  7. data/README.md +51 -45
  8. data/lib/potassium/assets/.circleci/config.yml.erb +83 -34
  9. data/lib/potassium/assets/.eslintrc.json +13 -4
  10. data/lib/potassium/assets/.github/pull_request_template.md +9 -0
  11. data/lib/potassium/assets/.rubocop.yml +13 -0
  12. data/lib/potassium/assets/README.yml +7 -7
  13. data/lib/potassium/assets/app/graphql/graphql_controller.rb +55 -0
  14. data/lib/potassium/assets/app/graphql/mutations/login_mutation.rb +23 -0
  15. data/lib/potassium/assets/app/graphql/queries/base_query.rb +4 -0
  16. data/lib/potassium/assets/app/graphql/types/base/base_argument.rb +4 -0
  17. data/lib/potassium/assets/app/graphql/types/base/base_enum.rb +4 -0
  18. data/lib/potassium/assets/app/graphql/types/base/base_field.rb +5 -0
  19. data/lib/potassium/assets/app/graphql/types/base/base_input_object.rb +5 -0
  20. data/lib/potassium/assets/app/graphql/types/base/base_interface.rb +7 -0
  21. data/lib/potassium/assets/app/graphql/types/base/base_object.rb +5 -0
  22. data/lib/potassium/assets/app/graphql/types/base/base_scalar.rb +4 -0
  23. data/lib/potassium/assets/app/graphql/types/base/base_union.rb +4 -0
  24. data/lib/potassium/assets/app/graphql/types/mutation_type.rb +10 -0
  25. data/lib/potassium/assets/app/graphql/types/query_type.rb +13 -0
  26. data/lib/potassium/assets/app/javascript/app.spec.js +1 -1
  27. data/lib/potassium/assets/app/uploaders/base_uploader.rb +1 -3
  28. data/lib/potassium/assets/app/views/shared/_gtm_body.html.erb +4 -0
  29. data/lib/potassium/assets/app/views/shared/_gtm_head.html.erb +7 -0
  30. data/lib/potassium/assets/config/graphql_playground.rb +20 -0
  31. data/lib/potassium/assets/config/puma.rb +1 -1
  32. data/lib/potassium/assets/config/shrine.rb +4 -1
  33. data/lib/potassium/assets/redis.yml +1 -2
  34. data/lib/potassium/assets/testing/rails_helper.rb +2 -0
  35. data/lib/potassium/cli/commands/create.rb +11 -19
  36. data/lib/potassium/cli_options.rb +70 -10
  37. data/lib/potassium/helpers/template-helpers.rb +4 -0
  38. data/lib/potassium/newest_version_ensurer.rb +19 -36
  39. data/lib/potassium/node_version_ensurer.rb +30 -0
  40. data/lib/potassium/recipes/admin.rb +26 -16
  41. data/lib/potassium/recipes/api.rb +92 -27
  42. data/lib/potassium/recipes/background_processor.rb +62 -18
  43. data/lib/potassium/recipes/ci.rb +9 -39
  44. data/lib/potassium/recipes/database.rb +4 -0
  45. data/lib/potassium/recipes/draper.rb +0 -9
  46. data/lib/potassium/recipes/file_storage.rb +2 -1
  47. data/lib/potassium/recipes/front_end.rb +84 -9
  48. data/lib/potassium/recipes/github.rb +93 -15
  49. data/lib/potassium/recipes/google_tag_manager.rb +94 -0
  50. data/lib/potassium/recipes/heroku.rb +42 -29
  51. data/lib/potassium/recipes/mailer.rb +18 -5
  52. data/lib/potassium/recipes/monitoring.rb +5 -0
  53. data/lib/potassium/recipes/schedule.rb +16 -1
  54. data/lib/potassium/recipes/style.rb +2 -2
  55. data/lib/potassium/templates/application.rb +5 -2
  56. data/lib/potassium/version.rb +5 -2
  57. data/potassium.gemspec +5 -2
  58. data/spec/features/api_spec.rb +25 -0
  59. data/spec/features/background_processor_spec.rb +19 -6
  60. data/spec/features/ci_spec.rb +7 -4
  61. data/spec/features/draper_spec.rb +1 -6
  62. data/spec/features/file_storage_spec.rb +5 -0
  63. data/spec/features/front_end_spec.rb +32 -1
  64. data/spec/features/github_spec.rb +53 -8
  65. data/spec/features/google_tag_manager_spec.rb +36 -0
  66. data/spec/features/graphql_spec.rb +71 -0
  67. data/spec/features/mailer_spec.rb +16 -0
  68. data/spec/features/schedule_spec.rb +11 -4
  69. data/spec/spec_helper.rb +1 -0
  70. data/spec/support/fake_octokit.rb +31 -0
  71. data/spec/support/potassium_test_helpers.rb +0 -1
  72. data/tmp/.keep +0 -0
  73. metadata +80 -15
  74. data/lib/potassium/assets/Dockerfile.ci +0 -6
  75. data/lib/potassium/assets/api/api_error_concern.rb +0 -32
  76. data/lib/potassium/assets/api/base_controller.rb +0 -7
  77. data/lib/potassium/assets/api/draper_responder.rb +0 -62
  78. data/lib/potassium/assets/api/responder.rb +0 -41
  79. data/lib/potassium/assets/bin/cibuild.erb +0 -117
  80. data/lib/potassium/assets/docker-compose.ci.yml +0 -12
  81. data/lib/potassium/assets/sidekiq_scheduler.yml +0 -9
@@ -46,5 +46,9 @@ class Recipes::Database < Rails::AppBuilder
46
46
  else
47
47
  gather_gem db[:gem_name]
48
48
  end
49
+
50
+ after(:gem_install) do
51
+ generate 'strong_migrations:install'
52
+ end
49
53
  end
50
54
  end
@@ -7,7 +7,6 @@ class Recipes::Draper < Rails::AppBuilder
7
7
  def create
8
8
  return unless selected?(:draper)
9
9
  add_draper
10
- add_api_responder if selected?(:api_support)
11
10
  end
12
11
 
13
12
  def installed?
@@ -16,8 +15,6 @@ class Recipes::Draper < Rails::AppBuilder
16
15
 
17
16
  def install
18
17
  add_draper
19
- api_recipe = load_recipe(:api)
20
- add_api_responder if api_recipe.installed?
21
18
  end
22
19
 
23
20
  def add_draper
@@ -25,10 +22,4 @@ class Recipes::Draper < Rails::AppBuilder
25
22
  add_readme_section :internal_dependencies, :draper
26
23
  create_file 'app/decorators/.keep'
27
24
  end
28
-
29
- def add_api_responder
30
- after(:gem_install) do
31
- copy_file '../assets/api/draper_responder.rb', 'app/responders/api_responder.rb', force: true
32
- end
33
- end
34
25
  end
@@ -39,10 +39,11 @@ class Recipes::FileStorage < Rails::AppBuilder
39
39
 
40
40
  def add_shrine
41
41
  gather_gem('shrine', '~> 3.0')
42
- gather_gem('marcel', '~> 0.3.3')
42
+ gather_gem('marcel', '~> 1.0')
43
43
  copy_file('../assets/config/shrine.rb', 'config/initializers/shrine.rb', force: true)
44
44
  copy_file('../assets/app/uploaders/image_uploader.rb', 'app/uploaders/image_uploader.rb')
45
45
  copy_file('../assets/app/uploaders/base_uploader.rb', 'app/uploaders/base_uploader.rb')
46
+ append_to_file('.gitignore', "/public/uploads\n")
46
47
  end
47
48
 
48
49
  def common_setup
@@ -1,4 +1,6 @@
1
1
  class Recipes::FrontEnd < Rails::AppBuilder
2
+ VUE_LOADER_VERSION = Potassium::VUE_LOADER_VERSION
3
+
2
4
  def ask
3
5
  frameworks = {
4
6
  vue: "Vue",
@@ -15,18 +17,13 @@ class Recipes::FrontEnd < Rails::AppBuilder
15
17
  end
16
18
 
17
19
  def create
18
- return if [:none, :None].include? get(:front_end).to_sym
19
-
20
20
  recipe = self
21
21
  after(:gem_install) do
22
22
  value = get(:front_end)
23
23
  run "rails webpacker:install"
24
- run "rails webpacker:install:#{value}" if value
24
+ run "rails webpacker:install:#{value}" unless [:none, :None].include? value.to_sym
25
25
 
26
- if value == :vue
27
- recipe.setup_vue_with_compiler_build
28
- recipe.setup_jest
29
- end
26
+ recipe.setup_vue if value == :vue
30
27
  recipe.add_responsive_meta_tag
31
28
  recipe.setup_tailwind
32
29
  add_readme_header :webpack
@@ -66,7 +63,8 @@ class Recipes::FrontEnd < Rails::AppBuilder
66
63
  end
67
64
 
68
65
  def setup_tailwind
69
- run 'bin/yarn add tailwindcss'
66
+ run "bin/yarn add tailwindcss@#{Potassium::TAILWINDCSS}"
67
+ specify_autoprefixer_postcss_compatibility_versions
70
68
  setup_client_css
71
69
  remove_server_css_requires
72
70
  setup_tailwind_requirements
@@ -83,6 +81,40 @@ class Recipes::FrontEnd < Rails::AppBuilder
83
81
  copy_file '../assets/app/javascript/app.spec.js', 'app/javascript/app.spec.js'
84
82
  end
85
83
 
84
+ def setup_apollo
85
+ run 'bin/yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag'
86
+
87
+ inject_into_file(
88
+ 'app/javascript/packs/application.js',
89
+ apollo_imports,
90
+ after: "import App from '../app.vue';"
91
+ )
92
+
93
+ inject_into_file(
94
+ 'app/javascript/packs/application.js',
95
+ apollo_loading,
96
+ after: "import VueApollo from 'vue-apollo';"
97
+ )
98
+ inject_into_file(
99
+ 'app/javascript/packs/application.js',
100
+ "\n apolloProvider,",
101
+ after: "components: { App },"
102
+ )
103
+ end
104
+
105
+ def foce_vue_loader_version
106
+ run "bin/yarn add vue-loader@#{VUE_LOADER_VERSION}"
107
+ end
108
+
109
+ def setup_vue
110
+ foce_vue_loader_version
111
+ setup_vue_with_compiler_build
112
+ setup_jest
113
+ if get(:api) == :graphql
114
+ setup_apollo
115
+ end
116
+ end
117
+
86
118
  private
87
119
 
88
120
  def frameworks(framework)
@@ -94,6 +126,39 @@ class Recipes::FrontEnd < Rails::AppBuilder
94
126
  frameworks[framework]
95
127
  end
96
128
 
129
+ def apollo_imports
130
+ <<~JS
131
+ \n
132
+ import { ApolloClient } from 'apollo-client';
133
+ import { createHttpLink } from 'apollo-link-http';
134
+ import { InMemoryCache } from 'apollo-cache-inmemory';
135
+ import VueApollo from 'vue-apollo';
136
+ JS
137
+ end
138
+
139
+ def apollo_loading
140
+ <<~JS
141
+ \n
142
+ const httpLink = createHttpLink({
143
+ uri: `${window.location.origin}/graphql`,
144
+ })
145
+ const cache = new InMemoryCache()
146
+ const apolloClient = new ApolloClient({
147
+ link: httpLink,
148
+ cache,
149
+ })
150
+
151
+ Vue.use(VueApollo)
152
+ const apolloProvider = new VueApollo({
153
+ defaultClient: apolloClient,
154
+ })
155
+ JS
156
+ end
157
+
158
+ def specify_autoprefixer_postcss_compatibility_versions
159
+ run 'bin/yarn -D add postcss@^7 autoprefixer@^9'
160
+ end
161
+
97
162
  def setup_client_css
98
163
  application_css = 'app/javascript/css/application.css'
99
164
  create_file application_css, "", force: true
@@ -163,6 +228,15 @@ class Recipes::FrontEnd < Rails::AppBuilder
163
228
  },
164
229
  variants: {},
165
230
  plugins: [],
231
+ purge: {
232
+ enabled: process.env.NODE_ENV === 'production',
233
+ content: [
234
+ './app/**/*.html',
235
+ './app/**/*.vue',
236
+ './app/**/*.js',
237
+ './app/**/*.erb',
238
+ ],
239
+ }
166
240
  };
167
241
  JS
168
242
  end
@@ -202,7 +276,8 @@ class Recipes::FrontEnd < Rails::AppBuilder
202
276
  },
203
277
  "snapshotSerializers": [
204
278
  "<rootDir>/node_modules/jest-serializer-vue"
205
- ]
279
+ ],
280
+ "testEnvironment": "jsdom"
206
281
  }
207
282
  }
208
283
  end
@@ -1,29 +1,107 @@
1
+ require 'octokit'
2
+
1
3
  class Recipes::Github < Rails::AppBuilder
2
4
  def ask
3
- repo_name = "platanus/#{get(:dasherized_app_name)}"
4
5
  github_repo_create = answer(:github) do
5
- q = "Do you want to create the Github repository (https://github.com/#{repo_name}) " +
6
- "for this project?"
7
- Ask.confirm(q)
8
- end
9
- if github_repo_create
10
- github_repo_private = answer(:"github-private") do
11
- Ask.confirm("Should the repository be private?")
12
- end
6
+ Ask.confirm('Do you want to create a Github repository?')
13
7
  end
14
- set(:github_repo_name, repo_name)
15
8
  set(:github_repo, github_repo_create)
16
- set(:github_repo_private, github_repo_private)
9
+ setup_repo if github_repo_create
10
+ end
11
+
12
+ def setup_repo
13
+ setup_repo_private
14
+ setup_repo_org
15
+ setup_repo_name
16
+ set(:github_access_token, get_access_token)
17
17
  end
18
18
 
19
19
  def create
20
- github_repo_create(get(:github_repo_name), get(:github_repo_private)) if selected?(:github_repo)
20
+ return unless selected?(:github_repo)
21
+
22
+ create_github_repo
23
+ copy_file '../assets/.github/pull_request_template.md', '.github/pull_request_template.md'
21
24
  end
22
25
 
23
26
  private
24
27
 
25
- def github_repo_create(repo_name, private_repo = false)
26
- flag = private_repo ? "-p" : ""
27
- run "hub create #{flag} #{repo_name}"
28
+ def setup_repo_private
29
+ repo_private = answer(:github_private) do
30
+ Ask.confirm('Should the repository be private?')
31
+ end
32
+ set(:github_repo_private, repo_private)
33
+ end
34
+
35
+ def setup_repo_org
36
+ has_organization = answer(:github_has_org) do
37
+ Ask.confirm('Is this repo for a Github organization?')
38
+ end
39
+ set(:github_has_org, has_organization)
40
+ if has_organization
41
+ repo_organization = answer(:github_org) do
42
+ Ask.input('What is the organization for this repository?', default: 'platanus')
43
+ end
44
+ set(:github_org, repo_organization)
45
+ end
46
+ end
47
+
48
+ def setup_repo_name
49
+ repo_name = answer(:github_name) do
50
+ Ask.input('What is the name for this repository?', default: get(:dasherized_app_name))
51
+ end
52
+ set(:github_repo_name, repo_name)
53
+ end
54
+
55
+ def create_github_repo
56
+ options = { private: get(:github_repo_private) }
57
+ options[:organization] = get(:github_org) if get(:github_has_org)
58
+ repo_name = get(:github_repo_name)
59
+
60
+ is_retry = false
61
+ begin
62
+ github_client(is_retry).create_repository(repo_name, options)
63
+ rescue Octokit::Unauthorized
64
+ is_retry = true
65
+ retry if retry_create_repo
66
+ end
67
+ end
68
+
69
+ def retry_create_repo
70
+ puts "Bad credentials, information on Personal Access Tokens here:"
71
+ puts "https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token"
72
+ puts "Make sure to give repo access to the personal access token"
73
+ Ask.confirm("Do you want to retry?")
74
+ end
75
+
76
+ def github_client(is_retry = false)
77
+ access_token = is_retry ? set_access_token : get(:github_access_token)
78
+ octokit_client.new(access_token: access_token)
79
+ end
80
+
81
+ def octokit_client
82
+ if answer(:test)
83
+ require_relative '../../../spec/support/fake_octokit'
84
+ FakeOctokit
85
+ else
86
+ Octokit::Client
87
+ end
88
+ end
89
+
90
+ def get_access_token
91
+ return File.open(config_filename, 'r').read if File.exists?(config_filename)
92
+
93
+ set_access_token
94
+ end
95
+
96
+ def set_access_token
97
+ access_token = answer(:github_access_token) do
98
+ Ask.input('Enter a GitHub personal access token', password: true)
99
+ end
100
+ File.open(config_filename, 'w') { |f| f.write(access_token) }
101
+ access_token
102
+ end
103
+
104
+ def config_filename
105
+ @config_filename ||= File.expand_path('~/.potassium')
28
106
  end
29
107
  end
@@ -0,0 +1,94 @@
1
+ class Recipes::GoogleTagManager < Rails::AppBuilder
2
+ def ask
3
+ use_google_tag_manager = answer(:google_tag_manager) do
4
+ Ask.confirm 'Do you want to use Google Tag Manager?'
5
+ end
6
+
7
+ set(:google_tag_manager, use_google_tag_manager)
8
+ end
9
+
10
+ def create
11
+ install if selected?(:google_tag_manager)
12
+ end
13
+
14
+ def install
15
+ add_google_tag_manager
16
+ end
17
+
18
+ def add_google_tag_manager
19
+ copy_tag_manager_files
20
+ append_to_file '.env.development', "GTM_CONTAINER_ID=\n"
21
+ include_tag_manager
22
+ add_content_security_policy
23
+ end
24
+
25
+ def add_content_security_policy
26
+ inject_into_file(
27
+ 'config/initializers/content_security_policy.rb',
28
+ content_security_policy_code
29
+ )
30
+ end
31
+
32
+ def copy_tag_manager_files
33
+ copy_file(
34
+ '../assets/app/views/shared/_gtm_head.html.erb',
35
+ 'app/views/shared/_gtm_head.html.erb',
36
+ force: true
37
+ )
38
+
39
+ copy_file(
40
+ '../assets/app/views/shared/_gtm_body.html.erb',
41
+ 'app/views/shared/_gtm_body.html.erb',
42
+ force: true
43
+ )
44
+ end
45
+
46
+ def include_tag_manager
47
+ inject_into_file(
48
+ 'app/views/layouts/application.html.erb',
49
+ render_string('shared/gtm_head'),
50
+ before: '</head>'
51
+ )
52
+
53
+ inject_into_file(
54
+ 'app/views/layouts/application.html.erb',
55
+ render_string('shared/gtm_body'),
56
+ after: '<body>'
57
+ )
58
+ end
59
+
60
+ private
61
+
62
+ def render_string(file_path)
63
+ " <%if Rails.env.production? %>
64
+ <%= render \"#{file_path}\" %>
65
+ <% end %>\n "
66
+ end
67
+
68
+ def content_security_policy_code
69
+ <<~HERE
70
+ Rails.application.config.content_security_policy do |policy|
71
+ policy.connect_src(
72
+ :self,
73
+ :https,
74
+ 'http://localhost:3035',
75
+ 'ws://localhost:3035',
76
+ 'https://www.google-analytics.com'
77
+ )
78
+ # google tag manager requires to enable unsafe inline and vue unsave eval:
79
+ # https://developers.google.com/tag-manager/web/csp
80
+ # https://vuejs.org/v2/guide/installation.html#CSP-environments
81
+ policy.script_src(
82
+ :self,
83
+ :https,
84
+ :unsafe_inline,
85
+ :unsafe_eval,
86
+ 'https://www.googletagmanager.com',
87
+ 'https://www.google-analytics.com',
88
+ 'https://ssl.google-analytics.com'
89
+ )
90
+ policy.img_src :self, :https, 'https://www.googletagmanager.com', 'https://www.google-analytics.com'
91
+ end
92
+ HERE
93
+ end
94
+ end
@@ -1,13 +1,7 @@
1
1
  class Recipes::Heroku < Rails::AppBuilder
2
2
  NAME_PREFIX = 'pl'
3
-
4
- attr_accessor :app_name_staging
5
-
6
- def initialize(args)
7
- super(args)
8
- set(:heroku_app_name_staging, app_name_for('staging'))
9
- set(:heroku_app_name_production, app_name_for('production'))
10
- end
3
+ ENVIRONMENTS = ['staging', 'production']
4
+ HEROKU_NAMES_MAX_CHARS = 30
11
5
 
12
6
  def ask
13
7
  heroku = answer(:heroku) do
@@ -16,6 +10,8 @@ class Recipes::Heroku < Rails::AppBuilder
16
10
 
17
11
  if heroku
18
12
  set(:heroku, heroku)
13
+
14
+ ENVIRONMENTS.each { |environment| set_app_name_for(environment) }
19
15
  end
20
16
  end
21
17
 
@@ -47,31 +43,34 @@ class Recipes::Heroku < Rails::AppBuilder
47
43
  template "../assets/bin/setup_heroku.erb", "bin/setup_heroku", force: true
48
44
  run "chmod a+x bin/setup_heroku"
49
45
 
50
- if logged_in?
51
- %w(staging production).each do |environment|
52
- create_app_on_heroku(environment)
53
- end
54
- puts "Remember to connect the github repository to the new pipeline"
55
- open_pipeline_command = "\e[33mheroku pipelines:open #{heroku_pipeline_name}\e[0m"
56
- puts "run #{open_pipeline_command} to open the dashboard"
57
- else
58
- puts "You are not logged in into heroku"
59
- login_command = "\e[33mheroku login\e[0m"
60
- puts "Run #{login_command} and enter your credentials"
61
- puts "You can install the heroku recipe again create the app in heroku"
62
- install_command = "\e[33mpostassium install heroku --force\e[0m"
63
- puts "Just run #{install_command}"
64
- end
46
+ logged_in? ? create_apps : puts_not_logged_in_msg
65
47
 
66
48
  add_readme_header :deployment
67
49
  end
68
50
 
51
+ def create_apps
52
+ ENVIRONMENTS.each { |environment| create_app_on_heroku(environment) }
53
+ puts "Remember to connect the github repository to the new pipeline"
54
+ open_pipeline_command = "\e[33mheroku pipelines:open #{heroku_pipeline_name}\e[0m"
55
+ puts "run #{open_pipeline_command} to open the dashboard"
56
+ end
57
+
58
+ def puts_not_logged_in_msg
59
+ puts "You are not logged in into heroku"
60
+ login_command = "\e[33mheroku login\e[0m"
61
+ puts "Run #{login_command} and enter your credentials"
62
+ puts "You can install the heroku recipe again to create the app in heroku"
63
+ install_command = "\e[33mpostassium install heroku --force\e[0m"
64
+ puts "Just run #{install_command}"
65
+ end
66
+
69
67
  def heroku_pipeline_name
70
- app_name.dasherize
68
+ @heroku_pipeline_name ||= valid_heroku_name(app_name.dasherize, 'pipeline', false)
71
69
  end
72
70
 
73
- def app_name_for(environment)
74
- "#{NAME_PREFIX}-#{app_name.dasherize}-#{environment}"
71
+ def set_app_name_for(environment)
72
+ default_name = "#{NAME_PREFIX}-#{app_name.dasherize}-#{environment}"
73
+ set("heroku_app_name_#{environment}".to_sym, valid_heroku_name(default_name, environment))
75
74
  end
76
75
 
77
76
  def logged_in?
@@ -84,7 +83,7 @@ class Recipes::Heroku < Rails::AppBuilder
84
83
 
85
84
  def create_app_on_heroku(environment)
86
85
  rack_env = "RACK_ENV=production"
87
- staged_app_name = app_name_for(environment)
86
+ staged_app_name = get("heroku_app_name_#{environment}".to_sym)
88
87
 
89
88
  run_toolbelt_command "create #{staged_app_name} --remote #{environment}"
90
89
  run_toolbelt_command "labs:enable runtime-dyno-metadata", staged_app_name
@@ -99,14 +98,14 @@ class Recipes::Heroku < Rails::AppBuilder
99
98
  def set_rails_secrets(environment)
100
99
  run_toolbelt_command(
101
100
  "config:add SECRET_KEY_BASE=#{generate_secret}",
102
- app_name_for(environment)
101
+ get("heroku_app_name_#{environment}".to_sym)
103
102
  )
104
103
  end
105
104
 
106
105
  def set_app_multi_buildpack(environment)
107
106
  run_toolbelt_command(
108
107
  "buildpacks:set https://github.com/heroku/heroku-buildpack-multi.git",
109
- app_name_for(environment)
108
+ get("heroku_app_name_#{environment}".to_sym)
110
109
  )
111
110
  end
112
111
 
@@ -133,4 +132,18 @@ class Recipes::Heroku < Rails::AppBuilder
133
132
  `heroku #{command} --app #{app_env_name}`
134
133
  end
135
134
  end
135
+
136
+ def valid_heroku_name(name, element, force_suffix = true)
137
+ suffix = "-#{element}"
138
+ while name.length > HEROKU_NAMES_MAX_CHARS
139
+ puts "Heroku names must be shorter than #{HEROKU_NAMES_MAX_CHARS} chars."
140
+ if force_suffix
141
+ puts "Potassium uses the heroku-stage gem, because of that '#{suffix}' will be "\
142
+ "added to your app name. The suffix, #{suffix}, counts towards the app name length."
143
+ end
144
+ name = Ask.input("Please enter a valid name for #{element}:")
145
+ name += suffix if force_suffix && !name.end_with?(suffix)
146
+ end
147
+ name
148
+ end
136
149
  end