kettle-jem 7.0.0 → 7.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 (201) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1428 -0
  4. data/LICENSE.md +13 -0
  5. data/README.md +807 -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 +2133 -0
  14. data/lib/kettle/jem/tasks/prepare_task.rb +251 -0
  15. data/lib/kettle/jem/tasks/self_test_task.rb +256 -0
  16. data/lib/kettle/jem/tasks/template_task.rb +123 -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 +284 -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 +49 -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 +545 -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/coverage.gemfile.example +14 -0
  121. data/lib/kettle/jem/templates/gemfiles/modular/coverage_local.gemfile.example +34 -0
  122. data/lib/kettle/jem/templates/gemfiles/modular/debug.gemfile.example +22 -0
  123. data/lib/kettle/jem/templates/gemfiles/modular/documentation.gemfile.example +24 -0
  124. data/lib/kettle/jem/templates/gemfiles/modular/documentation_local.gemfile.example +20 -0
  125. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2/v3.0.gemfile.example +1 -0
  126. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2.3/default.gemfile.example +6 -0
  127. data/lib/kettle/jem/templates/gemfiles/modular/erb/r2.6/v2.2.gemfile.example +3 -0
  128. data/lib/kettle/jem/templates/gemfiles/modular/erb/r3/v5.0.gemfile.example +1 -0
  129. data/lib/kettle/jem/templates/gemfiles/modular/erb/r3.1/v4.0.gemfile.example +2 -0
  130. data/lib/kettle/jem/templates/gemfiles/modular/erb/r4/v5.0.gemfile.example +1 -0
  131. data/lib/kettle/jem/templates/gemfiles/modular/erb/vHEAD.gemfile.example +2 -0
  132. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_22_3.gemfile.example +6 -0
  133. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_23_0.gemfile.example +6 -0
  134. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_23_1.gemfile.example +6 -0
  135. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r2/v0.3.gemfile.example +2 -0
  136. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile.example +3 -0
  137. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r3/v0.3.gemfile.example +2 -0
  138. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/r4/v0.3.gemfile.example +2 -0
  139. data/lib/kettle/jem/templates/gemfiles/modular/mutex_m/vHEAD.gemfile.example +2 -0
  140. data/lib/kettle/jem/templates/gemfiles/modular/optional.gemfile.example +16 -0
  141. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.3/recording.gemfile.example +16 -0
  142. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.4/recording.gemfile.example +18 -0
  143. data/lib/kettle/jem/templates/gemfiles/modular/recording/r2.5/recording.gemfile.example +16 -0
  144. data/lib/kettle/jem/templates/gemfiles/modular/recording/r3/recording.gemfile.example +16 -0
  145. data/lib/kettle/jem/templates/gemfiles/modular/recording/r4/recording.gemfile.example +16 -0
  146. data/lib/kettle/jem/templates/gemfiles/modular/recording/vHEAD.gemfile.example +2 -0
  147. data/lib/kettle/jem/templates/gemfiles/modular/rspec.gemfile.example +4 -0
  148. data/lib/kettle/jem/templates/gemfiles/modular/runtime_heads.gemfile.example +14 -0
  149. data/lib/kettle/jem/templates/gemfiles/modular/shunted.gemfile.example +21 -0
  150. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2/v3.0.gemfile.example +5 -0
  151. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile.example +4 -0
  152. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r3/v3.0.gemfile.example +5 -0
  153. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r4/v3.0.gemfile.example +5 -0
  154. data/lib/kettle/jem/templates/gemfiles/modular/stringio/vHEAD.gemfile.example +2 -0
  155. data/lib/kettle/jem/templates/gemfiles/modular/style.gemfile.example +31 -0
  156. data/lib/kettle/jem/templates/gemfiles/modular/style_local.gemfile.example +26 -0
  157. data/lib/kettle/jem/templates/gemfiles/modular/templating.gemfile.example +34 -0
  158. data/lib/kettle/jem/templates/gemfiles/modular/templating_local.gemfile.example +72 -0
  159. data/lib/kettle/jem/templates/gemfiles/modular/webrick/r4/v1.9.gemfile.example +1 -0
  160. data/lib/kettle/jem/templates/gemfiles/modular/webrick/vHEAD.gemfile.example +1 -0
  161. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2/libs.gemfile.example +3 -0
  162. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.3/libs.gemfile.example +3 -0
  163. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.4/libs.gemfile.example +3 -0
  164. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r2.6/libs.gemfile.example +3 -0
  165. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r3/libs.gemfile.example +3 -0
  166. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r3.1/libs.gemfile.example +3 -0
  167. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r4/libs.gemfile.example +6 -0
  168. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/vHEAD.gemfile.example +6 -0
  169. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs.gemfile.example +12 -0
  170. data/lib/kettle/jem/templates/lib/gem/version.rb.example +1 -0
  171. data/lib/kettle/jem/templates/lib/gem/version_gem.rb.example +1 -0
  172. data/lib/kettle/jem/templates/mise.toml.example +25 -0
  173. data/lib/kettle/jem/templates/mise.toml.no-osc.example +25 -0
  174. data/lib/kettle/jem/templates/parsers.toml.example +19 -0
  175. data/lib/kettle/jem/templates/readme/partials/basic_usage.md.example +28 -0
  176. data/lib/kettle/jem/templates/readme/partials/configuration.md.example +112 -0
  177. data/lib/kettle/jem/templates/readme/partials/synopsis.md.example +3 -0
  178. data/lib/kettle/jem/templates/shim/.github/workflows/current.yml.example +40 -0
  179. data/lib/kettle/jem/templates/shim/.rspec.example +5 -0
  180. data/lib/kettle/jem/templates/shim/.structuredmerge/kettle-jem.yml.example +28 -0
  181. data/lib/kettle/jem/templates/shim/CHANGELOG.md.example +9 -0
  182. data/lib/kettle/jem/templates/shim/Gemfile.example +14 -0
  183. data/lib/kettle/jem/templates/shim/README.md.example +25 -0
  184. data/lib/kettle/jem/templates/shim/Rakefile.example +8 -0
  185. data/lib/kettle/jem/templates/shim/gem.gemspec.example +45 -0
  186. data/lib/kettle/jem/templates/shim/lib/compat_require.rb.example +3 -0
  187. data/lib/kettle/jem/templates/shim/lib/entrypoint.rb.example +3 -0
  188. data/lib/kettle/jem/templates/shim/lib/version.rb.example +1 -0
  189. data/lib/kettle/jem/templates/shim/spec/shim_spec.rb.example +15 -0
  190. data/lib/kettle/jem/templates/shim/spec/spec_helper.rb.example +23 -0
  191. data/lib/kettle/jem/templates/sig/gem.rbs.example +1 -0
  192. data/lib/kettle/jem/templates/spec/README.md.example +28 -0
  193. data/lib/kettle/jem/templates/spec/spec_helper.rb.example +39 -0
  194. data/lib/kettle/jem/version.rb +5 -3
  195. data/lib/kettle/jem/version_gem.rb +8 -0
  196. data/lib/kettle/jem.rb +18317 -1525
  197. data/lib/kettle-jem.rb +2 -1
  198. data/sig/kettle/jem.rbs +8 -0
  199. data.tar.gz.sig +0 -0
  200. metadata +753 -24
  201. metadata.gz.sig +0 -0
