shipit-engine 0.38.0 → 0.40.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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -4
  3. data/Rakefile +2 -1
  4. data/app/assets/javascripts/shipit/continuous_delivery_schedule.js.coffee +15 -0
  5. data/app/controllers/concerns/shipit/active_model_serializers_patch.rb +1 -0
  6. data/app/controllers/concerns/shipit/api/cacheable.rb +1 -0
  7. data/app/controllers/concerns/shipit/api/paginable.rb +3 -2
  8. data/app/controllers/concerns/shipit/api/rendering.rb +1 -0
  9. data/app/controllers/concerns/shipit/authentication.rb +1 -0
  10. data/app/controllers/concerns/shipit/pagination.rb +3 -2
  11. data/app/controllers/shipit/api/base_controller.rb +12 -10
  12. data/app/controllers/shipit/api/ccmenu_controller.rb +2 -1
  13. data/app/controllers/shipit/api/commits_controller.rb +2 -3
  14. data/app/controllers/shipit/api/deploys_controller.rb +6 -1
  15. data/app/controllers/shipit/api/hooks_controller.rb +4 -3
  16. data/app/controllers/shipit/api/locks_controller.rb +1 -0
  17. data/app/controllers/shipit/api/merge_requests_controller.rb +6 -5
  18. data/app/controllers/shipit/api/outputs_controller.rb +1 -0
  19. data/app/controllers/shipit/api/release_statuses_controller.rb +2 -1
  20. data/app/controllers/shipit/api/rollbacks_controller.rb +1 -0
  21. data/app/controllers/shipit/api/stacks_controller.rb +15 -14
  22. data/app/controllers/shipit/api/tasks_controller.rb +6 -5
  23. data/app/controllers/shipit/api_clients_controller.rb +6 -7
  24. data/app/controllers/shipit/ccmenu_url_controller.rb +3 -2
  25. data/app/controllers/shipit/commit_checks_controller.rb +2 -1
  26. data/app/controllers/shipit/commits_controller.rb +1 -0
  27. data/app/controllers/shipit/continuous_delivery_schedules_controller.rb +42 -0
  28. data/app/controllers/shipit/deploys_controller.rb +6 -5
  29. data/app/controllers/shipit/github_authentication_controller.rb +6 -0
  30. data/app/controllers/shipit/merge_requests_controller.rb +1 -0
  31. data/app/controllers/shipit/merge_status_controller.rb +30 -26
  32. data/app/controllers/shipit/release_statuses_controller.rb +1 -0
  33. data/app/controllers/shipit/repositories_controller.rb +4 -7
  34. data/app/controllers/shipit/rollbacks_controller.rb +2 -1
  35. data/app/controllers/shipit/shipit_controller.rb +1 -0
  36. data/app/controllers/shipit/stacks_controller.rb +27 -31
  37. data/app/controllers/shipit/status_controller.rb +1 -0
  38. data/app/controllers/shipit/tasks_controller.rb +3 -1
  39. data/app/controllers/shipit/webhooks_controller.rb +2 -1
  40. data/app/helpers/shipit/api_clients_helper.rb +1 -0
  41. data/app/helpers/shipit/chunks_helper.rb +3 -1
  42. data/app/helpers/shipit/deploys_helper.rb +7 -3
  43. data/app/helpers/shipit/github_url_helper.rb +5 -4
  44. data/app/helpers/shipit/merge_status_helper.rb +1 -0
  45. data/app/helpers/shipit/shipit_helper.rb +11 -10
  46. data/app/helpers/shipit/stacks_helper.rb +10 -11
  47. data/app/helpers/shipit/tasks_helper.rb +2 -1
  48. data/app/jobs/shipit/background_job/unique.rb +3 -2
  49. data/app/jobs/shipit/background_job.rb +9 -1
  50. data/app/jobs/shipit/cache_deploy_spec_job.rb +2 -1
  51. data/app/jobs/shipit/chunk_rollup_job.rb +1 -0
  52. data/app/jobs/shipit/clear_git_cache_job.rb +1 -0
  53. data/app/jobs/shipit/continuous_delivery_job.rb +5 -0
  54. data/app/jobs/shipit/create_on_github_job.rb +1 -0
  55. data/app/jobs/shipit/create_release_statuses_job.rb +2 -0
  56. data/app/jobs/shipit/deferred_touch_job.rb +1 -0
  57. data/app/jobs/shipit/deliver_hook_job.rb +1 -0
  58. data/app/jobs/shipit/destroy_job.rb +1 -0
  59. data/app/jobs/shipit/destroy_repository_job.rb +1 -0
  60. data/app/jobs/shipit/destroy_stack_job.rb +36 -15
  61. data/app/jobs/shipit/emit_event_job.rb +1 -0
  62. data/app/jobs/shipit/fetch_commit_stats_job.rb +1 -0
  63. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -0
  64. data/app/jobs/shipit/github_sync_job.rb +4 -2
  65. data/app/jobs/shipit/mark_deploy_healthy_job.rb +1 -0
  66. data/app/jobs/shipit/perform_commit_checks_job.rb +1 -0
  67. data/app/jobs/shipit/perform_task_job.rb +1 -0
  68. data/app/jobs/shipit/process_merge_requests_job.rb +2 -0
  69. data/app/jobs/shipit/purge_old_deliveries_job.rb +1 -0
  70. data/app/jobs/shipit/reap_dead_tasks_job.rb +1 -0
  71. data/app/jobs/shipit/refresh_check_runs_job.rb +1 -0
  72. data/app/jobs/shipit/refresh_github_user_job.rb +1 -0
  73. data/app/jobs/shipit/refresh_merge_request_job.rb +1 -0
  74. data/app/jobs/shipit/refresh_statuses_job.rb +1 -0
  75. data/app/jobs/shipit/setup_github_hook_job.rb +1 -0
  76. data/app/jobs/shipit/update_estimated_deploy_duration_job.rb +1 -0
  77. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +6 -7
  78. data/app/models/concerns/shipit/deferred_touch.rb +5 -2
  79. data/app/models/shipit/anonymous_user.rb +4 -5
  80. data/app/models/shipit/api_client.rb +5 -3
  81. data/app/models/shipit/application_record.rb +1 -0
  82. data/app/models/shipit/check_run.rb +7 -6
  83. data/app/models/shipit/command_line_user.rb +4 -5
  84. data/app/models/shipit/commit.rb +46 -32
  85. data/app/models/shipit/commit_checks.rb +4 -2
  86. data/app/models/shipit/commit_deployment.rb +7 -5
  87. data/app/models/shipit/commit_deployment_status.rb +5 -3
  88. data/app/models/shipit/commit_message.rb +2 -0
  89. data/app/models/shipit/continuous_delivery_schedule.rb +84 -0
  90. data/app/models/shipit/delivery.rb +5 -4
  91. data/app/models/shipit/deploy.rb +46 -26
  92. data/app/models/shipit/deploy_spec/bundler_discovery.rb +3 -1
  93. data/app/models/shipit/deploy_spec/capistrano_discovery.rb +1 -0
  94. data/app/models/shipit/deploy_spec/file_system.rb +48 -17
  95. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +4 -3
  96. data/app/models/shipit/deploy_spec/lerna_discovery.rb +32 -31
  97. data/app/models/shipit/deploy_spec/npm_discovery.rb +18 -13
  98. data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -4
  99. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +1 -0
  100. data/app/models/shipit/deploy_spec.rb +25 -30
  101. data/app/models/shipit/deploy_stats.rb +6 -1
  102. data/app/models/shipit/duration.rb +5 -3
  103. data/app/models/shipit/ephemeral_commit_checks.rb +8 -7
  104. data/app/models/shipit/github_hook.rb +1 -0
  105. data/app/models/shipit/github_status.rb +1 -0
  106. data/app/models/shipit/hook.rb +9 -7
  107. data/app/models/shipit/membership.rb +1 -0
  108. data/app/models/shipit/merge_request.rb +26 -16
  109. data/app/models/shipit/output_chunk.rb +1 -0
  110. data/app/models/shipit/provisioning_handler.rb +1 -0
  111. data/app/models/shipit/pull_request.rb +2 -2
  112. data/app/models/shipit/record.rb +1 -0
  113. data/app/models/shipit/release_status.rb +4 -3
  114. data/app/models/shipit/repository.rb +12 -11
  115. data/app/models/shipit/review_stack.rb +3 -1
  116. data/app/models/shipit/review_stack_provisioning_queue.rb +2 -2
  117. data/app/models/shipit/rollback.rb +2 -0
  118. data/app/models/shipit/stack.rb +71 -60
  119. data/app/models/shipit/status/common.rb +1 -0
  120. data/app/models/shipit/status/group.rb +5 -3
  121. data/app/models/shipit/status/missing.rb +2 -1
  122. data/app/models/shipit/status/unknown.rb +1 -0
  123. data/app/models/shipit/status.rb +5 -4
  124. data/app/models/shipit/task.rb +40 -31
  125. data/app/models/shipit/task_definition.rb +10 -7
  126. data/app/models/shipit/task_execution_strategy/default.rb +13 -13
  127. data/app/models/shipit/team.rb +13 -12
  128. data/app/models/shipit/undeployed_commit.rb +8 -3
  129. data/app/models/shipit/unlimited_api_client.rb +2 -2
  130. data/app/models/shipit/user.rb +23 -16
  131. data/app/models/shipit/variable_definition.rb +2 -1
  132. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +1 -0
  133. data/app/models/shipit/webhooks/handlers/handler.rb +1 -0
  134. data/app/models/shipit/webhooks/handlers/membership_handler.rb +1 -0
  135. data/app/models/shipit/webhooks/handlers/pull_request/assigned_handler.rb +10 -10
  136. data/app/models/shipit/webhooks/handlers/pull_request/closed_handler.rb +1 -1
  137. data/app/models/shipit/webhooks/handlers/pull_request/edited_handler.rb +10 -10
  138. data/app/models/shipit/webhooks/handlers/pull_request/label_capturing_handler.rb +2 -2
  139. data/app/models/shipit/webhooks/handlers/pull_request/labeled_handler.rb +2 -2
  140. data/app/models/shipit/webhooks/handlers/pull_request/reopened_handler.rb +1 -1
  141. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +3 -3
  142. data/app/models/shipit/webhooks/handlers/pull_request/unlabeled_handler.rb +1 -1
  143. data/app/models/shipit/webhooks/handlers/push_handler.rb +2 -1
  144. data/app/models/shipit/webhooks/handlers/status_handler.rb +1 -0
  145. data/app/models/shipit/webhooks.rb +3 -2
  146. data/app/serializers/concerns/shipit/conditional_attributes.rb +1 -0
  147. data/app/serializers/shipit/anonymous_user_serializer.rb +1 -0
  148. data/app/serializers/shipit/command_line_user_serializer.rb +1 -0
  149. data/app/serializers/shipit/commit_serializer.rb +2 -1
  150. data/app/serializers/shipit/deploy_serializer.rb +1 -0
  151. data/app/serializers/shipit/hook_serializer.rb +1 -0
  152. data/app/serializers/shipit/merge_request_serializer.rb +2 -1
  153. data/app/serializers/shipit/rollback_serializer.rb +1 -0
  154. data/app/serializers/shipit/short_commit_serializer.rb +1 -0
  155. data/app/serializers/shipit/stack_serializer.rb +4 -3
  156. data/app/serializers/shipit/tail_task_serializer.rb +4 -1
  157. data/app/serializers/shipit/task_serializer.rb +1 -0
  158. data/app/serializers/shipit/user_serializer.rb +1 -0
  159. data/app/validators/ascii_only_validator.rb +4 -3
  160. data/app/validators/subset_validator.rb +1 -0
  161. data/app/views/shipit/_variables.html.erb +1 -1
  162. data/app/views/shipit/ccmenu/project.xml.builder +2 -1
  163. data/app/views/shipit/continuous_delivery_schedules/show.html.erb +59 -0
  164. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  165. data/app/views/shipit/missing_settings.html.erb +1 -1
  166. data/app/views/shipit/stacks/_settings_form.erb +1 -0
  167. data/config/initializers/inflections.rb +1 -0
  168. data/config/locales/en.yml +1 -0
  169. data/config/routes.rb +21 -18
  170. data/config/secrets.development.example.yml +1 -1
  171. data/config/secrets.development.shopify.yml +1 -1
  172. data/db/migrate/20240821003007_add_continuous_delivery_schedules.rb +13 -0
  173. data/db/migrate/20250207203053_embiggen_github_ids.rb +8 -0
  174. data/lib/shipit/cast_value.rb +1 -0
  175. data/lib/shipit/command.rb +29 -9
  176. data/lib/shipit/commands.rb +4 -2
  177. data/lib/shipit/csv_serializer.rb +3 -0
  178. data/lib/shipit/deploy_commands.rb +2 -1
  179. data/lib/shipit/engine.rb +6 -5
  180. data/lib/shipit/environment_variables.rb +2 -0
  181. data/lib/shipit/first_parent_commits_iterator.rb +2 -3
  182. data/lib/shipit/flock.rb +11 -9
  183. data/lib/shipit/github_app.rb +14 -16
  184. data/lib/shipit/github_http_cache_middleware.rb +1 -0
  185. data/lib/shipit/null_serializer.rb +1 -0
  186. data/lib/shipit/octokit_check_runs.rb +2 -3
  187. data/lib/shipit/octokit_iterator.rb +2 -0
  188. data/lib/shipit/paginator.rb +1 -0
  189. data/lib/shipit/rollback_commands.rb +2 -1
  190. data/lib/shipit/same_site_cookie_middleware.rb +1 -0
  191. data/lib/shipit/simple_message_verifier.rb +1 -0
  192. data/lib/shipit/stack_commands.rb +35 -27
  193. data/lib/shipit/stat.rb +1 -0
  194. data/lib/shipit/task_commands.rb +7 -6
  195. data/lib/shipit/version.rb +2 -1
  196. data/lib/shipit.rb +30 -17
  197. data/lib/tasks/cron.rake +2 -1
  198. data/lib/tasks/dev.rake +3 -2
  199. data/lib/tasks/shipit.rake +3 -2
  200. data/lib/tasks/teams.rake +3 -2
  201. data/test/controllers/api/base_controller_test.rb +1 -0
  202. data/test/controllers/api/ccmenu_controller_test.rb +4 -3
  203. data/test/controllers/api/commits_controller_test.rb +1 -0
  204. data/test/controllers/api/deploys_controller_test.rb +26 -1
  205. data/test/controllers/api/hooks_controller_test.rb +6 -5
  206. data/test/controllers/api/locks_controller_test.rb +1 -0
  207. data/test/controllers/api/merge_requests_controller_test.rb +1 -0
  208. data/test/controllers/api/outputs_controller_test.rb +1 -0
  209. data/test/controllers/api/release_statuses_controller_test.rb +4 -3
  210. data/test/controllers/api/rollback_controller_test.rb +3 -2
  211. data/test/controllers/api/stacks_controller_test.rb +13 -12
  212. data/test/controllers/api/tasks_controller_test.rb +7 -6
  213. data/test/controllers/api_clients_controller_test.rb +10 -10
  214. data/test/controllers/ccmenu_controller_test.rb +1 -0
  215. data/test/controllers/commit_checks_controller_test.rb +1 -0
  216. data/test/controllers/commits_controller_test.rb +9 -8
  217. data/test/controllers/continuous_delivery_schedules_controller_test.rb +66 -0
  218. data/test/controllers/deploys_controller_test.rb +4 -2
  219. data/test/controllers/github_authentication_controller_test.rb +6 -4
  220. data/test/controllers/merge_requests_controller_test.rb +1 -0
  221. data/test/controllers/merge_status_controller_test.rb +5 -4
  222. data/test/controllers/release_statuses_controller_test.rb +1 -0
  223. data/test/controllers/repositories_controller_test.rb +6 -5
  224. data/test/controllers/rollbacks_controller_test.rb +3 -2
  225. data/test/controllers/stacks_controller_test.rb +8 -6
  226. data/test/controllers/status_controller_test.rb +1 -0
  227. data/test/controllers/tasks_controller_test.rb +13 -5
  228. data/test/controllers/webhooks_controller_test.rb +10 -9
  229. data/test/dummy/config/application.rb +2 -1
  230. data/test/dummy/config/initializers/0_load_development_secrets.rb +2 -2
  231. data/test/dummy/config/secrets.development.json +3 -0
  232. data/test/dummy/config/secrets.test.json +21 -0
  233. data/test/dummy/db/schema.rb +33 -6
  234. data/test/fixtures/shipit/commits.yml +7 -7
  235. data/test/fixtures/shipit/stacks.yml +4 -10
  236. data/test/fixtures/shipit/tasks.yml +3 -3
  237. data/test/helpers/api_helper.rb +2 -3
  238. data/test/helpers/fixture_aliases_helper.rb +1 -0
  239. data/test/helpers/hooks_helper.rb +1 -0
  240. data/test/helpers/json_helper.rb +4 -3
  241. data/test/helpers/links_helper.rb +2 -1
  242. data/test/helpers/payloads_helper.rb +1 -0
  243. data/test/helpers/queries_helper.rb +4 -3
  244. data/test/jobs/cache_deploy_spec_job_test.rb +3 -2
  245. data/test/jobs/chunk_rollup_job_test.rb +3 -2
  246. data/test/jobs/deliver_hook_job_test.rb +1 -0
  247. data/test/jobs/destroy_repository_job_test.rb +1 -0
  248. data/test/jobs/destroy_stack_job_test.rb +12 -0
  249. data/test/jobs/emit_event_job_test.rb +1 -0
  250. data/test/jobs/fetch_commit_stats_job_test.rb +1 -0
  251. data/test/jobs/fetch_deployed_revision_job_test.rb +1 -0
  252. data/test/jobs/github_sync_job_test.rb +22 -21
  253. data/test/jobs/mark_deploy_healthy_job_test.rb +1 -0
  254. data/test/jobs/perform_task_job_test.rb +3 -3
  255. data/test/jobs/process_merge_requests_job_test.rb +7 -6
  256. data/test/jobs/purge_old_deliveries_job_test.rb +1 -0
  257. data/test/jobs/reap_dead_tasks_job_test.rb +1 -0
  258. data/test/jobs/refresh_github_user_job_test.rb +1 -0
  259. data/test/jobs/refresh_status_job_test.rb +1 -0
  260. data/test/jobs/shipit/background_job_test.rb +35 -0
  261. data/test/jobs/shipit/continuous_delivery_job_test.rb +31 -0
  262. data/test/jobs/unique_job_test.rb +3 -1
  263. data/test/jobs/update_github_last_deployed_ref_job_test.rb +1 -0
  264. data/test/middleware/same_site_cookie_middleware_test.rb +2 -2
  265. data/test/models/api_client_test.rb +1 -0
  266. data/test/models/commit_checks_test.rb +2 -1
  267. data/test/models/commit_deployment_status_test.rb +2 -2
  268. data/test/models/commit_deployment_test.rb +4 -3
  269. data/test/models/commits_test.rb +72 -70
  270. data/test/models/delivery_test.rb +3 -2
  271. data/test/models/deploy_spec_test.rb +113 -109
  272. data/test/models/deploy_stats_test.rb +1 -0
  273. data/test/models/deploys_test.rb +65 -56
  274. data/test/models/duration_test.rb +1 -1
  275. data/test/models/github_hook_test.rb +1 -0
  276. data/test/models/hook_test.rb +7 -4
  277. data/test/models/membership_test.rb +1 -0
  278. data/test/models/merge_request_test.rb +26 -20
  279. data/test/models/release_statuses_test.rb +2 -1
  280. data/test/models/rollbacks_test.rb +4 -3
  281. data/test/models/shipit/check_run_test.rb +16 -15
  282. data/test/models/shipit/continuous_delivery_schedule_test.rb +109 -0
  283. data/test/models/shipit/deploy_spec/file_system_test.rb +54 -10
  284. data/test/models/shipit/pull_request_test.rb +9 -9
  285. data/test/models/shipit/repository_test.rb +3 -2
  286. data/test/models/shipit/review_stack_provisioning_queue_test.rb +2 -2
  287. data/test/models/shipit/{stacks_test.rb → stack_test.rb} +48 -34
  288. data/test/models/shipit/webhooks/handlers/pull_request/closed_handler_test.rb +36 -34
  289. data/test/models/shipit/webhooks/handlers/pull_request/label_capturing_handler_test.rb +28 -28
  290. data/test/models/shipit/webhooks/handlers/pull_request/labeled_handler_test.rb +42 -42
  291. data/test/models/shipit/webhooks/handlers/pull_request/opened_handler_test.rb +33 -33
  292. data/test/models/shipit/webhooks/handlers/pull_request/reopened_handler_test.rb +37 -37
  293. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +1 -1
  294. data/test/models/shipit/webhooks/handlers/pull_request/unlabeled_handler_test.rb +44 -42
  295. data/test/models/shipit/webhooks/handlers_test.rb +1 -0
  296. data/test/models/status/group_test.rb +3 -2
  297. data/test/models/status/missing_test.rb +1 -0
  298. data/test/models/status_test.rb +2 -1
  299. data/test/models/task_definitions_test.rb +7 -6
  300. data/test/models/tasks_test.rb +5 -4
  301. data/test/models/team_test.rb +5 -4
  302. data/test/models/undeployed_commits_test.rb +10 -9
  303. data/test/models/users_test.rb +29 -20
  304. data/test/test_command_integration.rb +1 -1
  305. data/test/test_helper.rb +12 -10
  306. data/test/unit/anonymous_user_serializer_test.rb +1 -0
  307. data/test/unit/command_test.rb +10 -1
  308. data/test/unit/commands_test.rb +1 -0
  309. data/test/unit/commit_serializer_test.rb +1 -0
  310. data/test/unit/csv_serializer_test.rb +3 -2
  311. data/test/unit/deploy_commands_test.rb +33 -23
  312. data/test/unit/deploy_serializer_test.rb +1 -0
  313. data/test/unit/environment_variables_test.rb +2 -1
  314. data/test/unit/github_app_test.rb +11 -10
  315. data/test/unit/github_apps_test.rb +19 -18
  316. data/test/unit/github_url_helper_test.rb +1 -0
  317. data/test/unit/line_buffer_test.rb +1 -1
  318. data/test/unit/rollback_commands_test.rb +2 -1
  319. data/test/unit/shipit_helper_test.rb +1 -0
  320. data/test/unit/shipit_test.rb +47 -1
  321. data/test/unit/user_serializer_test.rb +1 -0
  322. data/test/unit/variable_definition_test.rb +4 -3
  323. metadata +61 -47
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class GitHubHTTPCacheMiddleware < Faraday::Middleware
4
5
  def call(request_env)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  module NullSerializer
