crspec 0.1.0 → 0.1.1

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -17
  3. data/benchmark/README.md +56 -0
  4. data/benchmark/run.rb +72 -0
  5. data/benchmark/spec/user_service_crspec_spec.rb +15 -0
  6. data/benchmark/spec/user_service_rspec_spec.rb +16 -0
  7. data/benchmark/target_service.rb +12 -0
  8. data/benchmark/test/user_service_test.rb +18 -0
  9. data/demo.rb +6 -3
  10. data/lib/crspec/cli.rb +34 -3
  11. data/lib/crspec/configuration.rb +59 -0
  12. data/lib/crspec/example_group.rb +31 -0
  13. data/lib/crspec/execution_context.rb +1 -1
  14. data/lib/crspec/generators/init.rb +94 -0
  15. data/lib/crspec/matchers.rb +55 -0
  16. data/lib/crspec/mock/double.rb +8 -1
  17. data/lib/crspec/rails/database_isolation.rb +6 -15
  18. data/lib/crspec/rails/parallel.rb +1 -1
  19. data/lib/crspec/rails/request_helpers.rb +96 -0
  20. data/lib/crspec/transpiler/cli.rb +6 -6
  21. data/lib/crspec/version.rb +1 -1
  22. data/lib/crspec.rb +3 -0
  23. data/samples/book_store/.dockerignore +51 -0
  24. data/samples/book_store/.gitattributes +9 -0
  25. data/samples/book_store/.github/dependabot.yml +12 -0
  26. data/samples/book_store/.github/workflows/ci.yml +130 -0
  27. data/samples/book_store/.gitignore +35 -0
  28. data/samples/book_store/.kamal/hooks/docker-setup.sample +3 -0
  29. data/samples/book_store/.kamal/hooks/post-app-boot.sample +3 -0
  30. data/samples/book_store/.kamal/hooks/post-deploy.sample +14 -0
  31. data/samples/book_store/.kamal/hooks/post-proxy-reboot.sample +3 -0
  32. data/samples/book_store/.kamal/hooks/pre-app-boot.sample +3 -0
  33. data/samples/book_store/.kamal/hooks/pre-build.sample +51 -0
  34. data/samples/book_store/.kamal/hooks/pre-connect.sample +47 -0
  35. data/samples/book_store/.kamal/hooks/pre-deploy.sample +122 -0
  36. data/samples/book_store/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  37. data/samples/book_store/.kamal/secrets +20 -0
  38. data/samples/book_store/.rubocop.yml +8 -0
  39. data/samples/book_store/.ruby-version +1 -0
  40. data/samples/book_store/Dockerfile +77 -0
  41. data/samples/book_store/Gemfile +67 -0
  42. data/samples/book_store/Gemfile.lock +572 -0
  43. data/samples/book_store/README.md +24 -0
  44. data/samples/book_store/Rakefile +6 -0
  45. data/samples/book_store/app/assets/images/.keep +0 -0
  46. data/samples/book_store/app/assets/stylesheets/application.css +10 -0
  47. data/samples/book_store/app/controllers/application_controller.rb +2 -0
  48. data/samples/book_store/app/controllers/books_controller.rb +30 -0
  49. data/samples/book_store/app/controllers/comments_controller.rb +21 -0
  50. data/samples/book_store/app/controllers/concerns/.keep +0 -0
  51. data/samples/book_store/app/controllers/users_controller.rb +21 -0
  52. data/samples/book_store/app/helpers/application_helper.rb +2 -0
  53. data/samples/book_store/app/helpers/books_helper.rb +2 -0
  54. data/samples/book_store/app/helpers/comments_helper.rb +2 -0
  55. data/samples/book_store/app/helpers/users_helper.rb +2 -0
  56. data/samples/book_store/app/javascript/application.js +3 -0
  57. data/samples/book_store/app/javascript/controllers/application.js +9 -0
  58. data/samples/book_store/app/javascript/controllers/hello_controller.js +7 -0
  59. data/samples/book_store/app/javascript/controllers/index.js +4 -0
  60. data/samples/book_store/app/jobs/application_job.rb +7 -0
  61. data/samples/book_store/app/mailers/application_mailer.rb +4 -0
  62. data/samples/book_store/app/models/application_record.rb +3 -0
  63. data/samples/book_store/app/models/book.rb +10 -0
  64. data/samples/book_store/app/models/comment.rb +6 -0
  65. data/samples/book_store/app/models/concerns/.keep +0 -0
  66. data/samples/book_store/app/models/user.rb +4 -0
  67. data/samples/book_store/app/views/layouts/application.html.erb +29 -0
  68. data/samples/book_store/app/views/layouts/mailer.html.erb +13 -0
  69. data/samples/book_store/app/views/layouts/mailer.text.erb +1 -0
  70. data/samples/book_store/app/views/pwa/manifest.json.erb +22 -0
  71. data/samples/book_store/app/views/pwa/service-worker.js +26 -0
  72. data/samples/book_store/bin/brakeman +7 -0
  73. data/samples/book_store/bin/bundler-audit +6 -0
  74. data/samples/book_store/bin/ci +6 -0
  75. data/samples/book_store/bin/dev +2 -0
  76. data/samples/book_store/bin/docker-entrypoint +8 -0
  77. data/samples/book_store/bin/importmap +4 -0
  78. data/samples/book_store/bin/jobs +6 -0
  79. data/samples/book_store/bin/kamal +16 -0
  80. data/samples/book_store/bin/rails +4 -0
  81. data/samples/book_store/bin/rake +4 -0
  82. data/samples/book_store/bin/rubocop +8 -0
  83. data/samples/book_store/bin/setup +35 -0
  84. data/samples/book_store/bin/thrust +5 -0
  85. data/samples/book_store/config/application.rb +27 -0
  86. data/samples/book_store/config/boot.rb +4 -0
  87. data/samples/book_store/config/bundler-audit.yml +5 -0
  88. data/samples/book_store/config/cable.yml +17 -0
  89. data/samples/book_store/config/cache.yml +16 -0
  90. data/samples/book_store/config/ci.rb +24 -0
  91. data/samples/book_store/config/credentials.yml.enc +1 -0
  92. data/samples/book_store/config/database.yml +45 -0
  93. data/samples/book_store/config/deploy.yml +119 -0
  94. data/samples/book_store/config/environment.rb +5 -0
  95. data/samples/book_store/config/environments/development.rb +78 -0
  96. data/samples/book_store/config/environments/production.rb +90 -0
  97. data/samples/book_store/config/environments/test.rb +53 -0
  98. data/samples/book_store/config/importmap.rb +7 -0
  99. data/samples/book_store/config/initializers/assets.rb +7 -0
  100. data/samples/book_store/config/initializers/content_security_policy.rb +29 -0
  101. data/samples/book_store/config/initializers/filter_parameter_logging.rb +8 -0
  102. data/samples/book_store/config/initializers/inflections.rb +16 -0
  103. data/samples/book_store/config/locales/en.yml +31 -0
  104. data/samples/book_store/config/puma.rb +42 -0
  105. data/samples/book_store/config/queue.yml +18 -0
  106. data/samples/book_store/config/recurring.yml +15 -0
  107. data/samples/book_store/config/routes.rb +17 -0
  108. data/samples/book_store/config/storage.yml +27 -0
  109. data/samples/book_store/config.ru +6 -0
  110. data/samples/book_store/db/cable_schema.rb +11 -0
  111. data/samples/book_store/db/cache_schema.rb +12 -0
  112. data/samples/book_store/db/migrate/20260731035441_create_users.rb +10 -0
  113. data/samples/book_store/db/migrate/20260731035506_create_books.rb +11 -0
  114. data/samples/book_store/db/migrate/20260731035528_create_comments.rb +11 -0
  115. data/samples/book_store/db/queue_schema.rb +129 -0
  116. data/samples/book_store/db/schema.rb +39 -0
  117. data/samples/book_store/db/seeds.rb +9 -0
  118. data/samples/book_store/lib/tasks/.keep +0 -0
  119. data/samples/book_store/log/.keep +0 -0
  120. data/samples/book_store/public/400.html +135 -0
  121. data/samples/book_store/public/404.html +135 -0
  122. data/samples/book_store/public/406-unsupported-browser.html +135 -0
  123. data/samples/book_store/public/422.html +135 -0
  124. data/samples/book_store/public/500.html +135 -0
  125. data/samples/book_store/public/icon.png +0 -0
  126. data/samples/book_store/public/icon.svg +3 -0
  127. data/samples/book_store/public/robots.txt +1 -0
  128. data/samples/book_store/script/.keep +0 -0
  129. data/samples/book_store/spec/models/book_spec.rb +42 -0
  130. data/samples/book_store/spec/models/comment_spec.rb +22 -0
  131. data/samples/book_store/spec/models/user_advanced_spec.rb +27 -0
  132. data/samples/book_store/spec/models/user_spec.rb +30 -0
  133. data/samples/book_store/spec/rails_helper.rb +36 -0
  134. data/samples/book_store/spec/rails_parallel_spec.rb +23 -0
  135. data/samples/book_store/spec/requests/books_spec.rb +21 -0
  136. data/samples/book_store/spec/requests/users_spec.rb +14 -0
  137. data/samples/book_store/spec/services/inventory_service_spec.rb +28 -0
  138. data/samples/book_store/spec/services/time_dependent_service_spec.rb +26 -0
  139. data/samples/book_store/spec/spec_helper.rb +16 -0
  140. data/samples/book_store/spec/system/book_store_system_spec.rb +18 -0
  141. data/samples/book_store/storage/.keep +0 -0
  142. data/samples/book_store/test/controllers/.keep +0 -0
  143. data/samples/book_store/test/controllers/books_controller_test.rb +7 -0
  144. data/samples/book_store/test/controllers/comments_controller_test.rb +7 -0
  145. data/samples/book_store/test/controllers/users_controller_test.rb +7 -0
  146. data/samples/book_store/test/fixtures/books.yml +11 -0
  147. data/samples/book_store/test/fixtures/comments.yml +11 -0
  148. data/samples/book_store/test/fixtures/files/.keep +0 -0
  149. data/samples/book_store/test/fixtures/users.yml +9 -0
  150. data/samples/book_store/test/helpers/.keep +0 -0
  151. data/samples/book_store/test/integration/.keep +0 -0
  152. data/samples/book_store/test/mailers/.keep +0 -0
  153. data/samples/book_store/test/models/.keep +0 -0
  154. data/samples/book_store/test/models/book_test.rb +7 -0
  155. data/samples/book_store/test/models/comment_test.rb +7 -0
  156. data/samples/book_store/test/models/user_test.rb +7 -0
  157. data/samples/book_store/test/test_helper.rb +15 -0
  158. data/samples/book_store/tmp/.keep +0 -0
  159. data/samples/book_store/tmp/pids/.keep +0 -0
  160. data/samples/book_store/tmp/storage/.keep +0 -0
  161. data/samples/book_store/vendor/.keep +0 -0
  162. data/samples/book_store/vendor/javascript/.keep +0 -0
  163. data/test/crspec/configuration_test.rb +76 -0
  164. metadata +152 -2
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Crspec
6
+ module Generators
7
+ class Init
8
+ SPEC_HELPER_CONTENT = <<~RUBY
9
+ # frozen_string_literal: true
10
+
11
+ require "crspec"
12
+ require "etc"
13
+
14
+ Crspec.configure do |config|
15
+ config.concurrency = Etc.nprocessors
16
+
17
+ config.before(:each) do
18
+ # Setup hooks executed before every spec example
19
+ end
20
+
21
+ config.after(:each) do
22
+ # Teardown hooks executed after every spec example
23
+ end
24
+ end
25
+ RUBY
26
+
27
+ RAILS_HELPER_CONTENT = <<~RUBY
28
+ # frozen_string_literal: true
29
+
30
+ require_relative "spec_helper"
31
+ ENV["RAILS_ENV"] ||= "test"
32
+ require File.expand_path("../config/environment", __dir__)
33
+
34
+ # Prevent database truncation if environment is production
35
+ abort("The Rails environment is running in production mode!") if defined?(Rails) && Rails.env.production?
36
+
37
+ require "crspec"
38
+
39
+ # Including support files for tests
40
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
41
+ Rails.root.glob("spec/support/**/*.rb").each { |f| require f }
42
+ end
43
+
44
+ # Checks for pending migrations and applies them before tests are run.
45
+ begin
46
+ ActiveRecord::Migration.maintain_test_schema! if defined?(ActiveRecord::Migration)
47
+ rescue ActiveRecord::PendingMigrationError => e
48
+ abort e.to_s
49
+ end
50
+
51
+ Crspec.configure do |config|
52
+ config.use_transactional_fixtures = true
53
+ config.infer_spec_type_from_file_location!
54
+ config.include(Crspec::Rails::RequestHelpers)
55
+
56
+ config.around(:each) do |example|
57
+ if defined?(ActiveRecord::Base) && config.use_transactional_fixtures
58
+ Crspec::Rails::DatabaseIsolation.wrap_example(example)
59
+ else
60
+ example.execute!
61
+ end
62
+ end
63
+ end
64
+ RUBY
65
+
66
+ def self.rails_project?(root_dir = Dir.pwd)
67
+ File.exist?(File.join(root_dir, "bin/rails")) ||
68
+ File.exist?(File.join(root_dir, "config/environment.rb")) ||
69
+ File.exist?(File.join(root_dir, "config/application.rb"))
70
+ end
71
+
72
+ def self.generate(target_dir = "spec", root_dir = Dir.pwd)
73
+ FileUtils.mkdir_p(target_dir)
74
+
75
+ spec_helper_path = File.join(target_dir, "spec_helper.rb")
76
+ rails_helper_path = File.join(target_dir, "rails_helper.rb")
77
+
78
+ created = []
79
+
80
+ unless File.exist?(spec_helper_path)
81
+ File.write(spec_helper_path, SPEC_HELPER_CONTENT)
82
+ created << spec_helper_path
83
+ end
84
+
85
+ if rails_project?(root_dir) && !File.exist?(rails_helper_path)
86
+ File.write(rails_helper_path, RAILS_HELPER_CONTENT)
87
+ created << rails_helper_path
88
+ end
89
+
90
+ created
91
+ end
92
+ end
93
+ end
94
+ end
@@ -168,6 +168,57 @@ module Crspec
168
168
  end
