makit 0.0.138 → 0.0.140

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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/auto.rb +48 -48
  6. data/lib/makit/cli/build_commands.rb +500 -500
  7. data/lib/makit/cli/generators/base_generator.rb +74 -74
  8. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  9. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  10. data/lib/makit/cli/generators/node_generator.rb +50 -50
  11. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  12. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  13. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  14. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  15. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  16. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
  17. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  18. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  19. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  20. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  21. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  22. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  23. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  24. data/lib/makit/cli/main.rb +69 -67
  25. data/lib/makit/cli/project_commands.rb +868 -868
  26. data/lib/makit/cli/repository_commands.rb +661 -661
  27. data/lib/makit/cli/strategy_commands.rb +51 -0
  28. data/lib/makit/cli/utility_commands.rb +521 -521
  29. data/lib/makit/commands/factory.rb +359 -359
  30. data/lib/makit/commands/middleware/base.rb +73 -73
  31. data/lib/makit/commands/middleware/cache.rb +248 -248
  32. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  33. data/lib/makit/commands/middleware/validator.rb +269 -269
  34. data/lib/makit/commands/request.rb +316 -316
  35. data/lib/makit/commands/result.rb +323 -323
  36. data/lib/makit/commands/runner.rb +385 -385
  37. data/lib/makit/commands/strategies/base.rb +171 -171
  38. data/lib/makit/commands/strategies/child_process.rb +1 -1
  39. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  40. data/lib/makit/commands.rb +50 -50
  41. data/lib/makit/configuration/dotnet_project.rb +12 -12
  42. data/lib/makit/configuration/gitlab_helper.rb +58 -58
  43. data/lib/makit/configuration/project.rb +168 -168
  44. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  45. data/lib/makit/configuration/step.rb +34 -34
  46. data/lib/makit/configuration/timeout.rb +74 -0
  47. data/lib/makit/configuration.rb +15 -14
  48. data/lib/makit/content/default_gitignore.rb +7 -7
  49. data/lib/makit/content/default_gitignore.txt +225 -225
  50. data/lib/makit/content/default_rakefile.rb +13 -13
  51. data/lib/makit/content/gem_rakefile.rb +16 -16
  52. data/lib/makit/context.rb +1 -1
  53. data/lib/makit/data.rb +49 -49
  54. data/lib/makit/directories.rb +140 -140
  55. data/lib/makit/directory.rb +262 -262
  56. data/lib/makit/docs/files.rb +89 -89
  57. data/lib/makit/docs/rake.rb +102 -102
  58. data/lib/makit/dotnet/cli.rb +69 -69
  59. data/lib/makit/dotnet/project.rb +217 -217
  60. data/lib/makit/dotnet/solution.rb +38 -38
  61. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  62. data/lib/makit/dotnet/solution_console.rb +264 -264
  63. data/lib/makit/dotnet/solution_maui.rb +354 -354
  64. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  65. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  66. data/lib/makit/dotnet.rb +102 -102
  67. data/lib/makit/email.rb +90 -90
  68. data/lib/makit/environment.rb +142 -142
  69. data/lib/makit/examples/runner.rb +370 -370
  70. data/lib/makit/exceptions.rb +45 -45
  71. data/lib/makit/fileinfo.rb +24 -24
  72. data/lib/makit/files.rb +43 -43
  73. data/lib/makit/gems.rb +40 -40
  74. data/lib/makit/git/cli.rb +54 -54
  75. data/lib/makit/git/repository.rb +90 -90
  76. data/lib/makit/git.rb +98 -98
  77. data/lib/makit/gitlab_runner.rb +59 -59
  78. data/lib/makit/humanize.rb +137 -137
  79. data/lib/makit/indexer.rb +47 -47
  80. data/lib/makit/logging/configuration.rb +308 -308
  81. data/lib/makit/logging/format_registry.rb +84 -84
  82. data/lib/makit/logging/formatters/base.rb +39 -39
  83. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  84. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  85. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  86. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  87. data/lib/makit/logging/log_request.rb +119 -119
  88. data/lib/makit/logging/logger.rb +199 -199
  89. data/lib/makit/logging/sinks/base.rb +91 -91
  90. data/lib/makit/logging/sinks/console.rb +72 -72
  91. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  92. data/lib/makit/logging/sinks/structured.rb +123 -123
  93. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  94. data/lib/makit/logging.rb +565 -565
  95. data/lib/makit/markdown.rb +75 -75
  96. data/lib/makit/mp/basic_object_mp.rb +17 -17
  97. data/lib/makit/mp/command_mp.rb +13 -13
  98. data/lib/makit/mp/command_request.mp.rb +17 -17
  99. data/lib/makit/mp/project_mp.rb +199 -199
  100. data/lib/makit/mp/string_mp.rb +191 -191
  101. data/lib/makit/nuget.rb +74 -74
  102. data/lib/makit/port.rb +32 -32
  103. data/lib/makit/process.rb +163 -163
  104. data/lib/makit/protoc.rb +107 -107
  105. data/lib/makit/rake/cli.rb +196 -196
  106. data/lib/makit/rake.rb +80 -80
  107. data/lib/makit/ruby/cli.rb +185 -185
  108. data/lib/makit/ruby.rb +25 -25
  109. data/lib/makit/secrets.rb +51 -51
  110. data/lib/makit/serializer.rb +130 -130
  111. data/lib/makit/services/builder.rb +186 -186
  112. data/lib/makit/services/error_handler.rb +226 -226
  113. data/lib/makit/services/repository_manager.rb +231 -231
  114. data/lib/makit/services/validator.rb +112 -112
  115. data/lib/makit/setup/classlib.rb +101 -101
  116. data/lib/makit/setup/gem.rb +268 -268
  117. data/lib/makit/setup/razorclasslib.rb +101 -101
  118. data/lib/makit/setup/runner.rb +54 -54
  119. data/lib/makit/setup.rb +5 -5
  120. data/lib/makit/show.rb +110 -110
  121. data/lib/makit/storage.rb +126 -126
  122. data/lib/makit/symbols.rb +170 -170
  123. data/lib/makit/task_info.rb +130 -130
  124. data/lib/makit/tasks/at_exit.rb +15 -15
  125. data/lib/makit/tasks/build.rb +22 -22
  126. data/lib/makit/tasks/clean.rb +13 -13
  127. data/lib/makit/tasks/configure.rb +10 -10
  128. data/lib/makit/tasks/format.rb +10 -10
  129. data/lib/makit/tasks/hook_manager.rb +443 -443
  130. data/lib/makit/tasks/init.rb +49 -49
  131. data/lib/makit/tasks/integrate.rb +29 -29
  132. data/lib/makit/tasks/pull_incoming.rb +13 -13
  133. data/lib/makit/tasks/setup.rb +13 -13
  134. data/lib/makit/tasks/sync.rb +17 -17
  135. data/lib/makit/tasks/tag.rb +16 -16
  136. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  137. data/lib/makit/tasks/test.rb +22 -22
  138. data/lib/makit/tasks/update.rb +18 -18
  139. data/lib/makit/tasks.rb +20 -20
  140. data/lib/makit/test_cache.rb +239 -239
  141. data/lib/makit/tree.rb +37 -37
  142. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  143. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  144. data/lib/makit/version.rb +99 -99
  145. data/lib/makit/version_util.rb +21 -21
  146. data/lib/makit/wix.rb +95 -95
  147. data/lib/makit/yaml.rb +29 -29
  148. data/lib/makit/zip.rb +17 -17
  149. data/lib/makit copy.rb +44 -44
  150. data/lib/makit.rb +42 -42
  151. metadata +3 -2
