makit 0.0.139 → 0.0.140

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 (151) 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 -67
  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 +51 -0
  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 +385 -385
  37. data/lib/makit/commands/strategies/base.rb +171 -171
  38. data/lib/makit/commands/strategies/child_process.rb +1 -1
  39. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  40. data/lib/makit/commands.rb +50 -50
  41. data/lib/makit/configuration/dotnet_project.rb +12 -12
  42. data/lib/makit/configuration/gitlab_helper.rb +58 -58
  43. data/lib/makit/configuration/project.rb +168 -168
  44. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  45. data/lib/makit/configuration/step.rb +34 -34
  46. data/lib/makit/configuration/timeout.rb +74 -0
  47. data/lib/makit/configuration.rb +15 -14
  48. data/lib/makit/content/default_gitignore.rb +7 -7
  49. data/lib/makit/content/default_gitignore.txt +225 -225
  50. data/lib/makit/content/default_rakefile.rb +13 -13
  51. data/lib/makit/content/gem_rakefile.rb +16 -16
  52. data/lib/makit/context.rb +1 -1
  53. data/lib/makit/data.rb +49 -49
  54. data/lib/makit/directories.rb +140 -140
  55. data/lib/makit/directory.rb +262 -262
  56. data/lib/makit/docs/files.rb +89 -89
  57. data/lib/makit/docs/rake.rb +102 -102
  58. data/lib/makit/dotnet/cli.rb +69 -69
  59. data/lib/makit/dotnet/project.rb +217 -217
  60. data/lib/makit/dotnet/solution.rb +38 -38
  61. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  62. data/lib/makit/dotnet/solution_console.rb +264 -264
  63. data/lib/makit/dotnet/solution_maui.rb +354 -354
  64. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  65. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  66. data/lib/makit/dotnet.rb +102 -102
  67. data/lib/makit/email.rb +90 -90
  68. data/lib/makit/environment.rb +142 -142
  69. data/lib/makit/examples/runner.rb +370 -370
  70. data/lib/makit/exceptions.rb +45 -45
  71. data/lib/makit/fileinfo.rb +24 -24
  72. data/lib/makit/files.rb +43 -43
  73. data/lib/makit/gems.rb +40 -40
  74. data/lib/makit/git/cli.rb +54 -54
  75. data/lib/makit/git/repository.rb +90 -90
  76. data/lib/makit/git.rb +98 -98
  77. data/lib/makit/gitlab_runner.rb +59 -59
  78. data/lib/makit/humanize.rb +137 -137
  79. data/lib/makit/indexer.rb +47 -47
  80. data/lib/makit/logging/configuration.rb +308 -308
  81. data/lib/makit/logging/format_registry.rb +84 -84
  82. data/lib/makit/logging/formatters/base.rb +39 -39
  83. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  84. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  85. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  86. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  87. data/lib/makit/logging/log_request.rb +119 -119
  88. data/lib/makit/logging/logger.rb +199 -199
  89. data/lib/makit/logging/sinks/base.rb +91 -91
  90. data/lib/makit/logging/sinks/console.rb +72 -72
  91. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  92. data/lib/makit/logging/sinks/structured.rb +123 -123
  93. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  94. data/lib/makit/logging.rb +565 -565
  95. data/lib/makit/markdown.rb +75 -75
  96. data/lib/makit/mp/basic_object_mp.rb +17 -17
  97. data/lib/makit/mp/command_mp.rb +13 -13
  98. data/lib/makit/mp/command_request.mp.rb +17 -17
  99. data/lib/makit/mp/project_mp.rb +199 -199
  100. data/lib/makit/mp/string_mp.rb +191 -191
  101. data/lib/makit/nuget.rb +74 -74
  102. data/lib/makit/port.rb +32 -32
  103. data/lib/makit/process.rb +163 -163
  104. data/lib/makit/protoc.rb +107 -107
  105. data/lib/makit/rake/cli.rb +196 -196
  106. data/lib/makit/rake.rb +80 -80
  107. data/lib/makit/ruby/cli.rb +185 -185
  108. data/lib/makit/ruby.rb +25 -25
  109. data/lib/makit/secrets.rb +51 -51
  110. data/lib/makit/serializer.rb +130 -130
  111. data/lib/makit/services/builder.rb +186 -186
  112. data/lib/makit/services/error_handler.rb +226 -226
  113. data/lib/makit/services/repository_manager.rb +231 -231
  114. data/lib/makit/services/validator.rb +112 -112
  115. data/lib/makit/setup/classlib.rb +101 -101
  116. data/lib/makit/setup/gem.rb +268 -268
  117. data/lib/makit/setup/razorclasslib.rb +101 -101
  118. data/lib/makit/setup/runner.rb +54 -54
  119. data/lib/makit/setup.rb +5 -5
  120. data/lib/makit/show.rb +110 -110
  121. data/lib/makit/storage.rb +126 -126
  122. data/lib/makit/symbols.rb +170 -170
  123. data/lib/makit/task_info.rb +130 -130
  124. data/lib/makit/tasks/at_exit.rb +15 -15
  125. data/lib/makit/tasks/build.rb +22 -22
  126. data/lib/makit/tasks/clean.rb +13 -13
  127. data/lib/makit/tasks/configure.rb +10 -10
  128. data/lib/makit/tasks/format.rb +10 -10
  129. data/lib/makit/tasks/hook_manager.rb +443 -443
  130. data/lib/makit/tasks/init.rb +49 -49
  131. data/lib/makit/tasks/integrate.rb +29 -29
  132. data/lib/makit/tasks/pull_incoming.rb +13 -13
  133. data/lib/makit/tasks/setup.rb +13 -13
  134. data/lib/makit/tasks/sync.rb +17 -17
  135. data/lib/makit/tasks/tag.rb +16 -16
  136. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  137. data/lib/makit/tasks/test.rb +22 -22
  138. data/lib/makit/tasks/update.rb +18 -18
  139. data/lib/makit/tasks.rb +20 -20
  140. data/lib/makit/test_cache.rb +239 -239
  141. data/lib/makit/tree.rb +37 -37
  142. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  143. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  144. data/lib/makit/version.rb +99 -99
  145. data/lib/makit/version_util.rb +21 -21
  146. data/lib/makit/wix.rb +95 -95
  147. data/lib/makit/yaml.rb +29 -29
  148. data/lib/makit/zip.rb +17 -17
  149. data/lib/makit copy.rb +44 -44
  150. data/lib/makit.rb +42 -42
  151. metadata +2 -1
