welaika-suspenders 2.28.0 → 2.29.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -1
  4. data/CONTRIBUTING.md +8 -3
  5. data/NEWS.md +35 -0
  6. data/README.md +1 -1
  7. data/RELEASING.md +6 -7
  8. data/Rakefile +1 -1
  9. data/diff_suspenders.sh +15 -0
  10. data/lib/suspenders.rb +21 -8
  11. data/lib/suspenders/actions.rb +55 -8
  12. data/lib/suspenders/app_builder.rb +20 -191
  13. data/lib/suspenders/generators/app_generator.rb +26 -55
  14. data/lib/suspenders/generators/ci_generator.rb +22 -0
  15. data/lib/suspenders/generators/db_optimizations_generator.rb +30 -0
  16. data/lib/suspenders/generators/decorator_generator.rb +19 -0
  17. data/lib/suspenders/generators/error_reporting_generator.rb +55 -0
  18. data/lib/suspenders/generators/factories_generator.rb +23 -0
  19. data/lib/suspenders/generators/faker_generator.rb +19 -0
  20. data/lib/suspenders/generators/forms_generator.rb +18 -0
  21. data/lib/suspenders/generators/js_driver_generator.rb +20 -0
  22. data/lib/suspenders/generators/lint_generator.rb +35 -0
  23. data/lib/suspenders/generators/production/email_generator.rb +45 -0
  24. data/lib/suspenders/generators/production/force_tls_generator.rb +14 -0
  25. data/lib/suspenders/generators/production/timeout_generator.rb +21 -0
  26. data/lib/suspenders/generators/security_generator.rb +29 -0
  27. data/lib/suspenders/generators/testing_generator.rb +72 -0
  28. data/lib/suspenders/generators/views_generator.rb +44 -0
  29. data/lib/suspenders/version.rb +2 -2
  30. data/spec/features/api_spec.rb +18 -0
  31. data/spec/features/new_project_spec.rb +9 -21
  32. data/spec/features/production/email_spec.rb +47 -0
  33. data/spec/support/contain_json_matcher.rb +24 -0
  34. data/spec/support/exist_as_a_file_matcher.rb +7 -0
  35. data/spec/support/match_contents_matcher.rb +6 -0
  36. data/spec/support/suspenders.rb +113 -30
  37. data/suspenders.gemspec +2 -2
  38. data/templates/Gemfile.erb +8 -31
  39. data/templates/Procfile +1 -1
  40. data/templates/_css_overrides.html.slim +4 -0
  41. data/templates/_javascript.html.slim +3 -3
  42. data/templates/action_mailer.rb +2 -0
  43. data/templates/app.json.erb +0 -15
  44. data/templates/brakeman.rake +2 -0
  45. data/templates/bundler_audit.rake +3 -1
  46. data/templates/capybara_helpers.rb +15 -0
  47. data/templates/chromedriver.rb +20 -0
  48. data/templates/dev.rake +4 -2
  49. data/templates/dotfiles/.env +2 -1
  50. data/templates/email.rb +5 -0
  51. data/templates/errors.rb +2 -0
  52. data/templates/factory_bot_rspec.rb +5 -0
  53. data/templates/faker_rspec.rb +2 -0
  54. data/templates/flashes_helper.rb +2 -0
  55. data/templates/i18n.rb +2 -0
  56. data/templates/json_encoding.rb +2 -0
  57. data/templates/puma.rb +2 -0
  58. data/templates/queries_helper_rspec.rb +2 -0
  59. data/templates/rack_mini_profiler.rb +2 -0
  60. data/templates/rails_helper.rb +11 -1
  61. data/templates/rubocop.rake +2 -0
  62. data/templates/rubocop.yml +2 -22
  63. data/templates/sentry.rb +8 -0
  64. data/templates/shoulda_matchers_config_rspec.rb +2 -0
  65. data/templates/slim-lint.rake +2 -0
  66. data/templates/slim.rb +2 -0
  67. data/templates/smtp.rb +2 -4
  68. data/templates/spec_helper.rb +12 -3
  69. data/templates/suspenders_layout.html.slim +3 -3
  70. metadata +42 -36
  71. data/lib/suspenders/generators/enforce_ssl_generator.rb +0 -12
  72. data/lib/suspenders/generators/initialize_active_job_generator.rb +0 -19
  73. data/templates/_css_overrides.html.erb +0 -7
  74. data/templates/active_job.rb +0 -13
  75. data/templates/capybara.rb +0 -29
  76. data/templates/circle.yml.erb +0 -15
  77. data/templates/database_cleaner_rspec.rb +0 -28
  78. data/templates/errbit.rb +0 -12
  79. data/templates/factories.rb +0 -2
  80. data/templates/factory_girl_rspec.rb +0 -3
  81. data/templates/fixtures_helper_rspec.rb +0 -9
  82. data/templates/sample_service.rb +0 -5