@@ -0,0 +1,251 @@
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
+ nomono_bootstrap_step = normalize_existing_local_gemfile_bootstraps_step(project_root, events: events)
35
+ setup_env = Kettle::Jem::Tasks::InstallTask.setup_command_env(project_root, env)
36
+ setup_env["BUNDLE_DISABLE_CHECKSUM_VALIDATION"] = "true"
37
+ setup_env["K_JEM_TEMPLATING"] = "true" if local_path_development_env?(env)
38
+ reset_step = reset_release_lockfiles_step(
39
+ project_root: project_root,
40
+ setup_env: setup_env,
41
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
42
+ command_runner: command_runner,
43
+ events: events
44
+ )
45
+ Kettle::Jem.emit_step_event(events, "command_step", reset_step, phase: "prepare")
46
+ bootstrap_name = templating_bootstrap_step_name(project_root)
47
+ bootstrap_command = templating_bootstrap_command(project_root)
48
+ Kettle::Jem.emit_step_event(
49
+ events,
50
+ "command_step",
51
+ {name: bootstrap_name, status: "started", command: bootstrap_command},
52
+ phase: "prepare"
53
+ )
54
+ bootstrap_step = Kettle::Jem::Tasks::InstallTask.run_command_step(
55
+ bootstrap_name,
56
+ bootstrap_command,
57
+ project_root: project_root,
58
+ env: setup_env,
59
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
60
+ command_runner: command_runner
61
+ )
62
+ Kettle::Jem.emit_step_event(events, "command_step", bootstrap_step, phase: "prepare")
63
+ bundle_step = bundle_install_after_bootstrap_step(
64
+ project_root: project_root,
65
+ setup_env: setup_env,
66
+ quiet: Kettle::Jem::DecisionPolicy.value_to_boolean(effective_run_options[:quiet]),
67
+ command_runner: command_runner,
68
+ events: events,
69
+ bootstrap_command: bootstrap_command
70
+ )
71
+ Kettle::Jem.emit_step_event(events, "command_step", bundle_step, phase: "prepare")
72
+
73
+ final_report = report.merge(
74
+ mode: "prepare",
75
+ prepared: bootstrap_step.fetch(:status) == "succeeded" &&
76
+ %w[skipped succeeded].include?(reset_step.fetch(:status)) &&
77
+ %w[skipped succeeded].include?(bundle_step.fetch(:status)),
78
+ prepare_only: PREPARE_ONLY_PATHS,
79
+ prepare_steps: [nomono_bootstrap_step, reset_step, bootstrap_step, bundle_step],
80
+ changed_files: (
81
+ report.fetch(:changed_files, []) +
82
+ nomono_bootstrap_step.fetch(:changed_files, []) +
83
+ reset_step.fetch(:changed_files, []) +
84
+ bootstrap_step.fetch(:changed_files, []) +
85
+ bundle_step.fetch(:changed_files, [])
86
+ ).uniq.sort,
87
+ diagnostics: report.fetch(:diagnostics, []) + [{
88
+ severity: "advisory",
89
+ message: "kettle:jem:prepare applied the templating dependency bootstrap payload, " \
90
+ "updated critical templating gems, and ran bundle install."
91
+ }]
92
+ )
93
+ Kettle::Jem.emit_summary_event(events, final_report)
94
+ final_report
95
+ end
96
+
97
+ def normalize_existing_local_gemfile_bootstraps_step(project_root, events:)
98
+ paths = Dir.glob(File.join(project_root.to_s, "gemfiles/modular/**/*_local.gemfile")).sort
99
+ changed_files = paths.filter_map do |path|
100
+ before = File.read(path)
101
+ after = Kettle::Jem.normalize_local_gemfile_nomono_bootstrap(before)
102
+ next if after == before
103
+
104
+ File.write(path, after)
105
+ Pathname.new(path).relative_path_from(Pathname.new(project_root.to_s)).to_s
106
+ end
107
+ step = {
108
+ name: "normalize_local_gemfile_bootstraps",
109
+ status: changed_files.empty? ? "already_current" : "applied",
110
+ changed_files: changed_files
111
+ }
112
+ Kettle::Jem.emit_step_event(events, "command_step", step, phase: "prepare")
113
+ step
114
+ end
115
+
116
+ def reset_release_lockfiles_step(project_root:, setup_env:, quiet:, command_runner:, events:)
117
+ if local_path_development_env?(setup_env)
118
+ return {
119
+ name: "reset_release_lockfiles",
120
+ command: %w[kettle-reset release-lockfiles],
121
+ status: "skipped",
122
+ reason: "local_path_development_env"
123
+ }
124
+ end
125
+
126
+ reset_command_runner = lambda do |command|
127
+ command_runner.call(Shellwords.split(command), chdir: project_root, env: setup_env, quiet: quiet)
128
+ end
129
+ resetter = Kettle::Dev::LockfileReset.new(
130
+ root: project_root,
131
+ command_runner: reset_command_runner
132
+ )
133
+ paths = resetter.lockfile_paths
134
+ if paths.empty?
135
+ return {
136
+ name: "reset_release_lockfiles",
137
+ command: %w[kettle-reset release-lockfiles],
138
+ status: "skipped",
139
+ reason: "no_release_lockfiles"
140
+ }
141
+ end
142
+
143
+ Kettle::Jem.emit_step_event(
144
+ events,
145
+ "command_step",
146
+ {name: "reset_release_lockfiles", status: "started", command: %w[kettle-reset release-lockfiles]},
147
+ phase: "prepare"
148
+ )
149
+ before = snapshot_files(paths)
150
+ resetter.reset(Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET)
151
+ changed_files = changed_files_since(project_root, before)
152
+ {
153
+ name: "reset_release_lockfiles",
154
+ command: %w[kettle-reset release-lockfiles],
155
+ status: "succeeded",
156
+ changed_files: changed_files,
157
+ quiet: quiet
158
+ }
159
+ end
160
+
161
+ def local_path_development_env?(env)
162
+ (env || {}).any? do |key, raw_value|
163
+ next false unless key.to_s.end_with?("_DEV")
164
+
165
+ value = raw_value.to_s.strip
166
+ !value.empty? && !Kettle::Jem::DecisionPolicy.falsey?(value)
167
+ end
168
+ end
169
+
170
+ def bundle_update_templating_bootstrap_command(project_root = Dir.pwd)
171
+ %w[bundle update] + CRITICAL_TEMPLATING_GEMS + locked_templating_gems(project_root)
172
+ end
173
+
174
+ def templating_bootstrap_command(project_root = Dir.pwd)
175
+ return %w[bundle install] unless templating_bootstrap_lockfile_ready?(project_root)
176
+
177
+ bundle_update_templating_bootstrap_command(project_root)
178
+ end
179
+
180
+ def templating_bootstrap_step_name(project_root = Dir.pwd)
181
+ return "bundle_install_templating_bootstrap" unless templating_bootstrap_lockfile_ready?(project_root)
182
+
183
+ "bundle_update_templating_bootstrap"
184
+ end
185
+
186
+ def templating_bootstrap_lockfile_ready?(project_root)
187
+ lock_path = File.join(project_root.to_s, "Gemfile.lock")
188
+ return false unless File.file?(lock_path)
189
+
190
+ locked_gem_names(project_root).then do |names|
191
+ CRITICAL_TEMPLATING_GEMS.all? { |gem_name| names.include?(gem_name) }
192
+ end
193
+ rescue Bundler::LockfileError
194
+ false
195
+ end
196
+
197
+ def bundle_install_after_bootstrap_step(project_root:, setup_env:, quiet:, command_runner:, events:, bootstrap_command:)
198
+ if bootstrap_command == %w[bundle install]
199
+ return {
200
+ name: "bundle_install",
201
+ command: %w[bundle install],
202
+ status: "skipped",
203
+ reason: "already_ran_as_templating_bootstrap"
204
+ }
205
+ end
206
+
207
+ Kettle::Jem.emit_step_event(
208
+ events,
209
+ "command_step",
210
+ {name: "bundle_install", status: "started", command: %w[bundle install]},
211
+ phase: "prepare"
212
+ )
213
+ Kettle::Jem::Tasks::InstallTask.run_command_step(
214
+ "bundle_install",
215
+ %w[bundle install],
216
+ project_root: project_root,
217
+ env: setup_env,
218
+ quiet: quiet,
219
+ command_runner: command_runner
220
+ )
221
+ end
222
+
223
+ def locked_templating_gems(project_root)
224
+ LOCKED_TEMPLATING_GEMS & locked_gem_names(project_root)
225
+ end
226
+
227
+ def locked_gem_names(project_root)
228
+ lock_path = File.join(project_root.to_s, "Gemfile.lock")
229
+ return [] unless File.file?(lock_path)
230
+
231
+ Bundler::LockfileParser.new(Bundler.read_file(lock_path)).specs.map(&:name)
232
+ end
233
+
234
+ def snapshot_files(paths)
235
+ paths.each_with_object({}) do |path, snapshot|
236
+ snapshot[path] = File.file?(path) ? File.binread(path) : nil
237
+ end
238
+ end
239
+
240
+ def changed_files_since(project_root, snapshot)
241
+ snapshot.filter_map do |path, before|
242
+ after = File.file?(path) ? File.binread(path) : nil
243
+ next if before == after
244
+
245
+ Pathname.new(path).relative_path_from(Pathname.new(project_root)).to_s
246
+ end.sort
247
+ end
248
+ end
249
+ end
250
+ end
251
+ 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,123 @@
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_lock_normalization: truthy?(env["KETTLE_JEM_SKIP_LOCK_NORMALIZATION"]),
70
+ checksums: env["KETTLE_JEM_CHECKSUMS"],
71
+ accept_config: truthy?(env["KETTLE_JEM_ACCEPT_CONFIG"]),
72
+ bootstrap_mode: truthy?(env["KETTLE_JEM_BOOTSTRAP_MODE"]),
73
+ quiet: truthy?(env["KETTLE_JEM_QUIET"]),
74
+ verbose: truthy?(env["KETTLE_JEM_VERBOSE"])
75
+ }.compact
76
+ end
77
+
78
+ def default_thread_worker_count
79
+ [1, Etc.nprocessors / 2].max
80
+ end
81
+
82
+ def worker_or_strategy_option_provided?(env, run_options)
83
+ option_keys = %i[
84
+ ractor_workers
85
+ recipe_planning_workers
86
+ thread_workers
87
+ recipe_planning_thread_workers
88
+ ractor_file_workers
89
+ file_work_workers
90
+ thread_file_workers
91
+ file_work_thread_workers
92
+ recipe_planning_strategy
93
+ ]
94
+ string_option_keys = option_keys.map(&:to_s)
95
+ env_keys = %w[
96
+ KETTLE_JEM_RACTOR_WORKERS
97
+ KETTLE_JEM_THREAD_WORKERS
98
+ KETTLE_JEM_RACTOR_FILE_WORKERS
99
+ KETTLE_JEM_THREAD_FILE_WORKERS
100
+ KETTLE_JEM_RECIPE_PLANNING_STRATEGY
101
+ ]
102
+ option_keys.any? { |key| run_options.key?(key) } ||
103
+ string_option_keys.any? { |key| run_options.key?(key) } ||
104
+ env_keys.any? { |key| env.key?(key) && !env[key].to_s.strip.empty? }
105
+ end
106
+
107
+ def truthy?(value)
108
+ Kettle::Jem::DecisionPolicy.truthy?(value)
109
+ end
110
+
111
+ def falsey?(value)
112
+ Kettle::Jem::DecisionPolicy.falsey?(value)
113
+ end
114
+
115
+ def monorepo_root_profile?(project_root, env, run_options)
116
+ profile = (run_options || {})[:template_profile] || (run_options || {})["template_profile"] || (env || {})["KETTLE_JEM_TEMPLATE_PROFILE"]
117
+ profile = Kettle::Jem.kettle_jem_config(project_root).dig("templates", "profile") if profile.to_s.empty?
118
+ Kettle::Jem.normalize_template_profile(profile) == Kettle::Jem::MONOREPO_ROOT_TEMPLATE_PROFILE
119
+ end
120
+ end
121
+ end
122
+ end
123
+ 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/
@@ -0,0 +1,26 @@
1
+ # shellcheck shell=bash
2
+
3
+ prepend_unique_path_value() {
4
+ local value="$1"
5
+ local current="$2"
6
+
7
+ if [[ -z "$current" ]]; then
8
+ printf '%s' "$value"
9
+ elif [[ ":$current:" == *":$value:"* ]]; then
10
+ printf '%s' "$current"
11
+ else
12
+ printf '%s:%s' "$value" "$current"
13
+ fi
14
+ }
15
+
16
+ # Preserve existing values while prepending the project defaults needed by TreeHaver.
17
+ _tree_sitter_runtime_lib="${TREE_SITTER_RUNTIME_LIB:-/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.6/lib/libtree-sitter.so}"
18
+ _tree_sitter_runtime_dir="$(dirname "${_tree_sitter_runtime_lib}")"
19
+ _tree_sitter_java_jars_dir="${TREE_SITTER_JAVA_JARS_DIR:-}"
20
+
21
+ if [[ -n "${_tree_sitter_java_jars_dir}" ]]; then
22
+ _tree_sitter_java_jar="${_tree_sitter_java_jars_dir}/jtreesitter-0.26.0.jar"
23
+ export CLASSPATH="$(prepend_unique_path_value "${_tree_sitter_java_jar}" "${CLASSPATH:-}")"
24
+ fi
25
+
26
+ export LD_LIBRARY_PATH="$(prepend_unique_path_value "${_tree_sitter_runtime_dir}" "${LD_LIBRARY_PATH:-}")"