@@ -1,171 +1,171 @@
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
- # Skip validation for dotnet commands on Windows due to path issues
139
- return true if command == "dotnet" && RUBY_PLATFORM.match?(/mswin|mingw/)
140
-
141
- # Try Unix-style which first
142
- return true if system("which #{command} > /dev/null 2>&1")
143
-
144
- # Try Windows-style where with proper quoting
145
- return true if system("where \"#{command}\" > nul 2>&1")
146
-
147
- # Fallback: try to execute the command directly
148
- system("#{command} --version > nul 2>&1") ||
149
- system("#{command} --help > nul 2>&1") ||
150
- system("#{command} /? > nul 2>&1")
151
- end
152
-
153
- # Get the full path to a command.
154
- #
155
- # @param command [String] command name
156
- # @return [String, nil] full path to command or nil if not found
157
- def which(command)
158
- # Try Unix-style which first
159
- path = `which #{command} 2>/dev/null`.strip
160
- return path unless path.empty?
161
-
162
- # Try Windows-style where
163
- path = `where #{command} 2>nul`.strip
164
- return path unless path.empty?
165
-
166
- nil
167
- end
168
- end
169
- end
170
- end
171
- 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
+ # Skip validation for dotnet commands on Windows due to path issues
139
+ return true if command == "dotnet" && RUBY_PLATFORM.match?(/mswin|mingw/)
140
+
141
+ # Try Unix-style which first
142
+ return true if system("which #{command} > /dev/null 2>&1")
143
+
144
+ # Try Windows-style where with proper quoting
145
+ return true if system("where \"#{command}\" > nul 2>&1")
146
+
147
+ # Fallback: try to execute the command directly
148
+ system("#{command} --version > nul 2>&1") ||
149
+ system("#{command} --help > nul 2>&1") ||
150
+ system("#{command} /? > nul 2>&1")
151
+ end
152
+
153
+ # Get the full path to a command.
154
+ #
155
+ # @param command [String] command name
156
+ # @return [String, nil] full path to command or nil if not found
157
+ def which(command)
158
+ # Try Unix-style which first
159
+ path = `which #{command} 2>/dev/null`.strip
160
+ return path unless path.empty?
161
+
162
+ # Try Windows-style where
163
+ path = `where #{command} 2>nul`.strip
164
+ return path unless path.empty?
165
+
166
+ nil
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -35,7 +35,7 @@ module Makit
35
35
  #
