makit 0.0.164 → 0.0.166

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 (178) 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/azure/blob_storage.rb +257 -257
  7. data/lib/makit/azure/cli.rb +284 -284
  8. data/lib/makit/azure-pipelines.rb +139 -0
  9. data/lib/makit/cli/base.rb +17 -17
  10. data/lib/makit/cli/build_commands.rb +500 -500
  11. data/lib/makit/cli/generators/base_generator.rb +74 -74
  12. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  13. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  14. data/lib/makit/cli/generators/node_generator.rb +50 -50
  15. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  16. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  17. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  18. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  19. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  20. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -41
  21. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  22. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  23. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  24. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  25. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  26. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  27. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  28. data/lib/makit/cli/main.rb +78 -78
  29. data/lib/makit/cli/pipeline_commands.rb +311 -311
  30. data/lib/makit/cli/project_commands.rb +868 -868
  31. data/lib/makit/cli/repository_commands.rb +661 -661
  32. data/lib/makit/cli/strategy_commands.rb +207 -207
  33. data/lib/makit/cli/utility_commands.rb +521 -521
  34. data/lib/makit/commands/factory.rb +359 -359
  35. data/lib/makit/commands/middleware/base.rb +73 -73
  36. data/lib/makit/commands/middleware/cache.rb +248 -248
  37. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  38. data/lib/makit/commands/middleware/validator.rb +269 -269
  39. data/lib/makit/commands/request.rb +316 -316
  40. data/lib/makit/commands/result.rb +323 -323
  41. data/lib/makit/commands/runner.rb +386 -386
  42. data/lib/makit/commands/strategies/base.rb +171 -171
  43. data/lib/makit/commands/strategies/child_process.rb +162 -162
  44. data/lib/makit/commands/strategies/factory.rb +136 -136
  45. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  46. data/lib/makit/commands.rb +50 -50
  47. data/lib/makit/configuration/dotnet_project.rb +48 -48
  48. data/lib/makit/configuration/gitlab_helper.rb +61 -61
  49. data/lib/makit/configuration/project.rb +292 -292
  50. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  51. data/lib/makit/configuration/step.rb +34 -34
  52. data/lib/makit/configuration/timeout.rb +74 -74
  53. data/lib/makit/configuration.rb +21 -21
  54. data/lib/makit/content/default_gitignore.rb +7 -7
  55. data/lib/makit/content/default_gitignore.txt +225 -225
  56. data/lib/makit/content/default_rakefile.rb +13 -13
  57. data/lib/makit/content/gem_rakefile.rb +16 -16
  58. data/lib/makit/context.rb +1 -1
  59. data/lib/makit/data.rb +49 -49
  60. data/lib/makit/directories.rb +170 -170
  61. data/lib/makit/directory.rb +262 -262
  62. data/lib/makit/docs/files.rb +89 -89
  63. data/lib/makit/docs/rake.rb +102 -102
  64. data/lib/makit/dotnet/cli.rb +224 -224
  65. data/lib/makit/dotnet/project.rb +217 -217
  66. data/lib/makit/dotnet/solution.rb +38 -38
  67. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  68. data/lib/makit/dotnet/solution_console.rb +264 -264
  69. data/lib/makit/dotnet/solution_maui.rb +354 -354
  70. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  71. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  72. data/lib/makit/dotnet.rb +110 -110
  73. data/lib/makit/email.rb +90 -90
  74. data/lib/makit/environment.rb +142 -142
  75. data/lib/makit/examples/runner.rb +370 -370
  76. data/lib/makit/exceptions.rb +45 -45
  77. data/lib/makit/fileinfo.rb +32 -32
  78. data/lib/makit/files.rb +43 -43
  79. data/lib/makit/gems.rb +49 -49
  80. data/lib/makit/git/cli.rb +103 -103
  81. data/lib/makit/git/repository.rb +100 -100
  82. data/lib/makit/git.rb +104 -104
  83. data/lib/makit/gitlab/pipeline.rb +857 -857
  84. data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
  85. data/lib/makit/gitlab_runner.rb +59 -59
  86. data/lib/makit/humanize.rb +218 -218
  87. data/lib/makit/indexer.rb +47 -47
  88. data/lib/makit/io/filesystem.rb +111 -111
  89. data/lib/makit/io/filesystem_service_impl.rb +337 -337
  90. data/lib/makit/lint.rb +212 -212
  91. data/lib/makit/logging/configuration.rb +309 -309
  92. data/lib/makit/logging/format_registry.rb +84 -84
  93. data/lib/makit/logging/formatters/base.rb +39 -39
  94. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  95. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  96. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  97. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  98. data/lib/makit/logging/log_request.rb +119 -119
  99. data/lib/makit/logging/logger.rb +199 -199
  100. data/lib/makit/logging/sinks/base.rb +91 -91
  101. data/lib/makit/logging/sinks/console.rb +72 -72
  102. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  103. data/lib/makit/logging/sinks/structured.rb +123 -123
  104. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  105. data/lib/makit/logging.rb +578 -578
  106. data/lib/makit/markdown.rb +75 -75
  107. data/lib/makit/mp/basic_object_mp.rb +17 -17
  108. data/lib/makit/mp/command_mp.rb +13 -13
  109. data/lib/makit/mp/command_request.mp.rb +17 -17
  110. data/lib/makit/mp/project_mp.rb +199 -199
  111. data/lib/makit/mp/string_mp.rb +205 -205
  112. data/lib/makit/nuget.rb +454 -454
  113. data/lib/makit/podman/podman.rb +458 -458
  114. data/lib/makit/podman/podman_service_impl.rb +1081 -1081
  115. data/lib/makit/port.rb +32 -32
  116. data/lib/makit/process.rb +377 -377
  117. data/lib/makit/protoc.rb +112 -112
  118. data/lib/makit/rake/cli.rb +196 -196
  119. data/lib/makit/rake/trace_controller.rb +174 -174
  120. data/lib/makit/rake.rb +81 -81
  121. data/lib/makit/ruby/cli.rb +185 -185
  122. data/lib/makit/ruby.rb +25 -25
  123. data/lib/makit/rubygems.rb +137 -137
  124. data/lib/makit/secrets/azure_key_vault.rb +322 -322
  125. data/lib/makit/secrets/azure_secrets.rb +221 -183
  126. data/lib/makit/secrets/local_secrets.rb +72 -72
  127. data/lib/makit/secrets/secrets_manager.rb +105 -105
  128. data/lib/makit/secrets.rb +96 -16
  129. data/lib/makit/serializer.rb +130 -130
  130. data/lib/makit/services/builder.rb +186 -186
  131. data/lib/makit/services/error_handler.rb +226 -226
  132. data/lib/makit/services/repository_manager.rb +367 -367
  133. data/lib/makit/services/validator.rb +112 -112
  134. data/lib/makit/setup/classlib.rb +101 -101
  135. data/lib/makit/setup/gem.rb +268 -268
  136. data/lib/makit/setup/pages.rb +11 -11
  137. data/lib/makit/setup/razorclasslib.rb +101 -101
  138. data/lib/makit/setup/runner.rb +54 -54
  139. data/lib/makit/setup.rb +5 -5
  140. data/lib/makit/show.rb +110 -110
  141. data/lib/makit/storage.rb +126 -126
  142. data/lib/makit/symbols.rb +175 -175
  143. data/lib/makit/task_info.rb +130 -130
  144. data/lib/makit/tasks/at_exit.rb +15 -15
  145. data/lib/makit/tasks/build.rb +22 -22
  146. data/lib/makit/tasks/bump.rb +7 -7
  147. data/lib/makit/tasks/clean.rb +13 -13
  148. data/lib/makit/tasks/configure.rb +10 -10
  149. data/lib/makit/tasks/format.rb +10 -10
  150. data/lib/makit/tasks/hook_manager.rb +443 -443
  151. data/lib/makit/tasks/info.rb +368 -368
  152. data/lib/makit/tasks/init.rb +49 -49
  153. data/lib/makit/tasks/integrate.rb +60 -60
  154. data/lib/makit/tasks/pull_incoming.rb +13 -13
  155. data/lib/makit/tasks/secrets.rb +7 -7
  156. data/lib/makit/tasks/setup.rb +16 -16
  157. data/lib/makit/tasks/sync.rb +14 -14
  158. data/lib/makit/tasks/tag.rb +27 -27
  159. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  160. data/lib/makit/tasks/test.rb +22 -22
  161. data/lib/makit/tasks/update.rb +21 -21
  162. data/lib/makit/tasks/version.rb +6 -6
  163. data/lib/makit/tasks.rb +24 -24
  164. data/lib/makit/test_cache.rb +239 -239
  165. data/lib/makit/tree.rb +37 -37
  166. data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
  167. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
  168. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  169. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  170. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
  171. data/lib/makit/version.rb +661 -661
  172. data/lib/makit/version_util.rb +21 -21
  173. data/lib/makit/wix.rb +95 -95
  174. data/lib/makit/yaml.rb +29 -29
  175. data/lib/makit/zip.rb +17 -17
  176. data/lib/makit copy.rb +44 -44
  177. data/lib/makit.rb +120 -119
  178. metadata +3 -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 == 0
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 == 0
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