makit 0.0.140 → 0.0.142

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 (153) 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 +203 -203
  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 +388 -385
  37. data/lib/makit/commands/strategies/base.rb +171 -171
  38. data/lib/makit/commands/strategies/child_process.rb +165 -165
  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 +12 -12
  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 +15 -15
  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 -191
  102. data/lib/makit/nuget.rb +74 -74
  103. data/lib/makit/port.rb +32 -32
  104. data/lib/makit/process.rb +163 -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 +173 -173
  108. data/lib/makit/rake.rb +80 -80
  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/razorclasslib.rb +101 -101
  120. data/lib/makit/setup/runner.rb +54 -54
  121. data/lib/makit/setup.rb +5 -5
  122. data/lib/makit/show.rb +110 -110
  123. data/lib/makit/storage.rb +126 -126
  124. data/lib/makit/symbols.rb +170 -170
  125. data/lib/makit/task_info.rb +130 -130
  126. data/lib/makit/tasks/at_exit.rb +15 -15
  127. data/lib/makit/tasks/build.rb +22 -22
  128. data/lib/makit/tasks/clean.rb +13 -13
  129. data/lib/makit/tasks/configure.rb +10 -10
  130. data/lib/makit/tasks/format.rb +10 -10
  131. data/lib/makit/tasks/hook_manager.rb +443 -443
  132. data/lib/makit/tasks/init.rb +49 -49
  133. data/lib/makit/tasks/integrate.rb +29 -29
  134. data/lib/makit/tasks/pull_incoming.rb +13 -13
  135. data/lib/makit/tasks/setup.rb +13 -13
  136. data/lib/makit/tasks/sync.rb +17 -17
  137. data/lib/makit/tasks/tag.rb +16 -16
  138. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  139. data/lib/makit/tasks/test.rb +22 -22
  140. data/lib/makit/tasks/update.rb +18 -18
  141. data/lib/makit/tasks.rb +20 -20
  142. data/lib/makit/test_cache.rb +239 -239
  143. data/lib/makit/tree.rb +37 -37
  144. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  145. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  146. data/lib/makit/version.rb +99 -99
  147. data/lib/makit/version_util.rb +21 -21
  148. data/lib/makit/wix.rb +95 -95
  149. data/lib/makit/yaml.rb +29 -29
  150. data/lib/makit/zip.rb +17 -17
  151. data/lib/makit copy.rb +44 -44
  152. data/lib/makit.rb +43 -42
  153. metadata +2 -2
@@ -1,385 +1,388 @@
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
+ # 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
+
336
+
337
+ # Log command execution result using the default logger.
338
+ #
339
+ # @param request [Request] the executed request
340
+ # @param result [Result] the execution result
341
+ # @return [void]
342
+ def log_command_result(request, result)
343
+ # Build the full command string
344
+ command_string = "#{request.command} #{request.arguments&.join(" ")}".strip
345
+
346
+ # Log success or error using the default logger
347
+ if result.success?
348
+ Makit::Logging.success(command_string)
349
+ else
350
+ Makit::Logging.error(command_string)
351
+ end
352
+
353
+ # Log the stdout, if there is an error or stdout is enabled
354
+ if result.failure? || request.show_stdout?
355
+ if (!result.stdout.empty?)
356
+ # split the stdout into lines and print each line
357
+ result.stdout.split("\n").each do |line|
358
+ Makit::Logging.info(line)
359
+ end
360
+ end
361
+ end
362
+
363
+ # Log the stderr
364
+ return unless request.show_stderr? && !result.stderr.empty?
365
+
366
+ # split the stderr into lines and print each line
367
+ result.stderr.split("\n").each do |line|
368
+ Makit::Logging.info(line)
369
+ end
370
+ end
371
+
372
+ # Log runner initialization for debugging.
373
+ #
374
+ # @return [void]
375
+ def log_runner_initialization
376
+ # Only log if we have a command logger middleware
377
+ logger_middleware = @middleware.find { |m| m.is_a?(Middleware::CommandLogger) }
378
+ return unless logger_middleware
379
+
380
+ logger_middleware.logger.debug("Command runner initialized",
381
+ middleware_count: @middleware.length,
382
+ strategy: @strategy.class.name,
383
+ working_directory: Dir.pwd,
384
+ options: @options)
385
+ end
386
+ end
387
+ end
388
+ end