makit 0.0.111 → 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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/cli/build_commands.rb +500 -500
  6. data/lib/makit/cli/generators/base_generator.rb +74 -74
  7. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  8. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  9. data/lib/makit/cli/generators/node_generator.rb +50 -50
  10. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  11. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  12. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  13. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  14. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  15. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
  16. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  17. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  18. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  19. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  20. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  21. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  22. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  23. data/lib/makit/cli/main.rb +62 -62
  24. data/lib/makit/cli/project_commands.rb +868 -868
  25. data/lib/makit/cli/repository_commands.rb +661 -661
  26. data/lib/makit/cli/utility_commands.rb +521 -521
  27. data/lib/makit/commands/factory.rb +359 -359
  28. data/lib/makit/commands/middleware/base.rb +73 -73
  29. data/lib/makit/commands/middleware/cache.rb +248 -248
  30. data/lib/makit/commands/middleware/command_logger.rb +320 -323
  31. data/lib/makit/commands/middleware/unified_logger.rb +243 -243
  32. data/lib/makit/commands/middleware/validator.rb +269 -269
  33. data/lib/makit/commands/request.rb +254 -254
  34. data/lib/makit/commands/result.rb +323 -323
  35. data/lib/makit/commands/runner.rb +337 -317
  36. data/lib/makit/commands/strategies/base.rb +160 -160
  37. data/lib/makit/commands/strategies/synchronous.rb +134 -134
  38. data/lib/makit/commands.rb +51 -42
  39. data/lib/makit/configuration/gitlab_helper.rb +60 -60
  40. data/lib/makit/configuration/project.rb +127 -127
  41. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  42. data/lib/makit/configuration/step.rb +34 -34
  43. data/lib/makit/configuration.rb +14 -14
  44. data/lib/makit/content/default_gitignore.rb +7 -7
  45. data/lib/makit/content/default_rakefile.rb +13 -13
  46. data/lib/makit/content/gem_rakefile.rb +16 -16
  47. data/lib/makit/context.rb +1 -1
  48. data/lib/makit/data.rb +49 -49
  49. data/lib/makit/directories.rb +141 -141
  50. data/lib/makit/directory.rb +262 -262
  51. data/lib/makit/docs/files.rb +89 -89
  52. data/lib/makit/docs/rake.rb +102 -102
  53. data/lib/makit/dotnet/project.rb +153 -153
  54. data/lib/makit/dotnet/solution.rb +38 -38
  55. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  56. data/lib/makit/dotnet/solution_console.rb +264 -264
  57. data/lib/makit/dotnet/solution_maui.rb +354 -354
  58. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  59. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  60. data/lib/makit/dotnet.rb +102 -102
  61. data/lib/makit/email.rb +90 -90
  62. data/lib/makit/environment.rb +142 -142
  63. data/lib/makit/examples/runner.rb +370 -370
  64. data/lib/makit/exceptions.rb +45 -45
  65. data/lib/makit/fileinfo.rb +24 -24
  66. data/lib/makit/files.rb +43 -43
  67. data/lib/makit/gems.rb +40 -40
  68. data/lib/makit/git/cli.rb +54 -54
  69. data/lib/makit/git/repository.rb +90 -90
  70. data/lib/makit/git.rb +98 -98
  71. data/lib/makit/gitlab_runner.rb +59 -59
  72. data/lib/makit/humanize.rb +137 -137
  73. data/lib/makit/indexer.rb +47 -47
  74. data/lib/makit/logging/configuration.rb +305 -305
  75. data/lib/makit/logging/formatters/base.rb +39 -39
  76. data/lib/makit/logging/formatters/console_formatter.rb +140 -127
  77. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  78. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  79. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  80. data/lib/makit/logging/log_request.rb +115 -115
  81. data/lib/makit/logging/logger.rb +163 -159
  82. data/lib/makit/logging/sinks/console.rb +72 -72
  83. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  84. data/lib/makit/logging/sinks/unified_file_sink.rb +303 -303
  85. data/lib/makit/logging.rb +530 -521
  86. data/lib/makit/markdown.rb +75 -75
  87. data/lib/makit/mp/basic_object_mp.rb +17 -17
  88. data/lib/makit/mp/command_mp.rb +13 -13
  89. data/lib/makit/mp/command_request.mp.rb +17 -17
  90. data/lib/makit/mp/project_mp.rb +199 -199
  91. data/lib/makit/mp/string_mp.rb +193 -348
  92. data/lib/makit/nuget.rb +74 -74
  93. data/lib/makit/port.rb +32 -32
  94. data/lib/makit/process.rb +163 -163
  95. data/lib/makit/protoc.rb +107 -107
  96. data/lib/makit/rake/cli.rb +196 -196
  97. data/lib/makit/rake.rb +25 -25
  98. data/lib/makit/ruby/cli.rb +185 -185
  99. data/lib/makit/ruby.rb +25 -25
  100. data/lib/makit/secrets.rb +51 -51
  101. data/lib/makit/serializer.rb +130 -117
  102. data/lib/makit/services/builder.rb +186 -186
  103. data/lib/makit/services/error_handler.rb +226 -226
  104. data/lib/makit/services/repository_manager.rb +229 -229
  105. data/lib/makit/services/validator.rb +112 -112
  106. data/lib/makit/setup/classlib.rb +53 -53
  107. data/lib/makit/setup/gem.rb +30 -17
  108. data/lib/makit/setup/runner.rb +45 -40
  109. data/lib/makit/setup.rb +5 -0
  110. data/lib/makit/show.rb +110 -110
  111. data/lib/makit/storage.rb +126 -126
  112. data/lib/makit/symbols.rb +170 -161
  113. data/lib/makit/task_info.rb +128 -128
  114. data/lib/makit/tasks/at_exit.rb +13 -13
  115. data/lib/makit/tasks/build.rb +19 -18
  116. data/lib/makit/tasks/clean.rb +11 -11
  117. data/lib/makit/tasks/hook_manager.rb +393 -239
  118. data/lib/makit/tasks/init.rb +47 -47
  119. data/lib/makit/tasks/integrate.rb +17 -15
  120. data/lib/makit/tasks/pull_incoming.rb +11 -12
  121. data/lib/makit/tasks/setup.rb +6 -6
  122. data/lib/makit/tasks/sync.rb +12 -11
  123. data/lib/makit/tasks/tag.rb +15 -0
  124. data/lib/makit/tasks/task_monkey_patch.rb +79 -79
  125. data/lib/makit/tasks.rb +10 -0
  126. data/lib/makit/test_cache.rb +239 -239
  127. data/lib/makit/tree.rb +37 -37
  128. data/lib/makit/v1/makit.v1_pb.rb +34 -34
  129. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  130. data/lib/makit/version.rb +5 -5
  131. data/lib/makit/version_util.rb +21 -0
  132. data/lib/makit/wix.rb +95 -95
  133. data/lib/makit/yaml.rb +29 -29
  134. data/lib/makit/zip.rb +17 -17
  135. data/lib/makit copy.rb +44 -0
  136. data/lib/makit.rb +40 -8
  137. metadata +50 -7
  138. data/lib/makit/command_runner.rb +0 -463
  139. data/lib/makit/commands/compatibility.rb +0 -365
  140. data/lib/makit/task_hooks.rb +0 -125
