potassium 6.1.0 → 6.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) 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/.node-version +1 -1
  6. data/.rubocop.yml +530 -0
  7. data/CHANGELOG.md +50 -1
  8. data/README.md +11 -3
  9. data/lib/potassium/assets/.circleci/config.yml.erb +107 -40
  10. data/lib/potassium/assets/.pryrc +0 -6
  11. data/lib/potassium/assets/.rubocop.yml +13 -0
  12. data/lib/potassium/assets/README.yml +45 -8
  13. data/lib/potassium/assets/active_admin/policies/admin_user_policy.rb +2 -0
  14. data/lib/potassium/assets/active_admin/policies/comment_policy.rb +2 -0
  15. data/lib/potassium/assets/active_admin/policies/default_policy.rb +49 -0
  16. data/lib/potassium/assets/active_admin/policies/page_policy.rb +2 -0
  17. data/lib/potassium/assets/app/javascript/app.spec.js +1 -1
  18. data/lib/potassium/assets/app/views/shared/_gtm_body.html.erb +4 -0
  19. data/lib/potassium/assets/app/views/shared/_gtm_head.html.erb +7 -0
  20. data/lib/potassium/assets/testing/.rspec +1 -0
  21. data/lib/potassium/assets/testing/devise_config.rb +6 -0
  22. data/lib/potassium/assets/testing/factory_bot_config.rb +3 -0
  23. data/lib/potassium/assets/testing/faker_config.rb +1 -0
  24. data/lib/potassium/assets/testing/power_types_config.rb +1 -0
  25. data/lib/potassium/assets/testing/rails_helper.rb +130 -49
  26. data/lib/potassium/assets/testing/shoulda_matchers_config.rb +8 -0
  27. data/lib/potassium/assets/testing/simplecov_config.rb +64 -0
  28. data/lib/potassium/assets/testing/system_tests_config.rb +6 -0
  29. data/lib/potassium/cli_options.rb +19 -3
  30. data/lib/potassium/helpers/template-helpers.rb +4 -0
  31. data/lib/potassium/recipes/admin.rb +27 -17
  32. data/lib/potassium/recipes/api.rb +2 -0
  33. data/lib/potassium/recipes/background_processor.rb +43 -32
  34. data/lib/potassium/recipes/ci.rb +9 -39
  35. data/lib/potassium/recipes/coverage.rb +35 -0
  36. data/lib/potassium/recipes/file_storage.rb +1 -1
  37. data/lib/potassium/recipes/front_end.rb +26 -13
  38. data/lib/potassium/recipes/google_tag_manager.rb +94 -0
  39. data/lib/potassium/recipes/heroku.rb +43 -31
  40. data/lib/potassium/recipes/mailer.rb +18 -5
  41. data/lib/potassium/recipes/monitoring.rb +5 -0
  42. data/lib/potassium/recipes/pundit.rb +29 -10
  43. data/lib/potassium/recipes/rails.rb +0 -4
  44. data/lib/potassium/recipes/schedule.rb +16 -1
  45. data/lib/potassium/recipes/spring.rb +9 -0
  46. data/lib/potassium/recipes/style.rb +2 -2
  47. data/lib/potassium/recipes/testing.rb +75 -18
  48. data/lib/potassium/templates/application.rb +7 -2
  49. data/lib/potassium/version.rb +7 -4
  50. data/potassium.gemspec +3 -1
  51. data/spec/features/background_processor_spec.rb +7 -5
  52. data/spec/features/ci_spec.rb +7 -4
  53. data/spec/features/coverage_spec.rb +26 -0
  54. data/spec/features/front_end_spec.rb +18 -1
  55. data/spec/features/google_tag_manager_spec.rb +36 -0
  56. data/spec/features/heroku_spec.rb +0 -4
  57. data/spec/features/mailer_spec.rb +16 -0
  58. data/spec/features/node_spec.rb +1 -1
  59. data/spec/features/pundit_spec.rb +34 -0
  60. data/spec/features/schedule_spec.rb +11 -4
  61. data/spec/features/testing_spec.rb +56 -0
  62. data/spec/support/potassium_test_helpers.rb +2 -3
  63. data/tmp/.keep +0 -0
  64. metadata +64 -15
  65. data/lib/potassium/assets/Dockerfile.ci +0 -6
  66. data/lib/potassium/assets/active_admin/admin_user_policy.rb +0 -2
  67. data/lib/potassium/assets/active_admin/comment_policy.rb +0 -2
  68. data/lib/potassium/assets/active_admin/pundit_page_policy.rb +0 -5
  69. data/lib/potassium/assets/bin/cibuild.erb +0 -117
  70. data/lib/potassium/assets/docker-compose.ci.yml +0 -12
  71. data/lib/potassium/assets/sidekiq_scheduler.yml +0 -9