169
169
  end
170
170
 
171
+ class ChangeMatcher < BaseMatcher
172
+ def initialize(receiver = nil, message = nil, &block)
173
+ super()
174
+ @receiver = receiver
175
+ @message = message
176
+ @block = block || -> { receiver.send(message) }
177
+ @expected_by = nil
178
+ @expected_from = nil
179
+ @expected_to = nil
180
+ end
181
+
182
+ def by(amount)
183
+ @expected_by = amount
184
+ self
185
+ end
186
+
187
+ def from(val)
188
+ @expected_from = val
189
+ self
190
+ end
191
+
192
+ def to(val)
193
+ @expected_to = val
194
+ self
195
+ end
196
+
197
+ def matches?(proc_to_run)
198
+ @before_val = @block.call
199
+ proc_to_run.call
200
+ @after_val = @block.call
201
+
202
+ if @expected_by
203
+ (@after_val - @before_val) == @expected_by
204
+ elsif !@expected_from.nil? && !@expected_to.nil?
205
+ @before_val == @expected_from && @after_val == @expected_to
206
+ elsif !@expected_to.nil?
207
+ @after_val == @expected_to
208
+ else
209
+ @before_val != @after_val
210
+ end
211
+ end
212
+
213
+ def failure_message
214
+ if @expected_by
215
+ "Expected result to change by #{@expected_by}, but changed by #{@after_val - @before_val} (from #{@before_val.inspect} to #{@after_val.inspect})"
216
+ else
217
+ "Expected result to change, but remained #{@before_val.inspect}"
218
+ end
219
+ end
220
+ end
221
+
171
222
  def eq(expected)
