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.
- checksums.yaml +4 -4
- data/README.md +41 -41
- data/exe/makit +5 -5
- data/lib/makit/apache.rb +28 -28
- data/lib/makit/auto.rb +48 -48
- data/lib/makit/cli/build_commands.rb +500 -500
- data/lib/makit/cli/generators/base_generator.rb +74 -74
- data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
- data/lib/makit/cli/generators/generator_factory.rb +49 -49
- data/lib/makit/cli/generators/node_generator.rb +50 -50
- data/lib/makit/cli/generators/ruby_generator.rb +77 -77
- data/lib/makit/cli/generators/rust_generator.rb +50 -50
- data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
- data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
- data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
- data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
- data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
- data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
- data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
- data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
- data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
- data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
- data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
- data/lib/makit/cli/main.rb +69 -69
- data/lib/makit/cli/project_commands.rb +868 -868
- data/lib/makit/cli/repository_commands.rb +661 -661
- data/lib/makit/cli/strategy_commands.rb +203 -203
- data/lib/makit/cli/utility_commands.rb +521 -521
- data/lib/makit/commands/factory.rb +359 -359
- data/lib/makit/commands/middleware/base.rb +73 -73
- data/lib/makit/commands/middleware/cache.rb +248 -248
- data/lib/makit/commands/middleware/command_logger.rb +312 -312
- data/lib/makit/commands/middleware/validator.rb +269 -269
- data/lib/makit/commands/request.rb +316 -316
- data/lib/makit/commands/result.rb +323 -323
- data/lib/makit/commands/runner.rb +388 -385
- data/lib/makit/commands/strategies/base.rb +171 -171
- data/lib/makit/commands/strategies/child_process.rb +165 -165
- data/lib/makit/commands/strategies/factory.rb +136 -136
- data/lib/makit/commands/strategies/synchronous.rb +139 -139
- data/lib/makit/commands.rb +50 -50
- data/lib/makit/configuration/dotnet_project.rb +12 -12
- data/lib/makit/configuration/gitlab_helper.rb +58 -58
- data/lib/makit/configuration/project.rb +168 -168
- data/lib/makit/configuration/rakefile_helper.rb +43 -43
- data/lib/makit/configuration/step.rb +34 -34
- data/lib/makit/configuration/timeout.rb +74 -74
- data/lib/makit/configuration.rb +15 -15
- data/lib/makit/content/default_gitignore.rb +7 -7
- data/lib/makit/content/default_gitignore.txt +225 -225
- data/lib/makit/content/default_rakefile.rb +13 -13
- data/lib/makit/content/gem_rakefile.rb +16 -16
- data/lib/makit/context.rb +1 -1
- data/lib/makit/data.rb +49 -49
- data/lib/makit/directories.rb +140 -140
- data/lib/makit/directory.rb +262 -262
- data/lib/makit/docs/files.rb +89 -89
- data/lib/makit/docs/rake.rb +102 -102
- data/lib/makit/dotnet/cli.rb +69 -69
- data/lib/makit/dotnet/project.rb +217 -217
- data/lib/makit/dotnet/solution.rb +38 -38
- data/lib/makit/dotnet/solution_classlib.rb +239 -239
- data/lib/makit/dotnet/solution_console.rb +264 -264
- data/lib/makit/dotnet/solution_maui.rb +354 -354
- data/lib/makit/dotnet/solution_wasm.rb +275 -275
- data/lib/makit/dotnet/solution_wpf.rb +304 -304
- data/lib/makit/dotnet.rb +102 -102
- data/lib/makit/email.rb +90 -90
- data/lib/makit/environment.rb +142 -142
- data/lib/makit/examples/runner.rb +370 -370
- data/lib/makit/exceptions.rb +45 -45
- data/lib/makit/fileinfo.rb +24 -24
- data/lib/makit/files.rb +43 -43
- data/lib/makit/gems.rb +40 -40
- data/lib/makit/git/cli.rb +54 -54
- data/lib/makit/git/repository.rb +90 -90
- data/lib/makit/git.rb +98 -98
- data/lib/makit/gitlab_runner.rb +59 -59
- data/lib/makit/humanize.rb +137 -137
- data/lib/makit/indexer.rb +47 -47
- data/lib/makit/logging/configuration.rb +308 -308
- data/lib/makit/logging/format_registry.rb +84 -84
- data/lib/makit/logging/formatters/base.rb +39 -39
- data/lib/makit/logging/formatters/console_formatter.rb +140 -140
- data/lib/makit/logging/formatters/json_formatter.rb +65 -65
- data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
- data/lib/makit/logging/formatters/text_formatter.rb +64 -64
- data/lib/makit/logging/log_request.rb +119 -119
- data/lib/makit/logging/logger.rb +199 -199
- data/lib/makit/logging/sinks/base.rb +91 -91
- data/lib/makit/logging/sinks/console.rb +72 -72
- data/lib/makit/logging/sinks/file_sink.rb +92 -92
- data/lib/makit/logging/sinks/structured.rb +123 -123
- data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
- data/lib/makit/logging.rb +565 -565
- data/lib/makit/markdown.rb +75 -75
- data/lib/makit/mp/basic_object_mp.rb +17 -17
- data/lib/makit/mp/command_mp.rb +13 -13
- data/lib/makit/mp/command_request.mp.rb +17 -17
- data/lib/makit/mp/project_mp.rb +199 -199
- data/lib/makit/mp/string_mp.rb +199 -191
- data/lib/makit/nuget.rb +74 -74
- data/lib/makit/port.rb +32 -32
- data/lib/makit/process.rb +163 -163
- data/lib/makit/protoc.rb +107 -107
- data/lib/makit/rake/cli.rb +196 -196
- data/lib/makit/rake/trace_controller.rb +173 -173
- data/lib/makit/rake.rb +80 -80
- data/lib/makit/ruby/cli.rb +185 -185
- data/lib/makit/ruby.rb +25 -25
- data/lib/makit/secrets.rb +51 -51
- data/lib/makit/serializer.rb +130 -130
- data/lib/makit/services/builder.rb +186 -186
- data/lib/makit/services/error_handler.rb +226 -226
- data/lib/makit/services/repository_manager.rb +231 -231
- data/lib/makit/services/validator.rb +112 -112
- data/lib/makit/setup/classlib.rb +101 -101
- data/lib/makit/setup/gem.rb +268 -268
- data/lib/makit/setup/razorclasslib.rb +101 -101
- data/lib/makit/setup/runner.rb +54 -54
- data/lib/makit/setup.rb +5 -5
- data/lib/makit/show.rb +110 -110
- data/lib/makit/storage.rb +126 -126
- data/lib/makit/symbols.rb +170 -170
- data/lib/makit/task_info.rb +130 -130
- data/lib/makit/tasks/at_exit.rb +15 -15
- data/lib/makit/tasks/build.rb +22 -22
- data/lib/makit/tasks/clean.rb +13 -13
- data/lib/makit/tasks/configure.rb +10 -10
- data/lib/makit/tasks/format.rb +10 -10
- data/lib/makit/tasks/hook_manager.rb +443 -443
- data/lib/makit/tasks/init.rb +49 -49
- data/lib/makit/tasks/integrate.rb +29 -29
- data/lib/makit/tasks/pull_incoming.rb +13 -13
- data/lib/makit/tasks/setup.rb +13 -13
- data/lib/makit/tasks/sync.rb +17 -17
- data/lib/makit/tasks/tag.rb +16 -16
- data/lib/makit/tasks/task_monkey_patch.rb +81 -81
- data/lib/makit/tasks/test.rb +22 -22
- data/lib/makit/tasks/update.rb +18 -18
- data/lib/makit/tasks.rb +20 -20
- data/lib/makit/test_cache.rb +239 -239
- data/lib/makit/tree.rb +37 -37
- data/lib/makit/v1/makit.v1_pb.rb +35 -35
- data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
- data/lib/makit/version.rb +99 -99
- data/lib/makit/version_util.rb +21 -21
- data/lib/makit/wix.rb +95 -95
- data/lib/makit/yaml.rb +29 -29
- data/lib/makit/zip.rb +17 -17
- data/lib/makit copy.rb +44 -44
- data/lib/makit.rb +43 -42
- metadata +2 -2
@@ -1,323 +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
|
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
|