4
5
  extend self
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module OctokitCheckRuns
3
4
  def check_runs(repo, sha, options = {})
4
- paginate("#{Octokit::Repository.path(repo)}/commits/#{sha}/check-runs", options.reverse_merge(
5
- accept: 'application/vnd.github.antiope-preview+json',
6
- ))
5
+ paginate("#{Octokit::Repository.path(repo)}/commits/#{sha}/check-runs", options)
7
6
  end
8
7
  end
9
8
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class OctokitIterator
4
5
  include Enumerable
@@ -20,6 +21,7 @@ module Shipit
20
21
 
21
22
  response.data.each(&block)
22
23
  return unless response.rels[:next]
24
+
23
25
  response = response.rels[:next].get
24
26
  end
25
27
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class Paginator
4
5
  def initialize(resources, controller, order: { id: :desc }, max_page_size: 100, default_page_size: 30)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class RollbackCommands < DeployCommands
4
5
  def steps
@@ -7,7 +8,7 @@ module Shipit
7
8
 
8
9
  def env
9
10
  super.merge(
10
- 'ROLLBACK' => '1',
11
+ 'ROLLBACK' => '1'
11
12
  )
12
13
  end
13
14
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class SameSiteCookieMiddleware
4
5
  COOKIE_SEPARATOR = "\n"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  class SimpleMessageVerifier < ActiveSupport::MessageVerifier