@@ -0,0 +1,64 @@
1
+ require 'simplecov'
2
+ require 'simplecov_text_formatter'
3
+ require 'simplecov_linter_formatter'
4
+
5
+ SimpleCovLinterFormatter.setup do |config|
6
+ config.scope = ENV.fetch(
7
+ "SIMPLE_COV_LINTER_SCOPE", :own_changes
8
+ )
9
+ config.json_filename = ENV.fetch(
10
+ "SIMPLE_COV_LINTER_JSON_FILENAME", ".resultset.json"
11
+ )
12
+ config.summary_enabled = ENV.fetch(
13
+ "SIMPLE_COV_LINTER_SUMMARY_ENABLED", true
14
+ )
15
+ config.summary_enabled_bg = ENV.fetch(
16
+ "SIMPLE_COV_LINTER_SUMMARY_BG_ENABLED", true
17
+ )
18
+ config.summary_covered_bg_color = ENV.fetch(
19
+ "SIMPLE_COV_LINTER_SUMMARY_COVERED_BG_COLOR", :darkgreen
20
+ )
21
+ config.summary_not_covered_bg_color = ENV.fetch(
22
+ "SIMPLE_COV_LINTER_SUMMARY_NOT_COVERED_BG_COLOR", :firebrick
23
+ )
24
+ config.summary_text_color = ENV.fetch(
25
+ "SIMPLE_COV_LINTER_SUMMARY_TEXT_COLOR", :white
26
+ )
27
+ config.summary_files_sorting = ENV.fetch(
28
+ "SIMPLE_COV_LINTER_SUMMARY_FILES_SORTING", :coverage
29
+ )
30
+ end
31
+
32
+ SimpleCov.start 'rails' do
33
+ add_group 'Commands', 'app/commands'
34
+ add_group 'Services', 'app/services'
35
+ add_group 'Observers', 'app/observers'
36
+ add_group 'Policies', 'app/policies'
37
+ add_group 'Utils', 'app/utils'
38
+ add_group 'Extensions', 'app/extensions'
39
+
40
+ add_filter %r{app/controllers/([a-z]|_)*_controller.rb}
41
+ add_filter 'app/admin'
42
+ add_filter 'app/channels'
43
+ add_filter 'app/uploaders'
44
+ add_filter 'app/serializers'
45
+ add_filter 'app/clients'
46
+ add_filter 'app/helpers'
47
+ add_filter 'app/decorators'
48
+ add_filter 'app/responders'
49
+ add_filter 'lib/fake_data_loader.rb'
50
+ add_filter 'lib/vue_component.rb'
51
+
52
+ if ENV["CIRCLECI"]
53
+ formatter(SimpleCov::Formatter::TextFormatter)
54
+ else
55
+ formatter(
56
+ SimpleCov::Formatter::MultiFormatter.new(
57
+ [
58
+ SimpleCov::Formatter::LinterFormatter,
59
+ SimpleCov::Formatter::HTMLFormatter
60
+ ]
61
+ )
62
+ )
63
+ end
64
+ end
@@ -0,0 +1,6 @@
1
+ RSpec.configure do |config|
2
+ config.before(:each, type: :system) do |example|
3
+ driver = example.metadata[:no_js] ? :rack_test : :selenium_chrome_headless
4
+ driven_by(driver)
5
+ end
6
+ end
@@ -97,10 +97,10 @@ module Potassium::CliOptions # rubocop:disable Metrics/ModuleLength
97
97
  default_test_value: false
