makit 0.0.144 → 0.0.145

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/auto.rb +48 -48
  6. data/lib/makit/cli/base.rb +17 -0
  7. data/lib/makit/cli/build_commands.rb +500 -500
  8. data/lib/makit/cli/generators/base_generator.rb +74 -74
  9. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  10. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  11. data/lib/makit/cli/generators/node_generator.rb +50 -50
  12. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  13. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  14. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  15. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  16. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  17. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -40
  18. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  19. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  20. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  21. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  22. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  23. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  24. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  25. data/lib/makit/cli/main.rb +78 -69
  26. data/lib/makit/cli/pipeline_commands.rb +311 -0
  27. data/lib/makit/cli/project_commands.rb +868 -868
  28. data/lib/makit/cli/repository_commands.rb +661 -661
  29. data/lib/makit/cli/strategy_commands.rb +207 -212
  30. data/lib/makit/cli/utility_commands.rb +521 -521
  31. data/lib/makit/commands/factory.rb +359 -359
  32. data/lib/makit/commands/middleware/base.rb +73 -73
  33. data/lib/makit/commands/middleware/cache.rb +248 -248
  34. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  35. data/lib/makit/commands/middleware/validator.rb +269 -269
  36. data/lib/makit/commands/request.rb +316 -316
  37. data/lib/makit/commands/result.rb +323 -323
  38. data/lib/makit/commands/runner.rb +386 -386
  39. data/lib/makit/commands/strategies/base.rb +171 -171
  40. data/lib/makit/commands/strategies/child_process.rb +162 -162
  41. data/lib/makit/commands/strategies/factory.rb +136 -136
  42. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  43. data/lib/makit/commands.rb +50 -50
  44. data/lib/makit/configuration/dotnet_project.rb +48 -48
  45. data/lib/makit/configuration/gitlab_helper.rb +61 -58
  46. data/lib/makit/configuration/project.rb +446 -168
  47. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  48. data/lib/makit/configuration/step.rb +34 -34
  49. data/lib/makit/configuration/timeout.rb +74 -74
  50. data/lib/makit/configuration.rb +21 -16
  51. data/lib/makit/content/default_gitignore.rb +7 -7
  52. data/lib/makit/content/default_gitignore.txt +225 -225
  53. data/lib/makit/content/default_rakefile.rb +13 -13
  54. data/lib/makit/content/gem_rakefile.rb +16 -16
  55. data/lib/makit/context.rb +1 -1
  56. data/lib/makit/data.rb +49 -49
  57. data/lib/makit/directories.rb +140 -140
  58. data/lib/makit/directory.rb +262 -262
  59. data/lib/makit/docs/files.rb +89 -89
  60. data/lib/makit/docs/rake.rb +102 -102
  61. data/lib/makit/dotnet/cli.rb +69 -69
  62. data/lib/makit/dotnet/project.rb +217 -217
  63. data/lib/makit/dotnet/solution.rb +38 -38
  64. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  65. data/lib/makit/dotnet/solution_console.rb +264 -264
  66. data/lib/makit/dotnet/solution_maui.rb +354 -354
  67. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  68. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  69. data/lib/makit/dotnet.rb +102 -102
  70. data/lib/makit/email.rb +90 -90
  71. data/lib/makit/environment.rb +142 -142
  72. data/lib/makit/examples/runner.rb +370 -370
  73. data/lib/makit/exceptions.rb +45 -45
  74. data/lib/makit/fileinfo.rb +32 -24
  75. data/lib/makit/files.rb +43 -43
  76. data/lib/makit/gems.rb +40 -40
  77. data/lib/makit/git/cli.rb +54 -54
  78. data/lib/makit/git/repository.rb +266 -90
  79. data/lib/makit/git.rb +104 -98
  80. data/lib/makit/gitlab/pipeline.rb +857 -0
  81. data/lib/makit/gitlab/pipeline_service_impl.rb +1536 -0
  82. data/lib/makit/gitlab_runner.rb +59 -59
  83. data/lib/makit/humanize.rb +218 -137
  84. data/lib/makit/indexer.rb +47 -47
  85. data/lib/makit/io/filesystem.rb +111 -0
  86. data/lib/makit/io/filesystem_service_impl.rb +337 -0
  87. data/lib/makit/logging/configuration.rb +308 -308
  88. data/lib/makit/logging/format_registry.rb +84 -84
  89. data/lib/makit/logging/formatters/base.rb +39 -39
  90. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  91. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  92. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  93. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  94. data/lib/makit/logging/log_request.rb +119 -119
  95. data/lib/makit/logging/logger.rb +199 -199
  96. data/lib/makit/logging/sinks/base.rb +91 -91
  97. data/lib/makit/logging/sinks/console.rb +72 -72
  98. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  99. data/lib/makit/logging/sinks/structured.rb +123 -123
  100. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  101. data/lib/makit/logging.rb +565 -565
  102. data/lib/makit/markdown.rb +75 -75
  103. data/lib/makit/mp/basic_object_mp.rb +17 -17
  104. data/lib/makit/mp/command_mp.rb +13 -13
  105. data/lib/makit/mp/command_request.mp.rb +17 -17
  106. data/lib/makit/mp/project_mp.rb +199 -199
  107. data/lib/makit/mp/string_mp.rb +205 -199
  108. data/lib/makit/nuget.rb +74 -74
  109. data/lib/makit/podman/podman.rb +458 -0
  110. data/lib/makit/podman/podman_service_impl.rb +1081 -0
  111. data/lib/makit/port.rb +32 -32
  112. data/lib/makit/process.rb +377 -377
  113. data/lib/makit/protoc.rb +112 -107
  114. data/lib/makit/rake/cli.rb +196 -196
  115. data/lib/makit/rake/trace_controller.rb +174 -174
  116. data/lib/makit/rake.rb +81 -81
  117. data/lib/makit/ruby/cli.rb +185 -185
  118. data/lib/makit/ruby.rb +25 -25
  119. data/lib/makit/secrets.rb +51 -51
  120. data/lib/makit/serializer.rb +130 -130
  121. data/lib/makit/services/builder.rb +186 -186
  122. data/lib/makit/services/error_handler.rb +226 -226
  123. data/lib/makit/services/repository_manager.rb +367 -231
  124. data/lib/makit/services/validator.rb +112 -112
  125. data/lib/makit/setup/classlib.rb +101 -101
  126. data/lib/makit/setup/gem.rb +268 -268
  127. data/lib/makit/setup/pages.rb +11 -11
  128. data/lib/makit/setup/razorclasslib.rb +101 -101
  129. data/lib/makit/setup/runner.rb +54 -54
  130. data/lib/makit/setup.rb +5 -5
  131. data/lib/makit/show.rb +110 -110
  132. data/lib/makit/storage.rb +126 -126
  133. data/lib/makit/symbols.rb +175 -170
  134. data/lib/makit/task_info.rb +130 -130
  135. data/lib/makit/tasks/at_exit.rb +15 -15
  136. data/lib/makit/tasks/build.rb +22 -22
  137. data/lib/makit/tasks/clean.rb +13 -13
  138. data/lib/makit/tasks/configure.rb +10 -10
  139. data/lib/makit/tasks/format.rb +10 -10
  140. data/lib/makit/tasks/hook_manager.rb +443 -443
  141. data/lib/makit/tasks/init.rb +49 -49
  142. data/lib/makit/tasks/integrate.rb +29 -29
  143. data/lib/makit/tasks/pull_incoming.rb +13 -13
  144. data/lib/makit/tasks/setup.rb +16 -16
  145. data/lib/makit/tasks/sync.rb +17 -17
  146. data/lib/makit/tasks/tag.rb +16 -16
  147. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  148. data/lib/makit/tasks/test.rb +22 -22
  149. data/lib/makit/tasks/update.rb +18 -18
  150. data/lib/makit/tasks.rb +20 -20
  151. data/lib/makit/test_cache.rb +239 -239
  152. data/lib/makit/tree.rb +37 -37
  153. data/lib/makit/v1/configuration/project_service_impl.rb +371 -0
  154. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -0
  155. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  156. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  157. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -0
  158. data/lib/makit/version.rb +100 -100
  159. data/lib/makit/version_util.rb +21 -21
  160. data/lib/makit/wix.rb +95 -95
  161. data/lib/makit/yaml.rb +29 -29
  162. data/lib/makit/zip.rb +17 -17
  163. data/lib/makit copy.rb +44 -44
  164. data/lib/makit.rb +111 -43
  165. metadata +61 -36
