makit 0.0.112 → 0.0.128

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