4
5
  def initialize(secret, **options)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- # rubocop:disable Lint/MissingSuper
2
+
3
+ # rubocop:disable Lint/MissingCopEnableDirective, Lint/MissingSuper
3
4
  require 'pathname'
4
5
  require 'fileutils'
5
6
 
@@ -16,26 +17,26 @@ module Shipit
16
17
  def fetch_commit(commit)
17
18
  create_directories
18
19
  if valid_git_repository?(@stack.git_path)
19
- git('fetch', 'origin', '--quiet', '--tags', commit.sha, env: env, chdir: @stack.git_path)
20
+ git('fetch', 'origin', *quiet_git_arg, '--tags', '--force', commit.sha, env:, chdir: @stack.git_path)
20
21
  else
21
22
  @stack.clear_git_cache!
22
- git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env: env, chdir: @stack.deploys_path)
23
+ git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env:, chdir: @stack.deploys_path)
23
24
  end
24
25
  end
25
26
 
26
27
  def fetch
27
28
  create_directories
28
29
  if valid_git_repository?(@stack.git_path)
29
- git('fetch', 'origin', '--quiet', '--tags', @stack.branch, env: env, chdir: @stack.git_path)
30
+ git('fetch', 'origin', *quiet_git_arg, '--tags', '--force', @stack.branch, env:, chdir: @stack.git_path)
30
31
  else