172
223
  EqMatcher.new(expected)
173
224
  end
@@ -199,5 +250,9 @@ module Crspec
199
250
  def respond_to(*methods)
200
251
  RespondToMatcher.new(*methods)
201
252
  end
253
+
254
+ def change(receiver = nil, message = nil, &block)
255
+ ChangeMatcher.new(receiver, message, &block)
256
+ end
202
257
  end
203
258
  end
@@ -270,7 +270,14 @@ module Crspec
270
270
  end
271
271
 
272
272
  def instance_double(target_class, name = nil, stubs = {})
273
- Double.new(name || target_class.name, stubs)
273
+ double_name = if target_class.is_a?(String) || target_class.is_a?(Symbol)
274
+ target_class.to_s
275
+ elsif target_class.respond_to?(:name)
276
+ target_class.name
277
+ else
278
+ target_class.to_s
279
+ end
280
+ Double.new(name || double_name, stubs)
274
281
  end
275
282
 
276
283
  def allow(target)
@@ -2,22 +2,13 @@
2
2
 
3
3
  module Crspec
4
4
  module Rails
5
- module DatabaseIsolation
5
+ class DatabaseIsolation
6
6
  def self.wrap_example(example)
7
- if defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:lease_connection)
8
- connection = ActiveRecord::Base.lease_connection
9
- connection.begin_transaction(joinable: false)
10
- savepoint_name = "ex_root"
11
- connection.create_savepoint(savepoint_name) if connection.respond_to?(:create_savepoint)
12
-
13
- begin
14
- example.execute!
15
- ensure
16
- if connection.respond_to?(:transaction_open?) && connection.transaction_open?
17
- connection.rollback_transaction
18
- end
19
- if ActiveRecord::Base.respond_to?(:connection_handler) && ActiveRecord::Base.connection_handler.respond_to?(:release_connection)
20
- ActiveRecord::Base.connection_handler.release_connection
7
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
8
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
9
+ conn.transaction(requires_new: true) do
10
+ example.execute!
11
+ raise ActiveRecord::Rollback
21
12
  end