@@ -1,160 +1,160 @@
1
- # frozen_string_literal: true
2
-
3
- require "open3"
4
- require "timeout"
5
-
6
- module Makit
7
- module Commands
8
- module Strategies
9
- # Base class for command execution strategies.
10
- #
11
- # Execution strategies define how commands are actually executed -
12
- # synchronously, asynchronously, in parallel, etc. This provides
13
- # flexibility in execution patterns while maintaining a consistent
14
- # interface.
15
- #
16
- # @example Creating custom strategy
17
- # class CustomStrategy < Base
18
- # def execute(request)
19
- # # Custom execution logic
20
- # result = Result.new(command: request.to_shell_command)
21
- # # ... perform execution ...
22
- # result.finish!(exit_code: 0, stdout: "success")
23
- # end
24
- # end
25
- class Base
26
- # Execute a command request.
27
- #
28
- # This method must be implemented by subclasses to provide the actual
29
- # command execution logic. The implementation should create a Result
30
- # object and populate it with execution details.
31
- #
32
- # @param request [Request] the command request to execute
33
- # @return [Result] the execution result
34
- # @raise [NotImplementedError] if not overridden by subclass
35
- def execute(request)
36
- raise NotImplementedError, "#{self.class.name} must implement #execute"
37
- end
38
-
39
- # Execute multiple requests (default: sequential execution).
40
- #
41
- # Override this method in subclasses to provide optimized batch execution
42
- # such as parallel execution.
43
- #
44
- # @param requests [Array<Request>] requests to execute
45
- # @return [Array<Result>] execution results in same order
46
- def execute_batch(requests)
47
- requests.map { |request| execute(request) }
48
- end
49
-
50
- # Check if this strategy can handle the given request.
51
- #
52
- # Override this method to provide conditional strategy selection
53
- # based on request properties.
54
- #
55
- # @param request [Request] the command request
56
- # @return [Boolean] true if strategy can handle the request
57
- def supports?(_request)
58
- true
59
- end
60
-
61
- # Get strategy name for logging and debugging.
62
- #
63
- # @return [String] strategy name
64
- def name
65
- self.class.name.split("::").last
66
- end
67
-
68
- # Get strategy configuration.
69
- #
70
- # @return [Hash] strategy configuration
71
- def config
72
- {}
73
- end
74
-
75
- protected
76
-
77
- # Execute command using Open3 for cross-platform compatibility.
78
- #
79
- # This is a helper method that subclasses can use for actual
80
- # system command execution.
81
- #
82
- # @param request [Request] the command request
83
- # @return [Result] execution result
84
- def execute_with_open3(request)
85
- result = Result.new(
86
- command: request.to_shell_command,
87
- started_at: Time.now,
88
- )
89
-
90
- begin
91
- # Change to specified directory if provided
92
- Dir.chdir(request.directory) do
93
- # Execute command with timeout using Timeout module
94
- stdout, stderr, status = if request.timeout&.positive?
95
- Timeout.timeout(request.timeout) do
96
- Open3.capture3(
97
- request.environment,
98
- request.command,
99
- *request.arguments
100
- )
101
- end
102
- else
103
- Open3.capture3(
104
- request.environment,
105
- request.command,
106
- *request.arguments
107
- )
108
- end
109
-
110
- result.finish!(
111
- exit_code: status.exitstatus,
112
- stdout: stdout,
113
- stderr: stderr,
114
- )
115
- end
116
- rescue Timeout::Error => e
117
- result.finish!(
118
- exit_code: 124, # timeout exit code
119
- stderr: "Command timed out after #{request.timeout} seconds",
120
- ).add_metadata(:timeout, true)
121
- .add_metadata(:error, e.message)
122
- rescue StandardError => e
123
- result.finish!(
124
- exit_code: 1,
125
- stderr: e.message,
126
- ).add_metadata(:error_class, e.class.name)
127
- .add_metadata(:error, e.message)
128
- end
129
-
130
- result
131
- end
132
-
133
- # Validate that the command exists and is executable.
134
- #
135
- # @param command [String] command to validate
136
- # @return [Boolean] true if command is available
137
- def command_available?(command)
138
- system("which #{command} > /dev/null 2>&1") ||
139
- system("where #{command} > nul 2>&1") # Windows
140
- end
141
-
142
- # Get the full path to a command.
143
- #
144
- # @param command [String] command name
145
- # @return [String, nil] full path to command or nil if not found
146
- def which(command)
147
- # Try Unix-style which first
148
- path = `which #{command} 2>/dev/null`.strip
149
- return path unless path.empty?
150
-
151
- # Try Windows-style where
152
- path = `where #{command} 2>nul`.strip
153
- return path unless path.empty?
154
-
155
- nil
156
- end
157
- end
158
- end
159
- end
160
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ module Makit
7
+ module Commands
8
+ module Strategies
9
+ # Base class for command execution strategies.
10
+ #
11
+ # Execution strategies define how commands are actually executed -
12
+ # synchronously, asynchronously, in parallel, etc. This provides
13
+ # flexibility in execution patterns while maintaining a consistent
14
+ # interface.
15
+ #
16
+ # @example Creating custom strategy
17
+ # class CustomStrategy < Base
18
+ # def execute(request)
19
+ # # Custom execution logic
20
+ # result = Result.new(command: request.to_shell_command)
21
+ # # ... perform execution ...
22
+ # result.finish!(exit_code: 0, stdout: "success")
23
+ # end
24
+ # end
25
+ class Base
26
+ # Execute a command request.
27
+ #
28
+ # This method must be implemented by subclasses to provide the actual
29
+ # command execution logic. The implementation should create a Result
30
+ # object and populate it with execution details.
31
+ #
32
+ # @param request [Request] the command request to execute
33
+ # @return [Result] the execution result
34
+ # @raise [NotImplementedError] if not overridden by subclass
35
+ def execute(request)
36
+ raise NotImplementedError, "#{self.class.name} must implement #execute"
37
+ end
38
+
39
+ # Execute multiple requests (default: sequential execution).
40
+ #
41
+ # Override this method in subclasses to provide optimized batch execution
42
+ # such as parallel execution.
43
+ #
44
+ # @param requests [Array<Request>] requests to execute
45
+ # @return [Array<Result>] execution results in same order
46
+ def execute_batch(requests)
47
+ requests.map { |request| execute(request) }
48
+ end
49
+
50
+ # Check if this strategy can handle the given request.
51
+ #
52
+ # Override this method to provide conditional strategy selection
53
+ # based on request properties.
54
+ #
55
+ # @param request [Request] the command request
56
+ # @return [Boolean] true if strategy can handle the request
57
+ def supports?(_request)
58
+ true
59
+ end
60
+
61
+ # Get strategy name for logging and debugging.
62
+ #
63
+ # @return [String] strategy name
64
+ def name
65
+ self.class.name.split("::").last
66
+ end
67
+
68
+ # Get strategy configuration.
69
+ #
70
+ # @return [Hash] strategy configuration
71
+ def config
72
+ {}
73
+ end
74
+
75
+ protected
76
+
77
+ # Execute command using Open3 for cross-platform compatibility.
78
+ #
79
+ # This is a helper method that subclasses can use for actual
80
+ # system command execution.
81
+ #
82
+ # @param request [Request] the command request
83
+ # @return [Result] execution result
84
+ def execute_with_open3(request)
85
+ result = Result.new(
86
+ command: request.to_shell_command,
87
+ started_at: Time.now,
88
+ )
89
+
90
+ begin
91
+ # Change to specified directory if provided
92
+ Dir.chdir(request.directory) do
93
+ # Execute command with timeout using Timeout module
94
+ stdout, stderr, status = if request.timeout&.positive?
95
+ Timeout.timeout(request.timeout) do
96
+ Open3.capture3(
97
+ request.environment,
98
+ request.command,
99
+ *request.arguments
100
+ )
101
+ end
102
+ else
103
+ Open3.capture3(
104
+ request.environment,
105
+ request.command,
106
+ *request.arguments
107
+ )
108
+ end
109
+
110
+ result.finish!(
111
+ exit_code: status.exitstatus,
112
+ stdout: stdout,
113
+ stderr: stderr,
114
+ )
115
+ end
116
+ rescue Timeout::Error => e
117
+ result.finish!(
118
+ exit_code: 124, # timeout exit code
119
+ stderr: "Command timed out after #{request.timeout} seconds",
120
+ ).add_metadata(:timeout, true)
121
+ .add_metadata(:error, e.message)
122
+ rescue StandardError => e
123
+ result.finish!(
124
+ exit_code: 1,
125
+ stderr: e.message,
126
+ ).add_metadata(:error_class, e.class.name)
127
+ .add_metadata(:error, e.message)
128
+ end
129
+
130
+ result
131
+ end
132
+
133
+ # Validate that the command exists and is executable.
134
+ #
135
+ # @param command [String] command to validate
136
+ # @return [Boolean] true if command is available
137
+ def command_available?(command)
138
+ system("which #{command} > /dev/null 2>&1") ||
139
+ system("where #{command} > nul 2>&1") # Windows
140
+ end
141
+
142
+ # Get the full path to a command.
143
+ #
144
+ # @param command [String] command name
145
+ # @return [String, nil] full path to command or nil if not found
146
+ def which(command)
147
+ # Try Unix-style which first
148
+ path = `which #{command} 2>/dev/null`.strip
149
+ return path unless path.empty?
150
+
151
+ # Try Windows-style where
152
+ path = `where #{command} 2>nul`.strip
153
+ return path unless path.empty?
154
+
155
+ nil
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -1,134 +1,134 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
- require_relative "../result"
5
-
6
- module Makit
7
- module Commands
8
- module Strategies
9
- # Synchronous command execution strategy.
10
- #
11
- # This strategy executes commands one at a time, blocking until each
12
- # command completes. This is the simplest and most reliable execution
13
- # pattern, suitable for most use cases.
14
- #
15
- # @example Using synchronous execution
16
- # strategy = Synchronous.new
17
- # request = Request.new(command: "echo", arguments: ["hello"])
18
- # result = strategy.execute(request)
19
- # puts result.stdout # "hello"
20
- class Synchronous < Base
21
- # Initialize synchronous execution strategy.
22
- #
23
- # @param options [Hash] strategy configuration
24
- # @option options [Boolean] :validate_commands (true) whether to validate commands exist
25
- # @option options [Integer] :max_output_size (1048576) maximum output size in bytes
26
- def initialize(**options)
27
- @validate_commands = options.fetch(:validate_commands, true)
28
- @max_output_size = options.fetch(:max_output_size, 1024 * 1024) # 1MB default
29
- end
30
-
31
- # Execute a single command request synchronously.
32
- #
33
- # @param request [Request] the command request to execute
34
- # @return [Result] execution result
35
- def execute(request)
36
- # Skip command validation for basic commands like echo on Windows
37
- # This is a simplified approach for demonstration purposes
38
- if @validate_commands && !basic_command?(request.command) && !command_available?(request.command)
39
- return Result.failure(
40
- command: request.to_shell_command,
41
- error: "Command not found: #{request.command}",
42
- )
43
- end
44
-
45
- # Execute using Open3 for cross-platform support
46
- result = execute_with_open3(request)
47
-
48
- # Truncate output if too large
49
- if result.stdout.bytesize > @max_output_size
50
- original_size = result.stdout.bytesize
51
- truncated_stdout = result.stdout.byteslice(0, @max_output_size)
52
- result.instance_variable_set(:@stdout, "#{truncated_stdout}\n[OUTPUT TRUNCATED]")
53
- result.add_metadata(:output_truncated, true)
54
- result.add_metadata(:original_stdout_size, original_size)
55
- end
56
-
57
- if result.stderr.bytesize > @max_output_size
58
- original_size = result.stderr.bytesize
59
- truncated_stderr = result.stderr.byteslice(0, @max_output_size)
60
- result.instance_variable_set(:@stderr, "#{truncated_stderr}\n[ERROR OUTPUT TRUNCATED]")
61
- result.add_metadata(:stderr_truncated, true)
62
- result.add_metadata(:original_stderr_size, original_size)
63
- end
64
-
65
- # Add strategy metadata
66
- result.add_metadata(:execution_strategy, "synchronous")
67
- result.add_metadata(:validated_command, @validate_commands)
68
-
69
- result
70
- end
71
-
72
- # Execute multiple requests sequentially.
73
- #
74
- # @param requests [Array<Request>] requests to execute
75
- # @return [Array<Result>] execution results in same order
76
- def execute_batch(requests)
77
- results = []
78
-
79
- requests.each_with_index do |request, index|
80
- result = execute(request)
81
- result.add_metadata(:batch_index, index)
82
- result.add_metadata(:batch_size, requests.size)
83
- results << result
84
-
85
- # Stop on first failure if configured
86
- break if result.failure? && fail_fast?
87
- end
88
-
89
- results
90
- end
91
-
92
- # Check if strategy supports the given request.
93
- #
94
- # Synchronous strategy supports all requests.
95
- #
96
- # @param request [Request] the command request
97
- # @return [Boolean] always true
98
- def supports?(_request)
99
- true
100
- end
101
-
102
- # Get strategy configuration.
103
- #
104
- # @return [Hash] strategy configuration
105
- def config
106
- {
107
- name: "synchronous",
108
- validate_commands: @validate_commands,
109
- max_output_size: @max_output_size,
110
- fail_fast: fail_fast?,
111
- }
112
- end
113
-
114
- private
115
-
116
- # Check if command is a basic system command that should always be available.
117
- #
118
- # @param command [String] command name
119
- # @return [Boolean] true if it's a basic command
120
- def basic_command?(command)
121
- %w[echo ruby bundle rake git].include?(command)
122
- end
123
-
124
- # Check if execution should stop on first failure.
125
- #
126
- # @return [Boolean] whether to fail fast
127
- def fail_fast?
128
- # Could be configurable in the future
129
- false
130
- end
131
- end
132
- end
133
- end
134
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../result"
5
+
6
+ module Makit
7
+ module Commands
8
+ module Strategies
9
+ # Synchronous command execution strategy.
10
+ #
11
+ # This strategy executes commands one at a time, blocking until each
12
+ # command completes. This is the simplest and most reliable execution
13
+ # pattern, suitable for most use cases.
14
+ #
15
+ # @example Using synchronous execution
16
+ # strategy = Synchronous.new
17
+ # request = Request.new(command: "echo", arguments: ["hello"])
18
+ # result = strategy.execute(request)
19
+ # puts result.stdout # "hello"
20
+ class Synchronous < Base
21
+ # Initialize synchronous execution strategy.
22
+ #
23
+ # @param options [Hash] strategy configuration
24
+ # @option options [Boolean] :validate_commands (true) whether to validate commands exist
25
+ # @option options [Integer] :max_output_size (1048576) maximum output size in bytes
26
+ def initialize(**options)
27
+ @validate_commands = options.fetch(:validate_commands, true)
28
+ @max_output_size = options.fetch(:max_output_size, 1024 * 1024) # 1MB default
29
+ end
30
+
31
+ # Execute a single command request synchronously.
32
+ #
33
+ # @param request [Request] the command request to execute
34
+ # @return [Result] execution result
35
+ def execute(request)
36
+ # Skip command validation for basic commands like echo on Windows
37
+ # This is a simplified approach for demonstration purposes
38
+ if @validate_commands && !basic_command?(request.command) && !command_available?(request.command)
39
+ return Result.failure(
40
+ command: request.to_shell_command,
41
+ error: "Command not found: #{request.command}",
42
+ )
43
+ end
44
+
45
+ # Execute using Open3 for cross-platform support
46
+ result = execute_with_open3(request)
47
+
48
+ # Truncate output if too large
49
+ if result.stdout.bytesize > @max_output_size
50
+ original_size = result.stdout.bytesize
51
+ truncated_stdout = result.stdout.byteslice(0, @max_output_size)
52
+ result.instance_variable_set(:@stdout, "#{truncated_stdout}\n[OUTPUT TRUNCATED]")
53
+ result.add_metadata(:output_truncated, true)
54
+ result.add_metadata(:original_stdout_size, original_size)
55
+ end
56
+
57
+ if result.stderr.bytesize > @max_output_size
58
+ original_size = result.stderr.bytesize
59
+ truncated_stderr = result.stderr.byteslice(0, @max_output_size)
60
+ result.instance_variable_set(:@stderr, "#{truncated_stderr}\n[ERROR OUTPUT TRUNCATED]")
61
+ result.add_metadata(:stderr_truncated, true)
62
+ result.add_metadata(:original_stderr_size, original_size)
63
+ end
64
+
65
+ # Add strategy metadata
66
+ result.add_metadata(:execution_strategy, "synchronous")
67
+ result.add_metadata(:validated_command, @validate_commands)
68
+
69
+ result
70
+ end
71
+
72
+ # Execute multiple requests sequentially.
73
+ #
74
+ # @param requests [Array<Request>] requests to execute
75
+ # @return [Array<Result>] execution results in same order
76
+ def execute_batch(requests)
77
+ results = []
78
+
79
+ requests.each_with_index do |request, index|
80
+ result = execute(request)
81
+ result.add_metadata(:batch_index, index)
82
+ result.add_metadata(:batch_size, requests.size)
83
+ results << result
84
+
85
+ # Stop on first failure if configured
86
+ break if result.failure? && fail_fast?
87
+ end
88
+
89
+ results
90
+ end
91
+
92
+ # Check if strategy supports the given request.
93
+ #
94
+ # Synchronous strategy supports all requests.
95
+ #
96
+ # @param request [Request] the command request
97
+ # @return [Boolean] always true
98
+ def supports?(_request)
99
+ true
100
+ end
101
+
102
+ # Get strategy configuration.
103
+ #
104
+ # @return [Hash] strategy configuration
105
+ def config
106
+ {
107
+ name: "synchronous",
108
+ validate_commands: @validate_commands,
109
+ max_output_size: @max_output_size,
110
+ fail_fast: fail_fast?,
111
+ }
112
+ end
113
+
114
+ private
115
+
116
+ # Check if command is a basic system command that should always be available.
117
+ #
118
+ # @param command [String] command name
119
+ # @return [Boolean] true if it's a basic command
120
+ def basic_command?(command)
121
+ %w[echo ruby bundle rake git].include?(command)
122
+ end
123
+
124
+ # Check if execution should stop on first failure.
125
+ #
126
+ # @return [Boolean] whether to fail fast
127
+ def fail_fast?
128
+ # Could be configurable in the future
129
+ false
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end