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,443 +1,443 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
- require "colorize"
5
- require "fileutils"
6
-
7
- # Task hooks system for Makit
8
- #
9
- # This module provides a comprehensive hook system for Rake tasks, allowing
10
- # developers to add pre and post execution hooks globally or for specific tasks.
11
- # It uses monkey patching to intercept all task invocations and execute hooks
12
- # at the appropriate times.
13
- #
14
- # @example Global hooks
15
- # Makit::Tasks::HookManager.add_pre_hook do |task_name|
16
- # Makit::Logging.info("Starting task: #{task_name}")
17
- # end
18
- #
19
- # Makit::Tasks::HookManager.add_post_hook do |task_name, duration, result, error|
20
- # if error
21
- # Makit::Logging.error("Task failed: #{task_name}")
22
- # else
23
- # Makit::Logging.success("Task completed: #{task_name}")
24
- # end
25
- # end
26
- #
27
- # @example Task-specific hooks
28
- # Makit::Tasks::HookManager.add_pre_hook_for(:build) do |task_name|
29
- # Makit::Logging.info("Building project...")
30
- # end
31
- #
32
- # Makit::Tasks::HookManager.add_post_hook_for(:test) do |task_name, duration, result, error|
33
- # Makit::Logging.info("Test execution took #{duration.round(2)}s")
34
- # end
35
- module Makit
36
- module Tasks
37
- class HookManager
38
- @pre_hooks = []
39
- @post_hooks = []
40
- @task_specific_pre_hooks = {}
41
- @task_specific_post_hooks = {}
42
- @excluded_tasks = Set.new
43
- @logged_tasks = Set.new
44
-
45
- # Timing functionality
46
- @timing_enabled = false
47
- @task_stack = []
48
- @performance_log = "artifacts/task_performance.log"
49
- @hooks_installed = false
50
-
51
- class << self
52
- # @return [Array<Proc>] Global pre-task hooks
53
- attr_reader :pre_hooks
54
-
55
- # @return [Array<Proc>] Global post-task hooks
56
- attr_reader :post_hooks
57
-
58
- # @return [Hash<String, Array<Proc>>] Task-specific pre-task hooks
59
- attr_reader :task_specific_pre_hooks
60
-
61
- # @return [Hash<String, Array<Proc>>] Task-specific post-task hooks
62
- attr_reader :task_specific_post_hooks
63
-
64
- # @return [Set<String>] Tasks excluded from hook execution
65
- attr_reader :excluded_tasks
66
-
67
- # @return [Boolean] Whether timing is enabled
68
- attr_reader :timing_enabled
69
-
70
- # @return [Array<Hash>] Stack of currently executing tasks
71
- attr_reader :task_stack
72
-
73
- # @return [String] Path to performance log file
74
- attr_reader :performance_log
75
-
76
- # Add a global pre-task hook
77
- #
78
- # The hook will be executed before every task runs.
79
- #
80
- # @yield [task_name] the name of the task being executed
81
- # @yieldparam task_name [String] the name of the task
82
- # @return [void]
83
- def add_pre_hook(&block)
84
- @pre_hooks << block
85
- end
86
-
87
- # Add a global post-task hook
88
- #
89
- # The hook will be executed after every task completes (success or failure).
90
- #
91
- # @yield [task_name, duration, result, error] hook parameters
92
- # @yieldparam task_name [String] the name of the task
93
- # @yieldparam duration [Float] execution duration in seconds
94
- # @yieldparam result [Object, nil] the result of the task execution
95
- # @yieldparam error [Exception, nil] any error that occurred during execution
96
- # @return [void]
97
- def add_post_hook(&block)
98
- @post_hooks << block
99
- end
100
-
101
- # Add a pre-task hook for a specific task
102
- #
103
- # The hook will only be executed before the specified task runs.
104
- #
105
- # @param task_name [String, Symbol] the name of the task
106
- # @yield [task_name] the name of the task being executed
107
- # @yieldparam task_name [String] the name of the task
108
- # @return [void]
109
- def add_pre_hook_for(task_name, &block)
110
- @task_specific_pre_hooks[task_name.to_s] ||= []
111
- @task_specific_pre_hooks[task_name.to_s] << block
112
- end
113
-
114
- # Add a post-task hook for a specific task
115
- #
116
- # The hook will only be executed after the specified task completes.
117
- #
118
- # @param task_name [String, Symbol] the name of the task
119
- # @yield [task_name, duration, result, error] hook parameters
120
- # @yieldparam task_name [String] the name of the task
121
- # @yieldparam duration [Float] execution duration in seconds
122
- # @yieldparam result [Object, nil] the result of the task execution
123
- # @yieldparam error [Exception, nil] any error that occurred during execution
124
- # @return [void]
125
- def add_post_hook_for(task_name, &block)
126
- @task_specific_post_hooks[task_name.to_s] ||= []
127
- @task_specific_post_hooks[task_name.to_s] << block
128
- end
129
-
130
- # Execute all pre-task hooks for a given task
131
- #
132
- # @param task_name [String] the name of the task
133
- # @return [void]
134
- def execute_pre_hooks(task_name)
135
- return if excluded?(task_name)
136
-
137
- # Track timing if enabled
138
- if timing_enabled?
139
- @task_stack.push({
140
- name: task_name,
141
- start_time: Time.now,
142
- level: @task_stack.length,
143
- })
144
- end
145
-
146
- # Execute global pre-hooks
147
- @pre_hooks.each { |hook| hook.call(task_name) }
148
-
149
- # Execute task-specific pre-hooks
150
- task_hooks = @task_specific_pre_hooks[task_name.to_s] || []
151
- task_hooks.each { |hook| hook.call(task_name) }
152
- end
153
-
154
- # Execute all post-task hooks for a given task
155
- #
156
- # @param task_name [String] the name of the task
157
- # @param duration [Float] execution duration in seconds
158
- # @param result [Object, nil] the result of the task execution
159
- # @param error [Exception, nil] any error that occurred during execution
160
- # @return [void]
161
- def execute_post_hooks(task_name, duration, result, error)
162
- return if excluded?(task_name)
163
-
164
- # Handle timing if enabled
165
- if timing_enabled?
166
- task_info = @task_stack.pop
167
- if task_info && task_info[:name] == task_name && (duration > 0.1)
168
- # Log performance for tasks that took longer than 0.1 seconds
169
- log_task_performance(task_name, duration, task_info[:level])
170
- end
171
-
172
- # Log task failure if there was an error
173
- log_task_failure(task_name, duration, error, task_info&.dig(:level) || 0) if error
174
- end
175
-
176
- # Execute global post-hooks
177
- @post_hooks.each { |hook| hook.call(task_name, duration, result, error) }
178
-
179
- # Execute task-specific post-hooks
180
- task_hooks = @task_specific_post_hooks[task_name.to_s] || []
181
- task_hooks.each { |hook| hook.call(task_name, duration, result, error) }
182
- end
183
-
184
- # Clear all hooks
185
- #
186
- # @return [void]
187
- def clear_all_hooks
188
- @pre_hooks.clear
189
- @post_hooks.clear
190
- @task_specific_pre_hooks.clear
191
- @task_specific_post_hooks.clear
192
- end
193
-
194
- # Exclude a task from hook execution
195
- #
196
- # @param task_name [String, Symbol] the name of the task to exclude
197
- # @return [void]
198
- def exclude_task(task_name)
199
- @excluded_tasks.add(task_name.to_s)
200
- end
201
-
202
- # Include a task in hook execution (remove from exclusions)
203
- #
204
- # @param task_name [String, Symbol] the name of the task to include
205
- # @return [void]
206
- def include_task(task_name)
207
- @excluded_tasks.delete(task_name.to_s)
208
- end
209
-
210
- # Check if a task is excluded from hook execution
211
- #
212
- # @param task_name [String, Symbol] the name of the task to check
213
- # @return [Boolean] true if the task is excluded
214
- def excluded?(task_name)
215
- @excluded_tasks.include?(task_name.to_s)
216
- end
217
-
218
- # Get statistics about registered hooks
219
- #
220
- # @return [Hash] hook statistics
221
- def stats
222
- {
223
- global_pre_hooks: @pre_hooks.size,
224
- global_post_hooks: @post_hooks.size,
225
- task_specific_pre_hooks: @task_specific_pre_hooks.values.flatten.size,
226
- task_specific_post_hooks: @task_specific_post_hooks.values.flatten.size,
227
- tasks_with_pre_hooks: @task_specific_pre_hooks.keys,
228
- tasks_with_post_hooks: @task_specific_post_hooks.keys,
229
- timing_enabled: @timing_enabled,
230
- task_stack_depth: @task_stack.size,
231
- }
232
- end
233
-
234
- # Enable task timing and performance logging
235
- #
236
- # @return [void]
237
- def enable_timing!
238
- @timing_enabled = true
239
- puts "🔧 Task timing enabled".colorize(:grey) if defined?(String.instance_method(:colorize))
240
- end
241
-
242
- # Disable task timing and performance logging
243
- #
244
- # @return [void]
245
- def disable_timing!
246
- @timing_enabled = false
247
- end
248
-
249
- # Check if timing is enabled
250
- #
251
- # @return [Boolean] true if timing is enabled
252
- def timing_enabled?
253
- @timing_enabled == true
254
- end
255
-
256
- # Clear performance logs
257
- #
258
- # @return [void]
259
- def clear_performance_logs
260
- FileUtils.rm_f(@performance_log)
261
- FileUtils.rm_f("artifacts/task_failures.log")
262
- end
263
-
264
- # Setup task hooks by monkey patching Rake::Task
265
- #
266
- # This method patches the Rake::Task#execute method to automatically
267
- # call pre and post hooks for all task executions.
268
- #
269
- # @return [void]
270
- def setup!
271
- return if @hooks_installed
272
-
273
- ::Rake::Task.class_eval do
274
- alias_method :makit_original_execute, :execute
275
-
276
- # Check if this task should have enhanced makit tracing
277
- #
278
- # @return [Boolean] true if task should be traced
279
- def should_trace_makit_task?
280
- # Check if trace is enabled
281
- return false unless ENV['RAKE_TRACE'] || ARGV.include?('--trace') || ENV['MAKIT_TRACE'] == 'true'
282
-
283
- # Check if task is makit-related
284
- makit_related_task?
285
- end
286
-
287
- # Check if task is makit-related
288
- #
289
- # @return [Boolean] true if task is makit-related
290
- def makit_related_task?
291
- name.downcase.include?('makit') ||
292
- name.include?('bundle') ||
293
- name.include?('gem') ||
294
- name.include?('dotnet') ||
295
- name.include?('protoc') ||
296
- name.include?('git') ||
297
- name.include?('setup') ||
298
- name.include?('build') ||
299
- name.include?('test')
300
- end
301
-
302
- # Get makit strategy information safely
303
- #
304
- # @return [String] strategy information
305
- def get_makit_strategy_info
306
- begin
307
- if defined?(Makit::Commands::Runner)
308
- runner = Makit::Commands::Runner.default
309
- info = runner.strategy_info
310
- "#{info[:type]} (#{info[:class]})"
311
- else
312
- "not available"
313
- end
314
- rescue => e
315
- "error: #{e.message}"
316
- end
317
- end
318
-
319
- def execute(args = nil)
320
- start_time = Time.now
321
- error = nil
322
- result = nil
323
-
324
- # Add enhanced trace output for makit-related tasks
325
- if should_trace_makit_task?
326
- puts " [MAKIT] Executing task: #{name}"
327
- puts " [MAKIT] Strategy: #{get_makit_strategy_info}"
328
- puts " [MAKIT] Environment: #{ENV['MAKIT_STRATEGY'] || 'auto'}"
329
- puts " [MAKIT] Working Directory: #{Dir.pwd}"
330
- puts " [MAKIT] Timestamp: #{Time.now.iso8601}"
331
- end
332
-
333
- begin
334
- # Execute pre-hooks
335
- Makit::Tasks::HookManager.execute_pre_hooks(name)
336
-
337
- # Execute the original task
338
- result = makit_original_execute(args)
339
- rescue StandardError => e
340
- error = e
341
- raise
342
- ensure
343
- # Calculate duration
344
- duration = Time.now - start_time
345
-
346
- # Execute post-hooks
347
- Makit::Tasks::HookManager.execute_post_hooks(name, duration, result, error)
348
- end
349
-
350
- result
351
- end
352
- end
353
-
354
- @hooks_installed = true
355
- end
356
-
357
- # Setup default task hooks
358
- #
359
- # This method sets up the default task hooks that provide basic
360
- # task execution feedback. It should be called during initialization.
361
- #
362
- # @return [void]
363
- def setup_default_hooks
364
- # Setup the monkey patching first
365
- setup!
366
-
367
- # Only setup default hooks if no hooks are already registered
368
- return unless @pre_hooks.empty? && @post_hooks.empty?
369
-
370
- # Exclude the init task from hooks to prevent duplication
371
- exclude_task(:init)
372
- exclude_task(:default)
373
-
374
- # Default pre-task hook: log task start to default logger
375
- add_pre_hook do |task_name|
376
- log_task_start(task_name)
377
- end
378
-
379
- # Default post-task hook: log task completion
380
- add_post_hook do |task_name, duration, result, error|
381
- log_task_completion(task_name, duration, result, error)
382
- end
383
- end
384
-
385
- def log_task_start(task_name)
386
- # return if @logged_tasks.include?(task_name)
387
- # @logged_tasks.add(task_name)
388
- # Makit::Logging.info("#{task_name}".colorize(:white).bold)
389
- end
390
-
391
- def log_task_completion(task_name, duration, result, error)
392
- # Makit::Logging.info("#{task_name}".colorize(:white).bold)
393
- # Makit::Logging.info("Task completed".colorize(:green).bold)
394
- # Makit::Logging.info("Duration: #{duration.round(2)}s".colorize(:green).bold)
395
- # Makit::Logging.info("Result: #{result}".colorize(:green).bold)
396
- # Makit::Logging.info("Error: #{error}".colorize(:red).bold)
397
- end
398
-
399
- private
400
-
401
- # Log task performance to file
402
- #
403
- # @param name [String] task name
404
- # @param duration [Float] execution duration
405
- # @param level [Integer] nesting level
406
- # @return [void]
407
- def log_task_performance(name, duration, level)
408
- return if ENV["CI"] == "true" && duration < 1.0
409
-
410
- FileUtils.mkdir_p("artifacts")
411
- File.open(@performance_log, "a") do |f|
412
- f.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")},#{name},#{duration.round(4)},#{level}"
413
- end
414
- rescue StandardError
415
- # Silently fail performance logging to not interrupt tasks
416
- end
417
-
418
- # Log task failure to file
419
- #
420
- # @param name [String] task name
421
- # @param duration [Float] execution duration
422
- # @param error [Exception] the error that occurred
423
- # @param level [Integer] nesting level
424
- # @return [void]
425
- def log_task_failure(name, duration, error, _level)
426
- failure_log = "artifacts/task_failures.log"
427
- FileUtils.mkdir_p("artifacts")
428
-
429
- File.open(failure_log, "a") do |f|
430
- timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
431
- message = error.message.gsub(",", ";")
432
- f.puts "#{timestamp},#{name},#{duration.round(4)},#{error.class.name},#{message}"
433
- end
434
- rescue StandardError
435
- # Silently fail error logging
436
- end
437
- end
438
- end
439
- end
440
- end
441
-
442
- # Setup default hooks when the module is loaded
443
- Makit::Tasks::HookManager.setup_default_hooks
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "colorize"
5
+ require "fileutils"
6
+
7
+ # Task hooks system for Makit
8
+ #
9
+ # This module provides a comprehensive hook system for Rake tasks, allowing
10
+ # developers to add pre and post execution hooks globally or for specific tasks.
11
+ # It uses monkey patching to intercept all task invocations and execute hooks
12
+ # at the appropriate times.
13
+ #
14
+ # @example Global hooks
15
+ # Makit::Tasks::HookManager.add_pre_hook do |task_name|
16
+ # Makit::Logging.info("Starting task: #{task_name}")
17
+ # end
18
+ #
19
+ # Makit::Tasks::HookManager.add_post_hook do |task_name, duration, result, error|
20
+ # if error
21
+ # Makit::Logging.error("Task failed: #{task_name}")
22
+ # else
23
+ # Makit::Logging.success("Task completed: #{task_name}")
24
+ # end
25
+ # end
26
+ #
27
+ # @example Task-specific hooks
28
+ # Makit::Tasks::HookManager.add_pre_hook_for(:build) do |task_name|
29
+ # Makit::Logging.info("Building project...")
30
+ # end
31
+ #
32
+ # Makit::Tasks::HookManager.add_post_hook_for(:test) do |task_name, duration, result, error|
33
+ # Makit::Logging.info("Test execution took #{duration.round(2)}s")
34
+ # end
35
+ module Makit
36
+ module Tasks
37
+ class HookManager
38
+ @pre_hooks = []
39
+ @post_hooks = []
40
+ @task_specific_pre_hooks = {}
41
+ @task_specific_post_hooks = {}
42
+ @excluded_tasks = Set.new
43
+ @logged_tasks = Set.new
44
+
45
+ # Timing functionality
46
+ @timing_enabled = false
47
+ @task_stack = []
48
+ @performance_log = "artifacts/task_performance.log"
49
+ @hooks_installed = false
50
+
51
+ class << self
52
+ # @return [Array<Proc>] Global pre-task hooks
53
+ attr_reader :pre_hooks
54
+
55
+ # @return [Array<Proc>] Global post-task hooks
56
+ attr_reader :post_hooks
57
+
58
+ # @return [Hash<String, Array<Proc>>] Task-specific pre-task hooks
59
+ attr_reader :task_specific_pre_hooks
60
+
61
+ # @return [Hash<String, Array<Proc>>] Task-specific post-task hooks
62
+ attr_reader :task_specific_post_hooks
63
+
64
+ # @return [Set<String>] Tasks excluded from hook execution
65
+ attr_reader :excluded_tasks
66
+
67
+ # @return [Boolean] Whether timing is enabled
68
+ attr_reader :timing_enabled
69
+
70
+ # @return [Array<Hash>] Stack of currently executing tasks
71
+ attr_reader :task_stack
72
+
73
+ # @return [String] Path to performance log file
74
+ attr_reader :performance_log
75
+
76
+ # Add a global pre-task hook
77
+ #
78
+ # The hook will be executed before every task runs.
79
+ #
80
+ # @yield [task_name] the name of the task being executed
81
+ # @yieldparam task_name [String] the name of the task
82
+ # @return [void]
83
+ def add_pre_hook(&block)
84
+ @pre_hooks << block
85
+ end
86
+
87
+ # Add a global post-task hook
88
+ #
89
+ # The hook will be executed after every task completes (success or failure).
90
+ #
91
+ # @yield [task_name, duration, result, error] hook parameters
92
+ # @yieldparam task_name [String] the name of the task
93
+ # @yieldparam duration [Float] execution duration in seconds
94
+ # @yieldparam result [Object, nil] the result of the task execution
95
+ # @yieldparam error [Exception, nil] any error that occurred during execution
96
+ # @return [void]
97
+ def add_post_hook(&block)
98
+ @post_hooks << block
99
+ end
100
+
101
+ # Add a pre-task hook for a specific task
102
+ #
103
+ # The hook will only be executed before the specified task runs.
104
+ #
105
+ # @param task_name [String, Symbol] the name of the task
106
+ # @yield [task_name] the name of the task being executed
107
+ # @yieldparam task_name [String] the name of the task
108
+ # @return [void]
109
+ def add_pre_hook_for(task_name, &block)
110
+ @task_specific_pre_hooks[task_name.to_s] ||= []
111
+ @task_specific_pre_hooks[task_name.to_s] << block
112
+ end
113
+
114
+ # Add a post-task hook for a specific task
115
+ #
116
+ # The hook will only be executed after the specified task completes.
117
+ #
118
+ # @param task_name [String, Symbol] the name of the task
119
+ # @yield [task_name, duration, result, error] hook parameters
120
+ # @yieldparam task_name [String] the name of the task
121
+ # @yieldparam duration [Float] execution duration in seconds
122
+ # @yieldparam result [Object, nil] the result of the task execution
123
+ # @yieldparam error [Exception, nil] any error that occurred during execution
124
+ # @return [void]
125
+ def add_post_hook_for(task_name, &block)
126
+ @task_specific_post_hooks[task_name.to_s] ||= []
127
+ @task_specific_post_hooks[task_name.to_s] << block
128
+ end
129
+
130
+ # Execute all pre-task hooks for a given task
131
+ #
132
+ # @param task_name [String] the name of the task
133
+ # @return [void]
134
+ def execute_pre_hooks(task_name)
135
+ return if excluded?(task_name)
136
+
137
+ # Track timing if enabled
138
+ if timing_enabled?
139
+ @task_stack.push({
140
+ name: task_name,
141
+ start_time: Time.now,
142
+ level: @task_stack.length,
143
+ })
144
+ end
145
+
146
+ # Execute global pre-hooks
147
+ @pre_hooks.each { |hook| hook.call(task_name) }
148
+
149
+ # Execute task-specific pre-hooks
150
+ task_hooks = @task_specific_pre_hooks[task_name.to_s] || []
151
+ task_hooks.each { |hook| hook.call(task_name) }
152
+ end
153
+
154
+ # Execute all post-task hooks for a given task
155
+ #
156
+ # @param task_name [String] the name of the task
157
+ # @param duration [Float] execution duration in seconds
158
+ # @param result [Object, nil] the result of the task execution
159
+ # @param error [Exception, nil] any error that occurred during execution
160
+ # @return [void]
161
+ def execute_post_hooks(task_name, duration, result, error)
162
+ return if excluded?(task_name)
163
+
164
+ # Handle timing if enabled
165
+ if timing_enabled?
166
+ task_info = @task_stack.pop
167
+ if task_info && task_info[:name] == task_name && (duration > 0.1)
168
+ # Log performance for tasks that took longer than 0.1 seconds
169
+ log_task_performance(task_name, duration, task_info[:level])
170
+ end
171
+
172
+ # Log task failure if there was an error
173
+ log_task_failure(task_name, duration, error, task_info&.dig(:level) || 0) if error
174
+ end
175
+
176
+ # Execute global post-hooks
177
+ @post_hooks.each { |hook| hook.call(task_name, duration, result, error) }
178
+
179
+ # Execute task-specific post-hooks
180
+ task_hooks = @task_specific_post_hooks[task_name.to_s] || []
181
+ task_hooks.each { |hook| hook.call(task_name, duration, result, error) }
182
+ end
183
+
184
+ # Clear all hooks
185
+ #
186
+ # @return [void]
187
+ def clear_all_hooks
188
+ @pre_hooks.clear
189
+ @post_hooks.clear
190
+ @task_specific_pre_hooks.clear
191
+ @task_specific_post_hooks.clear
192
+ end
193
+
194
+ # Exclude a task from hook execution
195
+ #
196
+ # @param task_name [String, Symbol] the name of the task to exclude
197
+ # @return [void]
198
+ def exclude_task(task_name)
199
+ @excluded_tasks.add(task_name.to_s)
200
+ end
201
+
202
+ # Include a task in hook execution (remove from exclusions)
203
+ #
204
+ # @param task_name [String, Symbol] the name of the task to include
205
+ # @return [void]
206
+ def include_task(task_name)
207
+ @excluded_tasks.delete(task_name.to_s)
208
+ end
209
+
210
+ # Check if a task is excluded from hook execution
211
+ #
212
+ # @param task_name [String, Symbol] the name of the task to check
213
+ # @return [Boolean] true if the task is excluded
214
+ def excluded?(task_name)
215
+ @excluded_tasks.include?(task_name.to_s)
216
+ end
217
+
218
+ # Get statistics about registered hooks
219
+ #
220
+ # @return [Hash] hook statistics
221
+ def stats
222
+ {
223
+ global_pre_hooks: @pre_hooks.size,
224
+ global_post_hooks: @post_hooks.size,
225
+ task_specific_pre_hooks: @task_specific_pre_hooks.values.flatten.size,
226
+ task_specific_post_hooks: @task_specific_post_hooks.values.flatten.size,
227
+ tasks_with_pre_hooks: @task_specific_pre_hooks.keys,
228
+ tasks_with_post_hooks: @task_specific_post_hooks.keys,
229
+ timing_enabled: @timing_enabled,
230
+ task_stack_depth: @task_stack.size,
231
+ }
232
+ end
233
+
234
+ # Enable task timing and performance logging
235
+ #
236
+ # @return [void]
237
+ def enable_timing!
238
+ @timing_enabled = true
239
+ puts "🔧 Task timing enabled".colorize(:grey) if defined?(String.instance_method(:colorize))
240
+ end
241
+
242
+ # Disable task timing and performance logging
243
+ #
244
+ # @return [void]
245
+ def disable_timing!
246
+ @timing_enabled = false
247
+ end
248
+
249
+ # Check if timing is enabled
250
+ #
251
+ # @return [Boolean] true if timing is enabled
252
+ def timing_enabled?
253
+ @timing_enabled == true
254
+ end
255
+
256
+ # Clear performance logs
257
+ #
258
+ # @return [void]
259
+ def clear_performance_logs
260
+ FileUtils.rm_f(@performance_log)
261
+ FileUtils.rm_f("artifacts/task_failures.log")
262
+ end
263
+
264
+ # Setup task hooks by monkey patching Rake::Task
265
+ #
266
+ # This method patches the Rake::Task#execute method to automatically
267
+ # call pre and post hooks for all task executions.
268
+ #
269
+ # @return [void]
270
+ def setup!
271
+ return if @hooks_installed
272
+
273
+ ::Rake::Task.class_eval do
274
+ alias_method :makit_original_execute, :execute
275
+
276
+ # Check if this task should have enhanced makit tracing
277
+ #
278
+ # @return [Boolean] true if task should be traced
279
+ def should_trace_makit_task?
280
+ # Check if trace is enabled
281
+ return false unless ENV['RAKE_TRACE'] || ARGV.include?('--trace') || ENV['MAKIT_TRACE'] == 'true'
282
+
283
+ # Check if task is makit-related
284
+ makit_related_task?
285
+ end
286
+
287
+ # Check if task is makit-related
288
+ #
289
+ # @return [Boolean] true if task is makit-related
290
+ def makit_related_task?
291
+ name.downcase.include?('makit') ||
292
+ name.include?('bundle') ||
293
+ name.include?('gem') ||
294
+ name.include?('dotnet') ||
295
+ name.include?('protoc') ||
296
+ name.include?('git') ||
297
+ name.include?('setup') ||
298
+ name.include?('build') ||
299
+ name.include?('test')
300
+ end
301
+
302
+ # Get makit strategy information safely
303
+ #
304
+ # @return [String] strategy information
305
+ def get_makit_strategy_info
306
+ begin
307
+ if defined?(Makit::Commands::Runner)
308
+ runner = Makit::Commands::Runner.default
309
+ info = runner.strategy_info
310
+ "#{info[:type]} (#{info[:class]})"
311
+ else
312
+ "not available"
313
+ end
314
+ rescue => e
315
+ "error: #{e.message}"
316
+ end
317
+ end
318
+
319
+ def execute(args = nil)
320
+ start_time = Time.now
321
+ error = nil
322
+ result = nil
323
+
324
+ # Add enhanced trace output for makit-related tasks
325
+ if should_trace_makit_task?
326
+ puts " [MAKIT] Executing task: #{name}"
327
+ puts " [MAKIT] Strategy: #{get_makit_strategy_info}"
328
+ puts " [MAKIT] Environment: #{ENV['MAKIT_STRATEGY'] || 'auto'}"
329
+ puts " [MAKIT] Working Directory: #{Dir.pwd}"
330
+ puts " [MAKIT] Timestamp: #{Time.now.iso8601}"
331
+ end
332
+
333
+ begin
334
+ # Execute pre-hooks
335
+ Makit::Tasks::HookManager.execute_pre_hooks(name)
336
+
337
+ # Execute the original task
338
+ result = makit_original_execute(args)
339
+ rescue StandardError => e
340
+ error = e
341
+ raise
342
+ ensure
343
+ # Calculate duration
344
+ duration = Time.now - start_time
345
+
346
+ # Execute post-hooks
347
+ Makit::Tasks::HookManager.execute_post_hooks(name, duration, result, error)
348
+ end
349
+
350
+ result
351
+ end
352
+ end
353
+
354
+ @hooks_installed = true
355
+ end
356
+
357
+ # Setup default task hooks
358
+ #
359
+ # This method sets up the default task hooks that provide basic
360
+ # task execution feedback. It should be called during initialization.
361
+ #
362
+ # @return [void]
363
+ def setup_default_hooks
364
+ # Setup the monkey patching first
365
+ setup!
366
+
367
+ # Only setup default hooks if no hooks are already registered
368
+ return unless @pre_hooks.empty? && @post_hooks.empty?
369
+
370
+ # Exclude the init task from hooks to prevent duplication
371
+ exclude_task(:init)
372
+ exclude_task(:default)
373
+
374
+ # Default pre-task hook: log task start to default logger
375
+ add_pre_hook do |task_name|
376
+ log_task_start(task_name)
377
+ end
378
+
379
+ # Default post-task hook: log task completion
380
+ add_post_hook do |task_name, duration, result, error|
381
+ log_task_completion(task_name, duration, result, error)
382
+ end
383
+ end
384
+
385
+ def log_task_start(task_name)
386
+ # return if @logged_tasks.include?(task_name)
387
+ # @logged_tasks.add(task_name)
388
+ # Makit::Logging.info("#{task_name}".colorize(:white).bold)
389
+ end
390
+
391
+ def log_task_completion(task_name, duration, result, error)
392
+ # Makit::Logging.info("#{task_name}".colorize(:white).bold)
393
+ # Makit::Logging.info("Task completed".colorize(:green).bold)
394
+ # Makit::Logging.info("Duration: #{duration.round(2)}s".colorize(:green).bold)
395
+ # Makit::Logging.info("Result: #{result}".colorize(:green).bold)
396
+ # Makit::Logging.info("Error: #{error}".colorize(:red).bold)
397
+ end
398
+
399
+ private
400
+
401
+ # Log task performance to file
402
+ #
403
+ # @param name [String] task name
404
+ # @param duration [Float] execution duration
405
+ # @param level [Integer] nesting level
406
+ # @return [void]
407
+ def log_task_performance(name, duration, level)
408
+ return if ENV["CI"] == "true" && duration < 1.0
409
+
410
+ FileUtils.mkdir_p("artifacts")
411
+ File.open(@performance_log, "a") do |f|
412
+ f.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")},#{name},#{duration.round(4)},#{level}"
413
+ end
414
+ rescue StandardError
415
+ # Silently fail performance logging to not interrupt tasks
416
+ end
417
+
418
+ # Log task failure to file
419
+ #
420
+ # @param name [String] task name
421
+ # @param duration [Float] execution duration
422
+ # @param error [Exception] the error that occurred
423
+ # @param level [Integer] nesting level
424
+ # @return [void]
425
+ def log_task_failure(name, duration, error, _level)
426
+ failure_log = "artifacts/task_failures.log"
427
+ FileUtils.mkdir_p("artifacts")
428
+
429
+ File.open(failure_log, "a") do |f|
430
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
431
+ message = error.message.gsub(",", ";")
432
+ f.puts "#{timestamp},#{name},#{duration.round(4)},#{error.class.name},#{message}"
433
+ end
434
+ rescue StandardError
435
+ # Silently fail error logging
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
441
+
442
+ # Setup default hooks when the module is loaded
443
+ Makit::Tasks::HookManager.setup_default_hooks