36
36
  # @param timeout [Integer] default timeout in seconds (default: 30)
37
37
  # @param max_output_size [Integer] maximum output size in bytes (default: 1MB)
38
- def initialize(timeout: 30, max_output_size: 1_000_000, **options)
38
+ def initialize(timeout: Makit::Configuration::Timeout.global_default, max_output_size: 1_000_000, **options)
39
39
  @timeout = timeout
40
40
  @max_output_size = max_output_size
41
41
  super(**options)
@@ -1,139 +1,139 @@
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 = false # Always disable validation to avoid path issues
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
- # For dotnet commands, try to execute anyway as validation might be unreliable
40
- if request.command == "dotnet"
41
- Makit::Logging.debug("Skipping validation for dotnet command due to potential path issues")
42
- else
43
- return Result.failure(
44
- command: request.to_shell_command,
45
- error: "Command not found: #{request.command}",
46
- )
47
- end
48
- end
49
-
50
- # Execute using Open3 for cross-platform support
51
- result = execute_with_open3(request)
52
-
53
- # Truncate output if too large
54
- if result.stdout.bytesize > @max_output_size
55
- original_size = result.stdout.bytesize
56
- truncated_stdout = result.stdout.byteslice(0, @max_output_size)
57
- result.instance_variable_set(:@stdout, "#{truncated_stdout}\n[OUTPUT TRUNCATED]")
58
- result.add_metadata(:output_truncated, true)
59
- result.add_metadata(:original_stdout_size, original_size)
60
- end
61
-
62
- if result.stderr.bytesize > @max_output_size
63
- original_size = result.stderr.bytesize
64
- truncated_stderr = result.stderr.byteslice(0, @max_output_size)
65
- result.instance_variable_set(:@stderr, "#{truncated_stderr}\n[ERROR OUTPUT TRUNCATED]")
66
- result.add_metadata(:stderr_truncated, true)
67
- result.add_metadata(:original_stderr_size, original_size)
68
- end
69
-
70
- # Add strategy metadata
71
- result.add_metadata(:execution_strategy, "synchronous")
72
- result.add_metadata(:validated_command, @validate_commands)
73
-
74
- result
75
- end
76
-
77
- # Execute multiple requests sequentially.
78
- #
79
- # @param requests [Array<Request>] requests to execute
80
- # @return [Array<Result>] execution results in same order
81
- def execute_batch(requests)
82
- results = []
83
-
84
- requests.each_with_index do |request, index|
85
- result = execute(request)
86
- result.add_metadata(:batch_index, index)
87
- result.add_metadata(:batch_size, requests.size)
88
- results << result
89
-
90
- # Stop on first failure if configured
91
- break if result.failure? && fail_fast?
92
- end
93
-
94
- results
95
- end
96
-
97
- # Check if strategy supports the given request.
98
- #
99
- # Synchronous strategy supports all requests.
100
- #
101
- # @param request [Request] the command request
102
- # @return [Boolean] always true
103
- def supports?(_request)
104
- true
105
- end
106
-
107
- # Get strategy configuration.
108
- #
109
- # @return [Hash] strategy configuration
110
- def config
111
- {
112
- name: "synchronous",
113
- validate_commands: @validate_commands,
114
- max_output_size: @max_output_size,
115
- fail_fast: fail_fast?,
116
- }
117
- end
118
-
119
- private
120
-
121
- # Check if command is a basic system command that should always be available.
122
- #
123
- # @param command [String] command name
124
- # @return [Boolean] true if it's a basic command
125
- def basic_command?(command)
126
- %w[echo ruby bundle rake git dotnet].include?(command)
127
- end
128
-
129
- # Check if execution should stop on first failure.
130
- #
131
- # @return [Boolean] whether to fail fast
132
- def fail_fast?
133
- # Could be configurable in the future
134
- false
135
- end
136
- end
137
- end
138
- end
139
- 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 = false # Always disable validation to avoid path issues
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
+ # For dotnet commands, try to execute anyway as validation might be unreliable
40
+ if request.command == "dotnet"
41
+ Makit::Logging.debug("Skipping validation for dotnet command due to potential path issues")
42
+ else
43
+ return Result.failure(
44
+ command: request.to_shell_command,
45
+ error: "Command not found: #{request.command}",
46
+ )
47
+ end
48
+ end
49
+
50
+ # Execute using Open3 for cross-platform support
51
+ result = execute_with_open3(request)
52
+
53
+ # Truncate output if too large
54
+ if result.stdout.bytesize > @max_output_size
55
+ original_size = result.stdout.bytesize
56
+ truncated_stdout = result.stdout.byteslice(0, @max_output_size)
57
+ result.instance_variable_set(:@stdout, "#{truncated_stdout}\n[OUTPUT TRUNCATED]")
58
+ result.add_metadata(:output_truncated, true)
59
+ result.add_metadata(:original_stdout_size, original_size)
60
+ end
61
+
62
+ if result.stderr.bytesize > @max_output_size
63
+ original_size = result.stderr.bytesize
64
+ truncated_stderr = result.stderr.byteslice(0, @max_output_size)
65
+ result.instance_variable_set(:@stderr, "#{truncated_stderr}\n[ERROR OUTPUT TRUNCATED]")
66
+ result.add_metadata(:stderr_truncated, true)
67
+ result.add_metadata(:original_stderr_size, original_size)
68
+ end
69
+
70
+ # Add strategy metadata
71
+ result.add_metadata(:execution_strategy, "synchronous")
72
+ result.add_metadata(:validated_command, @validate_commands)
73
+
74
+ result
75
+ end
76
+
77
+ # Execute multiple requests sequentially.
78
+ #
79
+ # @param requests [Array<Request>] requests to execute
80
+ # @return [Array<Result>] execution results in same order
81
+ def execute_batch(requests)
82
+ results = []
83
+
84
+ requests.each_with_index do |request, index|
85
+ result = execute(request)
86
+ result.add_metadata(:batch_index, index)
87
+ result.add_metadata(:batch_size, requests.size)
88
+ results << result
89
+
90
+ # Stop on first failure if configured
91
+ break if result.failure? && fail_fast?
92
+ end
93
+
94
+ results
95
+ end
96
+
97
+ # Check if strategy supports the given request.
98
+ #
99
+ # Synchronous strategy supports all requests.
100
+ #
101
+ # @param request [Request] the command request
102
+ # @return [Boolean] always true
103
+ def supports?(_request)
104
+ true
105
+ end
106
+
107
+ # Get strategy configuration.
108
+ #
109
+ # @return [Hash] strategy configuration
110
+ def config
111
+ {
112
+ name: "synchronous",
113
+ validate_commands: @validate_commands,
114
+ max_output_size: @max_output_size,
115
+ fail_fast: fail_fast?,
116
+ }
117
+ end
118
+
119
+ private
120
+
121
+ # Check if command is a basic system command that should always be available.
122
+ #
123
+ # @param command [String] command name
124
+ # @return [Boolean] true if it's a basic command
125
+ def basic_command?(command)
126
+ %w[echo ruby bundle rake git dotnet].include?(command)
127
+ end
128
+
129
+ # Check if execution should stop on first failure.
130
+ #
131
+ # @return [Boolean] whether to fail fast
132
+ def fail_fast?
133
+ # Could be configurable in the future
134
+ false
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end