bulldozer 1.6.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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +12 -0
  5. data/CONTRIBUTING.md +59 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +21 -0
  8. data/NEWS.md +638 -0
  9. data/README.md +225 -0
  10. data/RELEASING.md +18 -0
  11. data/Rakefile +8 -0
  12. data/USAGE +13 -0
  13. data/bin/bulldozer +23 -0
  14. data/bin/rake +16 -0
  15. data/bin/rspec +16 -0
  16. data/bin/setup +13 -0
  17. data/bulldozer.gemspec +35 -0
  18. data/docker-compose.yml +8 -0
  19. data/dockerfile +11 -0
  20. data/lib/bulldozer.rb +24 -0
  21. data/lib/bulldozer/actions.rb +85 -0
  22. data/lib/bulldozer/adapters/heroku.rb +123 -0
  23. data/lib/bulldozer/app_builder.rb +321 -0
  24. data/lib/bulldozer/generators/analytics_generator.rb +24 -0
  25. data/lib/bulldozer/generators/app_generator.rb +231 -0
  26. data/lib/bulldozer/generators/base.rb +20 -0
  27. data/lib/bulldozer/generators/ci_generator.rb +22 -0
  28. data/lib/bulldozer/generators/db_optimizations_generator.rb +30 -0
  29. data/lib/bulldozer/generators/factories_generator.rb +22 -0
  30. data/lib/bulldozer/generators/forms_generator.rb +18 -0
  31. data/lib/bulldozer/generators/jobs_generator.rb +38 -0
  32. data/lib/bulldozer/generators/js_driver_generator.rb +15 -0
  33. data/lib/bulldozer/generators/json_generator.rb +10 -0
  34. data/lib/bulldozer/generators/lint_generator.rb +9 -0
  35. data/lib/bulldozer/generators/production/deployment_generator.rb +27 -0
  36. data/lib/bulldozer/generators/production/email_generator.rb +37 -0
  37. data/lib/bulldozer/generators/production/force_tls_generator.rb +11 -0
  38. data/lib/bulldozer/generators/production/manifest_generator.rb +24 -0
  39. data/lib/bulldozer/generators/production/timeout_generator.rb +21 -0
  40. data/lib/bulldozer/generators/staging/pull_requests_generator.rb +33 -0
  41. data/lib/bulldozer/generators/static_generator.rb +10 -0
  42. data/lib/bulldozer/generators/stylesheet_base_generator.rb +31 -0
  43. data/lib/bulldozer/generators/testing_generator.rb +55 -0
  44. data/lib/bulldozer/generators/views_generator.rb +30 -0
  45. data/lib/bulldozer/version.rb +8 -0
  46. data/spec/adapters/heroku_spec.rb +72 -0
  47. data/spec/fakes/bin/heroku +5 -0
  48. data/spec/fakes/bin/hub +5 -0
  49. data/spec/features/api_spec.rb +18 -0
  50. data/spec/features/cli_help_spec.rb +36 -0
  51. data/spec/features/github_spec.rb +16 -0
  52. data/spec/features/heroku_spec.rb +71 -0
  53. data/spec/features/json_spec.rb +15 -0
  54. data/spec/features/new_project_spec.rb +341 -0
  55. data/spec/features/production/deployment_spec.rb +22 -0
  56. data/spec/features/production/email_spec.rb +47 -0
  57. data/spec/features/production/manifest_spec.rb +35 -0
  58. data/spec/features/staging/pull_requests_spec.rb +22 -0
  59. data/spec/spec_helper.rb +21 -0
  60. data/spec/support/be_executable_matcher.rb +7 -0
  61. data/spec/support/bulldozer.rb +182 -0
  62. data/spec/support/contain_json_matcher.rb +24 -0
  63. data/spec/support/exist_as_a_file_matcher.rb +7 -0
  64. data/spec/support/fake_github.rb +21 -0
  65. data/spec/support/fake_heroku.rb +53 -0
  66. data/spec/support/generators.rb +5 -0
  67. data/spec/support/match_contents_matcher.rb +6 -0
  68. data/spec/support/project_files.rb +13 -0
  69. data/templates/Gemfile.erb +51 -0
  70. data/templates/Procfile +2 -0
  71. data/templates/README.md.erb +28 -0
  72. data/templates/_analytics.html.erb +8 -0
  73. data/templates/_css_overrides.html.erb +7 -0
  74. data/templates/_flashes.html.erb +7 -0
  75. data/templates/_javascript.html.erb +3 -0
  76. data/templates/action_mailer.rb +5 -0
  77. data/templates/active_job.rb +13 -0
  78. data/templates/application.scss +8 -0
  79. data/templates/bin_deploy +12 -0
  80. data/templates/bin_setup +28 -0
  81. data/templates/bin_setup_review_app.erb +22 -0
  82. data/templates/browserslist +3 -0
  83. data/templates/bulldozer_gitignore +18 -0
  84. data/templates/bulldozer_layout.html.erb.erb +21 -0
  85. data/templates/bundler_audit.rake +4 -0
  86. data/templates/chromedriver.rb +17 -0
  87. data/templates/circle.yml.erb +6 -0
  88. data/templates/config_locales_en.yml.erb +19 -0
  89. data/templates/dev.rake +12 -0
  90. data/templates/dotfiles/.ctags +2 -0
  91. data/templates/dotfiles/.env +13 -0
  92. data/templates/email.rb +3 -0
  93. data/templates/errors.rb +34 -0
  94. data/templates/factories.rb +2 -0
  95. data/templates/factory_bot_rspec.rb +5 -0
  96. data/templates/flashes_helper.rb +5 -0
  97. data/templates/hound.yml +14 -0
  98. data/templates/i18n.rb +3 -0
  99. data/templates/json_encoding.rb +1 -0
  100. data/templates/postgresql_database.yml.erb +19 -0
  101. data/templates/puma.rb +28 -0
  102. data/templates/rack_mini_profiler.rb +5 -0
  103. data/templates/rails_helper.rb +22 -0
  104. data/templates/secrets.yml +8 -0
  105. data/templates/shoulda_matchers_config_rspec.rb +6 -0
  106. data/templates/smtp.rb +9 -0
  107. data/templates/spec_helper.rb +28 -0
  108. metadata +197 -0
