makit 0.0.99 → 0.0.112
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.
- checksums.yaml +4 -4
- data/README.md +41 -0
- data/exe/makit +5 -0
- data/lib/makit/apache.rb +28 -32
- data/lib/makit/cli/build_commands.rb +500 -0
- data/lib/makit/cli/generators/base_generator.rb +74 -0
- data/lib/makit/cli/generators/dotnet_generator.rb +50 -0
- data/lib/makit/cli/generators/generator_factory.rb +49 -0
- data/lib/makit/cli/generators/node_generator.rb +50 -0
- data/lib/makit/cli/generators/ruby_generator.rb +77 -0
- data/lib/makit/cli/generators/rust_generator.rb +50 -0
- data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -0
- data/lib/makit/cli/generators/templates/node_templates.rb +161 -0
- data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -0
- data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -0
- data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -0
- data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -0
- data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -0
- data/lib/makit/cli/generators/templates/ruby/test.rb +39 -0
- data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -0
- data/lib/makit/cli/generators/templates/ruby/version.rb +29 -0
- data/lib/makit/cli/generators/templates/rust_templates.rb +128 -0
- data/lib/makit/cli/main.rb +62 -33
- data/lib/makit/cli/project_commands.rb +868 -0
- data/lib/makit/cli/repository_commands.rb +661 -0
- data/lib/makit/cli/utility_commands.rb +521 -0
- data/lib/makit/commands/factory.rb +359 -0
- data/lib/makit/commands/middleware/base.rb +73 -0
- data/lib/makit/commands/middleware/cache.rb +248 -0
- data/lib/makit/commands/middleware/command_logger.rb +320 -0
- data/lib/makit/commands/middleware/unified_logger.rb +243 -0
- data/lib/makit/commands/middleware/validator.rb +269 -0
- data/lib/makit/commands/request.rb +254 -0
- data/lib/makit/commands/result.rb +323 -0
- data/lib/makit/commands/runner.rb +337 -0
- data/lib/makit/commands/strategies/base.rb +160 -0
- data/lib/makit/commands/strategies/synchronous.rb +134 -0
- data/lib/makit/commands.rb +51 -21
- data/lib/makit/configuration/gitlab_helper.rb +60 -0
- data/lib/makit/configuration/project.rb +127 -0
- data/lib/makit/configuration/rakefile_helper.rb +43 -0
- data/lib/makit/configuration/step.rb +34 -0
- data/lib/makit/configuration.rb +14 -0
- data/lib/makit/content/default_gitignore.rb +7 -5
- data/lib/makit/content/default_rakefile.rb +13 -11
- data/lib/makit/content/gem_rakefile.rb +16 -14
- data/lib/makit/context.rb +1 -0
- data/lib/makit/data.rb +49 -50
- data/lib/makit/directories.rb +141 -145
- data/lib/makit/directory.rb +262 -276
- data/lib/makit/docs/files.rb +89 -94
- data/lib/makit/docs/rake.rb +102 -106
- data/lib/makit/dotnet/cli.rb +65 -0
- data/lib/makit/dotnet/project.rb +153 -0
- data/lib/makit/dotnet/solution.rb +38 -0
- data/lib/makit/dotnet/solution_classlib.rb +239 -0
- data/lib/makit/dotnet/solution_console.rb +264 -0
- data/lib/makit/dotnet/solution_maui.rb +354 -0
- data/lib/makit/dotnet/solution_wasm.rb +275 -0
- data/lib/makit/dotnet/solution_wpf.rb +304 -0
- data/lib/makit/dotnet.rb +102 -219
- data/lib/makit/email.rb +90 -61
- data/lib/makit/environment.rb +142 -139
- data/lib/makit/examples/runner.rb +370 -0
- data/lib/makit/exceptions.rb +45 -0
- data/lib/makit/fileinfo.rb +24 -26
- data/lib/makit/files.rb +43 -47
- data/lib/makit/gems.rb +29 -28
- data/lib/makit/git/cli.rb +54 -0
- data/lib/makit/git/repository.rb +90 -0
- data/lib/makit/git.rb +98 -145
- data/lib/makit/gitlab_runner.rb +59 -60
- data/lib/makit/humanize.rb +137 -129
- data/lib/makit/indexer.rb +47 -56
- data/lib/makit/logging/configuration.rb +305 -0
- data/lib/makit/logging/format_registry.rb +84 -0
- data/lib/makit/logging/formatters/base.rb +39 -0
- data/lib/makit/logging/formatters/console_formatter.rb +140 -0
- data/lib/makit/logging/formatters/json_formatter.rb +65 -0
- data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -0
- data/lib/makit/logging/formatters/text_formatter.rb +64 -0
- data/lib/makit/logging/log_request.rb +115 -0
- data/lib/makit/logging/logger.rb +163 -0
- data/lib/makit/logging/sinks/base.rb +91 -0
- data/lib/makit/logging/sinks/console.rb +72 -0
- data/lib/makit/logging/sinks/file_sink.rb +92 -0
- data/lib/makit/logging/sinks/structured.rb +129 -0
- data/lib/makit/logging/sinks/unified_file_sink.rb +303 -0
- data/lib/makit/logging.rb +530 -106
- data/lib/makit/markdown.rb +75 -75
- data/lib/makit/mp/basic_object_mp.rb +17 -16
- data/lib/makit/mp/command_mp.rb +13 -13
- data/lib/makit/mp/command_request.mp.rb +17 -16
- data/lib/makit/mp/project_mp.rb +199 -210
- data/lib/makit/mp/string_mp.rb +193 -176
- data/lib/makit/nuget.rb +74 -72
- data/lib/makit/port.rb +32 -34
- data/lib/makit/process.rb +163 -65
- data/lib/makit/protoc.rb +107 -104
- data/lib/makit/rake/cli.rb +196 -0
- data/lib/makit/rake.rb +25 -25
- data/lib/makit/ruby/cli.rb +185 -0
- data/lib/makit/ruby.rb +25 -0
- data/lib/makit/secrets.rb +51 -51
- data/lib/makit/serializer.rb +130 -115
- data/lib/makit/services/builder.rb +186 -0
- data/lib/makit/services/error_handler.rb +226 -0
- data/lib/makit/services/repository_manager.rb +229 -0
- data/lib/makit/services/validator.rb +112 -0
- data/lib/makit/setup/classlib.rb +53 -0
- data/lib/makit/setup/gem.rb +263 -0
- data/lib/makit/setup/runner.rb +45 -0
- data/lib/makit/setup.rb +5 -0
- data/lib/makit/show.rb +110 -110
- data/lib/makit/storage.rb +126 -131
- data/lib/makit/symbols.rb +170 -149
- data/lib/makit/task_info.rb +128 -86
- data/lib/makit/tasks/at_exit.rb +13 -0
- data/lib/makit/tasks/build.rb +19 -0
- data/lib/makit/tasks/clean.rb +11 -0
- data/lib/makit/tasks/hook_manager.rb +393 -0
- data/lib/makit/tasks/init.rb +47 -0
- data/lib/makit/tasks/integrate.rb +17 -0
- data/lib/makit/tasks/pull_incoming.rb +11 -0
- data/lib/makit/tasks/setup.rb +6 -0
- data/lib/makit/tasks/sync.rb +12 -0
- data/lib/makit/tasks/tag.rb +15 -0
- data/lib/makit/tasks/task_monkey_patch.rb +79 -0
- data/lib/makit/tasks.rb +15 -150
- data/lib/makit/test_cache.rb +239 -0
- data/lib/makit/tree.rb +37 -37
- data/lib/makit/v1/makit.v1_pb.rb +3 -4
- data/lib/makit/v1/makit.v1_services_pb.rb +27 -25
- data/lib/makit/version.rb +5 -61
- data/lib/makit/version_util.rb +21 -0
- data/lib/makit/wix.rb +95 -95
- data/lib/makit/yaml.rb +29 -17
- data/lib/makit/zip.rb +17 -17
- data/lib/makit copy.rb +44 -0
- data/lib/makit.rb +40 -267
- metadata +117 -110
- data/lib/makit/cli/clean.rb +0 -14
- data/lib/makit/cli/clone.rb +0 -59
- data/lib/makit/cli/init.rb +0 -38
- data/lib/makit/cli/make.rb +0 -54
- data/lib/makit/cli/new.rb +0 -37
- data/lib/makit/cli/nuget_cache.rb +0 -38
- data/lib/makit/cli/pull.rb +0 -31
- data/lib/makit/cli/setup.rb +0 -71
- data/lib/makit/cli/work.rb +0 -21
- data/lib/makit/command_runner.rb +0 -404
- data/lib/makit/content/default_gitignore.txt +0 -222
@@ -0,0 +1,323 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module Makit
|
7
|
+
module Commands
|
8
|
+
# Structured result objects with comprehensive metadata.
|
9
|
+
# Uses native Ruby objects instead of protobuf for better performance.
|
10
|
+
#
|
11
|
+
# @example Basic result creation
|
12
|
+
# result = Result.new(
|
13
|
+
# command: "git --version",
|
14
|
+
# exit_code: 0,
|
15
|
+
# stdout: "git version 2.39.5\n",
|
16
|
+
# stderr: ""
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
# @example Result with full metadata
|
20
|
+
# result = Result.new(
|
21
|
+
# command: "bundle install",
|
22
|
+
# exit_code: 0,
|
23
|
+
# stdout: "Bundle complete!",
|
24
|
+
# stderr: "",
|
25
|
+
# started_at: Time.now - 10,
|
26
|
+
# finished_at: Time.now,
|
27
|
+
# metadata: { gems_installed: 42, cache_hit: true }
|
28
|
+
# )
|
29
|
+
class Result
|
30
|
+
# Command execution result with comprehensive metadata.
|
31
|
+
#
|
32
|
+
# @!attribute [r] command
|
33
|
+
# @return [String] the command that was executed
|
34
|
+
# @!attribute [r] exit_code
|
35
|
+
# @return [Integer] process exit code
|
36
|
+
# @!attribute [r] stdout
|
37
|
+
# @return [String] standard output
|
38
|
+
# @!attribute [r] stderr
|
39
|
+
# @return [String] standard error output
|
40
|
+
# @!attribute [r] started_at
|
41
|
+
# @return [Time] when execution started
|
42
|
+
# @!attribute [r] finished_at
|
43
|
+
# @return [Time, nil] when execution finished
|
44
|
+
# @!attribute [r] duration
|
45
|
+
# @return [Float, nil] execution duration in seconds
|
46
|
+
# @!attribute [r] metadata
|
47
|
+
# @return [Hash] additional metadata
|
48
|
+
attr_reader :command, :exit_code, :stdout, :stderr, :started_at, :finished_at, :duration, :metadata
|
49
|
+
|
50
|
+
# Initialize a new command result.
|
51
|
+
#
|
52
|
+
# @param command [String] the command that was executed
|
53
|
+
# @param exit_code [Integer] process exit code (default: nil)
|
54
|
+
# @param stdout [String] standard output (default: "")
|
55
|
+
# @param stderr [String] standard error output (default: "")
|
56
|
+
# @param started_at [Time] when execution started (default: Time.now)
|
57
|
+
# @param finished_at [Time, nil] when execution finished
|
58
|
+
# @param metadata [Hash] additional metadata (default: {})
|
59
|
+
def initialize(command:, **attributes)
|
60
|
+
@command = command.to_s
|
61
|
+
@exit_code = attributes[:exit_code]
|
62
|
+
@stdout = attributes[:stdout] || ""
|
63
|
+
@stderr = attributes[:stderr] || ""
|
64
|
+
@started_at = attributes[:started_at] || Time.now
|
65
|
+
@finished_at = attributes[:finished_at]
|
66
|
+
@duration = calculate_duration
|
67
|
+
@metadata = attributes[:metadata] || {}
|
68
|
+
|
69
|
+
# Ensure output is always strings
|
70
|
+
@stdout = @stdout.to_s
|
71
|
+
@stderr = @stderr.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
# Mark the result as finished.
|
75
|
+
#
|
76
|
+
# @param finished_at [Time] when execution finished (default: Time.now)
|
77
|
+
# @param exit_code [Integer] process exit code
|
78
|
+
# @param stdout [String] standard output
|
79
|
+
# @param stderr [String] standard error output
|
80
|
+
# @return [self] for method chaining
|
81
|
+
def finish!(finished_at: Time.now, exit_code: nil, stdout: nil, stderr: nil)
|
82
|
+
@finished_at = finished_at
|
83
|
+
@duration = calculate_duration
|
84
|
+
@exit_code = exit_code if exit_code
|
85
|
+
@stdout = stdout.to_s if stdout
|
86
|
+
@stderr = stderr.to_s if stderr
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if the command executed successfully.
|
91
|
+
#
|
92
|
+
# @return [Boolean] true if exit code is 0
|
93
|
+
def success?
|
94
|
+
exit_code.zero?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check if the command execution failed.
|
98
|
+
#
|
99
|
+
# @return [Boolean] true if exit code is not 0
|
100
|
+
def failure?
|
101
|
+
!success?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check if the command execution is complete.
|
105
|
+
#
|
106
|
+
# @return [Boolean] true if finished_at is set
|
107
|
+
def finished?
|
108
|
+
!finished_at.nil?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if the command is still running.
|
112
|
+
#
|
113
|
+
# @return [Boolean] true if execution started but not finished
|
114
|
+
def running?
|
115
|
+
!started_at.nil? && finished_at.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
# Get combined output (stdout + stderr).
|
119
|
+
#
|
120
|
+
# @return [String] combined output
|
121
|
+
def output
|
122
|
+
[stdout, stderr].reject(&:empty?).join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get the first line of stdout (useful for simple commands).
|
126
|
+
#
|
127
|
+
# @return [String, nil] first line of stdout
|
128
|
+
def first_line
|
129
|
+
return nil if stdout.empty?
|
130
|
+
|
131
|
+
stdout.lines.first&.chomp
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get the last line of stdout (useful for progress indicators).
|
135
|
+
#
|
136
|
+
# @return [String, nil] last line of stdout
|
137
|
+
def last_line
|
138
|
+
return nil if stdout.empty?
|
139
|
+
|
140
|
+
stdout.lines.last&.chomp
|
141
|
+
end
|
142
|
+
|
143
|
+
# Check if output contains specific text.
|
144
|
+
#
|
145
|
+
# @param text [String] text to search for
|
146
|
+
# @param case_sensitive [Boolean] whether to perform case-sensitive search
|
147
|
+
# @return [Boolean] true if text is found in stdout or stderr
|
148
|
+
def contains?(text, case_sensitive: true)
|
149
|
+
search_text = case_sensitive ? text : text.downcase
|
150
|
+
search_stdout = case_sensitive ? stdout : stdout.downcase
|
151
|
+
search_stderr = case_sensitive ? stderr : stderr.downcase
|
152
|
+
|
153
|
+
search_stdout.include?(search_text) || search_stderr.include?(search_text)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Convert result to hash representation.
|
157
|
+
#
|
158
|
+
# @return [Hash] hash representation of the result
|
159
|
+
def to_h
|
160
|
+
{
|
161
|
+
command: command,
|
162
|
+
exit_code: exit_code,
|
163
|
+
stdout: stdout,
|
164
|
+
stderr: stderr,
|
165
|
+
started_at: started_at&.iso8601,
|
166
|
+
finished_at: finished_at&.iso8601,
|
167
|
+
duration: duration,
|
168
|
+
success: success?,
|
169
|
+
metadata: metadata,
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
# Convert result to JSON representation.
|
174
|
+
#
|
175
|
+
# @param args [Array] arguments passed to JSON.generate
|
176
|
+
# @return [String] JSON representation
|
177
|
+
def to_json(*args)
|
178
|
+
JSON.generate(to_h, *args)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Create result from hash representation.
|
182
|
+
#
|
183
|
+
# @param hash [Hash] hash containing result information
|
184
|
+
# @return [Result] new result object
|
185
|
+
def self.from_hash(hash)
|
186
|
+
# Parse time strings back to Time objects
|
187
|
+
started_at = hash[:started_at] || hash["started_at"]
|
188
|
+
finished_at = hash[:finished_at] || hash["finished_at"]
|
189
|
+
|
190
|
+
started_at = Time.iso8601(started_at) if started_at.is_a?(String)
|
191
|
+
finished_at = Time.iso8601(finished_at) if finished_at.is_a?(String)
|
192
|
+
|
193
|
+
new(
|
194
|
+
command: hash[:command] || hash["command"],
|
195
|
+
exit_code: hash[:exit_code] || hash["exit_code"],
|
196
|
+
stdout: hash[:stdout] || hash["stdout"] || "",
|
197
|
+
stderr: hash[:stderr] || hash["stderr"] || "",
|
198
|
+
started_at: started_at,
|
199
|
+
finished_at: finished_at,
|
200
|
+
metadata: hash[:metadata] || hash["metadata"] || {},
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Create result from JSON string.
|
205
|
+
#
|
206
|
+
# @param json_string [String] JSON string representation
|
207
|
+
# @return [Result] new result object
|
208
|
+
# @raise [JSON::ParserError] if JSON is invalid
|
209
|
+
def self.from_json(json_string)
|
210
|
+
hash = JSON.parse(json_string)
|
211
|
+
from_hash(hash)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Create a failed result with error information.
|
215
|
+
#
|
216
|
+
# @param command [String] the command that failed
|
217
|
+
# @param error [Exception, String] error that occurred
|
218
|
+
# @param started_at [Time] when execution started
|
219
|
+
# @return [Result] failed result object
|
220
|
+
def self.failure(command:, error:, started_at: Time.now)
|
221
|
+
error_message = error.is_a?(Exception) ? error.message : error.to_s
|
222
|
+
error_class = error.is_a?(Exception) ? error.class.name : "Error"
|
223
|
+
|
224
|
+
new(
|
225
|
+
command: command,
|
226
|
+
exit_code: 1,
|
227
|
+
stdout: "",
|
228
|
+
stderr: error_message,
|
229
|
+
started_at: started_at,
|
230
|
+
finished_at: Time.now,
|
231
|
+
metadata: { error_class: error_class, failed: true },
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Create a successful result.
|
236
|
+
#
|
237
|
+
# @param command [String] the command that succeeded
|
238
|
+
# @param stdout [String] standard output
|
239
|
+
# @param started_at [Time] when execution started
|
240
|
+
# @param duration [Float] execution duration
|
241
|
+
# @return [Result] successful result object
|
242
|
+
def self.success(command:, stdout: "", started_at: Time.now, duration: nil)
|
243
|
+
finished_at = duration ? started_at + duration : Time.now
|
244
|
+
|
245
|
+
new(
|
246
|
+
command: command,
|
247
|
+
exit_code: 0,
|
248
|
+
stdout: stdout,
|
249
|
+
stderr: "",
|
250
|
+
started_at: started_at,
|
251
|
+
finished_at: finished_at,
|
252
|
+
)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Add metadata to the result.
|
256
|
+
#
|
257
|
+
# @param key [String, Symbol] metadata key
|
258
|
+
# @param value [Object] metadata value
|
259
|
+
# @return [self] for method chaining
|
260
|
+
def add_metadata(key, value)
|
261
|
+
@metadata[key] = value
|
262
|
+
self
|
263
|
+
end
|
264
|
+
|
265
|
+
# Merge additional metadata.
|
266
|
+
#
|
267
|
+
# @param additional_metadata [Hash] metadata to merge
|
268
|
+
# @return [self] for method chaining
|
269
|
+
def merge_metadata(additional_metadata)
|
270
|
+
@metadata.merge!(additional_metadata)
|
271
|
+
self
|
272
|
+
end
|
273
|
+
|
274
|
+
# Get execution summary for logging/display.
|
275
|
+
#
|
276
|
+
# @return [String] human-readable execution summary
|
277
|
+
def summary
|
278
|
+
status = success? ? "✓" : "✗"
|
279
|
+
duration_text = duration ? " (#{format("%.2f", duration)}s)" : ""
|
280
|
+
|
281
|
+
"#{status} #{command}#{duration_text}"
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get detailed execution information.
|
285
|
+
#
|
286
|
+
# @return [String] detailed execution information
|
287
|
+
def details
|
288
|
+
lines = []
|
289
|
+
lines << "Command: #{command}"
|
290
|
+
lines << "Exit Code: #{exit_code}"
|
291
|
+
lines << "Started: #{started_at}"
|
292
|
+
lines << "Finished: #{finished_at}" if finished_at
|
293
|
+
lines << "Duration: #{format("%.2f", duration)}s" if duration
|
294
|
+
lines << "Status: #{success? ? "Success" : "Failed"}"
|
295
|
+
|
296
|
+
unless stdout.empty?
|
297
|
+
lines << "STDOUT:"
|
298
|
+
lines << stdout.split("\n").map { |line| " #{line}" }
|
299
|
+
end
|
300
|
+
|
301
|
+
unless stderr.empty?
|
302
|
+
lines << "STDERR:"
|
303
|
+
lines << stderr.split("\n").map { |line| " #{line}" }
|
304
|
+
end
|
305
|
+
|
306
|
+
lines << "Metadata: #{metadata}" unless metadata.empty?
|
307
|
+
|
308
|
+
lines.flatten.join("\n")
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
|
313
|
+
# Calculate execution duration.
|
314
|
+
#
|
315
|
+
# @return [Float, nil] duration in seconds, or nil if not finished
|
316
|
+
def calculate_duration
|
317
|
+
return nil unless started_at && finished_at
|
318
|
+
|
319
|
+
finished_at - started_at
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
@@ -0,0 +1,337 @@
|
|
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
|