98
98
  },
99
99
  {
100
- type: :flag,
100
+ type: :switch,
101
101
  name: "background_processor",
102
- desc: "Decides which background processor to use. Available: sidekiq, delayed_job, none",
103
- default_test_value: "None"
102
+ desc: "Whether to use Sidekiq for background processing or not",
103
+ default_test_value: false
104
104
  },
105
105
  {
106
106
  type: :switch,
@@ -177,6 +177,14 @@ module Potassium::CliOptions # rubocop:disable Metrics/ModuleLength
177
177
  desc: "Decides which front-end framework to use. Available: Vue, Angular 2, None",
178
178
  default_test_value: "None"
179
179
  },
180
+ {
181
+ type: :switch,
182
+ name: 'google_tag_manager',
183
+ desc: 'Whether to use google tag manager',
184
+ negatable: true,
185
+ default_value: 'none',
186
+ default_test_value: false
187
+ },
180
188
  {
181
189
  type: :switch,
182
190
  name: "test",
@@ -184,6 +192,14 @@ module Potassium::CliOptions # rubocop:disable Metrics/ModuleLength
184
192
  negatable: true,
185
193
  default_value: false,
186
194
  default_test_value: true
195
+ },
196
+ {
197
+ type: :switch,
198
+ name: "spring",
199
+ desc: "Whether to use Spring",
200
+ negatable: true,
201
+ default_value: true,
202
+ default_test_value: false
187
203
  }
188
204
  ]
189
205
 
@@ -7,6 +7,10 @@ module TemplateHelpers
7
7
  "#{Potassium::NODE_VERSION}.x"
8
8
  end
9
9
 
10
+ def ruby_version
11
+ Semantic::Version.new(Potassium::RUBY_VERSION).instance_eval { "#{major}.#{minor}" }
12
+ end
13
+
10
14
  def load_recipe(recipe_name)
11
15
  @recipes ||= {}
12
16
  @recipes[recipe_name] ||= get_recipe_class(recipe_name.to_sym).new(self)
@@ -32,12 +32,11 @@ class Recipes::Admin < Rails::AppBuilder
32
32
  private
33
33
 
34
34
  def add_active_admin
35
- gather_gem 'activeadmin', '~> 2.6'
35
+ gather_gem 'activeadmin', '~> 2.9'
36
36
  gather_gem 'activeadmin_addons'
37
- gather_gem 'active_skin', github: 'SoftwareBrothers/active_skin'
38
37
  add_readme_section :internal_dependencies, :active_admin
39
38
  after(:gem_install, wrap_in_action: :admin_install) do
40
- generate "active_admin:install"
39
+ generate "active_admin:install --use_webpacker"
41
40
  line = "ActiveAdmin.setup do |config|"
42
41
  initializer = "config/initializers/active_admin.rb"