31
32
  @stack.clear_git_cache!
32
- git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env: env, chdir: @stack.deploys_path)
33
+ git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env:, chdir: @stack.deploys_path)
33
34
  end
34
35
  end
35
36
 
36
37
  def fetched?(commit)
37
38
  if valid_git_repository?(@stack.git_path)
38
- git('rev-parse', '--quiet', '--verify', "#{commit.sha}^{commit}", env: env, chdir: @stack.git_path)
39
+ git('rev-parse', *quiet_git_arg, '--verify', "#{commit.sha}^{commit}", env:, chdir: @stack.git_path)
39
40
  else
40
41
  # When the stack's git cache is not valid, the commit is
41
42
  # NOT fetched. To keep the interface of this method
@@ -43,53 +44,55 @@ module Shipit
43
44
  # method returns false - has a non-zero exit status. We utilize
44
45
  # the POSIX 'test' command with no arguments which should
45
46
  # always have an exit status of 1.
46
- Command.new('test', env: env, chdir: @stack.deploys_path)
47
+ Command.new('test', env:, chdir: @stack.deploys_path)
47
48
  end
48
49
  end
49
50
 
50
51
  def fetch_deployed_revision
51
52
  with_temporary_working_directory(commit: @stack.commits.reachable.last) do |dir|