22
13
  end
23
14
  else
@@ -22,7 +22,7 @@ module Crspec
22
22
  @setup_blocks ||= []
23
23
  @teardown_blocks ||= []
24
24
 
25
- return unless block_given?
25
+ return unless block
26
26
 
27
27
  instance_eval(&block)
28
28
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "stringio"
5
+
6
+ module Crspec
7
+ module Rails
8
+ module RequestHelpers
9
+ if defined?(ActiveSupport::Testing::Assertions)
10
+ include ActiveSupport::Testing::Assertions
11
+ end
12
+
13
+ if defined?(ActiveSupport::Testing::TimeHelpers)
14
+ include ActiveSupport::Testing::TimeHelpers
15
+ end
16
+
17
+ if defined?(ActiveSupport::Testing::FileFixtures)
18
+ include ActiveSupport::Testing::FileFixtures
19
+ end
20
+
21
+ ResponseStruct = Struct.new(:status, :body, :headers)
22
+
23
+ def response
24
+ execution_context[:last_response]
25
+ end
26
+
27
+ def json_response
28
+ return nil unless response&.body
29
+ JSON.parse(response.body, symbolize_names: true)
30
+ rescue JSON::ParserError
31
+ nil
32
+ end
33
+
34
+ def process_request(method, path, params = {}, headers = {})
35
+ body_string = params.is_a?(String) ? params : params.to_json
36
+ env = {
37
+ "REQUEST_METHOD" => method.to_s.upcase,
38
+ "PATH_INFO" => path,
39
+ "rack.input" => StringIO.new(body_string),
40
+ "CONTENT_TYPE" => headers["CONTENT_TYPE"] || "application/json",
41
+ "HTTP_ACCEPT" => headers["HTTP_ACCEPT"] || "application/json",
42
+ "CONTENT_LENGTH" => body_string.bytesize.to_s
43
+ }
44
+
45
+ headers.each do |k, v|
46
+ env["HTTP_#{k.to_s.upcase.tr('-', '_')}"] = v unless k.to_s.start_with?("HTTP_")
47
+ end
48
+
49
+ status = 200
50
+ response_headers = { "Content-Type" => "application/json" }
51
+ response_body = ""
52
+
53
+ begin
54
+ if defined?(::Rails) && ::Rails.application && ::Rails.application.routes.routes.any?
55
+ begin
56
+ status, response_headers, body_obj = ::Rails.application.call(env)
57
+ response_body = body_obj.respond_to?(:body) ? body_obj.body : body_obj.join
58
+ rescue StandardError
59
+ response_body = params.to_json
60
+ end
61
+ else
62
+ response_body = params.to_json
63
+ end
64
+ ensure
65
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:connection_handler)
66
+ ActiveRecord::Base.connection_handler.clear_active_connections!
67
+ end
68
+ end
69
+
70
+ res = ResponseStruct.new(status, response_body, response_headers)
71
+ execution_context[:last_response] = res
72
+ res
73
+ end
74
+
75
+ def get(path, params: {}, headers: {})
76
+ process_request(:get, path, params, headers)
77
+ end
78
+
79
+ def post(path, params: {}, headers: {})
80
+ process_request(:post, path, params, headers)
81
+ end
82
+
83
+ def put(path, params: {}, headers: {})
84
+ process_request(:put, path, params, headers)
85
+ end
86
+
87
+ def patch(path, params: {}, headers: {})
88
+ process_request(:patch, path, params, headers)
89
+ end
90
+
91
+ def delete(path, params: {}, headers: {})
92
+ process_request(:delete, path, params, headers)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -25,7 +25,7 @@ module Crspec
25
25
  when :write