@@ -0,0 +1,14 @@
1
+ require "rails/generators"
2
+ require_relative "../../actions"
3
+
4
+ module Suspenders
5
+ module Production
6
+ class ForceTlsGenerator < Rails::Generators::Base
7
+ include Suspenders::Actions
8
+
9
+ def config_enforce_ssl
10
+ configure_environment "production", "config.force_ssl = true"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ require "rails/generators"
2
+
3
+ module Suspenders
4
+ module Production
5
+ class TimeoutGenerator < Rails::Generators::Base
6
+ def add_gem
7
+ gem "rack-timeout", group: :production
8
+ end
9
+
10
+ def configure_rack_timeout
11
+ append_file ".env", rack_timeout_config
12
+ end
13
+
14
+ private
15
+
16
+ def rack_timeout_config
17
+ %{RACK_TIMEOUT_SERVICE_TIMEOUT=10}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ require "rails/generators"
2
+
3
+ module Suspenders
4
+ class SecurityGenerator < Rails::Generators::Base
5
+ source_root File.expand_path(
6
+ File.join("..", "..", "..", "templates"),
7
+ File.dirname(__FILE__),
8
+ )
9
+
10
+ def add_checkers_gems
11
+ gem 'brakeman', require: false, group: :development
12
+ gem 'bundler-audit', '>= 0.5.0', require: false, group: :development
13
+ Bundler.with_clean_env { run "bundle install" }
14
+ end
15
+
16
+
17
+ def setup_brakeman
18
+ copy_file "brakeman.rake", "lib/tasks/brakeman.rake"
19
+ end
20
+
21
+ def setup_bundler_audit
22
+ copy_file "bundler_audit.rake", "lib/tasks/bundler_audit.rake"
23
+ end
24
+
25
+ def create_binstubs
26
+ Bundler.with_clean_env { run "bundle binstubs brakeman" }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,72 @@
1
+ require "rails/generators"
2
+
3
+ module Suspenders
4
+ class TestingGenerator < Rails::Generators::Base
5
+ source_root File.expand_path(
6
+ File.join("..", "..", "..", "templates"),
7
+ File.dirname(__FILE__),
8
+ )
9
+
10
+ def add_testing_gems
11
+ gem "spring-commands-rspec", group: :development
12
+ gem "rspec-rails", "~> 3.8", group: %i(development test)
13
+ gem "shoulda-matchers", group: :test
14
+
15
+ Bundler.with_clean_env { run "bundle install" }
16
+ end
17
+
18
+ def generate_rspec
19
+ generate "rspec:install"
20
+ end
21
+
22
+ def configure_rspec
23
+ remove_file "spec/rails_helper.rb"
24
+ remove_file "spec/spec_helper.rb"
25
+ copy_file "rails_helper.rb", "spec/rails_helper.rb"
26
+ copy_file "spec_helper.rb", "spec/spec_helper.rb"
27
+ end
28
+
29
+ def provide_shoulda_matchers_config
30
+ copy_file(
31
+ "shoulda_matchers_config_rspec.rb",
32
+ "spec/support/shoulda_matchers.rb",
33
+ )
34
+ end
35
+
36
+ def configure_spec_support_features
37
+ empty_directory_with_keep_file "spec/features"
38
+ empty_directory_with_keep_file "spec/support/features"
39
+ end
40
+
41
+ def configure_i18n_for_test_environment
42
+ copy_file "i18n.rb", "spec/support/i18n.rb"
43
+ end
44
+
45
+ def configure_action_mailer_in_specs
46
+ copy_file "action_mailer.rb", "spec/support/action_mailer.rb"
47
+ end
48
+
49
+ def create_binstubs
50
+ Bundler.with_clean_env { run "bundle binstubs rspec-core" }
51
+ end
52
+
53
+ def add_helpers_for_rspec
54
+ copy_file 'queries_helper_rspec.rb', 'spec/support/queries_helper.rb'
55
+ end
56
+
57
+ def add_capybara_helper
58
+ copy_file 'capybara_helpers.rb', 'spec/support/capybara_helpers.rb'
59
+ end
60
+
61
+ private
62
+
63
+ def empty_directory_with_keep_file(destination)
64
+ empty_directory(destination, {})
65
+ keep_file(destination)
66
+ end
67
+
68
+ def keep_file(destination)
69
+ create_file(File.join(destination, ".keep"))
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ require "rails/generators"
2
+
3
+ module Suspenders
4
+ class ViewsGenerator < Rails::Generators::Base
5
+ source_root File.expand_path(
6
+ File.join("..", "..", "..", "templates"),
7
+ File.dirname(__FILE__),
8
+ )
9
+
10
+ def add_slim_gem
11
+ gem "slim-rails"
12
+ Bundler.with_clean_env { run "bundle install" }
13
+ end
14
+
15
+ def configure_slim
16
+ copy_file 'slim.rb', 'config/initializers/slim.rb'
17
+ end
18
+
19
+ def create_partials_directory
20
+ empty_directory "app/views/application"
21
+ end
22
+
23
+ def create_shared_flashes
24
+ copy_file "_flashes.html.slim", "app/views/application/_flashes.html.slim"
25
+ copy_file "flashes_helper.rb", "app/helpers/flashes_helper.rb"
26
+ end
27
+
28
+ def create_shared_javascripts
29
+ copy_file "_javascript.html.slim",
30
+ "app/views/application/_javascript.html.slim"
31
+ end
32
+
33
+ def create_shared_css_overrides
34
+ copy_file "_css_overrides.html.slim",
35
+ "app/views/application/_css_overrides.html.slim"
36
+ end
37
+
38
+ def create_application_layout
39
+ template "suspenders_layout.html.slim",
40
+ "app/views/layouts/application.html.slim",
41
+ force: true
42
+ end
43
+ end
44
+ end
@@ -1,8 +1,8 @@
1
1
  module Suspenders