52
- spec = DeploySpec::FileSystem.new(dir, @stack.environment)
53
+ spec = DeploySpec::FileSystem.new(dir, @stack)
53
54
  outputs = spec.fetch_deployed_revision_steps!.map do |command_line|
54
- Command.new(command_line, env: env, chdir: dir).run
55
+ Command.new(command_line, env:, chdir: dir).run
55
56
  end
56
57
  outputs.find(&:present?).try(:strip)
57
58
  end
58
59
  end
59
60
 
60
61
  def build_cacheable_deploy_spec
61
- with_temporary_working_directory do |dir|
62
- DeploySpec::FileSystem.new(dir, @stack.environment).cacheable
62
+ with_temporary_working_directory(recursive: false) do |dir|
63
+ DeploySpec::FileSystem.new(dir, @stack).cacheable
63
64
  end
64
65
  end
65
66
 
66
- def with_temporary_working_directory(commit: nil)
67
+ def with_temporary_working_directory(commit: nil, recursive: true)
67
68
  commit ||= @stack.last_deployed_commit.presence || @stack.commits.reachable.last
68
69
 
69
70
  if !commit || !fetched?(commit).tap(&:run).success?
70
71
  @stack.acquire_git_cache_lock do
71
- unless fetched?(commit).tap(&:run).success?
72
- fetch.run!
73
- end
72
+ fetch.run! unless fetched?(commit).tap(&:run).success?
74
73
  end
75
74
  end
76
75
 
76
+ git_args = []
77
+ git_args << '--recursive' if recursive
77
78
  Dir.mktmpdir do |dir|
78
79
  git(
79
80
  'clone', @stack.git_path, @stack.repo_name,
80
- '--recursive', '--origin', 'cache',
81
+ *git_args, '--origin', 'cache',
81
82
  chdir: dir
82
83
  ).run!
83
84
 
84
85
  git_dir = File.join(dir, @stack.repo_name)