@@ -0,0 +1,123 @@
1
+ module Bulldozer
2
+ module Adapters
3
+ class Heroku
4
+ def initialize(app_builder)
5
+ @app_builder = app_builder
6
+ end
7
+
8
+ def set_heroku_remotes
9
+ remotes = <<~SHELL
10
+ #{command_to_join_heroku_app('staging')}
11
+ #{command_to_join_heroku_app('production')}
12
+
13
+ git config heroku.remote staging
14
+ SHELL
15
+
16
+ app_builder.append_file "bin/setup", remotes
17
+ end
18
+
19
+ def create_staging_heroku_app(flags)
20
+ app_name = heroku_app_name_for("staging")
21
+
22
+ run_toolbelt_command "create #{app_name} #{flags}", "staging"
23
+ end
24
+
25
+ def create_production_heroku_app(flags)
26
+ app_name = heroku_app_name_for("production")
27
+
28
+ run_toolbelt_command "create #{app_name} #{flags}", "production"
29
+ end
30
+
31
+ def set_heroku_rails_secrets
32
+ %w(staging production).each do |environment|
33
+ run_toolbelt_command(
34
+ "config:add SECRET_KEY_BASE=#{generate_secret}",
35
+ environment,
36
+ )
37
+ end
38
+ end
39
+
40
+ def set_heroku_honeybadger_env
41
+ %w(staging production).each do |environment|
42
+ run_toolbelt_command(
43
+ "config:add HONEYBADGER_ENV=#{environment}",
44
+ environment,
45
+ )
46
+ end
47
+ end
48
+
49
+ def set_heroku_backup_schedule
50
+ %w(staging production).each do |environment|
51
+ run_toolbelt_command(
52
+ "pg:backups:schedule DATABASE_URL --at '10:00 UTC'",
53
+ environment,
54
+ )
55
+ end
56
+ end
57
+
58
+ def create_heroku_pipeline
59
+ pipelines_plugin = `heroku help | grep pipelines`
60
+ if pipelines_plugin.empty?
61
+ puts "You need heroku pipelines plugin. Run: brew upgrade heroku-toolbelt"
62
+ exit 1
63
+ end
64
+
65
+ run_toolbelt_command(
66
+ "pipelines:create #{heroku_app_name} \
67
+ -a #{heroku_app_name}-staging --stage staging",
68
+ "staging",
69
+ )
70
+
71
+ run_toolbelt_command(
72
+ "pipelines:add #{heroku_app_name} \
73
+ -a #{heroku_app_name}-production --stage production",
74
+ "production",
75
+ )
76
+ end
77
+
78
+ def set_heroku_application_host
79
+ %w(staging production).each do |environment|
80
+ run_toolbelt_command(
81
+ "config:add APPLICATION_HOST=#{heroku_app_name}-#{environment}.herokuapp.com",
82
+ environment,
83
+ )
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ attr_reader :app_builder
90
+
91
+ def command_to_join_heroku_app(environment)
92
+ heroku_app_name = heroku_app_name_for(environment)
93
+ <<~SHELL
94
+
95
+ if heroku join --app #{heroku_app_name} > /dev/null 2>&1; then
96
+ git remote add #{environment} git@heroku.com:#{heroku_app_name}.git || true
97
+ printf 'You are a collaborator on the "#{heroku_app_name}" Heroku app\n'
98
+ else
99
+ printf 'Ask for access to the "#{heroku_app_name}" Heroku app\n'
100
+ fi
101
+ SHELL
102
+ end
103
+
104
+ def heroku_app_name
105
+ app_builder.app_name.dasherize
106
+ end
107
+
108
+ def heroku_app_name_for(environment)
109
+ "#{heroku_app_name}-#{environment}"
110
+ end
111
+
112
+ def generate_secret
113
+ SecureRandom.hex(64)
114
+ end
115
+
116
+ def run_toolbelt_command(command, environment)
117
+ app_builder.run(
118
+ "heroku #{command} --remote #{environment}",
119
+ )
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,321 @@
1
+ require "forwardable"
2
+
3
+ module Bulldozer
4
+ class AppBuilder < Rails::AppBuilder
5
+ include Bulldozer::Actions
6
+ extend Forwardable
7
+
8
+ def_delegators(
9
+ :heroku_adapter,
10
+ :create_heroku_pipeline,
11
+ :create_production_heroku_app,
12
+ :create_staging_heroku_app,
13
+ :set_heroku_application_host,
14
+ :set_heroku_backup_schedule,
15
+ :set_heroku_honeybadger_env,
16
+ :set_heroku_rails_secrets,
17
+ :set_heroku_remotes,
18
+ )
19
+
20
+ def readme
21
+ template 'README.md.erb', 'README.md'
22
+ end
23
+
24
+ def gitignore
25
+ copy_file "bulldozer_gitignore", ".gitignore"
26
+ end
27
+
28
+ def gemfile
29
+ template "Gemfile.erb", "Gemfile"
30
+ end
31
+
32
+ def setup_rack_mini_profiler
33
+ copy_file(
34
+ "rack_mini_profiler.rb",
35
+ "config/initializers/rack_mini_profiler.rb",
36
+ )
37
+ end
38
+
39
+ def raise_on_missing_assets_in_test
40
+ configure_environment "test", "config.assets.raise_runtime_errors = true"
41
+ end
42
+
43
+ def raise_on_delivery_errors
44
+ replace_in_file 'config/environments/development.rb',
45
+ 'raise_delivery_errors = false', 'raise_delivery_errors = true'
46
+ end
47
+
48
+ def set_test_delivery_method
49
+ inject_into_file(
50
+ "config/environments/development.rb",
51
+ "\n config.action_mailer.delivery_method = :file",
52
+ after: "config.action_mailer.raise_delivery_errors = true",
53
+ )
54
+ end
55
+
56
+ def raise_on_unpermitted_parameters
57
+ config = <<-RUBY
58
+ config.action_controller.action_on_unpermitted_parameters = :raise
59
+ RUBY
60
+
61
+ inject_into_class "config/application.rb", "Application", config
62
+ end
63
+
64
+ def configure_quiet_assets
65
+ config = <<-RUBY
66
+ config.assets.quiet = true
67
+ RUBY
68
+
69
+ inject_into_class "config/application.rb", "Application", config
70
+ end
71
+
72
+ def provide_setup_script
73
+ template "bin_setup", "bin/setup", force: true
74
+ run "chmod a+x bin/setup"
75
+ end
76
+
77
+ def configure_generators
78
+ config = <<-RUBY
79
+
80
+ config.generators do |generate|
81
+ generate.helper false
82
+ generate.javascripts false
83
+ generate.request_specs false
84
+ generate.routing_specs false
85
+ generate.stylesheets false
86
+ generate.test_framework :rspec
87
+ generate.view_specs false
88
+ end
89
+
90
+ RUBY
91
+
92
+ inject_into_class 'config/application.rb', 'Application', config
93
+ end
94
+
95
+ def configure_local_mail
96
+ copy_file "email.rb", "config/initializers/email.rb"
97
+ end
98
+
99
+ def enable_rack_canonical_host
100
+ config = <<-RUBY
101
+ config.middleware.use Rack::CanonicalHost, ENV.fetch("APPLICATION_HOST")
102
+ RUBY
103
+
104
+ configure_environment "production", config
105
+ end
106
+
107
+ def enable_rack_deflater
108
+ configure_environment "production", "config.middleware.use Rack::Deflater"
109
+ end
110
+
111
+ def setup_asset_host
112
+ replace_in_file 'config/environments/production.rb',
113
+ "# config.action_controller.asset_host = 'http://assets.example.com'",
114
+ 'config.action_controller.asset_host = ENV.fetch("ASSET_HOST", ENV.fetch("APPLICATION_HOST"))'
115
+
116
+ if File.exist?("config/initializers/assets.rb")
117
+ replace_in_file 'config/initializers/assets.rb',
118
+ "config.assets.version = '1.0'",
119
+ 'config.assets.version = (ENV["ASSETS_VERSION"] || "1.0")'
120
+ end
121
+
122
+ config = <<-EOD
123
+ config.public_file_server.headers = {
124
+ "Cache-Control" => "public, max-age=31557600",
125
+ }
126
+ EOD
127
+
128
+ configure_environment("production", config)
129
+ end
130
+
131
+ def setup_secret_token
132
+ template 'secrets.yml', 'config/secrets.yml', force: true
133
+ end
134
+
135
+ def disallow_wrapping_parameters
136
+ remove_file "config/initializers/wrap_parameters.rb"
137
+ end
138
+
139
+ def use_postgres_config_template
140
+ template 'postgresql_database.yml.erb', 'config/database.yml',
141
+ force: true
142
+ end
143
+
144
+ def create_database
145
+ bundle_command "exec rails db:create db:migrate"
146
+ end
147
+
148
+ def replace_gemfile(path)
149
+ template 'Gemfile.erb', 'Gemfile', force: true do |content|
150
+ if path
151
+ content.gsub(%r{gem .bulldozer.}) { |s| %{#{s}, path: "#{path}"} }
152
+ else
153
+ content
154
+ end
155
+ end
156
+ end
157
+
158
+ def ruby_version
159
+ create_file '.ruby-version', "#{Bulldozer::RUBY_VERSION}\n"
160
+ end
161
+
162
+ def configure_i18n_for_missing_translations
163
+ raise_on_missing_translations_in("development")
164
+ raise_on_missing_translations_in("test")
165
+ end
166
+
167
+ def configure_action_mailer_in_specs
168
+ copy_file 'action_mailer.rb', 'spec/support/action_mailer.rb'
169
+ end
170
+
171
+ def configure_time_formats
172
+ remove_file "config/locales/en.yml"
173
+ template "config_locales_en.yml.erb", "config/locales/en.yml"
174
+ end
175
+
176
+ def configure_action_mailer
177
+ action_mailer_host "development", %{"localhost:3000"}
178
+ action_mailer_asset_host "development", %{"http://localhost:3000"}
179
+ action_mailer_host "test", %{"www.example.com"}
180
+ action_mailer_asset_host "test", %{"http://www.example.com"}
181
+ action_mailer_host "production", %{ENV.fetch("APPLICATION_HOST")}
182
+ action_mailer_asset_host(
183
+ "production",
184
+ %{ENV.fetch("ASSET_HOST", ENV.fetch("APPLICATION_HOST"))},
185
+ )
186
+ end
187
+
188
+ def replace_default_puma_configuration
189
+ copy_file "puma.rb", "config/puma.rb", force: true
190
+ end
191
+
192
+ def set_up_forego
193
+ copy_file "Procfile", "Procfile"
194
+ end
195
+
196
+ def setup_default_directories
197
+ [
198
+ 'app/views/pages',
199
+ 'spec/lib',
200
+ 'spec/controllers',
201
+ 'spec/helpers',
202
+ 'spec/support/matchers',
203
+ 'spec/support/mixins',
204
+ 'spec/support/shared_examples'
205
+ ].each do |dir|
206
+ empty_directory_with_keep_file dir
207
+ end
208
+ end
209
+
210
+ def copy_dotfiles
211
+ directory("dotfiles", ".")
212
+ end
213
+
214
+ def create_heroku_apps(flags)
215
+ create_staging_heroku_app(flags)
216
+ create_production_heroku_app(flags)
217
+ end
218
+
219
+ def configure_automatic_deployment
220
+ deploy_command = <<-YML.strip_heredoc
221
+ deployment:
222
+ staging:
223
+ branch: master
224
+ commands:
225
+ - bin/deploy staging
226
+ YML
227
+
228
+ append_file "circle.yml", deploy_command
229
+ end
230
+
231
+ def create_github_repo(repo_name)
232
+ run "hub create #{repo_name}"
233
+ end
234
+
235
+ def setup_bundler_audit
236
+ copy_file "bundler_audit.rake", "lib/tasks/bundler_audit.rake"
237
+ append_file "Rakefile", %{\ntask default: "bundle:audit"\n}
238
+ end
239
+
240
+ def setup_spring
241
+ bundle_command "exec spring binstub --all"
242
+ end
243
+
244
+ def copy_miscellaneous_files
245
+ copy_file "browserslist", "browserslist"
246
+ copy_file "errors.rb", "config/initializers/errors.rb"
247
+ copy_file "json_encoding.rb", "config/initializers/json_encoding.rb"
248
+ end
249
+
250
+ def customize_error_pages
251
+ meta_tags =<<-EOS
252
+ <meta charset="utf-8" />
253
+ <meta name="viewport" content="initial-scale=1" />
254
+ EOS
255
+
256
+ %w(500 404 422).each do |page|
257
+ path = "public/#{page}.html"
258
+ if File.exist?(path)
259
+ inject_into_file path, meta_tags, after: "<head>\n"
260
+ replace_in_file path, /<!--.+-->\n/, ''
261
+ end
262
+ end
263
+ end
264
+
265
+ def remove_config_comment_lines
266
+ config_files = [
267
+ "application.rb",
268
+ "environment.rb",
269
+ "environments/development.rb",
270
+ "environments/production.rb",
271
+ "environments/test.rb",
272
+ ]
273
+
274
+ config_files.each do |config_file|
275
+ path = File.join(destination_root, "config/#{config_file}")
276
+
277
+ accepted_content = File.readlines(path).reject do |line|
278
+ line =~ /^.*#.*$/ || line =~ /^$\n/
279
+ end
280
+
281
+ File.open(path, "w") do |file|
282
+ accepted_content.each { |line| file.puts line }
283
+ end
284
+ end
285
+ end
286
+
287
+ def remove_routes_comment_lines
288
+ replace_in_file 'config/routes.rb',
289
+ /Rails\.application\.routes\.draw do.*end/m,
290
+ "Rails.application.routes.draw do\nend"
291
+ end
292
+
293
+ def setup_default_rake_task
294
+ append_file 'Rakefile' do
295
+ <<-EOS
296
+ task(:default).clear
297
+ task default: [:spec]
298
+
299
+ if defined? RSpec
300
+ task(:spec).clear
301
+ RSpec::Core::RakeTask.new(:spec) do |t|
302
+ t.verbose = false
303
+ end
304
+ end
305
+ EOS
306
+ end
307
+ end
308
+
309
+ private
310
+
311
+ def raise_on_missing_translations_in(environment)
312
+ config = 'config.action_view.raise_on_missing_translations = true'
313
+
314
+ uncomment_lines("config/environments/#{environment}.rb", config)
315
+ end
316
+
317
+ def heroku_adapter
318
+ @heroku_adapter ||= Adapters::Heroku.new(self)
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "base"
2
+
3
+ module Bulldozer
4
+ class AnalyticsGenerator < Generators::Base
5
+ def install_partial
6
+ copy_file "_analytics.html.erb",
7
+ "app/views/application/_analytics.html.erb"
8
+ end
9
+
10
+ def render_partial
11
+ if File.exist?(js_partial)
12
+ inject_into_file js_partial,
13
+ %{\n\n<%= render "analytics" %>},
14
+ after: "<%= yield :javascript %>"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def js_partial
21
+ "app/views/application/_javascript.html.erb"
22
+ end
23
+ end
24
+ end