26
26
  write_paths(@paths)
27
27
  else
28
- puts "Usage: crspec-transpile [--analyze|--write] <files or directories>"
28
+ Rails.logger.debug "Usage: crspec-transpile [--analyze|--write] <files or directories>"
29
29
  end
30
30
  end
31
31
 
@@ -75,14 +75,14 @@ module Crspec
75
75
  rewriter.transpile
76
76
  next if rewriter.warnings.empty?
77
77
 
78
- puts "File: #{file}"
78
+ Rails.logger.debug { "File: #{file}" }
79
79
  rewriter.warnings.each do |w|
80
- puts " #{w}"
80
+ Rails.logger.debug { " #{w}" }
81
81
  total_warnings += 1
82
82
  end
83
83
  end
84
84
 
85
- puts "Analysis complete. Total warnings found: #{total_warnings}"
85
+ Rails.logger.debug { "Analysis complete. Total warnings found: #{total_warnings}" }
86
86
  end
87
87
 
88
88
  def write_paths(paths)
@@ -97,11 +97,11 @@ module Crspec
97
97
  next unless new_code != content
98
98
 
99
99
  File.write(file, new_code)
100
- puts "Transpiled: #{file}"
100
+ Rails.logger.debug { "Transpiled: #{file}" }
101
101
  count += 1