43
42
  gsub_file initializer, /(#{Regexp.escape(line)})/mi do |_match|
@@ -50,26 +49,37 @@ class Recipes::Admin < Rails::AppBuilder
50
49
  end\n
51
50
  ActiveAdmin.setup do |config|
52
51
  config.view_factory.footer = CustomFooter
52
+ meta_tags_options = { viewport: 'width=device-width, initial-scale=1' }
53
+ config.meta_tags = meta_tags_options
54
+ config.meta_tags_for_logged_out_pages = meta_tags_options
53
55
  HERE
54
56
  end
55
57
 
56
- line = "@import \"active_admin/base\";"
57
- style = "app/assets/stylesheets/active_admin.css.scss"
58
- style = File.exist?(style) ? style : "app/assets/stylesheets/active_admin.scss"
58
+ generate "activeadmin_addons:install"
59
59
 
60
- gsub_file style, /(#{Regexp.escape(line)})/mi do |_match|
61
- <<~HERE
62
- #{line}
63
- $skinActiveColor: #001CEE;
64
- $skinHeaderBck: #002744;
65
- $panelHeaderBck: #002744;
66
- //$skinLogo: $skinHeaderBck image-url("logo_admin.png") no-repeat center center;
60
+ run "bin/yarn add arctic_admin @fortawesome/fontawesome-free"
67
61
 
68
- @import "active_skin";
69
- HERE
70
- end
62
+ aa_style = "app/javascript/stylesheets/active_admin.scss"
71
63
 
72
- generate "activeadmin_addons:install"
64
+ gsub_file(
65
+ aa_style,
66
+ "@import \"~@activeadmin/activeadmin/src/scss/mixins\";\n" +
67
+ "@import \"~@activeadmin/activeadmin/src/scss/base\";",
68
+ "@import '~arctic_admin/src/scss/main'; \n"
69
+ )
70
+
71
+ aa_js = "app/javascript/packs/active_admin.js"
72
+ js_line = "import \"@activeadmin/activeadmin\";\n"
73
+
74
+ gsub_file(
75
+ aa_js,
76
+ js_line,
77
+ <<~HERE
78
+ #{js_line}
79
+ import '@fortawesome/fontawesome-free/css/all.css';
80
+ import 'arctic_admin';
81
+ HERE
82
+ )
73
83
  end
74
84
  end
75
85
  end
@@ -38,6 +38,8 @@ class Recipes::Api < Rails::AppBuilder
38
38
  end
39
39
 
40
40
  add_readme_section :internal_dependencies, :power_api
41
+ rubocop_example = "RSpec:\n Language:\n Includes:\n Examples:\n - run_test!"
42
+ append_to_file('.rubocop.yml', rubocop_example)
41
43
 
42
44
  after(:gem_install) do
43
45
  generate "power_api:install"
@@ -1,12 +1,12 @@
1
1
  class Recipes::BackgroundProcessor < Rails::AppBuilder
2
2
  def ask
3
- response = if selected?(:email_service, :none)
3
+ response = if enabled_mailer?
4
+ info "Note: Emails should be sent on background jobs. We'll install sidekiq"
5
+ true
6
+ else
4
7
  answer(:background_processor) do
5
8
  Ask.confirm("Do you want to use Sidekiq for background job processing?")
6
9
  end
7
- else
8
- info "Note: Emails should be sent on background jobs. We'll install sidekiq"
9
- true
10
10
  end
11
11
  set(:background_processor, response)
12
12
  end
@@ -30,6 +30,42 @@ class Recipes::BackgroundProcessor < Rails::AppBuilder
30
30
  gem_exists?(/sidekiq/)
31
31
  end
32
32
 
33
+ def add_sidekiq
34
+ recipe = self
35
+ run_action(:install_sidekiq) do
36
+ gather_gem("sidekiq")
37
+ recipe.add_adapters("sidekiq")
38
+ add_readme_section :internal_dependencies, :sidekiq
39
+ recipe.edit_procfile("bundle exec sidekiq")
40
+ append_to_file(".env.development", "DB_POOL=25\n")
41
+ template("../assets/sidekiq.rb.erb", "config/initializers/sidekiq.rb", force: true)
42
+ copy_file("../assets/sidekiq.yml", "config/sidekiq.yml", force: true)
43
+ copy_file("../assets/redis.yml", "config/redis.yml", force: true)
44
+ recipe.mount_sidekiq_routes
45
+ end
46
+ end
47
+
48
+ def edit_procfile(cmd)
49
+ heroku = load_recipe(:heroku)
50
+ if selected?(:heroku) || heroku.installed?
51
+ gsub_file('Procfile', /^.*$/m) { |match| "#{match}worker: #{cmd}" }
52
+ end
53
+ end
54
+
55
+ def add_adapters(name)
56
+ application("config.active_job.queue_adapter = :#{name}")
57
+ application "config.active_job.queue_adapter = :async", env: "development"
58
+ application "config.active_job.queue_adapter = :test", env: "test"
59
+ end
60
+
61
+ def mount_sidekiq_routes
62
+ insert_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do
63
+ <<-HERE.gsub(/^ {6}/, '')
64
+ mount Sidekiq::Web => '/queue'
65
+ HERE
66
+ end
67
+ end
68
+
33
69
  private
34
70
 
35
71
  def add_docker_compose_redis_config
@@ -59,33 +95,8 @@ class Recipes::BackgroundProcessor < Rails::AppBuilder
59
95
  )
60
96
  end
61
97
 
62
- def add_sidekiq
63
- gather_gem("sidekiq")
64
- add_adapters("sidekiq")
65
- add_readme_section :internal_dependencies, :sidekiq
66
- edit_procfile("bundle exec sidekiq")
67
- append_to_file(".env.development", "DB_POOL=25\n")
68
- template("../assets/sidekiq.rb.erb", "config/initializers/sidekiq.rb", force: true)
69
- copy_file("../assets/sidekiq.yml", "config/sidekiq.yml", force: true)
70
- copy_file("../assets/redis.yml", "config/redis.yml", force: true)
71
-
72
- insert_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do
73
- <<-HERE.gsub(/^ {6}/, '')
74
- mount Sidekiq::Web => '/queue'
75
- HERE
76
- end
77
- end
78
-
79
- def edit_procfile(cmd)
80
- heroku = load_recipe(:heroku)
81
- if selected?(:heroku) || heroku.installed?
82
- gsub_file('Procfile', /^.*$/m) { |match| "#{match}worker: #{cmd}" }
83
- end
84
- end
85
-
86
- def add_adapters(name)
87
- application("config.active_job.queue_adapter = :#{name}")
88
- application "config.active_job.queue_adapter = :async", env: "development"
89
- application "config.active_job.queue_adapter = :test", env: "test"
98
+ def enabled_mailer?
99
+ mailer_answer = get(:email_service)
100
+ mailer_answer && ![:none, :None].include?(mailer_answer.to_sym)
90
101
  end
91
102
  end
@@ -1,50 +1,20 @@
1
1
  class Recipes::Ci < Rails::AppBuilder
2
2
  def create
3
- copy_file '../assets/Dockerfile.ci', 'Dockerfile.ci'
4
3
  template '../assets/.circleci/config.yml.erb', '.circleci/config.yml'
5
4
 
6
- template '../assets/bin/cibuild.erb', 'bin/cibuild'
7
- run "chmod a+x bin/cibuild"
8
-
9
- copy_file '../assets/docker-compose.ci.yml', 'docker-compose.ci.yml'
10
-
11
5
  gather_gems(:test) do
12
- gather_gem 'rspec_junit_formatter', '0.2.2'
13
- end
14
-
15
- compose = DockerHelpers.new('docker-compose.ci.yml')
16
-
17
- if selected?(:database, :mysql)
18
- srv =
19
- <<~YAML
20
- image: mysql:#{Potassium::MYSQL_VERSION}
21
- environment:
22
- MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
23
- YAML
24
- compose.add_service("mysql", srv)
25
- compose.add_link('test', 'mysql')
26
- compose.add_env('test', 'MYSQL_HOST', 'mysql')
27
- compose.add_env('test', 'MYSQL_PORT', '3306')
28
-
29
- elsif selected?(:database, :postgresql)
30
- srv =
31
- <<~YAML
32
- image: "postgres:#{Potassium::POSTGRES_VERSION}"
33
- environment:
34
- POSTGRES_USER: postgres
35
- POSTGRES_PASSWORD: ''
36
- YAML
37
- compose.add_service("postgresql", srv)
38
- compose.add_link('test', 'postgresql')
39
- compose.add_env('test', 'DB_USER', 'postgres')
40
- compose.add_env('test', 'DB_HOST', 'postgresql')
41
- compose.add_env('test', 'DB_PORT', '5432')
6
+ gather_gem 'rspec_junit_formatter', '~> 0.4'
42
7
  end
43
8
 
44
- compose.add_volume('test_data')
45
-
46
9
  add_readme_header :ci
47
-
48
10
  application 'config.assets.js_compressor = :uglifier', env: 'test'
49
11
  end
12
+
13
+ def install
14
+ create
15
+ end
16
+
17
+ def installed?
18
+ file_exist?('.circleci/config.yml')
19
+ end
50
20
  end
@@ -0,0 +1,35 @@
1
+ class Recipes::Coverage < Rails::AppBuilder
2
+ def create
3
+ load_gems
4
+ configure_rails_helper
5
+ append_to_file('.gitignore', "/coverage/*\n")
6
+ end
7
+
8
+ def installed?
9
+ gem_exists?(/simplecov/)
10
+ end
11
+
12
+ def install
13
+ create
14
+ end
15
+
16
+ private
17
+
18
+ def load_gems
19
+ gather_gems(:test) do
20
+ gather_gem 'simplecov'
21
+ gather_gem 'simplecov_linter_formatter', '~> 0.2'
22
+ gather_gem 'simplecov_text_formatter'
23
+ end
24
+ end
25
+
26
+ def configure_rails_helper
27
+ copy_file '../assets/testing/simplecov_config.rb', 'spec/simplecov_config.rb'
28
+
29
+ after(:gem_install) do
30
+ gsub_file 'spec/rails_helper.rb', "ENV['RACK_ENV'] ||= 'test'" do |match|
31
+ "#{match}\nrequire 'simplecov_config'"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -39,7 +39,7 @@ 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')
@@ -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,21 +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
25
-
26
- if value == :vue
27
- recipe.setup_vue_with_compiler_build
28
- recipe.setup_jest
29
- if get(:api) == :graphql
30
- recipe.setup_apollo
31
- end
32
- end
24
+ run "rails webpacker:install:#{value}" unless [:none, :None].include? value.to_sym
25
+
26
+ recipe.setup_vue if value == :vue
33
27
  recipe.add_responsive_meta_tag
34
28
  recipe.setup_tailwind
35
29
  add_readme_header :webpack
@@ -69,7 +63,8 @@ class Recipes::FrontEnd < Rails::AppBuilder
69
63
  end
70
64
 
71
65
  def setup_tailwind
72
- run 'bin/yarn add tailwindcss'
66
+ run "bin/yarn add tailwindcss@#{Potassium::TAILWINDCSS}"
67
+ specify_autoprefixer_postcss_compatibility_versions
73
68
  setup_client_css
74
69
  remove_server_css_requires
75
70
  setup_tailwind_requirements
@@ -107,6 +102,19 @@ class Recipes::FrontEnd < Rails::AppBuilder
107
102
  )
108
103
  end
109
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
+
110
118
  private
111
119
 
112
120
  def frameworks(framework)
@@ -147,6 +155,10 @@ class Recipes::FrontEnd < Rails::AppBuilder
147
155
  JS
148
156
  end
149
157
 
158
+ def specify_autoprefixer_postcss_compatibility_versions
159
+ run 'bin/yarn -D add postcss@^7 autoprefixer@^9'
160
+ end
161
+
150
162
  def setup_client_css
151
163
  application_css = 'app/javascript/css/application.css'
152
164
  create_file application_css, "", force: true
@@ -264,7 +276,8 @@ class Recipes::FrontEnd < Rails::AppBuilder
264
276
  },
265
277
  "snapshotSerializers": [
266
278
  "<rootDir>/node_modules/jest-serializer-vue"
267
- ]
279
+ ],
280
+ "testEnvironment": "jsdom"
268
281
  }
269
282
  }
270
283
  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