potassium 6.0.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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