85
- git(
86
- '-c',
87
- 'advice.detachedHead=false',
88
- 'checkout',
89
- '--quiet',
90
- commit.sha,
91
- chdir: git_dir
92
- ).run! if commit
86
+ if commit
87
+ git(
88
+ '-c',
89
+ 'advice.detachedHead=false',
90
+ 'checkout',
91
+ *quiet_git_arg,
92
+ commit.sha,
93
+ chdir: git_dir
94
+ ).run!
95
+ end
93
96
  yield Pathname.new(git_dir)
94
97
  end
95
98
  end
@@ -106,19 +109,24 @@ module Shipit
106
109
  .success?
107
110
  end
108
111
 
109
- def git_clone(url, path, branch: 'master', **kwargs)
110
- git('clone', '--quiet', *modern_git_args, '--recursive', '--branch', branch, url, path, **kwargs)
112
+ def git_clone(url, path, branch: 'main', **kwargs)
113
+ git('clone', *quiet_git_arg, *modern_git_args, '--recursive', '--branch', branch, url, path, **kwargs)
111
114
  end
112
115
 
113
116
  def modern_git_args
114
117
  return [] unless git_version >= Gem::Version.new('1.7.10')
115
- %w(--single-branch)
118
+
119
+ %w[--single-branch]
116
120
  end
117
121
 
118
122
  def create_directories
119
123
  FileUtils.mkdir_p(@stack.deploys_path)
120
124
  end
121
125
 
126
+ def quiet_git_arg
127
+ Shipit.git_progress_output ? [] : ['--quiet']
128
+ end
129
+
122
130
  private
123
131
 
124
132
  def github
data/lib/shipit/stat.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
4
  module Stat
4
5
  extend self
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- # rubocop:disable Lint/MissingSuper
2
+
3
+ # rubocop:disable Lint/MissingCopEnableDirective, Lint/MissingSuper
3
4
  module Shipit
4
5
  class TaskCommands < Commands
5
6
  delegate :fetch_commit, :fetch, :fetched?, to: :stack_commands
@@ -10,18 +11,18 @@ module Shipit
10
11
  end
11
12
 
12
13
  def deploy_spec
13
- @deploy_spec ||= DeploySpec::FileSystem.new(@task.working_directory, @stack.environment)
14
+ @deploy_spec ||= DeploySpec::FileSystem.new(@task.working_directory, @stack)
14
15
  end
15
16
 
16
17
  def install_dependencies
17
18
  deploy_spec.dependencies_steps!.map do |command_line|
18
- Command.new(command_line, env: env, chdir: steps_directory)
19
+ Command.new(command_line, env:, chdir: steps_directory)
19
20
  end
20
21
  end
21
22
 
22
23
  def perform
23
24
  steps.map do |command_line|
24
- Command.new(command_line, env: env, chdir: steps_directory)
25
+ Command.new(command_line, env:, chdir: steps_directory)
25
26
  end
26
27
  end
27
28
 
@@ -40,7 +41,7 @@ module Shipit
40
41
  'TASK_ID' => @task.id.to_s,
41
42
  'IGNORED_SAFETIES' => @task.ignored_safeties? ? '1' : '0',
42
43
  'GIT_COMMITTER_NAME' => @task.user&.name || Shipit.committer_name,
43
- 'GIT_COMMITTER_EMAIL' => @task.user&.email || Shipit.committer_email,
44
+ 'GIT_COMMITTER_EMAIL' => @task.user&.email || Shipit.committer_email
44
45
  )
45
46
  .merge(deploy_spec.machine_env)
46
47
  .merge(@task.env)
@@ -68,7 +69,7 @@ module Shipit
68
69
  @task.working_directory,
69
70
  chdir: @stack.deploys_path
70
71
  ),
71
- git('remote', 'add', 'origin', @stack.repo_git_url, chdir: @task.working_directory),
72
+ git('remote', 'add', 'origin', @stack.repo_git_url, chdir: @task.working_directory)
72
73
  ]
73
74
  end
74
75
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shipit
3
- VERSION = '0.38.0'
4
+ VERSION = '0.40.0'
4
5
  end
data/lib/shipit.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/all'
3
4
  require 'active_model_serializers'
4
5
  require 'state_machines-activerecord'
@@ -60,17 +61,17 @@ module Shipit
60
61
  extend self
61
62
 
62
63
  GithubOrganizationUnknown = Class.new(StandardError)
63
- TOP_LEVEL_GH_KEYS = [:app_id, :installation_id, :webhook_secret, :private_key, :oauth, :domain]
64
+ TOP_LEVEL_GH_KEYS = [:app_id, :installation_id, :webhook_secret, :private_key, :oauth, :domain].freeze
64
65
 
65
66
  delegate :table_name_prefix, to: :secrets
66
67
 
67
68
  attr_accessor :disable_api_authentication, :timeout_exit_codes, :deployment_checks, :respect_bare_shipit_file,
68
- :database_serializer
69
+ :database_serializer
69
70
  attr_writer(
70
71
  :internal_hook_receivers,
71
72
  :preferred_org_emails,
72
73
  :task_execution_strategy,
73
- :task_logger,
74
+ :task_logger
74
75
  )
75
76
 
76
77
  def task_execution_strategy
@@ -80,7 +81,7 @@ module Shipit
80
81
  self.timeout_exit_codes = [].freeze
81
82
  self.respect_bare_shipit_file = true
82
83
 
83
- alias_method :respect_bare_shipit_file?, :respect_bare_shipit_file
84
+ alias respect_bare_shipit_file? respect_bare_shipit_file
84
85
 
85
86
  def authentication_disabled?
86
87
  ENV['SHIPIT_DISABLE_AUTH'].present?
@@ -95,7 +96,7 @@ module Shipit
95
96
  end
96
97
 
97
98
  def redis_url