2
- RAILS_VERSION = "~> 5.1.4".freeze
2
+ RAILS_VERSION = "~> 5.2.0".freeze
3
3
  RUBY_VERSION = IO.
4
4
  read("#{File.dirname(__FILE__)}/../../.ruby-version").
5
5
  strip.
6
6
  freeze
7
- VERSION = "2.28.0".freeze
7
+ VERSION = "2.29.0.alpha.1".freeze
8
8
  end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "Suspend a new project with --api flag" do
4
+ before(:all) do
5
+ drop_dummy_database
6
+ remove_project_directory
7
+ end
8
+
9
+ it "ensures project specs pass" do
10
+ run_suspenders("--api")
11
+
12
+ Dir.chdir(project_path) do
13
+ Bundler.with_clean_env do
14
+ expect(`rspec spec/`).to include("0 failures")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -74,18 +74,14 @@ RSpec.describe "Suspend a new project with default configuration" do
74
74
  expect(File).to exist("#{project_path}/spec/support/action_mailer.rb")
75
75
  end
76
76
 
77
- it "configures capybara" do
78
- expect(File).to exist("#{project_path}/spec/support/capybara.rb")
77
+ it "configures capybara-chromedriver" do
78
+ expect(File).to exist("#{project_path}/spec/support/chromedriver.rb")
79
79
  end