102
102
  end
103
103
 
104
- puts "Transpilation complete. Updated #{count} file(s)."
104
+ Rails.logger.debug { "Transpilation complete. Updated #{count} file(s)." }
105
105
  end
106
106
  end
107
107
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crspec
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/crspec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "crspec/version"
4
+ require_relative "crspec/configuration"
4
5
  require_relative "crspec/execution_context"
5
6
  require_relative "crspec/matchers"
6
7
  require_relative "crspec/expectations"
@@ -15,8 +16,10 @@ require_relative "crspec/dsl"
15
16
  require_relative "crspec/rails/database_isolation"
16
17
  require_relative "crspec/rails/system_server"
17
18
  require_relative "crspec/rails/parallel"
19
+ require_relative "crspec/rails/request_helpers"
18
20
  require_relative "crspec/transpiler/rewriter"
19
21
  require_relative "crspec/transpiler/cli"
22
+ require_relative "crspec/generators/init"
20
23
 
21
24
  module Crspec
22
25
  class Error < StandardError; end
@@ -0,0 +1,51 @@
1
+ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2
+
3
+ # Ignore git directory.
4
+ /.git/
5
+ /.gitignore
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all default key files.
14
+ /config/master.key
15
+ /config/credentials/*.key
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /log/*
19
+ /tmp/*
20
+ !/log/.keep
21
+ !/tmp/.keep
22
+
23
+ # Ignore pidfiles, but keep the directory.
24
+ /tmp/pids/*
25
+ !/tmp/pids/.keep
26
+
27
+ # Ignore storage (uploaded files in development and any SQLite databases).
28
+ /storage/*
29
+ !/storage/.keep
30
+ /tmp/storage/*
31
+ !/tmp/storage/.keep
32
+
33
+ # Ignore assets.
34
+ /node_modules/
35
+ /app/assets/builds/*
36
+ !/app/assets/builds/.keep
37
+ /public/assets
38
+
39
+ # Ignore CI service files.
40
+ /.github
41
+
42
+ # Ignore Kamal files.
43
+ /config/deploy*.yml
44
+ /.kamal
45
+
46
+ # Ignore development files
47
+ /.devcontainer
48
+
49
+ # Ignore Docker-related files
50
+ /.dockerignore
51
+ /Dockerfile*
@@ -0,0 +1,9 @@
1
+ # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2
+
3
+ # Mark the database schema as having been generated.
4
+ db/schema.rb linguist-generated
5
+
6
+ # Mark any vendored files as having been vendored.
7
+ vendor/* linguist-vendored
8
+ config/credentials/*.yml.enc diff=rails_credentials
9
+ config/credentials.yml.enc diff=rails_credentials
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: weekly
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,130 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ scan_ruby:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v6
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ bundler-cache: true
20
+
21
+ - name: Scan for common Rails security vulnerabilities using static analysis
22
+ run: bin/brakeman --no-pager
23
+
24
+ - name: Scan for known security vulnerabilities in gems used
25
+ run: bin/bundler-audit
26
+
27
+ scan_js:
28
+ runs-on: ubuntu-latest
29
+
30
+ steps:
31
+ - name: Checkout code
32
+ uses: actions/checkout@v6
33
+
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ bundler-cache: true
38
+
39
+ - name: Scan for security vulnerabilities in JavaScript dependencies
40
+ run: bin/importmap audit
41
+
42
+ lint:
43
+ runs-on: ubuntu-latest
44
+ env:
45
+ RUBOCOP_CACHE_ROOT: tmp/rubocop
46
+ steps:
47
+ - name: Checkout code
48
+ uses: actions/checkout@v6
49
+
50
+ - name: Set up Ruby
51
+ uses: ruby/setup-ruby@v1
52
+ with:
53
+ bundler-cache: true
54
+
55
+ - name: Prepare RuboCop cache
56
+ uses: actions/cache@v4
57
+ env:
58
+ DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }}
59
+ with:
60
+ path: ${{ env.RUBOCOP_CACHE_ROOT }}
61
+ key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
62
+ restore-keys: |
63
+ rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-
64
+
65
+ - name: Lint code for consistent style
66
+ run: bin/rubocop -f github
67
+
68
+ test:
69
+ runs-on: ubuntu-latest
70
+
71
+ # services:
72
+ # redis:
73
+ # image: valkey/valkey:8
74
+ # ports:
75
+ # - 6379:6379
76
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
77
+ steps:
78
+ - name: Install packages
79
+ run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libvips
80
+
81
+ - name: Checkout code
82
+ uses: actions/checkout@v6
83
+
84
+ - name: Set up Ruby
85
+ uses: ruby/setup-ruby@v1
86
+ with:
87
+ bundler-cache: true
88
+
89
+ - name: Run tests
90
+ env:
91
+ RAILS_ENV: test
92
+ # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
93
+ # REDIS_URL: redis://localhost:6379/0
94
+ run: bin/rails db:test:prepare test
95
+
96
+ system-test:
97
+ runs-on: ubuntu-latest
98
+
99
+ # services:
100
+ # redis:
101
+ # image: valkey/valkey:8
102
+ # ports:
103
+ # - 6379:6379
104
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
105
+ steps:
106
+ - name: Install packages
107
+ run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libvips
108
+
109
+ - name: Checkout code
110
+ uses: actions/checkout@v6
111
+
112
+ - name: Set up Ruby
113
+ uses: ruby/setup-ruby@v1
114
+ with:
115
+ bundler-cache: true
116
+
117
+ - name: Run System Tests
118
+ env:
119
+ RAILS_ENV: test
120
+ # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
121
+ # REDIS_URL: redis://localhost:6379/0
122
+ run: bin/rails db:test:prepare test:system
123
+
124
+ - name: Keep screenshots from failed system tests
125
+ uses: actions/upload-artifact@v4
126
+ if: failure()
127
+ with:
128
+ name: screenshots
129
+ path: ${{ github.workspace }}/tmp/screenshots
130
+ if-no-files-found: ignore
@@ -0,0 +1,35 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # Temporary files generated by your text editor or operating system
4
+ # belong in git's global ignore instead:
5
+ # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore`
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*
15
+ /tmp/*
16
+ !/log/.keep
17
+ !/tmp/.keep
18
+
19
+ # Ignore pidfiles, but keep the directory.
20
+ /tmp/pids/*
21
+ !/tmp/pids/
22
+ !/tmp/pids/.keep
23
+
24
+ # Ignore storage (uploaded files in development and any SQLite databases).
25
+ /storage/*
26
+ !/storage/.keep
27
+ /tmp/storage/*
28
+ !/tmp/storage/
29
+ !/tmp/storage/.keep
30
+
31
+ /public/assets
32
+
33
+ # Ignore key files for decrypting credentials and more.
34
+ /config/*.key
35
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ echo "Docker set up on $KAMAL_HOSTS..."
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # A sample post-deploy hook
4
+ #
5
+ # These environment variables are available:
6
+ # KAMAL_RECORDED_AT
7
+ # KAMAL_PERFORMER
8
+ # KAMAL_VERSION
9
+ # KAMAL_HOSTS
10
+ # KAMAL_ROLES (if set)
11
+ # KAMAL_DESTINATION (if set)
12
+ # KAMAL_RUNTIME
13
+
14
+ echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ echo "Rebooted kamal-proxy on $KAMAL_HOSTS"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."