98
- secrets.redis_url.present? ? URI(secrets.redis_url) : nil
99
+ secrets.redis_url.present? ? URI(secrets.redis_url) : ENV["REDIS_URL"]
99
100
  end
100
101
 
101
102
  def redis
@@ -104,14 +105,19 @@ module Shipit
104
105
  logger: Rails.logger,
105
106
  reconnect_attempts: 3,
106
107
  reconnect_delay: 0.5,
107
- reconnect_delay_max: 1,
108
+ reconnect_delay_max: 1
108
109
  )
109
110
  end
110
111
 
112
+ def redis=(client)
113
+ @redis ||= client
114
+ end
115
+
111
116
  module SafeJSON
112
117
  class << self
113
118
  def load(serial)
114
119
  return nil if serial.nil?
120
+
115
121
  # JSON.load is unsafe, we should use parse instead
116
122
  JSON.parse(serial)
117
123
  end
@@ -136,6 +142,7 @@ module Shipit
136
142
 
137
143
  def dump(object)
138
144
  return if object.nil?
145
+
139
146
  JSON.dump(object)
140
147
  end
141
148
  end
@@ -144,7 +151,7 @@ module Shipit
144
151
  self.database_serializer = TransitionalSerializer
145
152
 
146
153
  def serialized_column(attribute_name, type: nil, coder: nil)
147
- column = Paquito::SerializedColumn.new(database_serializer, type, attribute_name: attribute_name)
154
+ column = Paquito::SerializedColumn.new(database_serializer, type, attribute_name:)
148
155
  if coder
149
156
  Paquito.chain(coder, column)
150
157
  else
@@ -167,12 +174,14 @@ module Shipit
167
174
 
168
175
  def github_default_organization
169
176
  return nil unless secrets&.github
177
+
170
178
  org = secrets.github.keys.first
171
179
  TOP_LEVEL_GH_KEYS.include?(org) ? nil : org
172
180
  end
173
181
 
174
182
  def github_organizations
175
183
  return [nil] unless github_default_organization
184
+
176
185
  secrets.github.keys
177
186
  end
178
187
 
@@ -183,9 +192,9 @@ module Shipit
183
192
  end
184
193
 
185
194
  def legacy_github_api
186
- if secrets&.github_api.present?
187
- @legacy_github_api ||= github.new_client(access_token: secrets.github_api[:access_token])
188
- end
195
+ return unless secrets&.github_api.present?
196
+
197
+ @legacy_github_api ||= github.new_client(access_token: secrets.github_api[:access_token])
189
198
  end
190
199
 
191
200
  def user
@@ -204,7 +213,7 @@ module Shipit
204
213
  if secrets.user_access_tokens_key.present?
205
214
  secrets.user_access_tokens_key
206
215
  elsif secrets.secret_key_base
207
- Digest::SHA256.digest("user_access_tokens_key" + secrets.secret_key_base)
216
+ Digest::SHA256.digest("user_access_tokens_key#{secrets.secret_key_base}")
208
217
  end
209
218
  end
210
219
 
@@ -220,6 +229,10 @@ module Shipit
220
229
  secrets.update_latest_deployed_ref
221
230
  end
222
231
 
232
+ def git_progress_output
233
+ secrets.git_progress_output || false
234
+ end
235
+
223
236
  def enforce_publish_config
224
237
  secrets.enforce_publish_config.presence
225
238
  end
@@ -240,7 +253,7 @@ module Shipit
240
253
  @all_settings_present ||= [
241
254
  secrets.github, # TODO: handle GitHub settings
242
255
  redis_url,
243
- host,
256
+ host
244
257
  ].all?(&:present?)
245
258
  end
246
259
 
@@ -254,10 +267,10 @@ module Shipit
254
267
 
255
268
  def revision
256
269
  @revision ||= if revision_file.exist?
257
- revision_file.read
258
- else
259
- %x(git rev-parse HEAD)
260
- end.strip
270
+ revision_file.read
271
+ else
272
+ `git rev-parse HEAD`
273
+ end.strip
261
274
  end
262
275
 
263
276
  def default_inactivity_timeout
@@ -291,7 +304,7 @@ module Shipit
291
304
  end
292
305
 
293
306
  def secrets
294
- Rails.application.secrets
307
+ Rails.application.credentials
295
308
  end
296
309
  end
297
310
 
data/lib/tasks/cron.rake CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :cron do
3
4
  desc "Updates deployed revisions"
4
5
  task minutely: :environment do
@@ -10,7 +11,7 @@ namespace :cron do
10
11
  Shipit::ReviewStackProvisioningQueue.work
11
12
  end
12
13
 
13
- task hourly: %i(rollup refresh_users clear_stale_caches delete_old_deployment_directories)
14
+ task hourly: %i[rollup refresh_users clear_stale_caches delete_old_deployment_directories]
14
15
 
15
16
  desc "Rolls-up output chunks for completed deploys older than an hour"
16
17
  task rollup: :environment do
data/lib/tasks/dev.rake CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :dev do
3
4
  desc "Appends chunks to the last deploy, or specify with DEPLOY=id"
4
5
  task stream: :environment do
5
6
  require 'faker'
6
- logger = Logger.new(STDOUT)
7
+ logger = Logger.new($stdout)
7
8
 
8
9
  deploy = Shipit::Deploy.find(ENV['DEPLOY']) if ENV['DEPLOY']
9
10
  deploy ||= Deploy.last
@@ -25,7 +26,7 @@ namespace :dev do
25
26
 
26
27
  logger.error(sentence)
27
28
 
28
- deploy.chunks.create(text: sentence + "\n")
29
+ deploy.chunks.create(text: "#{sentence}\n")
29
30
  sleep 1
30
31
  end
31
32
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :shipit do
3
4
  desc "Deploy from a running instance. "
4
5
  task deploy: :environment do