80
80
 
81
81
  it "adds support file for i18n" do
82
82
  expect(File).to exist("#{project_path}/spec/support/i18n.rb")
83
83
  end
84
84
 
85
- it "adds support file for factory girl" do
86
- expect(File).to exist("#{project_path}/spec/support/factory_girl.rb")
87
- end
88
-
89
85
  it "adds rspec helper for fixtures" do
90
86
  expect(File).to exist("#{project_path}/spec/support/fixtures_helper.rb")
91
87
  end
@@ -192,21 +188,12 @@ RSpec.describe "Suspend a new project with default configuration" do
192
188
  expect(production_config).not_to match(/"HOST"/)
193
189
  end
194
190
 
195
- it "configures email interceptor in smtp config" do
196
- smtp_file = IO.read("#{project_path}/config/smtp.rb")
197
- expect(smtp_file).
198
- to match(/RecipientInterceptor.new\(ENV\["EMAIL_RECIPIENTS"\]\)/)
199
- end
200
-
201
- it "configs active job queue adapter" do
202
- application_config = IO.read("#{project_path}/config/application.rb")
191
+ it "configures email interceptor" do
192
+ email_file = File.join(project_path, "config", "initializers", "email.rb")
193
+ email_config = IO.read(email_file)
203
194
 
204
- expect(application_config).to match(
205
- /^ +config.active_job.queue_adapter = :delayed_job$/
206
- )
207
- expect(test_config).to match(
208
- /^ +config.active_job.queue_adapter = :inline$/
209
- )
195
+ expect(email_config).
196
+ to include(%{RecipientInterceptor.new(ENV["EMAIL_RECIPIENTS"])})
210
197
  end
211
198
 
212
199
  it "configs bullet gem in development" do
@@ -242,6 +229,7 @@ RSpec.describe "Suspend a new project with default configuration" do
242
229
  it "creates binstubs" do
243
230
  expect(File).to exist("#{project_path}/bin/brakeman")
244
231
  expect(File).to exist("#{project_path}/bin/rubocop")
232
+ expect(File).to exist("#{project_path}/bin/rspec")
245
233
  end
246
234
 
247
235
  it "removes comments and extra newlines from config files" do
@@ -284,7 +272,7 @@ RSpec.describe "Suspend a new project with default configuration" do
284
272
  it "creates heroku application manifest file with application name in it" do
285
273
  app_json_file = IO.read("#{project_path}/app.json")
286
274
 
287
- expect(app_json_file).to match(/"name":"#{app_name.dasherize}"/)
275
+ expect(app_json_file).to match(/"name":\s*"#{app_name.dasherize}"/)
288
276
  end
289
277
 
