makit 0.0.143 → 0.0.144

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 (154) 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/build_commands.rb +500 -500
  7. data/lib/makit/cli/generators/base_generator.rb +74 -74
  8. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  9. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  10. data/lib/makit/cli/generators/node_generator.rb +50 -50
  11. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  12. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  13. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  14. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  15. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  16. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
  17. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  18. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  19. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  20. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  21. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  22. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  23. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  24. data/lib/makit/cli/main.rb +69 -69
  25. data/lib/makit/cli/project_commands.rb +868 -868
  26. data/lib/makit/cli/repository_commands.rb +661 -661
  27. data/lib/makit/cli/strategy_commands.rb +212 -212
  28. data/lib/makit/cli/utility_commands.rb +521 -521
  29. data/lib/makit/commands/factory.rb +359 -359
  30. data/lib/makit/commands/middleware/base.rb +73 -73
  31. data/lib/makit/commands/middleware/cache.rb +248 -248
  32. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  33. data/lib/makit/commands/middleware/validator.rb +269 -269
  34. data/lib/makit/commands/request.rb +316 -316
  35. data/lib/makit/commands/result.rb +323 -323
  36. data/lib/makit/commands/runner.rb +386 -386
  37. data/lib/makit/commands/strategies/base.rb +171 -171
  38. data/lib/makit/commands/strategies/child_process.rb +162 -162
  39. data/lib/makit/commands/strategies/factory.rb +136 -136
  40. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  41. data/lib/makit/commands.rb +50 -50
  42. data/lib/makit/configuration/dotnet_project.rb +48 -39
  43. data/lib/makit/configuration/gitlab_helper.rb +58 -58
  44. data/lib/makit/configuration/project.rb +168 -168
  45. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  46. data/lib/makit/configuration/step.rb +34 -34
  47. data/lib/makit/configuration/timeout.rb +74 -74
  48. data/lib/makit/configuration.rb +16 -16
  49. data/lib/makit/content/default_gitignore.rb +7 -7
  50. data/lib/makit/content/default_gitignore.txt +225 -225
  51. data/lib/makit/content/default_rakefile.rb +13 -13
  52. data/lib/makit/content/gem_rakefile.rb +16 -16
  53. data/lib/makit/context.rb +1 -1
  54. data/lib/makit/data.rb +49 -49
  55. data/lib/makit/directories.rb +140 -140
  56. data/lib/makit/directory.rb +262 -262
  57. data/lib/makit/docs/files.rb +89 -89
  58. data/lib/makit/docs/rake.rb +102 -102
  59. data/lib/makit/dotnet/cli.rb +69 -69
  60. data/lib/makit/dotnet/project.rb +217 -217
  61. data/lib/makit/dotnet/solution.rb +38 -38
  62. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  63. data/lib/makit/dotnet/solution_console.rb +264 -264
  64. data/lib/makit/dotnet/solution_maui.rb +354 -354
  65. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  66. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  67. data/lib/makit/dotnet.rb +102 -102
  68. data/lib/makit/email.rb +90 -90
  69. data/lib/makit/environment.rb +142 -142
  70. data/lib/makit/examples/runner.rb +370 -370
  71. data/lib/makit/exceptions.rb +45 -45
  72. data/lib/makit/fileinfo.rb +24 -24
  73. data/lib/makit/files.rb +43 -43
  74. data/lib/makit/gems.rb +40 -40
  75. data/lib/makit/git/cli.rb +54 -54
  76. data/lib/makit/git/repository.rb +90 -90
  77. data/lib/makit/git.rb +98 -98
  78. data/lib/makit/gitlab_runner.rb +59 -59
  79. data/lib/makit/humanize.rb +137 -137
  80. data/lib/makit/indexer.rb +47 -47
  81. data/lib/makit/logging/configuration.rb +308 -308
  82. data/lib/makit/logging/format_registry.rb +84 -84
  83. data/lib/makit/logging/formatters/base.rb +39 -39
  84. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  85. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  86. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  87. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  88. data/lib/makit/logging/log_request.rb +119 -119
  89. data/lib/makit/logging/logger.rb +199 -199
  90. data/lib/makit/logging/sinks/base.rb +91 -91
  91. data/lib/makit/logging/sinks/console.rb +72 -72
  92. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  93. data/lib/makit/logging/sinks/structured.rb +123 -123
  94. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  95. data/lib/makit/logging.rb +565 -565
  96. data/lib/makit/markdown.rb +75 -75
  97. data/lib/makit/mp/basic_object_mp.rb +17 -17
  98. data/lib/makit/mp/command_mp.rb +13 -13
  99. data/lib/makit/mp/command_request.mp.rb +17 -17
  100. data/lib/makit/mp/project_mp.rb +199 -199
  101. data/lib/makit/mp/string_mp.rb +199 -199
  102. data/lib/makit/nuget.rb +74 -74
  103. data/lib/makit/port.rb +32 -32
  104. data/lib/makit/process.rb +377 -163
  105. data/lib/makit/protoc.rb +107 -107
  106. data/lib/makit/rake/cli.rb +196 -196
  107. data/lib/makit/rake/trace_controller.rb +174 -174
  108. data/lib/makit/rake.rb +81 -81
  109. data/lib/makit/ruby/cli.rb +185 -185
  110. data/lib/makit/ruby.rb +25 -25
  111. data/lib/makit/secrets.rb +51 -51
  112. data/lib/makit/serializer.rb +130 -130
  113. data/lib/makit/services/builder.rb +186 -186
  114. data/lib/makit/services/error_handler.rb +226 -226
  115. data/lib/makit/services/repository_manager.rb +231 -231
  116. data/lib/makit/services/validator.rb +112 -112
  117. data/lib/makit/setup/classlib.rb +101 -101
  118. data/lib/makit/setup/gem.rb +268 -268
  119. data/lib/makit/setup/pages.rb +11 -11
  120. data/lib/makit/setup/razorclasslib.rb +101 -101
  121. data/lib/makit/setup/runner.rb +54 -54
  122. data/lib/makit/setup.rb +5 -5
  123. data/lib/makit/show.rb +110 -110
  124. data/lib/makit/storage.rb +126 -126
  125. data/lib/makit/symbols.rb +170 -170
  126. data/lib/makit/task_info.rb +130 -130
  127. data/lib/makit/tasks/at_exit.rb +15 -15
  128. data/lib/makit/tasks/build.rb +22 -22
  129. data/lib/makit/tasks/clean.rb +13 -13
  130. data/lib/makit/tasks/configure.rb +10 -10
  131. data/lib/makit/tasks/format.rb +10 -10
  132. data/lib/makit/tasks/hook_manager.rb +443 -443
  133. data/lib/makit/tasks/init.rb +49 -49
  134. data/lib/makit/tasks/integrate.rb +29 -29
  135. data/lib/makit/tasks/pull_incoming.rb +13 -13
  136. data/lib/makit/tasks/setup.rb +16 -16
  137. data/lib/makit/tasks/sync.rb +17 -17
  138. data/lib/makit/tasks/tag.rb +16 -16
  139. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  140. data/lib/makit/tasks/test.rb +22 -22
  141. data/lib/makit/tasks/update.rb +18 -18
  142. data/lib/makit/tasks.rb +20 -20
  143. data/lib/makit/test_cache.rb +239 -239
  144. data/lib/makit/tree.rb +37 -37
  145. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  146. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  147. data/lib/makit/version.rb +100 -100
  148. data/lib/makit/version_util.rb +21 -21
  149. data/lib/makit/wix.rb +95 -95
  150. data/lib/makit/yaml.rb +29 -29
  151. data/lib/makit/zip.rb +17 -17
  152. data/lib/makit copy.rb +44 -44
  153. data/lib/makit.rb +43 -43
  154. metadata +2 -2
@@ -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