@@ -12,12 +13,12 @@ namespace :shipit do
12
13
  class Task
13
14
  def write(text)
14
15
  p(text)
15
- chunks.create!(text: text)
16
+ chunks.create!(text:)
16
17
  end
17
18
  end
18
19
  end
19
20
 
20
- Shipit::Stack.run_deploy_in_foreground(stack: stack, revision: revision)
21
+ Shipit::Stack.run_deploy_in_foreground(stack:, revision:)
21
22
  rescue ArgumentError
22
23
  p("Use this command as follows:")
23
24
  p("bundle exec rake shipit:deploy stack='shopify/shipit/production' revision='$SHA'")
data/lib/tasks/teams.rake CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :teams do
3
4
  desc "Import the members of each team configured through the github.oauth.teams config"
4
5
  task fetch: :environment do
@@ -6,9 +7,9 @@ namespace :teams do
6
7
  puts "Fetching @#{team.handle} members"
7
8
  begin
8
9
  team.refresh_members!
9
- rescue Octokit::Unauthorized, Octokit::NotFound => error
10
+ rescue Octokit::Unauthorized, Octokit::NotFound => e
10
11
  puts "Failed to fetch @#{team.handle} members. Do you have enough permissions?"
11
- puts "#{error.class}: #{error.message}"
12
+ puts "#{e.class}: #{e.message}"
12
13
  end
13
14
  end
14
15
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -32,7 +33,7 @@ module Shipit
32
33
  test "xml contains required attributes" do
33
34
  get :show, params: { stack_id: @stack.to_param }
34
35
  project = get_project_from_xml(response.body)
35
- %w(name activity lastBuildStatus lastBuildLabel lastBuildTime webUrl).each do |attribute|
36
+ %w[name activity lastBuildStatus lastBuildLabel lastBuildTime webUrl].each do |attribute|
36
37
  assert_includes project, attribute, "Response missing required attribute: #{attribute}"
37
38
  end
38
39
  end
@@ -55,9 +56,9 @@ module Shipit
55
56
  Hash.from_xml(xml)['Projects']['Project']
56
57
  end
57
58
 
58
- def assert_payload(k, v)
59
+ def assert_payload(key, value)
59
60
  @project ||= get_project_from_xml(response.body)
60
- assert_equal(v, @project[k])
61
+ assert_equal(value, @project[key])
61
62
  end
62
63
  end
63
64
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -49,7 +50,7 @@ module Shipit
49
50
  request.headers['X-Shipit-User'] = @user.login
50
51
  post :create, params: { stack_id: @stack.to_param, sha: @commit.sha }
51
52
  deploy = Deploy.last
52
- deploy.user == @user
53
+ assert_equal @user, deploy.user
53
54
  end
54
55
 
55
56
  test "#create normalises the claimed user" do
@@ -81,6 +82,7 @@ module Shipit
81
82
  end
82
83
 
83
84
  assert_response :conflict
85
+ assert_json 'error', 'A task is already running.'
84
86
  end
85
87
 
86
88
  test "#create refuses to deploy unsuccessful commits if the require_ci flag is passed" do
@@ -119,6 +121,29 @@ module Shipit
119
121
  assert_response :accepted
120
122
  assert_json 'status', 'pending'
121
123
  end
124
+
125
+ test "#create uses allow_concurrency param when provided" do
126
+ @stack.update!(lock_reason: 'Something broken')
127
+
128
+ assert_difference -> { @stack.deploys.count }, 1 do
129
+ post :create, params: { stack_id: @stack.to_param, sha: @commit.sha, force: 'true', allow_concurrency: 'false' }
130
+ end
131
+ assert_response :accepted
132
+ assert_json 'status', 'pending'
133
+ refute @stack.deploys.last.allow_concurrency
134
+ end
135
+
136
+ test "#create defaults allow_concurrency to force param when not provided" do
137
+ @stack.update!(lock_reason: 'Something broken')
138
+ expected_force = true
139
+
140
+ assert_difference -> { @stack.deploys.count }, 1 do
141
+ post :create, params: { stack_id: @stack.to_param, sha: @commit.sha, force: expected_force }
142
+ end
143
+ assert_response :accepted
144
+ assert_json 'status', 'pending'
145
+ assert_equal expected_force, @stack.deploys.last.allow_concurrency
146
+ end
122
147
  end
123
148
  end
124
149
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -49,7 +50,7 @@ module Shipit
49
50
 
50
51
  test "#create adds a new hook" do
51
52
  assert_difference -> { Hook.count }, 1 do
52
- post :create, params: { delivery_url: 'https://example.com/hook', events: %w(deploy rollback) }
53
+ post :create, params: { delivery_url: 'https://example.com/hook', events: %w[deploy rollback] }
53
54
  end
54
55
  hook = Hook.last
55
56
  assert_json 'delivery_url', 'https://example.com/hook'
@@ -60,14 +61,14 @@ module Shipit
60
61
  test "#create do not allow to set protected attributes" do
61
62
  post :create, params: {
62
63
  delivery_url: 'https://example.com/hook',
63
- events: %w(deploy rollback),
64
- created_at: 2.months.ago.to_formatted_s(:db),
64
+ events: %w[deploy rollback],
65
+ created_at: 2.months.ago.to_formatted_s(:db)
65
66
  }
66
- Hook.last.created_at > 2.seconds.ago
67
+ assert_operator Hook.last.created_at, :>, 2.seconds.ago
67
68
  end
68
69
 
69
70
  test "#create returns validation errors" do
70
- post :create, params: { delivery_url: '../etc/passwd', events: %w(deploy) }
71
+ post :create, params: { delivery_url: '../etc/passwd', events: %w[deploy] }
71
72
  assert_response :unprocessable_entity
72
73
  assert_json 'errors', 'delivery_url' => ['is not a valid URL']
73
74
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  module Shipit