@@ -1,385 +1,385 @@
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
- private
183
-
184
- # Execute request with middleware chain.
185
- #
186
- # @param request [Request] normalized request
187
- # @yield [Request] yields processed request to execution
188
- # @return [Result] execution result
189
- def execute_with_middleware(request, &block)
190
- # Build middleware chain
191
- chain = build_middleware_chain(request)
192
-
193
- # Execute chain
194
- result = chain.call(request, &block)
195
-
196
- # Log high-level success/error using the default logger
197
- log_command_result(request, result)
198
-
199
- # Update statistics
200
- update_stats(result)
201
-
202
- result
203
- end
204
-
205
- # Execute batch with middleware chain.
206
- #
207
- # @param requests [Array<Request>] normalized requests
208
- # @yield [Array<Request>] yields processed requests to execution
209
- # @return [Array<Result>] execution results
210
- def execute_with_middleware_batch(requests, &block)
211
- # For batch execution, apply middleware to each request individually
212
- # This maintains the middleware contract while supporting batch execution
213
- results = requests.map do |request|
214
- execute_with_middleware(request) { |req| [req] }
215
- end.flatten
216
-
217
- # Then execute the batch
218
- processed_requests = results.map(&:command).map { |cmd| Request.from_string(cmd) }
219
- batch_results = block.call(processed_requests)
220
-
221
- # Update statistics for batch
222
- batch_results.each { |result| update_stats(result) }
223
-
224
- batch_results
225
- end
226
-
227
- # Build middleware execution chain.
228
- #
229
- # @param request [Request] the request to process
230
- # @return [Proc] middleware chain
231
- def build_middleware_chain(request)
232
- # Filter middleware that applies to this request
233
- applicable_middleware = @middleware.select { |m| m.applicable?(request) }
234
-
235
- # Build chain in reverse order so first middleware wraps everything
236
- applicable_middleware.reverse.reduce(method(:execute_final)) do |chain, middleware|
237
- ->(req) { middleware.call(req, &chain) }
238
- end
239
- end
240
-
241
- # Final execution step (after all middleware).
242
- #
243
- # @param request [Request] processed request
244
- # @return [Result] execution result
245
- def execute_final(request)
246
- @strategy.execute(request)
247
- end
248
-
249
- # Normalize various input types to Request objects.
250
- #
251
- # @param request [Request, String, Hash] request in various formats
252
- # @return [Request] normalized request
253
- # @raise [ArgumentError] if request cannot be normalized
254
- def normalize_request(request)
255
- case request
256
- when Request
257
- request
258
- when String
259
- Request.from_string(request)
260
- when Hash
261
- Request.from_hash(request)
262
- else
263
- raise ArgumentError, "Invalid request type: #{request.class}. Expected Request, String, or Hash."
264
- end
265
- end
266
-
267
- # Update execution statistics.
268
- #
269
- # @param result [Result] execution result
270
- def update_stats(result)
271
- stats[:total_executions] += 1
272
-
273
- if result.success?
274
- stats[:successful_executions] += 1
275
- else
276
- stats[:failed_executions] += 1
277
- end
278
-
279
- stats[:total_duration] += result.duration if result.duration
280
- end
281
-
282
- # Get default middleware chain.
283
- #
284
- # @return [Array<Middleware::Base>] default middleware
285
- def default_middleware
286
- # No default middleware for now - keep it simple
287
- # Subclasses or configuration can add middleware as needed
288
- []
289
- end
290
-
291
- # Validate middleware array.
292
- def validate_middleware
293
- @middleware.each { |m| validate_middleware_instance(m) }
294
- end
295
-
296
- # Validate individual middleware instance.
297
- #
298
- # @param middleware [Object] middleware to validate
299
- # @raise [ArgumentError] if middleware is invalid
300
- def validate_middleware_instance(middleware)
301
- unless middleware.respond_to?(:call)
302
- raise ArgumentError, "Middleware must respond to #call: #{middleware.class}"
303
- end
304
-
305
- return if middleware.respond_to?(:applicable?)
306
-
307
- raise ArgumentError, "Middleware must respond to #applicable?: #{middleware.class}"
308
- end
309
-
310
- # Validate execution strategy.
311
- #
312
- # @raise [ArgumentError] if strategy is invalid
313
- def validate_strategy
314
- unless @strategy.respond_to?(:execute)
315
- raise ArgumentError, "Strategy must respond to #execute: #{@strategy.class}"
316
- end
317
-
318
- return if @strategy.respond_to?(:supports?)
319
-
320
- raise ArgumentError, "Strategy must respond to #supports?: #{@strategy.class}"
321
- end
322
-
323
- # Get information about the current strategy
324
- #
325
- # @return [Hash] strategy information
326
- def strategy_info
327
- {
328
- class: @strategy.class.name,
329
- type: @strategy.class.name.split('::').last.downcase,
330
- factory_info: Strategies::Factory.strategy_info
331
- }
332
- end
333
-
334
- # Log command execution result using the default logger.
335
- #
336
- # @param request [Request] the executed request
337
- # @param result [Result] the execution result
338
- # @return [void]
339
- def log_command_result(request, result)
340
- # Build the full command string
341
- command_string = "#{request.command} #{request.arguments&.join(" ")}".strip
342
-
343
- # Log success or error using the default logger
344
- if result.success?
345
- Makit::Logging.success(command_string)
346
- else
347
- Makit::Logging.error(command_string)
348
- end
349
-
350
- # Log the stdout, if there is an error or stdout is enabled
351
- if result.failure? || request.show_stdout?
352
- if (!result.stdout.empty?)
353
- # split the stdout into lines and print each line
354
- result.stdout.split("\n").each do |line|
355
- Makit::Logging.info(line)
356
- end
357
- end
358
- end
359
-
360
- # Log the stderr
361
- return unless request.show_stderr? && !result.stderr.empty?
362
-
363
- # split the stderr into lines and print each line
364
- result.stderr.split("\n").each do |line|
365
- Makit::Logging.info(line)
366
- end
367
- end
368
-
369
- # Log runner initialization for debugging.
370
- #
371
- # @return [void]
372
- def log_runner_initialization
373
- # Only log if we have a command logger middleware
374
- logger_middleware = @middleware.find { |m| m.is_a?(Middleware::CommandLogger) }
375
- return unless logger_middleware
376
-
377
- logger_middleware.logger.debug("Command runner initialized",
378
- middleware_count: @middleware.length,
379
- strategy: @strategy.class.name,
380
- working_directory: Dir.pwd,
381
- options: @options)
382
- end
383
- end
384
- end
385
- 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
+ private
183
+
184
+ # Execute request with middleware chain.
185
+ #
186
+ # @param request [Request] normalized request
187
+ # @yield [Request] yields processed request to execution
188
+ # @return [Result] execution result
189
+ def execute_with_middleware(request, &block)
190
+ # Build middleware chain
191
+ chain = build_middleware_chain(request)
192
+
193
+ # Execute chain
194
+ result = chain.call(request, &block)
195
+
196
+ # Log high-level success/error using the default logger
197
+ log_command_result(request, result)
198
+
199
+ # Update statistics
200
+ update_stats(result)
201
+
202
+ result
203
+ end
204
+
205
+ # Execute batch with middleware chain.
206
+ #
207
+ # @param requests [Array<Request>] normalized requests
208
+ # @yield [Array<Request>] yields processed requests to execution
209
+ # @return [Array<Result>] execution results
210
+ def execute_with_middleware_batch(requests, &block)
211
+ # For batch execution, apply middleware to each request individually
212
+ # This maintains the middleware contract while supporting batch execution
213
+ results = requests.map do |request|
214
+ execute_with_middleware(request) { |req| [req] }
215
+ end.flatten
216
+
217
+ # Then execute the batch
218
+ processed_requests = results.map(&:command).map { |cmd| Request.from_string(cmd) }
219
+ batch_results = block.call(processed_requests)
220
+
221
+ # Update statistics for batch
222
+ batch_results.each { |result| update_stats(result) }
223
+
224
+ batch_results
225
+ end
226
+
227
+ # Build middleware execution chain.
228
+ #
229
+ # @param request [Request] the request to process
230
+ # @return [Proc] middleware chain
231
+ def build_middleware_chain(request)
232
+ # Filter middleware that applies to this request
233
+ applicable_middleware = @middleware.select { |m| m.applicable?(request) }
234
+
235
+ # Build chain in reverse order so first middleware wraps everything
236
+ applicable_middleware.reverse.reduce(method(:execute_final)) do |chain, middleware|
237
+ ->(req) { middleware.call(req, &chain) }
238
+ end
239
+ end
240
+
241
+ # Final execution step (after all middleware).
242
+ #
243
+ # @param request [Request] processed request
244
+ # @return [Result] execution result
245
+ def execute_final(request)
246
+ @strategy.execute(request)
247
+ end
248
+
249
+ # Normalize various input types to Request objects.
250
+ #
251
+ # @param request [Request, String, Hash] request in various formats
252
+ # @return [Request] normalized request
253
+ # @raise [ArgumentError] if request cannot be normalized
254
+ def normalize_request(request)
255
+ case request
256
+ when Request
257
+ request
258
+ when String
259
+ Request.from_string(request)
260
+ when Hash
261
+ Request.from_hash(request)
262
+ else
263
+ raise ArgumentError, "Invalid request type: #{request.class}. Expected Request, String, or Hash."
264
+ end
265
+ end
266
+
267
+ # Update execution statistics.
268
+ #
269
+ # @param result [Result] execution result
270
+ def update_stats(result)
271
+ stats[:total_executions] += 1
272
+
273
+ if result.success?
274
+ stats[:successful_executions] += 1
275
+ else
276
+ stats[:failed_executions] += 1
277
+ end
278
+
279
+ stats[:total_duration] += result.duration if result.duration
280
+ end
281
+
282
+ # Get default middleware chain.
283
+ #
284
+ # @return [Array<Middleware::Base>] default middleware
285
+ def default_middleware
286
+ # No default middleware for now - keep it simple
287
+ # Subclasses or configuration can add middleware as needed
288
+ []
289
+ end
290
+
291
+ # Validate middleware array.
292
+ def validate_middleware
293
+ @middleware.each { |m| validate_middleware_instance(m) }
294
+ end
295
+
296
+ # Validate individual middleware instance.
297
+ #
298
+ # @param middleware [Object] middleware to validate
299
+ # @raise [ArgumentError] if middleware is invalid
300
+ def validate_middleware_instance(middleware)
301
+ unless middleware.respond_to?(:call)
302
+ raise ArgumentError, "Middleware must respond to #call: #{middleware.class}"
303
+ end
304
+
305
+ return if middleware.respond_to?(:applicable?)
306
+
307
+ raise ArgumentError, "Middleware must respond to #applicable?: #{middleware.class}"
308
+ end
309
+
310
+ # Validate execution strategy.
311
+ #
312
+ # @raise [ArgumentError] if strategy is invalid
313
+ def validate_strategy
314
+ unless @strategy.respond_to?(:execute)
315
+ raise ArgumentError, "Strategy must respond to #execute: #{@strategy.class}"
316
+ end
317
+
318
+ return if @strategy.respond_to?(:supports?)
319
+
320
+ raise ArgumentError, "Strategy must respond to #supports?: #{@strategy.class}"
321
+ end
322
+
323
+ # Get information about the current strategy
324
+ #
325
+ # @return [Hash] strategy information
326
+ def strategy_info
327
+ {
328
+ class: @strategy.class.name,
329
+ type: @strategy.class.name.split('::').last.downcase,
330
+ factory_info: Strategies::Factory.strategy_info
331
+ }
332
+ end
333
+
334
+ # Log command execution result using the default logger.
335
+ #
336
+ # @param request [Request] the executed request
337
+ # @param result [Result] the execution result
338
+ # @return [void]
339
+ def log_command_result(request, result)
340
+ # Build the full command string
341
+ command_string = "#{request.command} #{request.arguments&.join(" ")}".strip
342
+
343
+ # Log success or error using the default logger
344
+ if result.success?
345
+ Makit::Logging.success(command_string)
346
+ else
347
+ Makit::Logging.error(command_string)
348
+ end
349
+
350
+ # Log the stdout, if there is an error or stdout is enabled
351
+ if result.failure? || request.show_stdout?
352
+ if (!result.stdout.empty?)
353
+ # split the stdout into lines and print each line
354
+ result.stdout.split("\n").each do |line|
355
+ Makit::Logging.info(line)
356
+ end
357
+ end
358
+ end
359
+
360
+ # Log the stderr
361
+ return unless request.show_stderr? && !result.stderr.empty?
362
+
363
+ # split the stderr into lines and print each line
364
+ result.stderr.split("\n").each do |line|
365
+ Makit::Logging.info(line)
366
+ end
367
+ end
368
+
369
+ # Log runner initialization for debugging.
370
+ #
371
+ # @return [void]
372
+ def log_runner_initialization
373
+ # Only log if we have a command logger middleware
374
+ logger_middleware = @middleware.find { |m| m.is_a?(Middleware::CommandLogger) }
375
+ return unless logger_middleware
376
+
377
+ logger_middleware.logger.debug("Command runner initialized",
378
+ middleware_count: @middleware.length,
379
+ strategy: @strategy.class.name,
380
+ working_directory: Dir.pwd,
381
+ options: @options)
382
+ end
383
+ end
384
+ end
385
+ end