290
278
  def app_name
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "suspenders:production:email" do
4
+ it "generates the configuration for a production email deployment" do
5
+ with_app { generate("suspenders:production:email") }
6
+
7
+ expect("config/smtp.rb").to match_contents(%r{SMTP_SETTINGS\s*=})
8
+
9
+ expect("config/environments/production.rb").to \
10
+ match_contents(%r{require.+config/smtp})
11
+ expect("config/environments/production.rb").to \
12
+ match_contents(%r{action_mailer.delivery_method\s*=\s*:smtp})
13
+ expect("config/environments/production.rb").to \
14
+ match_contents(%r{action_mailer.smtp_settings\s*=\s*SMTP_SETTINGS})
15
+
16
+ expect("app.json").to contain_json(
17
+ env: {
18
+ SMTP_ADDRESS: { required: true },
19
+ SMTP_DOMAIN: { required: true },
20
+ SMTP_PASSWORD: { required: true },
21
+ SMTP_USERNAME: { required: true },
22
+ },
23
+ )
24
+ end
25
+
26
+ it "destroys the configuration for a production email deployment" do
27
+ with_app { destroy("suspenders:production:email") }
28
+
29
+ expect("config/smtp.rb").not_to exist_as_a_file
30
+
31
+ expect("config/environments/production.rb").not_to \
32
+ match_contents(%r{require.+config/smtp})
33
+ expect("config/environments/production.rb").not_to \
34
+ match_contents(%r{action_mailer.delivery_method\s*=\s*:smtp})
35
+ expect("config/environments/production.rb").not_to \
36
+ match_contents(%r{action_mailer.smtp_settings\s*=\s*SMTP_SETTINGS})
37
+
38
+ expect("app.json").not_to contain_json(
39
+ env: {
40
+ SMTP_ADDRESS: { required: true },
41
+ SMTP_DOMAIN: { required: true },
42
+ SMTP_PASSWORD: { required: true },
43
+ SMTP_USERNAME: { required: true },
44
+ },
45
+ )
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ RSpec::Matchers.define :contain_json do
6
+ match do
7
+ sub_json = expected
8
+ filename = actual
9
+
10
+ filepath = File.join(project_path, filename)
11
+ json = JSON.parse(IO.read(filepath), symbolize_names: true)
12
+ sub_json <= json
13
+ end
14
+
15
+ failure_message do
16
+ sub_json = expected
17
+ filename = actual
18
+
19
+ filepath = File.join(project_path, filename)
20
+ json = JSON.parse(IO.read(filepath), symbolize_names: true)
21
+
22
+ "in #{filename}, expected to find\n#{sub_json.inspect}\nin\n#{json.inspect}"
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Matchers.define :exist_as_a_file do
4
+ match do |filename|
5
+ File.exist?(File.join(project_path, filename))
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ RSpec::Matchers.define :match_contents do |regexp|
2
+ match do |filename|
3
+ contents = IO.read(File.join(project_path, filename))
4
+ contents =~ regexp
5
+ end
6
+ end
@@ -11,52 +11,92 @@ module SuspendersTestHelpers
11
11
 
12
12
  def run_suspenders(arguments = nil)
13
13
  arguments = "--path=#{root_path} #{arguments}"
14
- Dir.chdir(tmp_path) do
15
- Bundler.with_clean_env do
16
- add_fakes_to_path
17
- `
18
- #{suspenders_bin} #{APP_NAME} #{arguments}
19
- `
20
- Dir.chdir(APP_NAME) do
21
- with_env("HOME", tmp_path) do
22
- `git add .`
23
- `git commit -m 'Initial commit'`
24
- end
14
+ run_in_tmp do
15
+ add_fakes_to_path
16
+
17
+ with_revision_for_honeybadger do
18
+ debug `#{suspenders_bin} #{APP_NAME} #{arguments}`
19
+ end
20
+
21
+ Dir.chdir(APP_NAME) do
22
+ with_env("HOME", tmp_path) do
23
+ debug `git add .`
24
+ debug `git commit -m 'Initial commit'`
25
25
  end
26
26
  end
27
27
  end
28
28
  end
29
29
 
