kettle-jem 7.0.0 → 7.1.3

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 (202) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1505 -0
  4. data/LICENSE.md +13 -0
  5. data/README.md +794 -0
  6. data/exe/kettle-jem +20 -0
  7. data/lib/kettle/jem/cli.rb +334 -0
  8. data/lib/kettle/jem/license_txt_migrator.rb +33 -0
  9. data/lib/kettle/jem/rakelib/install.rake +10 -0
  10. data/lib/kettle/jem/rakelib/prepare.rake +10 -0
  11. data/lib/kettle/jem/rakelib/selftest.rake +10 -0
  12. data/lib/kettle/jem/rakelib/template.rake +10 -0
  13. data/lib/kettle/jem/tasks/install_task.rb +2178 -0
  14. data/lib/kettle/jem/tasks/prepare_task.rb +274 -0
  15. data/lib/kettle/jem/tasks/self_test_task.rb +256 -0
  16. data/lib/kettle/jem/tasks/template_task.rb +124 -0
  17. data/lib/kettle/jem/tasks.rb +6 -0
  18. data/lib/kettle/jem/templates/.aiignore.example +19 -0
  19. data/lib/kettle/jem/templates/.config/mise/env.sh +26 -0
  20. data/lib/kettle/jem/templates/.devcontainer/apt-install/devcontainer-feature.json.example +9 -0
  21. data/lib/kettle/jem/templates/.devcontainer/apt-install/install.sh.example +19 -0
  22. data/lib/kettle/jem/templates/.devcontainer/devcontainer.json.example +27 -0
  23. data/lib/kettle/jem/templates/.devcontainer/scripts/setup-tree-sitter.sh.example +210 -0
  24. data/lib/kettle/jem/templates/.env.local.example +67 -0
  25. data/lib/kettle/jem/templates/.envrc.example +4 -0
  26. data/lib/kettle/jem/templates/.envrc.no-osc.example +4 -0
  27. data/lib/kettle/jem/templates/.gemrc.example +1 -0
  28. data/lib/kettle/jem/templates/.git-hooks/commit-msg.example +60 -0
  29. data/lib/kettle/jem/templates/.git-hooks/commit-subjects-goalie.txt.example +8 -0
  30. data/lib/kettle/jem/templates/.git-hooks/footer-template.erb.txt.example +16 -0
  31. data/lib/kettle/jem/templates/.git-hooks/prepare-commit-msg.example +9 -0
  32. data/lib/kettle/jem/templates/.github/.codecov.yml.example +14 -0
  33. data/lib/kettle/jem/templates/.github/FUNDING.yml.example +10 -0
  34. data/lib/kettle/jem/templates/.github/FUNDING.yml.no-osc.example +10 -0
  35. data/lib/kettle/jem/templates/.github/copilot_instructions.md.example +1 -0
  36. data/lib/kettle/jem/templates/.github/dependabot.yml.example +13 -0
  37. data/lib/kettle/jem/templates/.github/workflows/auto-assign.yml.example +21 -0
  38. data/lib/kettle/jem/templates/.github/workflows/coverage.yml.example +102 -0
  39. data/lib/kettle/jem/templates/.github/workflows/current.yml.example +86 -0
  40. data/lib/kettle/jem/templates/.github/workflows/dep-heads.yml.example +249 -0
  41. data/lib/kettle/jem/templates/.github/workflows/dependency-review.yml.example +22 -0
  42. data/lib/kettle/jem/templates/.github/workflows/discord-notifier.yml.example +39 -0
  43. data/lib/kettle/jem/templates/.github/workflows/framework-ci.yml.example +81 -0
  44. data/lib/kettle/jem/templates/.github/workflows/heads.yml.example +212 -0
  45. data/lib/kettle/jem/templates/.github/workflows/jruby-10.0.yml.example +91 -0
  46. data/lib/kettle/jem/templates/.github/workflows/jruby-9.1.yml.example +90 -0
  47. data/lib/kettle/jem/templates/.github/workflows/jruby-9.2.yml.example +95 -0
  48. data/lib/kettle/jem/templates/.github/workflows/jruby-9.3.yml.example +95 -0
  49. data/lib/kettle/jem/templates/.github/workflows/jruby-9.4.yml.example +95 -0
  50. data/lib/kettle/jem/templates/.github/workflows/jruby.yml.example +91 -0
  51. data/lib/kettle/jem/templates/.github/workflows/license-eye.yml.example +43 -0
  52. data/lib/kettle/jem/templates/.github/workflows/locked_deps.yml.example +89 -0
  53. data/lib/kettle/jem/templates/.github/workflows/opencollective.yml.example +44 -0
  54. data/lib/kettle/jem/templates/.github/workflows/ruby-2.3.yml.example +80 -0
  55. data/lib/kettle/jem/templates/.github/workflows/ruby-2.4.yml.example +84 -0
  56. data/lib/kettle/jem/templates/.github/workflows/ruby-2.5.yml.example +84 -0
  57. data/lib/kettle/jem/templates/.github/workflows/ruby-2.6.yml.example +84 -0
  58. data/lib/kettle/jem/templates/.github/workflows/ruby-2.7.yml.example +84 -0
  59. data/lib/kettle/jem/templates/.github/workflows/ruby-3.0.yml.example +85 -0
  60. data/lib/kettle/jem/templates/.github/workflows/ruby-3.1.yml.example +83 -0
  61. data/lib/kettle/jem/templates/.github/workflows/ruby-3.2.yml.example +80 -0
  62. data/lib/kettle/jem/templates/.github/workflows/ruby-3.3.yml.example +80 -0
  63. data/lib/kettle/jem/templates/.github/workflows/ruby-3.4.yml.example +80 -0
  64. data/lib/kettle/jem/templates/.github/workflows/style.yml.example +78 -0
  65. data/lib/kettle/jem/templates/.github/workflows/templating.yml.example +86 -0
  66. data/lib/kettle/jem/templates/.github/workflows/truffle.yml.example +91 -0
  67. data/lib/kettle/jem/templates/.github/workflows/truffleruby-22.3.yml.example +92 -0
  68. data/lib/kettle/jem/templates/.github/workflows/truffleruby-23.0.yml.example +92 -0
  69. data/lib/kettle/jem/templates/.github/workflows/truffleruby-23.1.yml.example +78 -0
  70. data/lib/kettle/jem/templates/.github/workflows/truffleruby-24.2.yml.example +91 -0
  71. data/lib/kettle/jem/templates/.github/workflows/truffleruby-25.0.yml.example +96 -0
  72. data/lib/kettle/jem/templates/.github/workflows/truffleruby-33.0.yml.example +91 -0
  73. data/lib/kettle/jem/templates/.github/workflows/unlocked_deps.yml.example +93 -0
  74. data/lib/kettle/jem/templates/.gitignore.example +53 -0
  75. data/lib/kettle/jem/templates/.gitlab-ci.yml.example +134 -0
  76. data/lib/kettle/jem/templates/.idea/.gitignore.example +44 -0
  77. data/lib/kettle/jem/templates/.licenserc.yaml.example +8 -0
  78. data/lib/kettle/jem/templates/.opencollective.yml.example +3 -0
  79. data/lib/kettle/jem/templates/.qlty/qlty.toml.example +79 -0
  80. data/lib/kettle/jem/templates/.rspec.example +10 -0
  81. data/lib/kettle/jem/templates/.rubocop.example +1 -0
  82. data/lib/kettle/jem/templates/.rubocop.yml.example +47 -0
  83. data/lib/kettle/jem/templates/.rubocop_rspec.yml.example +55 -0
  84. data/lib/kettle/jem/templates/.simplecov.example +18 -0
  85. data/lib/kettle/jem/templates/.structuredmerge/git-drivers.toml.example +86 -0
  86. data/lib/kettle/jem/templates/.structuredmerge/kettle-jem.yml.example +519 -0
  87. data/lib/kettle/jem/templates/.yard-lint.yml.example +54 -0
  88. data/lib/kettle/jem/templates/.yardignore.example +13 -0
  89. data/lib/kettle/jem/templates/.yardopts.example +14 -0
  90. data/lib/kettle/jem/templates/AGENTS.md.example +283 -0
  91. data/lib/kettle/jem/templates/AGPL-3.0-only.md.example +235 -0
  92. data/lib/kettle/jem/templates/Appraisal.root.gemfile.example +26 -0
  93. data/lib/kettle/jem/templates/Appraisals.example +150 -0
  94. data/lib/kettle/jem/templates/Big-Time-Public-License.md.example +99 -0
  95. data/lib/kettle/jem/templates/CHANGELOG.md.example +33 -0
  96. data/lib/kettle/jem/templates/CHANGELOG.transfer.md +109 -0
  97. data/lib/kettle/jem/templates/CITATION.cff.example +20 -0
  98. data/lib/kettle/jem/templates/CODE_OF_CONDUCT.md.example +134 -0
  99. data/lib/kettle/jem/templates/CONTRIBUTING.md.example +292 -0
  100. data/lib/kettle/jem/templates/FUNDING.md.example +74 -0
  101. data/lib/kettle/jem/templates/FUNDING.md.no-osc.example +63 -0
  102. data/lib/kettle/jem/templates/Gemfile.example +52 -0
  103. data/lib/kettle/jem/templates/IRP.md.example +34 -0
  104. data/lib/kettle/jem/templates/LICENSE.md.example +3 -0
  105. data/lib/kettle/jem/templates/MIT.md.example +21 -0
  106. data/lib/kettle/jem/templates/PolyForm-Noncommercial-1.0.0.md.example +131 -0
  107. data/lib/kettle/jem/templates/PolyForm-Small-Business-1.0.0.md.example +121 -0
  108. data/lib/kettle/jem/templates/README.md.example +544 -0
  109. data/lib/kettle/jem/templates/REEK +0 -0
  110. data/lib/kettle/jem/templates/RUBOCOP.md.example +78 -0
  111. data/lib/kettle/jem/templates/Rakefile.example +163 -0
  112. data/lib/kettle/jem/templates/SECURITY.md.example +21 -0
  113. data/lib/kettle/jem/templates/bin/setup.example +36 -0
  114. data/lib/kettle/jem/templates/certs/pboling.pem.example +27 -0
  115. data/lib/kettle/jem/templates/gem.gemspec.example +161 -0
  116. data/lib/kettle/jem/templates/gemfiles/modular/benchmark/r4/v0.5.gemfile.example +2 -0
  117. data/lib/kettle/jem/templates/gemfiles/modular/benchmark/vHEAD.gemfile.example +2 -0
  118. data/lib/kettle/jem/templates/gemfiles/modular/cgi/r4/v0.5.gemfile.example +1 -0
  119. data/lib/kettle/jem/templates/gemfiles/modular/cgi/vHEAD.gemfile.example +1 -0
  120. data/lib/kettle/jem/templates/gemfiles/modular/changelog.gemfile.example +6 -0
  121. data/lib/kettle/jem/templates/gemfiles/modular/coverage.gemfile.example +14 -0
  122. data/lib/kettle/jem/templates/gemfiles/modular/coverage_local.gemfile.example +34 -0
  123. data/lib/kettle/jem/templates/gemfiles/modular/debug.gemfile.example +22 -0
  124. data/lib/kettle/jem/templates/gemfiles/modular/documentation.gemfile.example +24 -0
  125. data/lib/kettle/jem/templates/gemfiles/modular/documentation_local.gemfile.example +20 -0
  126. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2/v3.0.gemfile.example +1 -0
  127. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2.3/default.gemfile.example +6 -0
  128. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2.6/v2.2.gemfile.example +3 -0
  129. data/lib/kettle/jem/templates/gemfiles/modular/erb/r3/v5.0.gemfile.example +1 -0
  130. data/lib/kettle/jem/templates/gemfiles/modular/erb/r3.1/v4.0.gemfile.example +2 -0
  131. data/lib/kettle/jem/templates/gemfiles/modular/erb/r4/v5.0.gemfile.example +1 -0
  132. data/lib/kettle/jem/templates/gemfiles/modular/erb/vHEAD.gemfile.example +2 -0
  133. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_22_3.gemfile.example +6 -0
  134. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_23_0.gemfile.example +6 -0
  135. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_23_1.gemfile.example +6 -0
  136. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r2/v0.3.gemfile.example +2 -0
  137. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile.example +3 -0
  138. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r3/v0.3.gemfile.example +2 -0
  139. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r4/v0.3.gemfile.example +2 -0
  140. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/vHEAD.gemfile.example +2 -0
  141. data/lib/kettle/jem/templates/gemfiles/modular/optional.gemfile.example +16 -0
  142. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.3/recording.gemfile.example +16 -0
  143. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.4/recording.gemfile.example +18 -0
  144. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.5/recording.gemfile.example +16 -0
  145. data/lib/kettle/jem/templates/gemfiles/modular/recording/r3/recording.gemfile.example +16 -0
  146. data/lib/kettle/jem/templates/gemfiles/modular/recording/r4/recording.gemfile.example +16 -0
  147. data/lib/kettle/jem/templates/gemfiles/modular/recording/vHEAD.gemfile.example +2 -0
  148. data/lib/kettle/jem/templates/gemfiles/modular/rspec.gemfile.example +4 -0
  149. data/lib/kettle/jem/templates/gemfiles/modular/runtime_heads.gemfile.example +14 -0
  150. data/lib/kettle/jem/templates/gemfiles/modular/shunted.gemfile.example +21 -0
  151. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2/v3.0.gemfile.example +5 -0
  152. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile.example +4 -0
  153. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r3/v3.0.gemfile.example +5 -0
  154. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r4/v3.0.gemfile.example +5 -0
  155. data/lib/kettle/jem/templates/gemfiles/modular/stringio/vHEAD.gemfile.example +2 -0
  156. data/lib/kettle/jem/templates/gemfiles/modular/style.gemfile.example +33 -0
  157. data/lib/kettle/jem/templates/gemfiles/modular/style_local.gemfile.example +26 -0
  158. data/lib/kettle/jem/templates/gemfiles/modular/templating.gemfile.example +34 -0
  159. data/lib/kettle/jem/templates/gemfiles/modular/templating_local.gemfile.example +73 -0
  160. data/lib/kettle/jem/templates/gemfiles/modular/webrick/r4/v1.9.gemfile.example +1 -0
  161. data/lib/kettle/jem/templates/gemfiles/modular/webrick/vHEAD.gemfile.example +1 -0
  162. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2/libs.gemfile.example +3 -0
  163. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.3/libs.gemfile.example +3 -0
  164. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.4/libs.gemfile.example +3 -0
  165. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.6/libs.gemfile.example +3 -0
  166. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r3/libs.gemfile.example +3 -0
  167. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r3.1/libs.gemfile.example +5 -0
  168. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r4/libs.gemfile.example +6 -0
  169. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/vHEAD.gemfile.example +6 -0
  170. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs.gemfile.example +12 -0
  171. data/lib/kettle/jem/templates/lib/gem/version.rb.example +1 -0
  172. data/lib/kettle/jem/templates/lib/gem/version_gem.rb.example +1 -0
  173. data/lib/kettle/jem/templates/mise.toml.example +25 -0
  174. data/lib/kettle/jem/templates/mise.toml.no-osc.example +25 -0
  175. data/lib/kettle/jem/templates/parsers.toml.example +19 -0
  176. data/lib/kettle/jem/templates/readme/partials/basic_usage.md.example +28 -0
  177. data/lib/kettle/jem/templates/readme/partials/configuration.md.example +112 -0
  178. data/lib/kettle/jem/templates/readme/partials/synopsis.md.example +3 -0
  179. data/lib/kettle/jem/templates/shim/.github/workflows/current.yml.example +40 -0
  180. data/lib/kettle/jem/templates/shim/.rspec.example +5 -0
  181. data/lib/kettle/jem/templates/shim/.structuredmerge/kettle-jem.yml.example +28 -0
  182. data/lib/kettle/jem/templates/shim/CHANGELOG.md.example +9 -0
  183. data/lib/kettle/jem/templates/shim/Gemfile.example +14 -0
  184. data/lib/kettle/jem/templates/shim/README.md.example +25 -0
  185. data/lib/kettle/jem/templates/shim/Rakefile.example +8 -0
  186. data/lib/kettle/jem/templates/shim/gem.gemspec.example +45 -0
  187. data/lib/kettle/jem/templates/shim/lib/compat_require.rb.example +3 -0
  188. data/lib/kettle/jem/templates/shim/lib/entrypoint.rb.example +3 -0
  189. data/lib/kettle/jem/templates/shim/lib/version.rb.example +1 -0
  190. data/lib/kettle/jem/templates/shim/spec/shim_spec.rb.example +15 -0
  191. data/lib/kettle/jem/templates/shim/spec/spec_helper.rb.example +23 -0
  192. data/lib/kettle/jem/templates/sig/gem.rbs.example +1 -0
  193. data/lib/kettle/jem/templates/spec/README.md.example +28 -0
  194. data/lib/kettle/jem/templates/spec/spec_helper.rb.example +39 -0
  195. data/lib/kettle/jem/version.rb +5 -3
  196. data/lib/kettle/jem/version_gem.rb +8 -0
  197. data/lib/kettle/jem.rb +18540 -1517
  198. data/lib/kettle-jem.rb +2 -1
  199. data/sig/kettle/jem.rbs +8 -0
  200. data.tar.gz.sig +0 -0
  201. metadata +870 -32
  202. metadata.gz.sig +0 -0
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "kettle/dev/lockfile_reset"
5
+ require "pathname"
6
+ require "shellwords"
7
+
8
+ module Kettle
9
+ module Jem
10
+ module Tasks
11
+ module PrepareTask
12
+ PREPARE_ONLY_PATHS = [
13
+ Kettle::Jem::KETTLE_CONFIG_PATH,
14
+ "Gemfile",
15
+ # Gemfile evaluates these fragments immediately. Preparing only the
16
+ # templating pair leaves a newly migrated project with references to
17
+ # files that do not exist yet, so Bundler cannot perform the bootstrap.
18
+ "gemfiles/modular/**",
19
+ "mise.toml"
20
+ ].freeze
21
+ CRITICAL_TEMPLATING_GEMS = %w[nomono].freeze
22
+ LOCKED_TEMPLATING_GEMS = %w[tree_sitter_language_pack].freeze
23
+
24
+ module_function
25
+
26
+ def run(project_root: Dir.pwd, env: ENV, run_options: {}, command_runner: Kettle::Jem::Tasks::InstallTask.method(:run_system_command))
27
+ effective_run_options = Kettle::Jem::Tasks::TemplateTask.env_run_options(env || {}).merge(run_options || {})
28
+ prepare_run_options = effective_run_options.merge(
29
+ only: PREPARE_ONLY_PATHS,
30
+ skip_lock_normalization: true
31
+ )
32
+ events = Kettle::Jem.event_stream_from_options(effective_run_options)
33
+ report = Kettle::Jem.apply_project(project_root, env: env, run_options: prepare_run_options)
34
+ profile_sync_step = synchronize_monorepo_subgem_profile_step(project_root, report)
35
+ supplemental_report = if profile_sync_step && profile_sync_step.fetch(:status) == "applied"
36
+ Kettle::Jem.apply_project(project_root, env: env, run_options: prepare_run_options)
37
+ end
38
+ report = merge_supplemental_prepare_report(report, supplemental_report)
39
+ nomono_bootstrap_step = normalize_existing_local_gemfile_bootstraps_step(project_root, events: events)
40
+ setup_env = Kettle::Jem::Tasks::InstallTask.setup_command_env(project_root, env)
41
+ setup_env["BUNDLE_DISABLE_CHECKSUM_VALIDATION"] = "true"
42
+ setup_env["K_JEM_TEMPLATING"] = "true" if local_path_development_env?(env)
43
+ reset_step = reset_release_lockfiles_step(
44
+ project_root: project_root,
45
+ setup_env: setup_env,
46
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
47
+ command_runner: command_runner,
48
+ events: events
49
+ )
50
+ Kettle::Jem.emit_step_event(events, "command_step", reset_step, phase: "prepare")
51
+ bootstrap_name = templating_bootstrap_step_name(project_root)
52
+ bootstrap_command = templating_bootstrap_command(project_root)
53
+ Kettle::Jem.emit_step_event(
54
+ events,
55
+ "command_step",
56
+ {name: bootstrap_name, status: "started", command: bootstrap_command},
57
+ phase: "prepare"
58
+ )
59
+ bootstrap_step = Kettle::Jem::Tasks::InstallTask.run_command_step(
60
+ bootstrap_name,
61
+ bootstrap_command,
62
+ project_root: project_root,
63
+ env: setup_env,
64
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
65
+ command_runner: command_runner
66
+ )
67
+ Kettle::Jem.emit_step_event(events, "command_step", bootstrap_step, phase: "prepare")
68
+ bundle_step = bundle_install_after_bootstrap_step(
69
+ project_root: project_root,
70
+ setup_env: setup_env,
71
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
72
+ command_runner: command_runner,
73
+ events: events,
74
+ bootstrap_command: bootstrap_command
75
+ )
76
+ Kettle::Jem.emit_step_event(events, "command_step", bundle_step, phase: "prepare")
77
+
78
+ final_report = report.merge(
79
+ mode: "prepare",
80
+ prepared: bootstrap_step.fetch(:status) == "succeeded" &&
81
+ %w[skipped succeeded].include?(reset_step.fetch(:status)) &&
82
+ %w[skipped succeeded].include?(bundle_step.fetch(:status)),
83
+ prepare_only: PREPARE_ONLY_PATHS,
84
+ prepare_steps: [profile_sync_step, nomono_bootstrap_step, reset_step, bootstrap_step, bundle_step].compact,
85
+ changed_files: (
86
+ report.fetch(:changed_files, []) +
87
+ nomono_bootstrap_step.fetch(:changed_files, []) +
88
+ reset_step.fetch(:changed_files, []) +
89
+ bootstrap_step.fetch(:changed_files, []) +
90
+ bundle_step.fetch(:changed_files, [])
91
+ ).uniq.sort,
92
+ diagnostics: report.fetch(:diagnostics, []) + [{
93
+ severity: "advisory",
94
+ message: "kettle:jem:prepare applied the templating dependency bootstrap payload, " \
95
+ "updated critical templating gems, and ran bundle install."
96
+ }]
97
+ )
98
+ Kettle::Jem.emit_summary_event(events, final_report)
99
+ final_report
100
+ end
101
+
102
+ def synchronize_monorepo_subgem_profile_step(project_root, report)
103
+ facts = report.fetch(:facts, {})
104
+ return unless Kettle::Jem.send(:monorepo_subgem_template_profile?, facts)
105
+
106
+ Kettle::Jem.send(:monorepo_subgem_kettle_config_profile_sync_step, project_root, report)
107
+ end
108
+
109
+ def merge_supplemental_prepare_report(report, supplemental_report)
110
+ return report unless supplemental_report
111
+
112
+ report.merge(
113
+ changed_files: (
114
+ report.fetch(:changed_files, []) + supplemental_report.fetch(:changed_files, [])
115
+ ).uniq.sort,
116
+ diagnostics: report.fetch(:diagnostics, []) + supplemental_report.fetch(:diagnostics, [])
117
+ )
118
+ end
119
+
120
+ def normalize_existing_local_gemfile_bootstraps_step(project_root, events:)
121
+ paths = Dir.glob(File.join(project_root.to_s, "gemfiles/modular/**/*_local.gemfile")).sort
122
+ changed_files = paths.filter_map do |path|
123
+ before = File.read(path)
124
+ after = Kettle::Jem.normalize_local_gemfile_nomono_bootstrap(before)
125
+ next if after == before
126
+
127
+ File.write(path, after)
128
+ Pathname.new(path).relative_path_from(Pathname.new(project_root.to_s)).to_s
129
+ end
130
+ step = {
131
+ name: "normalize_local_gemfile_bootstraps",
132
+ status: changed_files.empty? ? "already_current" : "applied",
133
+ changed_files: changed_files
134
+ }
135
+ Kettle::Jem.emit_step_event(events, "command_step", step, phase: "prepare")
136
+ step
137
+ end
138
+
139
+ def reset_release_lockfiles_step(project_root:, setup_env:, quiet:, command_runner:, events:)
140
+ if local_path_development_env?(setup_env)
141
+ return {
142
+ name: "reset_release_lockfiles",
143
+ command: %w[kettle-reset release-lockfiles],
144
+ status: "skipped",
145
+ reason: "local_path_development_env"
146
+ }
147
+ end
148
+
149
+ reset_command_runner = lambda do |command|
150
+ command_runner.call(Shellwords.split(command), chdir: project_root, env: setup_env, quiet: quiet)
151
+ end
152
+ resetter = Kettle::Dev::LockfileReset.new(
153
+ root: project_root,
154
+ command_runner: reset_command_runner
155
+ )
156
+ paths = resetter.lockfile_paths
157
+ if paths.empty?
158
+ return {
159
+ name: "reset_release_lockfiles",
160
+ command: %w[kettle-reset release-lockfiles],
161
+ status: "skipped",
162
+ reason: "no_release_lockfiles"
163
+ }
164
+ end
165
+
166
+ Kettle::Jem.emit_step_event(
167
+ events,
168
+ "command_step",
169
+ {name: "reset_release_lockfiles", status: "started", command: %w[kettle-reset release-lockfiles]},
170
+ phase: "prepare"
171
+ )
172
+ before = snapshot_files(paths)
173
+ resetter.reset(Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET)
174
+ changed_files = changed_files_since(project_root, before)
175
+ {
176
+ name: "reset_release_lockfiles",
177
+ command: %w[kettle-reset release-lockfiles],
178
+ status: "succeeded",
179
+ changed_files: changed_files,
180
+ quiet: quiet
181
+ }
182
+ end
183
+
184
+ def local_path_development_env?(env)
185
+ (env || {}).any? do |key, raw_value|
186
+ next false unless key.to_s.end_with?("_DEV")
187
+
188
+ value = raw_value.to_s.strip
189
+ !value.empty? && !Kettle::Jem::DecisionPolicy.falsey?(value)
190
+ end
191
+ end
192
+
193
+ def bundle_update_templating_bootstrap_command(project_root = Dir.pwd)
194
+ %w[bundle update] + CRITICAL_TEMPLATING_GEMS + locked_templating_gems(project_root)
195
+ end
196
+
197
+ def templating_bootstrap_command(project_root = Dir.pwd)
198
+ return %w[bundle install] unless templating_bootstrap_lockfile_ready?(project_root)
199
+
200
+ bundle_update_templating_bootstrap_command(project_root)
201
+ end
202
+
203
+ def templating_bootstrap_step_name(project_root = Dir.pwd)
204
+ return "bundle_install_templating_bootstrap" unless templating_bootstrap_lockfile_ready?(project_root)
205
+
206
+ "bundle_update_templating_bootstrap"
207
+ end
208
+
209
+ def templating_bootstrap_lockfile_ready?(project_root)
210
+ lock_path = File.join(project_root.to_s, "Gemfile.lock")
211
+ return false unless File.file?(lock_path)
212
+
213
+ locked_gem_names(project_root).then do |names|
214
+ CRITICAL_TEMPLATING_GEMS.all? { |gem_name| names.include?(gem_name) }
215
+ end
216
+ rescue Bundler::LockfileError
217
+ false
218
+ end
219
+
220
+ def bundle_install_after_bootstrap_step(project_root:, setup_env:, quiet:, command_runner:, events:, bootstrap_command:)
221
+ if bootstrap_command == %w[bundle install]
222
+ return {
223
+ name: "bundle_install",
224
+ command: %w[bundle install],
225
+ status: "skipped",
226
+ reason: "already_ran_as_templating_bootstrap"
227
+ }
228
+ end
229
+
230
+ Kettle::Jem.emit_step_event(
231
+ events,
232
+ "command_step",
233
+ {name: "bundle_install", status: "started", command: %w[bundle install]},
234
+ phase: "prepare"
235
+ )
236
+ Kettle::Jem::Tasks::InstallTask.run_command_step(
237
+ "bundle_install",
238
+ %w[bundle install],
239
+ project_root: project_root,
240
+ env: setup_env,
241
+ quiet: quiet,
242
+ command_runner: command_runner
243
+ )
244
+ end
245
+
246
+ def locked_templating_gems(project_root)
247
+ LOCKED_TEMPLATING_GEMS & locked_gem_names(project_root)
248
+ end
249
+
250
+ def locked_gem_names(project_root)
251
+ lock_path = File.join(project_root.to_s, "Gemfile.lock")
252
+ return [] unless File.file?(lock_path)
253
+
254
+ Bundler::LockfileParser.new(Bundler.read_file(lock_path)).specs.map(&:name)
255
+ end
256
+
257
+ def snapshot_files(paths)
258
+ paths.each_with_object({}) do |path, snapshot|
259
+ snapshot[path] = File.file?(path) ? File.binread(path) : nil
260
+ end
261
+ end
262
+
263
+ def changed_files_since(project_root, snapshot)
264
+ snapshot.filter_map do |path, before|
265
+ after = File.file?(path) ? File.binread(path) : nil
266
+ next if before == after
267
+
268
+ Pathname.new(path).relative_path_from(Pathname.new(project_root)).to_s
269
+ end.sort
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Kettle
6
+ module Jem
7
+ module Tasks
8
+ module SelfTestTask
9
+ SKIPPED_PREFIXES = %w[
10
+ examples/
11
+ exe/
12
+ lib/
13
+ sig/
14
+ spec/
15
+ template/
16
+ ].freeze
17
+ SKIPPED_FILES = %w[
18
+ .kettle-jem.yml
19
+ .rubocop_gradual.lock
20
+ Gemfile.lock
21
+ ].freeze
22
+ GENERATED_RUNTIME_PREFIXES = %w[
23
+ tmp/kettle-jem/templating-report-
24
+ ].freeze
25
+ SELFTEST_IGNORED_FILES = %w[
26
+ gemfiles/modular/shunted.gemfile
27
+ results/test_results.html
28
+ tmp/.gitignore
29
+ ].freeze
30
+
31
+ module_function
32
+
33
+ def run(project_root: Dir.pwd, destination_root: project_root, template_root: nil, output_root: nil, min_divergence_threshold: nil)
34
+ root = File.expand_path(destination_root)
35
+ base_dir = output_root || File.join(root, "tmp", "template_test")
36
+ before_dir = File.join(base_dir, "destination")
37
+ after_dir = File.join(base_dir, "output")
38
+ report_dir = File.join(base_dir, "report")
39
+ diffs_dir = File.join(report_dir, "diffs")
40
+
41
+ FileUtils.rm_rf(base_dir)
42
+ [before_dir, after_dir, report_dir, diffs_dir].each { |path| FileUtils.mkdir_p(path) }
43
+ copy_tree(root, before_dir)
44
+ copy_tree(root, after_dir)
45
+
46
+ before = Kettle::Jem::SelfTest::Manifest.generate(before_dir)
47
+ File.write(File.join(report_dir, "before.json"), "#{JSON.pretty_generate(before)}\n")
48
+
49
+ write_template_root_override(after_dir, template_root) if template_root
50
+ Kettle::Jem.apply_project(after_dir, env: ENV, run_options: {accept: true, force: true, skip_commit: true})
51
+
52
+ after = Kettle::Jem::SelfTest::Manifest.generate(after_dir)
53
+ File.write(File.join(report_dir, "after.json"), "#{JSON.pretty_generate(after)}\n")
54
+
55
+ comparison = classify_comparison(Kettle::Jem::SelfTest::Manifest.compare(before, after))
56
+ diff_count = write_diffs(comparison, before_dir: before_dir, after_dir: after_dir, diffs_dir: diffs_dir)
57
+ drift = drift_report(after_dir: after_dir, template_root: template_root || Kettle::Jem.template_root_path(after_dir))
58
+ summary = Kettle::Jem::SelfTest::Reporter.summary(
59
+ comparison,
60
+ output_dir: after_dir,
61
+ templating_environment: Kettle::Jem::TemplatingReport.snapshot,
62
+ diff_count: diff_count
63
+ )
64
+ summary = append_drift_summary(summary, drift)
65
+ summary_path = File.join(report_dir, "summary.md")
66
+ File.write(summary_path, summary)
67
+ score, divergence = score_and_divergence(comparison)
68
+ threshold = selftest_threshold(min_divergence_threshold, root)
69
+
70
+ result = {
71
+ mode: "selftest",
72
+ destination_root: root,
73
+ template_root: template_root || Kettle::Jem.template_root_path(root),
74
+ output_root: after_dir,
75
+ report_path: summary_path,
76
+ comparison: comparison,
77
+ score: score,
78
+ divergence: divergence,
79
+ min_divergence_threshold: threshold,
80
+ drift: drift
81
+ }
82
+ if threshold && divergence > threshold
83
+ raise Kettle::Jem::Error, "selftest divergence #{divergence}% exceeds threshold #{threshold}%"
84
+ end
85
+ result
86
+ end
87
+
88
+ def copy_tree(source, destination)
89
+ Find.find(source) do |path|
90
+ relative = path.sub(%r{\A#{Regexp.escape(source)}/?}, "")
91
+ next if relative.empty?
92
+ next Find.prune if ignored_tree_entry?(relative, path)
93
+
94
+ target = File.join(destination, relative)
95
+ if File.directory?(path)
96
+ FileUtils.mkdir_p(target)
97
+ else
98
+ FileUtils.mkdir_p(File.dirname(target))
99
+ FileUtils.cp(path, target)
100
+ end
101
+ end
102
+ end
103
+
104
+ def ignored_tree_entry?(relative, path)
105
+ return false unless File.directory?(path)
106
+
107
+ %w[.git .yardoc coverage docs node_modules pkg tmp].include?(relative.split("/").first)
108
+ end
109
+
110
+ def write_diffs(comparison, before_dir:, after_dir:, diffs_dir:)
111
+ count = 0
112
+ comparison.fetch(:changed, []).each do |relative|
113
+ diff = Kettle::Jem::SelfTest::Reporter.diff(
114
+ File.join(before_dir, relative),
115
+ File.join(after_dir, relative)
116
+ )
117
+ next if diff.empty?
118
+
119
+ diff_path = File.join(diffs_dir, "#{relative}.diff")
120
+ FileUtils.mkdir_p(File.dirname(diff_path))
121
+ File.write(diff_path, diff)
122
+ count += 1
123
+ end
124
+ count
125
+ end
126
+
127
+ def classify_comparison(comparison)
128
+ changed = comparison.fetch(:changed, []).reject { |relative| ignored_selftest_artifact?(relative) }
129
+ added = comparison.fetch(:added, []).reject { |relative| ignored_selftest_artifact?(relative) }
130
+ skipped, removed = comparison.fetch(:removed, []).partition do |relative|
131
+ ignored_selftest_artifact?(relative) || expected_non_templated_path?(relative)
132
+ end
133
+ comparison.merge(changed: changed, added: added, removed: removed, skipped: skipped)
134
+ end
135
+
136
+ def expected_non_templated_path?(relative_path)
137
+ path_parts = relative_path.to_s.split("/")
138
+ SKIPPED_FILES.include?(relative_path) ||
139
+ SKIPPED_PREFIXES.any? { |prefix| relative_path.start_with?(prefix) } ||
140
+ (path_parts.length == 2 && path_parts.first == "gemfiles" && path_parts.last.end_with?(".gemfile")) ||
141
+ relative_path.to_s.end_with?(".gemspec")
142
+ end
143
+
144
+ def ignored_selftest_artifact?(relative_path)
145
+ generated_runtime_artifact?(relative_path) || SELFTEST_IGNORED_FILES.include?(relative_path)
146
+ end
147
+
148
+ def generated_runtime_artifact?(relative_path)
149
+ GENERATED_RUNTIME_PREFIXES.any? { |prefix| relative_path.start_with?(prefix.to_s) }
150
+ end
151
+
152
+ def score_and_divergence(comparison)
153
+ total = comparison.fetch(:matched, []).size +
154
+ comparison.fetch(:changed, []).size +
155
+ comparison.fetch(:added, []).size +
156
+ comparison.fetch(:removed, []).size
157
+ score = total.zero? ? 0.0 : (comparison.fetch(:matched, []).size.to_f / total * 100).round(1)
158
+ [score, (100.0 - score).round(1)]
159
+ end
160
+
161
+ def selftest_threshold(explicit, root)
162
+ return explicit.to_f if explicit
163
+
164
+ env_threshold = ENV["KJ_MIN_DIVERGENCE_THRESHOLD"].to_s.strip
165
+ return env_threshold.to_f unless env_threshold.empty?
166
+
167
+ config_path = File.join(root, ".kettle-jem.yml")
168
+ return unless File.file?(config_path)
169
+
170
+ config = YAML.load_file(config_path)
171
+ value = config["min_divergence_threshold"] if config.is_a?(Hash)
172
+ return if value.nil? || value.to_s.strip.empty?
173
+
174
+ Float(value)
175
+ rescue ArgumentError, TypeError
176
+ raise Kettle::Jem::Error, "Invalid selftest min_divergence_threshold"
177
+ end
178
+
179
+ def write_template_root_override(root, template_root)
180
+ config_path = File.join(root, ".kettle-jem.yml")
181
+ content = File.file?(config_path) ? File.read(config_path) : ""
182
+ File.write(config_path, upsert_template_root_override(content, File.expand_path(template_root)))
183
+ end
184
+
185
+ def upsert_template_root_override(content, template_root)
186
+ # This is line-oriented instead of YAML round-tripping because the self-test fixture config must preserve
187
+ # comments, key order, and scalar spellings while injecting a temporary local template root.
188
+ lines = content.to_s.lines
189
+ template_index = lines.index { |line| line.match?(/\Atemplates:\s*(?:#.*)?\n?\z/) }
190
+ if template_index
191
+ root_index = ((template_index + 1)...lines.length).find do |index|
192
+ line = lines[index]
193
+ break nil unless line.strip.empty? || line.start_with?(" ")
194
+
195
+ line.match?(/\A\s+root\s*:/)
196
+ end
197
+ if root_index
198
+ lines[root_index] = " root: #{template_root}\n"
199
+ else
200
+ lines.insert(template_index + 1, " root: #{template_root}\n")
201
+ end
202
+ else
203
+ lines << "\n" unless lines.empty? || lines.last.to_s.strip.empty?
204
+ lines << "templates:\n"
205
+ lines << " root: #{template_root}\n"
206
+ end
207
+ output = lines.join
208
+ output.end_with?("\n") ? output : "#{output}\n"
209
+ end
210
+
211
+ def drift_report(after_dir:, template_root:)
212
+ unless defined?(Kettle::Drift)
213
+ return {
214
+ available: false,
215
+ reason: "kettle-drift is not available"
216
+ }
217
+ end
218
+
219
+ outcome = Kettle::Drift.run(
220
+ project_root: after_dir,
221
+ template_dir: template_root,
222
+ lock_path: File.join("tmp", "template_test", ".kettle-drift.lock"),
223
+ mode: :force_update,
224
+ printer_class: nil
225
+ )
226
+ {
227
+ available: true,
228
+ warning_count: outcome.warning_count,
229
+ json_path: outcome.json_path,
230
+ lock_path: outcome.lock_path,
231
+ exit_code: outcome.exit_code
232
+ }
233
+ rescue => error
234
+ {
235
+ available: false,
236
+ reason: "#{error.class}: #{error.message}"
237
+ }
238
+ end
239
+
240
+ def append_drift_summary(summary, drift)
241
+ return summary unless drift
242
+
243
+ lines = [summary, "", "## Drift Analysis", ""]
244
+ if drift[:available]
245
+ lines << "**Duplicate drift warnings**: #{drift.fetch(:warning_count)}"
246
+ lines << "**Drift report**: `#{drift[:json_path]}`" if drift[:json_path]
247
+ else
248
+ lines << "Drift analysis unavailable: #{drift[:reason]}"
249
+ end
250
+ lines << ""
251
+ lines.join("\n")
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+
5
+ module Kettle
6
+ module Jem
7
+ module Tasks
8
+ module TemplateTask
9
+ module_function
10
+
11
+ def run(project_root: Dir.pwd, env: ENV, run_options: nil, command_runner: Kettle::Jem::Tasks::InstallTask.method(:run_system_command))
12
+ effective_run_options = templating_run_options(env, run_options)
13
+ report = if Dir.glob(File.join(project_root.to_s, "*.gemspec")).empty? && !monorepo_root_profile?(project_root, env, effective_run_options)
14
+ {
15
+ mode: "apply",
16
+ changed_files: [],
17
+ diagnostics: [],
18
+ recipe_reports: []
19
+ }
20
+ else
21
+ Kettle::Jem.apply_project(project_root, env: env, run_options: effective_run_options)
22
+ end
23
+ setup_env = Kettle::Jem::Tasks::InstallTask.setup_command_env(project_root, env)
24
+ hook_step = Kettle::Jem::Tasks::InstallTask.hook_templates_step(project_root, effective_run_options)
25
+ git_drivers_step = Kettle::Jem::Tasks::InstallTask.git_drivers_step(project_root, effective_run_options)
26
+ lock_step = Kettle::Jem::Tasks::InstallTask.normalize_lockfile_step(project_root, env: setup_env, run_options: effective_run_options)
27
+ template_steps = Kettle::Jem::Tasks::InstallTask.execute_orchestration_steps(
28
+ [hook_step, git_drivers_step, lock_step],
29
+ project_root: project_root,
30
+ env: setup_env,
31
+ run_options: effective_run_options,
32
+ command_runner: command_runner,
33
+ event_phase: "template"
34
+ )
35
+ final_report = report.merge(mode: "template", template_steps: template_steps)
36
+ Kettle::Jem.emit_summary_event(Kettle::Jem.event_stream_from_options(effective_run_options), final_report)
37
+ final_report
38
+ end
39
+
40
+ def templating_run_options(env, run_options)
41
+ options = env_run_options(env || {}).merge(run_options || {})
42
+ return options if worker_or_strategy_option_provided?(env || {}, options)
43
+
44
+ workers = default_thread_worker_count
45
+ options.merge(
46
+ recipe_planning_strategy: "classified",
47
+ recipe_planning_thread_workers: workers,
48
+ file_work_thread_workers: workers
49
+ )
50
+ end
51
+
52
+ def env_run_options(env)
53
+ {
54
+ accept: truthy?(env["accept"]) || truthy?(env["force"]),
55
+ force: truthy?(env["force"]),
56
+ interactive: falsey?(env["force"]),
57
+ failure_mode: env["FAILURE_MODE"] || env["failure_mode"],
58
+ allowed: env["allowed"],
59
+ hook_templates: env["hook_templates"],
60
+ git_drivers: env["git_drivers"] || env["KETTLE_JEM_GIT_DRIVERS"],
61
+ only: env["only"],
62
+ include: env["include"],
63
+ template_profile: env["KETTLE_JEM_TEMPLATE_PROFILE"],
64
+ dry_run: truthy?(env["KETTLE_JEM_DRY_RUN"]),
65
+ skip_commit: truthy?(env["KETTLE_JEM_SKIP_COMMIT"]),
66
+ skip_drift_check: truthy?(env["KETTLE_JEM_SKIP_DRIFT_CHECK"]),
67
+ skip_rubocop_gradual: truthy?(env["KETTLE_JEM_SKIP_RUBOCOP_GRADUAL"]),
68
+ skip_binstubs: truthy?(env["KETTLE_JEM_SKIP_BINSTUBS"]),
69
+ skip_appraisal_generate: truthy?(env["KETTLE_JEM_SKIP_APPRAISAL_GENERATE"]),
70
+ skip_lock_normalization: truthy?(env["KETTLE_JEM_SKIP_LOCK_NORMALIZATION"]),
71
+ checksums: env["KETTLE_JEM_CHECKSUMS"],
72
+ accept_config: truthy?(env["KETTLE_JEM_ACCEPT_CONFIG"]),
73
+ bootstrap_mode: truthy?(env["KETTLE_JEM_BOOTSTRAP_MODE"]),
74
+ quiet: truthy?(env["KETTLE_JEM_QUIET"]),
75
+ verbose: truthy?(env["KETTLE_JEM_VERBOSE"])
76
+ }.compact
77
+ end
78
+
79
+ def default_thread_worker_count
80
+ [1, Etc.nprocessors / 2].max
81
+ end
82
+
83
+ def worker_or_strategy_option_provided?(env, run_options)
84
+ option_keys = %i[
85
+ ractor_workers
86
+ recipe_planning_workers
87
+ thread_workers
88
+ recipe_planning_thread_workers
89
+ ractor_file_workers
90
+ file_work_workers
91
+ thread_file_workers
92
+ file_work_thread_workers
93
+ recipe_planning_strategy
94
+ ]
95
+ string_option_keys = option_keys.map(&:to_s)
96
+ env_keys = %w[
97
+ KETTLE_JEM_RACTOR_WORKERS
98
+ KETTLE_JEM_THREAD_WORKERS
99
+ KETTLE_JEM_RACTOR_FILE_WORKERS
100
+ KETTLE_JEM_THREAD_FILE_WORKERS
101
+ KETTLE_JEM_RECIPE_PLANNING_STRATEGY
102
+ ]
103
+ option_keys.any? { |key| run_options.key?(key) } ||
104
+ string_option_keys.any? { |key| run_options.key?(key) } ||
105
+ env_keys.any? { |key| env.key?(key) && !env[key].to_s.strip.empty? }
106
+ end
107
+
108
+ def truthy?(value)
109
+ Kettle::Jem::DecisionPolicy.truthy?(value)
110
+ end
111
+
112
+ def falsey?(value)
113
+ Kettle::Jem::DecisionPolicy.falsey?(value)
114
+ end
115
+
116
+ def monorepo_root_profile?(project_root, env, run_options)
117
+ profile = (run_options || {})[:template_profile] || (run_options || {})["template_profile"] || (env || {})["KETTLE_JEM_TEMPLATE_PROFILE"]
118
+ profile = Kettle::Jem.kettle_jem_config(project_root).dig("templates", "profile") if profile.to_s.empty?
119
+ Kettle::Jem.normalize_template_profile(profile) == Kettle::Jem::MONOREPO_ROOT_TEMPLATE_PROFILE
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ rakelib = File.expand_path("rakelib", __dir__)
6
+ Dir[File.join(rakelib, "*.rake")].sort.each { |path| load path }
@@ -0,0 +1,19 @@
1
+ # An .aiignore file follows the same syntax as a .gitignore file.
2
+ # .gitignore documentation: https://git-scm.com/docs/gitignore
3
+
4
+ # you can ignore files
5
+ .DS_Store
6
+ *.log
7
+ *.tmp
8
+
9
+ # or folders
10
+ .devcontainer/
11
+ .qlty/
12
+ .yardoc/
13
+ dist/
14
+ build/
15
+ out/
16
+ coverage/
17
+ docs/
18
+ pkg/
19
+ results/