@@ -1,386 +1,386 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "request"
4
- require_relative "result"
5
- require_relative "strategies/synchronous"
6
- require_relative "strategies/factory"
7
- require_relative "middleware/base"
8
- require_relative "middleware/command_logger"
9
-
10
- module Makit
11
- module Commands
12
- # Modern command execution engine with proper separation of concerns.
13
- #
14
- # The Runner coordinates command execution through a middleware chain and
15
- # execution strategies. It provides a clean, extensible architecture for
16
- # command processing with support for caching, logging, validation, and
17
- # custom execution patterns.
18
- #
19
- # @example Basic usage
20
- # runner = Runner.new
21
- # request = Request.from_string("git --version")
22
- # result = runner.execute(request)
23
- # puts result.stdout
24
- #
25
- # @example With custom middleware
26
- # runner = Runner.new(middleware: [Logger.new, Cache.new])
27
- # result = runner.execute(request)
28
- #
29
- # @example With custom strategy
30
- # runner = Runner.new(strategy: Strategies::Parallel.new)
31
- # results = runner.execute_batch(requests)
32
- class Runner
33
- # @!attribute [r] middleware
34
- # @return [Array<Middleware::Base>] middleware chain
35
- # @!attribute [r] strategy
36
- # @return [Strategies::Base] execution strategy
37
- attr_reader :middleware, :strategy
38
-
39
- # Get the default configured runner instance.
40
- #
41
- # @return [Runner] default runner with standard middleware
42
- def self.default
43
- # Recreate the runner if the log level has changed
44
- current_log_level = defined?(Makit::Logging) ? Makit::Logging.current_log_level : :info
45
- if @default.nil? || @last_log_level != current_log_level
46
- @last_log_level = current_log_level
47
- begin
48
- @default = new(
49
- middleware: [
50
- Middleware::CommandLogger.new(
51
- log_stdout: true,
52
- log_stderr: true,
53
- log_performance: true,
54
- max_output_lines: 50,
55
- ),
56
- ],
57
- )
58
- rescue => e
59
- # Fallback to basic runner if there are issues
60
- @default = new
61
- end
62
- end
63
- @default
64
- end
65
-
66
- # Initialize a new command runner.
67
- #
68
- # @param middleware [Array<Middleware::Base>] middleware chain
69
- # @param strategy [Strategies::Base] execution strategy
70
- # @param options [Hash] additional configuration
71
- def initialize(middleware: nil, strategy: nil, **options)
72
- @middleware = Array(middleware || default_middleware)
73
- @strategy = strategy || Strategies::Factory.create(options)
74
- @options = options
75
-
76
- validate_middleware
77
- validate_strategy
78
-
79
- # Log runner initialization
80
- log_runner_initialization
81
- end
82
-
83
- # Execute a single command request.
84
- #
85
- # @param request [Request, String, Hash] command request to execute
86
- # @return [Result] execution result
87
- # @raise [ArgumentError] if request is invalid
88
- def execute(request)
89
- Makit::Logging.debug("Makit::Commands::Runner.execute starting")
90
- # Makit::Logging.debug("Executing request: #{request.to_json_pretty}")
91
- # Normalize request to Request object
92
- normalized_request = normalize_request(request)
93
-
94
- # Apply middleware chain
95
- Makit::Logging.debug("Makit::Commands::Runner.execute applying middleware chain")
96
- result = execute_with_middleware(normalized_request) do |processed_request|
97
- Makit::Logging.debug("Makit::Commands::Runner.execute executing strategy")
98
- result = @strategy.execute(processed_request)
99
- Makit::Logging.debug("Makit::Commands::Runner.execute strategy returned result")
100
- result
101
- end
102
- Makit::Logging.debug("Makit::Commands::Runner.execute returning result")
103
- Makit::Logging.debug("Makit::Commands::Runner request.exit_on_failure?: #{request.exit_on_failure?}")
104
- # stderr often contains warnings that we don't want to exit on
105
- if request.exit_on_failure? && (result.failure?)
106
- Makit::Logging.error("exiting with exit code: #{result.exit_code}")
107
- exit result.exit_code
108
- end
109
- result
110
- end
111
-
112
- # Execute multiple command requests.
113
- #
114
- # @param requests [Array<Request, String, Hash>] requests to execute
115
- # @return [Array<Result>] execution results
116
- def execute_batch(requests)
117
- # Normalize all requests
118
- normalized_requests = requests.map { |req| normalize_request(req) }
119
-
120
- # Use strategy's batch execution if available, otherwise execute individually
121
- if @strategy.respond_to?(:execute_batch)
122
- # Apply middleware to the entire batch
123
- execute_with_middleware_batch(normalized_requests) do |processed_requests|
124
- @strategy.execute_batch(processed_requests)
125
- end
126
- else
127
- # Execute each request individually with middleware
128
- normalized_requests.map { |request| execute(request) }
129
- end
130
- end
131
-
132
- # Add middleware to the execution chain.
133
- #
134
- # @param middleware_instance [Middleware::Base] middleware to add
135
- # @return [self] for method chaining
136
- def add_middleware(middleware_instance)
137
- validate_middleware_instance(middleware_instance)
138
- @middleware << middleware_instance
139
- self
140
- end
141
-
142
- # Remove middleware from the execution chain.
143
- #
144
- # @param middleware_class [Class] middleware class to remove
145
- # @return [self] for method chaining
146
- def remove_middleware(middleware_class)
147
- @middleware.reject! { |m| m.is_a?(middleware_class) }
148
- self
149
- end
150
-
151
- # Check if specific middleware is present.
152
- #
153
- # @param middleware_class [Class] middleware class to check
154
- # @return [Boolean] true if middleware is present
155
- def has_middleware?(middleware_class)
156
- @middleware.any? { |m| m.is_a?(middleware_class) }
157
- end
158
-
159
- # Get runner configuration for debugging.
160
- #
161
- # @return [Hash] runner configuration
162
- def config
163
- {
164
- middleware: @middleware.map(&:config),
165
- strategy: @strategy.config,
166
- options: @options,
167
- }
168
- end
169
-
170
- # Get execution statistics.
171
- #
172
- # @return [Hash] execution statistics
173
- def stats
174
- @stats ||= {
175
- total_executions: 0,
176
- successful_executions: 0,
177
- failed_executions: 0,
178
- total_duration: 0.0,
179
- }
180
- end
181
-
182
- # Make this method public as it is used by rake hooks and CLI output
183
- # Get information about the current strategy
184
- #
185
- # @return [Hash] strategy information
186
- def strategy_info
187
- {
188
- class: @strategy.class.name,
189
- type: @strategy.class.name.split("::").last.downcase,
190
- factory_info: Strategies::Factory.strategy_info,
191
- }
192
- end
193
-
194
- private
195
-
196
- # Execute request with middleware chain.
197
- #
198
- # @param request [Request] normalized request
199
- # @yield [Request] yields processed request to execution
200
- # @return [Result] execution result
201
- def execute_with_middleware(request, &block)
202
- # Build middleware chain
203
- chain = build_middleware_chain(request)
204
-
205
- # Execute chain
206
- result = chain.call(request, &block)
207
-
208
- # Log high-level success/error using the default logger
209
- log_command_result(request, result)
210
-
211
- # Update statistics
212
- update_stats(result)
213
-
214
- result
215
- end
216
-
217
- # Execute batch with middleware chain.
218
- #
219
- # @param requests [Array<Request>] normalized requests
220
- # @yield [Array<Request>] yields processed requests to execution
221
- # @return [Array<Result>] execution results
222
- def execute_with_middleware_batch(requests, &block)
223
- # For batch execution, apply middleware to each request individually
224
- # This maintains the middleware contract while supporting batch execution
225
- results = requests.map do |request|
226
- execute_with_middleware(request) { |req| [req] }
227
- end.flatten
228
-
229
- # Then execute the batch
230
- processed_requests = results.map(&:command).map { |cmd| Request.from_string(cmd) }
231
- batch_results = block.call(processed_requests)
232
-
233
- # Update statistics for batch
234
- batch_results.each { |result| update_stats(result) }
235
-
236
- batch_results
237
- end
238
-
239
- # Build middleware execution chain.
240
- #
241
- # @param request [Request] the request to process
242
- # @return [Proc] middleware chain
243
- def build_middleware_chain(request)
244
- # Filter middleware that applies to this request
245
- applicable_middleware = @middleware.select { |m| m.applicable?(request) }
246
-
247
- # Build chain in reverse order so first middleware wraps everything
248
- applicable_middleware.reverse.reduce(method(:execute_final)) do |chain, middleware|
249
- ->(req) { middleware.call(req, &chain) }
250
- end
251
- end
252
-
253
- # Final execution step (after all middleware).
254
- #
255
- # @param request [Request] processed request
256
- # @return [Result] execution result
257
- def execute_final(request)
258
- @strategy.execute(request)
259
- end
260
-
261
- # Normalize various input types to Request objects.
262
- #
263
- # @param request [Request, String, Hash] request in various formats
264
- # @return [Request] normalized request
265
- # @raise [ArgumentError] if request cannot be normalized
266
- def normalize_request(request)
267
- case request
268
- when Request
269
- request
270
- when String
271
- Request.from_string(request)
272
- when Hash
273
- Request.from_hash(request)
274
- else
275
- raise ArgumentError, "Invalid request type: #{request.class}. Expected Request, String, or Hash."
276
- end
277
- end
278
-
279
- # Update execution statistics.
280
- #
281
- # @param result [Result] execution result
282
- def update_stats(result)
283
- stats[:total_executions] += 1
284
-
285
- if result.success?
286
- stats[:successful_executions] += 1
287
- else
288
- stats[:failed_executions] += 1
289
- end
290
-
291
- stats[:total_duration] += result.duration if result.duration
292
- end
293
-
294
- # Get default middleware chain.
295
- #
296
- # @return [Array<Middleware::Base>] default middleware
297
- def default_middleware
298
- # No default middleware for now - keep it simple
299
- # Subclasses or configuration can add middleware as needed
300
- []
301
- end
302
-
303
- # Validate middleware array.
304
- def validate_middleware
305
- @middleware.each { |m| validate_middleware_instance(m) }
306
- end
307
-
308
- # Validate individual middleware instance.
309
- #
310
- # @param middleware [Object] middleware to validate
311
- # @raise [ArgumentError] if middleware is invalid
312
- def validate_middleware_instance(middleware)
313
- unless middleware.respond_to?(:call)
314
- raise ArgumentError, "Middleware must respond to #call: #{middleware.class}"
315
- end
316
-
317
- return if middleware.respond_to?(:applicable?)
318
-
319
- raise ArgumentError, "Middleware must respond to #applicable?: #{middleware.class}"
320
- end
321
-
322
- # Validate execution strategy.
323
- #
324
- # @raise [ArgumentError] if strategy is invalid
325
- def validate_strategy
326
- unless @strategy.respond_to?(:execute)
327
- raise ArgumentError, "Strategy must respond to #execute: #{@strategy.class}"
328
- end
329
-
330
- return if @strategy.respond_to?(:supports?)
331
-
332
- raise ArgumentError, "Strategy must respond to #supports?: #{@strategy.class}"
333
- end
334
-
335
- # Log command execution result using the default logger.
336
- #
337
- # @param request [Request] the executed request
338
- # @param result [Result] the execution result
339
- # @return [void]
340
- def log_command_result(request, result)
341
- # Build the full command string
342
- command_string = "#{request.command} #{request.arguments&.join(" ")}".strip
343
-
344
- # Log success or error using the default logger
345
- if result.success?
346
- Makit::Logging.success(command_string)
347
- else
348
- Makit::Logging.error(command_string)
349
- end
350
-
351
- # Log the stdout, if there is an error or stdout is enabled
352
- if result.failure? || request.show_stdout?
353
- if (!result.stdout.empty?)
354
- # split the stdout into lines and print each line
355
- result.stdout.split("\n").each do |line|
356
- Makit::Logging.info(line)
357
- end
358
- end
359
- end
360
-
361
- # Log the stderr
362
- return unless request.show_stderr? && !result.stderr.empty?
363
-
364
- # split the stderr into lines and print each line
365
- result.stderr.split("\n").each do |line|
366
- Makit::Logging.info(line)
367
- end
368
- end
369
-
370
- # Log runner initialization for debugging.
371
- #
372
- # @return [void]
373
- def log_runner_initialization
374
- # Only log if we have a command logger middleware
375
- logger_middleware = @middleware.find { |m| m.is_a?(Middleware::CommandLogger) }
376
- return unless logger_middleware
377
-
378
- logger_middleware.logger.debug("Command runner initialized",
379
- middleware_count: @middleware.length,
380
- strategy: @strategy.class.name,
381
- working_directory: Dir.pwd,
382
- options: @options)
383
- end
384
- end
385
- end
386
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "request"
4
+ require_relative "result"
5
+ require_relative "strategies/synchronous"
6
+ require_relative "strategies/factory"
7
+ require_relative "middleware/base"
8
+ require_relative "middleware/command_logger"
9
+
10
+ module Makit
11
+ module Commands
12
+ # Modern command execution engine with proper separation of concerns.
13
+ #
14
+ # The Runner coordinates command execution through a middleware chain and
15
+ # execution strategies. It provides a clean, extensible architecture for
16
+ # command processing with support for caching, logging, validation, and
17
+ # custom execution patterns.
18
+ #
19
+ # @example Basic usage
20
+ # runner = Runner.new
21
+ # request = Request.from_string("git --version")
22
+ # result = runner.execute(request)
23
+ # puts result.stdout
24
+ #
25
+ # @example With custom middleware
26
+ # runner = Runner.new(middleware: [Logger.new, Cache.new])
27
+ # result = runner.execute(request)
28
+ #
29
+ # @example With custom strategy
30
+ # runner = Runner.new(strategy: Strategies::Parallel.new)
31
+ # results = runner.execute_batch(requests)
32
+ class Runner
33
+ # @!attribute [r] middleware
34
+ # @return [Array<Middleware::Base>] middleware chain
35
+ # @!attribute [r] strategy
36
+ # @return [Strategies::Base] execution strategy
37
+ attr_reader :middleware, :strategy
38
+
39
+ # Get the default configured runner instance.
40
+ #
41
+ # @return [Runner] default runner with standard middleware
42
+ def self.default
43
+ # Recreate the runner if the log level has changed
44
+ current_log_level = defined?(Makit::Logging) ? Makit::Logging.current_log_level : :info
45
+ if @default.nil? || @last_log_level != current_log_level
46
+ @last_log_level = current_log_level
47
+ begin
48
+ @default = new(
49
+ middleware: [
50
+ Middleware::CommandLogger.new(
51
+ log_stdout: true,
52
+ log_stderr: true,
53
+ log_performance: true,
54
+ max_output_lines: 50,
55
+ ),
56
+ ],
57
+ )
58
+ rescue => e
59
+ # Fallback to basic runner if there are issues
60
+ @default = new
61
+ end
62
+ end
63
+ @default
64
+ end
65
+
66
+ # Initialize a new command runner.
67
+ #
68
+ # @param middleware [Array<Middleware::Base>] middleware chain
69
+ # @param strategy [Strategies::Base] execution strategy
70
+ # @param options [Hash] additional configuration
71
+ def initialize(middleware: nil, strategy: nil, **options)
72
+ @middleware = Array(middleware || default_middleware)
73
+ @strategy = strategy || Strategies::Factory.create(options)
74
+ @options = options
75
+
76
+ validate_middleware
77
+ validate_strategy
78
+
79
+ # Log runner initialization
80
+ log_runner_initialization
81
+ end
82
+
83
+ # Execute a single command request.
84
+ #
85
+ # @param request [Request, String, Hash] command request to execute
86
+ # @return [Result] execution result
87
+ # @raise [ArgumentError] if request is invalid
88
+ def execute(request)
89
+ Makit::Logging.debug("Makit::Commands::Runner.execute starting")
90
+ # Makit::Logging.debug("Executing request: #{request.to_json_pretty}")
91
+ # Normalize request to Request object
92
+ normalized_request = normalize_request(request)
93
+
94
+ # Apply middleware chain
95
+ Makit::Logging.debug("Makit::Commands::Runner.execute applying middleware chain")
96
+ result = execute_with_middleware(normalized_request) do |processed_request|
97
+ Makit::Logging.debug("Makit::Commands::Runner.execute executing strategy")
98
+ result = @strategy.execute(processed_request)
99
+ Makit::Logging.debug("Makit::Commands::Runner.execute strategy returned result")
100
+ result
101
+ end
102
+ Makit::Logging.debug("Makit::Commands::Runner.execute returning result")
103
+ Makit::Logging.debug("Makit::Commands::Runner request.exit_on_failure?: #{request.exit_on_failure?}")
104
+ # stderr often contains warnings that we don't want to exit on
105
+ if request.exit_on_failure? && (result.failure?)
106
+ Makit::Logging.error("exiting with exit code: #{result.exit_code}")
107
+ exit result.exit_code
108
+ end
109
+ result
110
+ end
111
+
112
+ # Execute multiple command requests.
113
+ #
114
+ # @param requests [Array<Request, String, Hash>] requests to execute
115
+ # @return [Array<Result>] execution results
116
+ def execute_batch(requests)
117
+ # Normalize all requests
118
+ normalized_requests = requests.map { |req| normalize_request(req) }
119
+
120
+ # Use strategy's batch execution if available, otherwise execute individually
121
+ if @strategy.respond_to?(:execute_batch)
122
+ # Apply middleware to the entire batch
123
+ execute_with_middleware_batch(normalized_requests) do |processed_requests|
124
+ @strategy.execute_batch(processed_requests)
125
+ end
126
+ else
127
+ # Execute each request individually with middleware
128
+ normalized_requests.map { |request| execute(request) }
129
+ end
130
+ end
131
+
132
+ # Add middleware to the execution chain.
133
+ #
134
+ # @param middleware_instance [Middleware::Base] middleware to add
135
+ # @return [self] for method chaining
136
+ def add_middleware(middleware_instance)
137
+ validate_middleware_instance(middleware_instance)
138
+ @middleware << middleware_instance
139
+ self
140
+ end
141
+
142
+ # Remove middleware from the execution chain.
143
+ #
144
+ # @param middleware_class [Class] middleware class to remove
145
+ # @return [self] for method chaining
146
+ def remove_middleware(middleware_class)
147
+ @middleware.reject! { |m| m.is_a?(middleware_class) }
148
+ self
149
+ end
150
+
151
+ # Check if specific middleware is present.
152
+ #
153
+ # @param middleware_class [Class] middleware class to check
154
+ # @return [Boolean] true if middleware is present
155
+ def has_middleware?(middleware_class)
156
+ @middleware.any? { |m| m.is_a?(middleware_class) }
157
+ end
158
+
159
+ # Get runner configuration for debugging.
160
+ #
161
+ # @return [Hash] runner configuration
162
+ def config
163
+ {
164
+ middleware: @middleware.map(&:config),
165
+ strategy: @strategy.config,
166
+ options: @options,
167
+ }
168
+ end
169
+
170
+ # Get execution statistics.
171
+ #
172
+ # @return [Hash] execution statistics
173
+ def stats
174
+ @stats ||= {
175
+ total_executions: 0,
176
+ successful_executions: 0,
177
+ failed_executions: 0,
178
+ total_duration: 0.0,
179
+ }
180
+ end
181
+
182
+ # Make this method public as it is used by rake hooks and CLI output
183
+ # Get information about the current strategy
184
+ #
185
+ # @return [Hash] strategy information
186
+ def strategy_info
187
+ {
188
+ class: @strategy.class.name,
189
+ type: @strategy.class.name.split("::").last.downcase,
190
+ factory_info: Strategies::Factory.strategy_info,
191
+ }
192
+ end
193
+
194
+ private
195
+
196
+ # Execute request with middleware chain.
197
+ #
198
+ # @param request [Request] normalized request
199
+ # @yield [Request] yields processed request to execution
200
+ # @return [Result] execution result
201
+ def execute_with_middleware(request, &block)
202
+ # Build middleware chain
203
+ chain = build_middleware_chain(request)
204
+
205
+ # Execute chain
206
+ result = chain.call(request, &block)
207
+
208
+ # Log high-level success/error using the default logger
209
+ log_command_result(request, result)
210
+
211
+ # Update statistics
212
+ update_stats(result)
213
+
214
+ result
215
+ end
216
+
217
+ # Execute batch with middleware chain.
218
+ #
219
+ # @param requests [Array<Request>] normalized requests
220
+ # @yield [Array<Request>] yields processed requests to execution
221
+ # @return [Array<Result>] execution results
222
+ def execute_with_middleware_batch(requests, &block)
223
+ # For batch execution, apply middleware to each request individually
224
+ # This maintains the middleware contract while supporting batch execution
225
+ results = requests.map do |request|
226
+ execute_with_middleware(request) { |req| [req] }
227
+ end.flatten
228
+
229
+ # Then execute the batch
230
+ processed_requests = results.map(&:command).map { |cmd| Request.from_string(cmd) }
231
+ batch_results = block.call(processed_requests)
232
+
233
+ # Update statistics for batch
234
+ batch_results.each { |result| update_stats(result) }
235
+
236
+ batch_results
237
+ end
238
+
239
+ # Build middleware execution chain.
240
+ #
241
+ # @param request [Request] the request to process
242
+ # @return [Proc] middleware chain
243
+ def build_middleware_chain(request)
244
+ # Filter middleware that applies to this request
245
+ applicable_middleware = @middleware.select { |m| m.applicable?(request) }
246
+
247
+ # Build chain in reverse order so first middleware wraps everything
248
+ applicable_middleware.reverse.reduce(method(:execute_final)) do |chain, middleware|
249
+ ->(req) { middleware.call(req, &chain) }
250
+ end
251
+ end
252
+
253
+ # Final execution step (after all middleware).
254
+ #
255
+ # @param request [Request] processed request
256
+ # @return [Result] execution result
257
+ def execute_final(request)
258
+ @strategy.execute(request)
259
+ end
260
+
261
+ # Normalize various input types to Request objects.
262
+ #
263
+ # @param request [Request, String, Hash] request in various formats
264
+ # @return [Request] normalized request
265
+ # @raise [ArgumentError] if request cannot be normalized
266
+ def normalize_request(request)
267
+ case request
268
+ when Request
269
+ request
270
+ when String
271
+ Request.from_string(request)
272
+ when Hash
273
+ Request.from_hash(request)
274
+ else
275
+ raise ArgumentError, "Invalid request type: #{request.class}. Expected Request, String, or Hash."
276
+ end
277
+ end
278
+
279
+ # Update execution statistics.
280
+ #
281
+ # @param result [Result] execution result
282
+ def update_stats(result)
283
+ stats[:total_executions] += 1
284
+
285
+ if result.success?
286
+ stats[:successful_executions] += 1
287
+ else
288
+ stats[:failed_executions] += 1
289
+ end
290
+
291
+ stats[:total_duration] += result.duration if result.duration
292
+ end
293
+
294
+ # Get default middleware chain.
295
+ #
296
+ # @return [Array<Middleware::Base>] default middleware
297
+ def default_middleware
298
+ # No default middleware for now - keep it simple
299
+ # Subclasses or configuration can add middleware as needed
300
+ []
301
+ end
302
+
303
+ # Validate middleware array.
304
+ def validate_middleware
305
+ @middleware.each { |m| validate_middleware_instance(m) }
306
+ end
307
+
308
+ # Validate individual middleware instance.
309
+ #
310
+ # @param middleware [Object] middleware to validate
311
+ # @raise [ArgumentError] if middleware is invalid
312
+ def validate_middleware_instance(middleware)
313
+ unless middleware.respond_to?(:call)
314
+ raise ArgumentError, "Middleware must respond to #call: #{middleware.class}"
315
+ end
316
+
317
+ return if middleware.respond_to?(:applicable?)
318
+
319
+ raise ArgumentError, "Middleware must respond to #applicable?: #{middleware.class}"
320
+ end
321
+
322
+ # Validate execution strategy.
323
+ #
324
+ # @raise [ArgumentError] if strategy is invalid
325
+ def validate_strategy
326
+ unless @strategy.respond_to?(:execute)
327
+ raise ArgumentError, "Strategy must respond to #execute: #{@strategy.class}"
328
+ end
329
+
330
+ return if @strategy.respond_to?(:supports?)
331
+
332
+ raise ArgumentError, "Strategy must respond to #supports?: #{@strategy.class}"
333
+ end
334
+
335
+ # Log command execution result using the default logger.
336
+ #
337
+ # @param request [Request] the executed request
338
+ # @param result [Result] the execution result
339
+ # @return [void]
340
+ def log_command_result(request, result)
341
+ # Build the full command string
342
+ command_string = "#{request.command} #{request.arguments&.join(" ")}".strip
343
+
344
+ # Log success or error using the default logger
345
+ if result.success?
346
+ Makit::Logging.success(command_string)
347
+ else
348
+ Makit::Logging.error(command_string)
349
+ end
350
+
351
+ # Log the stdout, if there is an error or stdout is enabled
352
+ if result.failure? || request.show_stdout?
353
+ if (!result.stdout.empty?)
354
+ # split the stdout into lines and print each line
355
+ result.stdout.split("\n").each do |line|
356
+ Makit::Logging.info(line)
357
+ end
358
+ end
359
+ end
360
+
361
+ # Log the stderr
362
+ return unless request.show_stderr? && !result.stderr.empty?
363
+
364
+ # split the stderr into lines and print each line
365
+ result.stderr.split("\n").each do |line|
366
+ Makit::Logging.info(line)
367
+ end
368
+ end
369
+
370
+ # Log runner initialization for debugging.
371
+ #
372
+ # @return [void]
373
+ def log_runner_initialization
374
+ # Only log if we have a command logger middleware
375
+ logger_middleware = @middleware.find { |m| m.is_a?(Middleware::CommandLogger) }
376
+ return unless logger_middleware
377
+
378
+ logger_middleware.logger.debug("Command runner initialized",
379
+ middleware_count: @middleware.length,
380
+ strategy: @strategy.class.name,
381
+ working_directory: Dir.pwd,
382
+ options: @options)
383
+ end
384
+ end
385
+ end
386
+ end