30
- def suspenders_help_command
31
- Dir.chdir(tmp_path) do
32
- Bundler.with_clean_env do
33
- `
34
- #{suspenders_bin} -h
35
- `
30
+ def with_app
31
+ drop_dummy_database
32
+ remove_project_directory
33
+ rails_new
34
+ setup_app_dependencies
35
+
36
+ yield
37
+ end
38
+
39
+ def rails_new
40
+ run_in_tmp do
41
+ add_fakes_to_path
42
+
43
+ with_revision_for_honeybadger do
44
+ debug `#{system_rails_bin} new #{APP_NAME}`
45
+ end
46
+
47
+ Dir.chdir(APP_NAME) do
48
+ File.open("Gemfile", "a") do |file|
49
+ file.puts %{gem "suspenders", path: #{root_path.inspect}}
50
+ end
51
+
52
+ with_env("HOME", tmp_path) do
53
+ debug `git add .`
54
+ debug `git commit -m 'Initial commit'`
55
+ end
36
56
  end
37
57
  end
38
58
  end
39
59
 
40
- def setup_app_dependencies
41
- if File.exist?(project_path)
42
- Dir.chdir(project_path) do
43
- Bundler.with_clean_env do
44
- `bundle check || bundle install`
45
- end
60
+ def generate(generator)
61
+ run_in_project do
62
+ with_revision_for_honeybadger do
63
+ debug `bin/spring stop`
64
+ debug `#{project_rails_bin} generate #{generator}`
46
65
  end
47
66
  end
48
67
  end
49
68
 
50
- def drop_dummy_database
51
- if File.exist?(project_path)
52
- Dir.chdir(project_path) do
53
- Bundler.with_clean_env do
54
- `rails db:drop`
55
- end
69
+ def destroy(generator)
70
+ run_in_project do
71
+ with_revision_for_honeybadger do
72
+ `bin/spring stop`
73
+ `#{project_rails_bin} destroy #{generator}`
56
74
  end
57
75
  end
58
76
  end
59
77
 
78
+ def suspenders_help_command
79
+ run_in_tmp do
80
+ debug `#{suspenders_bin} -h`
81
+ end
82
+ end
83
+
84
+ def setup_app_dependencies
85
+ run_in_project do
86
+ debug `bundle check || bundle install`
87
+ end
88
+ rescue Errno::ENOENT
89
+ # The project_path might not exist, in which case we can skip this.
90
+ end
91
+
92
+ def drop_dummy_database
93
+ run_in_project do
94
+ debug `#{project_rails_bin} db:drop 2>&1`
95
+ end
96
+ rescue Errno::ENOENT
97
+ # The project_path might not exist, in which case we can skip this.
98
+ end
99
+
60
100
  def add_fakes_to_path
61
101
  ENV["PATH"] = "#{support_bin}:#{ENV['PATH']}"
62
102
  end
@@ -79,6 +119,14 @@ module SuspendersTestHelpers
79
119
  File.join(root_path, 'bin', 'welaika-suspenders')
80
120
  end
81
121
 
122
+ def system_rails_bin
123
+ "rails"
124
+ end
125
+
126
+ def project_rails_bin
127
+ "bin/rails"
128
+ end
129
+
82
130
  def support_bin
83
131
  File.join(root_path, "spec", "fakes", "bin")
84
132
  end
@@ -88,12 +136,47 @@ module SuspendersTestHelpers
88
136
  end
89
137
 
90
138
  def with_env(name, new_value)
139
+ had_key = ENV.has_key?(name)
91
140
  prior = ENV[name]
92
141
  ENV[name] = new_value.to_s
93
142
 
94
143
  yield
95
144
 
96
145
  ensure
97
- ENV[name] = prior
146
+ ENV.delete(name)
147
+
148
+ if had_key
149
+ ENV[name] = prior
150
+ end
151
+ end
152
+
153
+ def with_revision_for_honeybadger
154
+ with_env("HEROKU_SLUG_COMMIT", 1) do
155
+ yield
156
+ end
157
+ end
158
+
159
+ def run_in_tmp
160
+ Dir.chdir(tmp_path) do
161
+ Bundler.with_clean_env do
162
+ yield
163
+ end
164
+ end
165
+ end
166
+
167
+ def run_in_project
168
+ Dir.chdir(project_path) do
169
+ Bundler.with_clean_env do
170
+ yield
171
+ end
172
+ end
173
+ end
174
+
175
+ def debug(output)
176
+ if ENV["DEBUG"]
177
+ warn output
178
+ end
179
+
180
+ output
98
181
  end
99
182
  end