makit 0.0.157 → 0.0.158

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 (177) 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/cli/base.rb +17 -17
  9. data/lib/makit/cli/build_commands.rb +500 -500
  10. data/lib/makit/cli/generators/base_generator.rb +74 -74
  11. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  12. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  13. data/lib/makit/cli/generators/node_generator.rb +50 -50
  14. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  15. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  16. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  17. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  18. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  19. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -41
  20. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  21. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  22. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  23. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  24. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  25. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  26. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  27. data/lib/makit/cli/main.rb +78 -78
  28. data/lib/makit/cli/pipeline_commands.rb +311 -311
  29. data/lib/makit/cli/project_commands.rb +868 -868
  30. data/lib/makit/cli/repository_commands.rb +661 -661
  31. data/lib/makit/cli/strategy_commands.rb +207 -207
  32. data/lib/makit/cli/utility_commands.rb +521 -521
  33. data/lib/makit/commands/factory.rb +359 -359
  34. data/lib/makit/commands/middleware/base.rb +73 -73
  35. data/lib/makit/commands/middleware/cache.rb +248 -248
  36. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  37. data/lib/makit/commands/middleware/validator.rb +269 -269
  38. data/lib/makit/commands/request.rb +316 -316
  39. data/lib/makit/commands/result.rb +323 -323
  40. data/lib/makit/commands/runner.rb +386 -386
  41. data/lib/makit/commands/strategies/base.rb +171 -171
  42. data/lib/makit/commands/strategies/child_process.rb +162 -162
  43. data/lib/makit/commands/strategies/factory.rb +136 -136
  44. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  45. data/lib/makit/commands.rb +50 -50
  46. data/lib/makit/configuration/dotnet_project.rb +48 -48
  47. data/lib/makit/configuration/gitlab_helper.rb +61 -61
  48. data/lib/makit/configuration/project.rb +292 -292
  49. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  50. data/lib/makit/configuration/step.rb +34 -34
  51. data/lib/makit/configuration/timeout.rb +74 -74
  52. data/lib/makit/configuration.rb +21 -21
  53. data/lib/makit/content/default_gitignore.rb +7 -7
  54. data/lib/makit/content/default_gitignore.txt +225 -225
  55. data/lib/makit/content/default_rakefile.rb +13 -13
  56. data/lib/makit/content/gem_rakefile.rb +16 -16
  57. data/lib/makit/context.rb +1 -1
  58. data/lib/makit/data.rb +49 -49
  59. data/lib/makit/directories.rb +170 -170
  60. data/lib/makit/directory.rb +262 -262
  61. data/lib/makit/docs/files.rb +89 -89
  62. data/lib/makit/docs/rake.rb +102 -102
  63. data/lib/makit/dotnet/cli.rb +69 -69
  64. data/lib/makit/dotnet/project.rb +217 -217
  65. data/lib/makit/dotnet/solution.rb +38 -38
  66. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  67. data/lib/makit/dotnet/solution_console.rb +264 -264
  68. data/lib/makit/dotnet/solution_maui.rb +354 -354
  69. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  70. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  71. data/lib/makit/dotnet.rb +102 -102
  72. data/lib/makit/email.rb +90 -90
  73. data/lib/makit/environment.rb +142 -142
  74. data/lib/makit/examples/runner.rb +370 -370
  75. data/lib/makit/exceptions.rb +45 -45
  76. data/lib/makit/fileinfo.rb +32 -32
  77. data/lib/makit/files.rb +43 -43
  78. data/lib/makit/gems.rb +40 -40
  79. data/lib/makit/git/cli.rb +78 -54
  80. data/lib/makit/git/repository.rb +100 -100
  81. data/lib/makit/git.rb +104 -104
  82. data/lib/makit/gitlab/pipeline.rb +857 -857
  83. data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
  84. data/lib/makit/gitlab_runner.rb +59 -59
  85. data/lib/makit/humanize.rb +218 -218
  86. data/lib/makit/indexer.rb +47 -47
  87. data/lib/makit/io/filesystem.rb +111 -111
  88. data/lib/makit/io/filesystem_service_impl.rb +337 -337
  89. data/lib/makit/lint.rb +212 -212
  90. data/lib/makit/logging/configuration.rb +309 -309
  91. data/lib/makit/logging/format_registry.rb +84 -84
  92. data/lib/makit/logging/formatters/base.rb +39 -39
  93. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  94. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  95. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  96. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  97. data/lib/makit/logging/log_request.rb +119 -119
  98. data/lib/makit/logging/logger.rb +199 -199
  99. data/lib/makit/logging/sinks/base.rb +91 -91
  100. data/lib/makit/logging/sinks/console.rb +72 -72
  101. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  102. data/lib/makit/logging/sinks/structured.rb +123 -123
  103. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  104. data/lib/makit/logging.rb +578 -578
  105. data/lib/makit/markdown.rb +75 -75
  106. data/lib/makit/mp/basic_object_mp.rb +17 -17
  107. data/lib/makit/mp/command_mp.rb +13 -13
  108. data/lib/makit/mp/command_request.mp.rb +17 -17
  109. data/lib/makit/mp/project_mp.rb +199 -199
  110. data/lib/makit/mp/string_mp.rb +205 -205
  111. data/lib/makit/nuget.rb +74 -74
  112. data/lib/makit/podman/podman.rb +458 -458
  113. data/lib/makit/podman/podman_service_impl.rb +1081 -1081
  114. data/lib/makit/port.rb +32 -32
  115. data/lib/makit/process.rb +377 -377
  116. data/lib/makit/protoc.rb +112 -112
  117. data/lib/makit/rake/cli.rb +196 -196
  118. data/lib/makit/rake/trace_controller.rb +174 -174
  119. data/lib/makit/rake.rb +81 -81
  120. data/lib/makit/ruby/cli.rb +185 -185
  121. data/lib/makit/ruby.rb +25 -25
  122. data/lib/makit/rubygems.rb +137 -0
  123. data/lib/makit/secrets/azure_key_vault.rb +322 -322
  124. data/lib/makit/secrets/azure_secrets.rb +183 -183
  125. data/lib/makit/secrets/local_secrets.rb +72 -72
  126. data/lib/makit/secrets/secrets_manager.rb +105 -105
  127. data/lib/makit/secrets.rb +16 -16
  128. data/lib/makit/serializer.rb +130 -130
  129. data/lib/makit/services/builder.rb +186 -186
  130. data/lib/makit/services/error_handler.rb +226 -226
  131. data/lib/makit/services/repository_manager.rb +367 -367
  132. data/lib/makit/services/validator.rb +112 -112
  133. data/lib/makit/setup/classlib.rb +101 -101
  134. data/lib/makit/setup/gem.rb +268 -268
  135. data/lib/makit/setup/pages.rb +11 -11
  136. data/lib/makit/setup/razorclasslib.rb +101 -101
  137. data/lib/makit/setup/runner.rb +54 -54
  138. data/lib/makit/setup.rb +5 -5
  139. data/lib/makit/show.rb +110 -110
  140. data/lib/makit/storage.rb +126 -126
  141. data/lib/makit/symbols.rb +175 -175
  142. data/lib/makit/task_info.rb +130 -130
  143. data/lib/makit/tasks/at_exit.rb +15 -15
  144. data/lib/makit/tasks/build.rb +22 -22
  145. data/lib/makit/tasks/bump.rb +7 -7
  146. data/lib/makit/tasks/clean.rb +13 -13
  147. data/lib/makit/tasks/configure.rb +10 -10
  148. data/lib/makit/tasks/format.rb +10 -10
  149. data/lib/makit/tasks/hook_manager.rb +443 -443
  150. data/lib/makit/tasks/info.rb +368 -368
  151. data/lib/makit/tasks/init.rb +49 -49
  152. data/lib/makit/tasks/integrate.rb +60 -56
  153. data/lib/makit/tasks/pull_incoming.rb +13 -13
  154. data/lib/makit/tasks/secrets.rb +7 -7
  155. data/lib/makit/tasks/setup.rb +16 -16
  156. data/lib/makit/tasks/sync.rb +14 -17
  157. data/lib/makit/tasks/tag.rb +27 -27
  158. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  159. data/lib/makit/tasks/test.rb +22 -22
  160. data/lib/makit/tasks/update.rb +18 -18
  161. data/lib/makit/tasks/version.rb +6 -6
  162. data/lib/makit/tasks.rb +24 -24
  163. data/lib/makit/test_cache.rb +239 -239
  164. data/lib/makit/tree.rb +37 -37
  165. data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
  166. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
  167. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  168. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  169. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
  170. data/lib/makit/version.rb +661 -503
  171. data/lib/makit/version_util.rb +21 -21
  172. data/lib/makit/wix.rb +95 -95
  173. data/lib/makit/yaml.rb +29 -29
  174. data/lib/makit/zip.rb +17 -17
  175. data/lib/makit copy.rb +44 -44
  176. data/lib/makit.rb +115 -114
  177. metadata +3 -2
@@ -1,337 +1,337 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
- require "mime/types"
5
- require "digest"
6
- require "time"
7
-
8
- module Makit
9
- module IO
10
- class FileSystemServiceImpl
11
- def initialize
12
- # Initialize any required state
13
- end
14
-
15
- # Check if gRPC service is available
16
- def self.grpc_available?
17
- defined?(Makit::V1::Io::FileSystemService)
18
- end
19
-
20
- # Get a single file's content and/or metadata
21
- def get_file(request, _unused_call = nil)
22
- path = request.respond_to?(:path) ? request.path : request[:path]
23
- include_metadata = request.respond_to?(:include_metadata) ? request.include_metadata : request[:include_metadata]
24
- include_content = request.respond_to?(:include_content) ? request.include_content : request[:include_content]
25
-
26
- # Default to metadata only if not specified
27
- include_metadata = true if include_metadata.nil?
28
- include_content = false if include_content.nil?
29
-
30
- unless File.exist?(path)
31
- raise Errno::ENOENT, "No such file or directory: #{path}"
32
- end
33
-
34
- file_data = {
35
- path: path
36
- }
37
-
38
- if include_content
39
- if binary_file?(path)
40
- file_data[:binary_data] = File.binread(path)
41
- else
42
- file_data[:text_lines] = File.readlines(path).map(&:chomp)
43
- end
44
- end
45
-
46
- if include_metadata
47
- file_data[:metadata] = get_file_metadata(path)
48
- end
49
-
50
- if self.class.grpc_available?
51
- Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(file_data))
52
- else
53
- { file: file_data }
54
- end
55
- end
56
-
57
- # Create or update a file
58
- def save_file(request, _unused_call = nil)
59
- path = request.respond_to?(:path) ? request.path : request[:path]
60
- binary_data = request.respond_to?(:binary_data) ? request.binary_data : request[:binary_data]
61
- text_lines = request.respond_to?(:text_lines) ? request.text_lines : request[:text_lines]
62
-
63
- # Ensure directory exists
64
- FileUtils.mkdir_p(File.dirname(path))
65
-
66
- if binary_data && !binary_data.empty?
67
- File.binwrite(path, binary_data)
68
- elsif text_lines && !text_lines.empty?
69
- File.write(path, text_lines.join("\n") + "\n")
70
- else
71
- raise ArgumentError, "Either binary_data or text_lines must be provided"
72
- end
73
-
74
- file_data = {
75
- path: path,
76
- metadata: get_file_metadata(path)
77
- }
78
-
79
- if binary_data && !binary_data.empty?
80
- file_data[:binary_data] = binary_data
81
- elsif text_lines && !text_lines.empty?
82
- file_data[:text_lines] = text_lines
83
- end
84
-
85
- if self.class.grpc_available?
86
- Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(file_data))
87
- else
88
- { file: file_data }
89
- end
90
- end
91
-
92
- # Delete a file by path
93
- def delete_file(request, _unused_call = nil)
94
- path = request.respond_to?(:path) ? request.path : request[:path]
95
-
96
- unless File.exist?(path)
97
- raise Errno::ENOENT, "No such file or directory: #{path}"
98
- end
99
-
100
- File.delete(path)
101
-
102
- if self.class.grpc_available?
103
- Google::Protobuf::Empty.new
104
- else
105
- {}
106
- end
107
- end
108
-
109
- # List files in a directory according to query flags
110
- def list_files(request, _unused_call = nil)
111
- directory = request.respond_to?(:directory) ? request.directory : request[:directory]
112
- recursive = request.respond_to?(:recursive) ? request.recursive : request[:recursive]
113
- pattern = request.respond_to?(:pattern) ? request.pattern : request[:pattern]
114
- respect_gitignore = request.respond_to?(:respect_gitignore) ? request.respect_gitignore : request[:respect_gitignore]
115
- include_metadata = request.respond_to?(:include_metadata) ? request.include_metadata : request[:include_metadata]
116
- include_content = request.respond_to?(:include_content) ? request.include_content : request[:include_content]
117
-
118
- # Default values
119
- recursive = false if recursive.nil?
120
- respect_gitignore = false if respect_gitignore.nil?
121
- include_metadata = true if include_metadata.nil?
122
- include_content = false if include_content.nil?
123
-
124
- unless Dir.exist?(directory)
125
- raise Errno::ENOENT, "No such file or directory: #{directory}"
126
- end
127
-
128
- files = []
129
-
130
- # Get gitignore patterns if requested
131
- gitignore_patterns = []
132
- if respect_gitignore
133
- gitignore_file = File.join(directory, ".gitignore")
134
- if File.exist?(gitignore_file)
135
- gitignore_patterns = File.readlines(gitignore_file).map(&:strip).reject(&:empty?)
136
- end
137
- end
138
-
139
- # Find files
140
- if recursive
141
- Dir.glob(File.join(directory, "**", "*")).each do |file_path|
142
- next if File.directory?(file_path)
143
- next if should_ignore_file?(file_path, gitignore_patterns)
144
- next if pattern && !File.fnmatch(pattern, File.basename(file_path))
145
-
146
- files << build_file_data(file_path, include_metadata, include_content)
147
- end
148
- else
149
- Dir.entries(directory).each do |entry|
150
- next if entry == "." || entry == ".."
151
-
152
- file_path = File.join(directory, entry)
153
- next if File.directory?(file_path)
154
- next if should_ignore_file?(file_path, gitignore_patterns)
155
- next if pattern && !File.fnmatch(pattern, entry)
156
-
157
- files << build_file_data(file_path, include_metadata, include_content)
158
- end
159
- end
160
-
161
- if self.class.grpc_available?
162
- Makit::V1::Io::FileList.new(files: files.map { |f| Makit::V1::Io::File.new(f) })
163
- else
164
- { files: files }
165
- end
166
- end
167
-
168
- # Create a directory
169
- def create_directory(request, _unused_call = nil)
170
- path = request.respond_to?(:path) ? request.path : request[:path]
171
-
172
- FileUtils.mkdir_p(path)
173
-
174
- if self.class.grpc_available?
175
- Google::Protobuf::Empty.new
176
- else
177
- {}
178
- end
179
- end
180
-
181
- # Delete a directory
182
- def delete_directory(request, _unused_call = nil)
183
- path = request.respond_to?(:path) ? request.path : request[:path]
184
-
185
- unless Dir.exist?(path)
186
- raise Errno::ENOENT, "No such file or directory: #{path}"
187
- end
188
-
189
- Dir.rmdir(path)
190
-
191
- if self.class.grpc_available?
192
- Google::Protobuf::Empty.new
193
- else
194
- {}
195
- end
196
- end
197
-
198
- # Upload large files in chunks
199
- def upload(request, _unused_call = nil)
200
- chunks = request.respond_to?(:chunks) ? request.chunks : request[:chunks]
201
-
202
- raise ArgumentError, "No chunks provided" if chunks.nil? || chunks.empty?
203
-
204
- # Group chunks by path
205
- chunks_by_path = {}
206
- chunks.each do |chunk|
207
- path = chunk.respond_to?(:path) ? chunk.path : chunk[:path]
208
- data = chunk.respond_to?(:data) ? chunk.data : chunk[:data]
209
- metadata = chunk.respond_to?(:metadata) ? chunk.metadata : chunk[:metadata]
210
- is_final = chunk.respond_to?(:is_final) ? chunk.is_final : chunk[:is_final]
211
-
212
- chunks_by_path[path] ||= { data: "", metadata: metadata }
213
- chunks_by_path[path][:data] += data
214
- end
215
-
216
- # Save each file
217
- results = []
218
- chunks_by_path.each do |path, file_info|
219
- File.binwrite(path, file_info[:data])
220
-
221
- file_data = {
222
- path: path,
223
- binary_data: file_info[:data],
224
- metadata: get_file_metadata(path)
225
- }
226
-
227
- results << file_data
228
- end
229
-
230
- # Return the first (or only) file result
231
- result = results.first
232
-
233
- if self.class.grpc_available?
234
- Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(result))
235
- else
236
- { file: result }
237
- end
238
- end
239
-
240
- # Download a file in chunks
241
- def download(request, _unused_call = nil)
242
- path = request.respond_to?(:path) ? request.path : request[:path]
243
-
244
- unless File.exist?(path)
245
- raise Errno::ENOENT, "No such file or directory: #{path}"
246
- end
247
-
248
- content = File.binread(path)
249
- chunk_size = 1024 # 1KB chunks
250
- chunks = []
251
-
252
- (0...content.length).step(chunk_size) do |i|
253
- chunk_data = content[i, chunk_size]
254
- is_final = (i + chunk_size >= content.length)
255
-
256
- chunk = {
257
- path: path,
258
- data: chunk_data,
259
- metadata: is_final ? get_file_metadata(path) : nil,
260
- is_final: is_final
261
- }
262
-
263
- chunks << chunk
264
- end
265
-
266
- if self.class.grpc_available?
267
- chunks.map { |c| Makit::V1::Io::FileChunk.new(c) }
268
- else
269
- chunks
270
- end
271
- end
272
-
273
- private
274
-
275
- def get_file_metadata(path)
276
- stat = File.stat(path)
277
-
278
- {
279
- mime_type: get_mime_type(path),
280
- size_bytes: stat.size,
281
- created_at: stat.birthtime.iso8601,
282
- modified_at: stat.mtime.iso8601,
283
- owner: stat.uid.to_s,
284
- is_directory: stat.directory?,
285
- checksum: calculate_checksum(path)
286
- }
287
- end
288
-
289
- def get_mime_type(path)
290
- mime_type = MIME::Types.type_for(path).first
291
- mime_type ? mime_type.content_type : "application/octet-stream"
292
- end
293
-
294
- def calculate_checksum(path)
295
- Digest::SHA256.file(path).hexdigest
296
- end
297
-
298
- def binary_file?(path)
299
- # Simple heuristic: check if file contains null bytes or high bytes
300
- File.open(path, "rb") do |file|
301
- chunk = file.read(1024)
302
- return true if chunk.nil? || chunk.include?("\x00") || chunk.bytes.any? { |b| b > 127 }
303
- end
304
- false
305
- rescue
306
- true # If we can't read it, assume it's binary
307
- end
308
-
309
- def should_ignore_file?(file_path, gitignore_patterns)
310
- return false if gitignore_patterns.empty?
311
-
312
- relative_path = file_path
313
- gitignore_patterns.any? do |pattern|
314
- File.fnmatch(pattern, relative_path) || File.fnmatch(pattern, File.basename(relative_path))
315
- end
316
- end
317
-
318
- def build_file_data(file_path, include_metadata, include_content)
319
- file_data = { path: file_path }
320
-
321
- if include_content
322
- if binary_file?(file_path)
323
- file_data[:binary_data] = File.binread(file_path)
324
- else
325
- file_data[:text_lines] = File.readlines(file_path).map(&:chomp)
326
- end
327
- end
328
-
329
- if include_metadata
330
- file_data[:metadata] = get_file_metadata(file_path)
331
- end
332
-
333
- file_data
334
- end
335
- end
336
- end
337
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "mime/types"
5
+ require "digest"
6
+ require "time"
7
+
8
+ module Makit
9
+ module IO
10
+ class FileSystemServiceImpl
11
+ def initialize
12
+ # Initialize any required state
13
+ end
14
+
15
+ # Check if gRPC service is available
16
+ def self.grpc_available?
17
+ defined?(Makit::V1::Io::FileSystemService)
18
+ end
19
+
20
+ # Get a single file's content and/or metadata
21
+ def get_file(request, _unused_call = nil)
22
+ path = request.respond_to?(:path) ? request.path : request[:path]
23
+ include_metadata = request.respond_to?(:include_metadata) ? request.include_metadata : request[:include_metadata]
24
+ include_content = request.respond_to?(:include_content) ? request.include_content : request[:include_content]
25
+
26
+ # Default to metadata only if not specified
27
+ include_metadata = true if include_metadata.nil?
28
+ include_content = false if include_content.nil?
29
+
30
+ unless File.exist?(path)
31
+ raise Errno::ENOENT, "No such file or directory: #{path}"
32
+ end
33
+
34
+ file_data = {
35
+ path: path
36
+ }
37
+
38
+ if include_content
39
+ if binary_file?(path)
40
+ file_data[:binary_data] = File.binread(path)
41
+ else
42
+ file_data[:text_lines] = File.readlines(path).map(&:chomp)
43
+ end
44
+ end
45
+
46
+ if include_metadata
47
+ file_data[:metadata] = get_file_metadata(path)
48
+ end
49
+
50
+ if self.class.grpc_available?
51
+ Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(file_data))
52
+ else
53
+ { file: file_data }
54
+ end
55
+ end
56
+
57
+ # Create or update a file
58
+ def save_file(request, _unused_call = nil)
59
+ path = request.respond_to?(:path) ? request.path : request[:path]
60
+ binary_data = request.respond_to?(:binary_data) ? request.binary_data : request[:binary_data]
61
+ text_lines = request.respond_to?(:text_lines) ? request.text_lines : request[:text_lines]
62
+
63
+ # Ensure directory exists
64
+ FileUtils.mkdir_p(File.dirname(path))
65
+
66
+ if binary_data && !binary_data.empty?
67
+ File.binwrite(path, binary_data)
68
+ elsif text_lines && !text_lines.empty?
69
+ File.write(path, text_lines.join("\n") + "\n")
70
+ else
71
+ raise ArgumentError, "Either binary_data or text_lines must be provided"
72
+ end
73
+
74
+ file_data = {
75
+ path: path,
76
+ metadata: get_file_metadata(path)
77
+ }
78
+
79
+ if binary_data && !binary_data.empty?
80
+ file_data[:binary_data] = binary_data
81
+ elsif text_lines && !text_lines.empty?
82
+ file_data[:text_lines] = text_lines
83
+ end
84
+
85
+ if self.class.grpc_available?
86
+ Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(file_data))
87
+ else
88
+ { file: file_data }
89
+ end
90
+ end
91
+
92
+ # Delete a file by path
93
+ def delete_file(request, _unused_call = nil)
94
+ path = request.respond_to?(:path) ? request.path : request[:path]
95
+
96
+ unless File.exist?(path)
97
+ raise Errno::ENOENT, "No such file or directory: #{path}"
98
+ end
99
+
100
+ File.delete(path)
101
+
102
+ if self.class.grpc_available?
103
+ Google::Protobuf::Empty.new
104
+ else
105
+ {}
106
+ end
107
+ end
108
+
109
+ # List files in a directory according to query flags
110
+ def list_files(request, _unused_call = nil)
111
+ directory = request.respond_to?(:directory) ? request.directory : request[:directory]
112
+ recursive = request.respond_to?(:recursive) ? request.recursive : request[:recursive]
113
+ pattern = request.respond_to?(:pattern) ? request.pattern : request[:pattern]
114
+ respect_gitignore = request.respond_to?(:respect_gitignore) ? request.respect_gitignore : request[:respect_gitignore]
115
+ include_metadata = request.respond_to?(:include_metadata) ? request.include_metadata : request[:include_metadata]
116
+ include_content = request.respond_to?(:include_content) ? request.include_content : request[:include_content]
117
+
118
+ # Default values
119
+ recursive = false if recursive.nil?
120
+ respect_gitignore = false if respect_gitignore.nil?
121
+ include_metadata = true if include_metadata.nil?
122
+ include_content = false if include_content.nil?
123
+
124
+ unless Dir.exist?(directory)
125
+ raise Errno::ENOENT, "No such file or directory: #{directory}"
126
+ end
127
+
128
+ files = []
129
+
130
+ # Get gitignore patterns if requested
131
+ gitignore_patterns = []
132
+ if respect_gitignore
133
+ gitignore_file = File.join(directory, ".gitignore")
134
+ if File.exist?(gitignore_file)
135
+ gitignore_patterns = File.readlines(gitignore_file).map(&:strip).reject(&:empty?)
136
+ end
137
+ end
138
+
139
+ # Find files
140
+ if recursive
141
+ Dir.glob(File.join(directory, "**", "*")).each do |file_path|
142
+ next if File.directory?(file_path)
143
+ next if should_ignore_file?(file_path, gitignore_patterns)
144
+ next if pattern && !File.fnmatch(pattern, File.basename(file_path))
145
+
146
+ files << build_file_data(file_path, include_metadata, include_content)
147
+ end
148
+ else
149
+ Dir.entries(directory).each do |entry|
150
+ next if entry == "." || entry == ".."
151
+
152
+ file_path = File.join(directory, entry)
153
+ next if File.directory?(file_path)
154
+ next if should_ignore_file?(file_path, gitignore_patterns)
155
+ next if pattern && !File.fnmatch(pattern, entry)
156
+
157
+ files << build_file_data(file_path, include_metadata, include_content)
158
+ end
159
+ end
160
+
161
+ if self.class.grpc_available?
162
+ Makit::V1::Io::FileList.new(files: files.map { |f| Makit::V1::Io::File.new(f) })
163
+ else
164
+ { files: files }
165
+ end
166
+ end
167
+
168
+ # Create a directory
169
+ def create_directory(request, _unused_call = nil)
170
+ path = request.respond_to?(:path) ? request.path : request[:path]
171
+
172
+ FileUtils.mkdir_p(path)
173
+
174
+ if self.class.grpc_available?
175
+ Google::Protobuf::Empty.new
176
+ else
177
+ {}
178
+ end
179
+ end
180
+
181
+ # Delete a directory
182
+ def delete_directory(request, _unused_call = nil)
183
+ path = request.respond_to?(:path) ? request.path : request[:path]
184
+
185
+ unless Dir.exist?(path)
186
+ raise Errno::ENOENT, "No such file or directory: #{path}"
187
+ end
188
+
189
+ Dir.rmdir(path)
190
+
191
+ if self.class.grpc_available?
192
+ Google::Protobuf::Empty.new
193
+ else
194
+ {}
195
+ end
196
+ end
197
+
198
+ # Upload large files in chunks
199
+ def upload(request, _unused_call = nil)
200
+ chunks = request.respond_to?(:chunks) ? request.chunks : request[:chunks]
201
+
202
+ raise ArgumentError, "No chunks provided" if chunks.nil? || chunks.empty?
203
+
204
+ # Group chunks by path
205
+ chunks_by_path = {}
206
+ chunks.each do |chunk|
207
+ path = chunk.respond_to?(:path) ? chunk.path : chunk[:path]
208
+ data = chunk.respond_to?(:data) ? chunk.data : chunk[:data]
209
+ metadata = chunk.respond_to?(:metadata) ? chunk.metadata : chunk[:metadata]
210
+ is_final = chunk.respond_to?(:is_final) ? chunk.is_final : chunk[:is_final]
211
+
212
+ chunks_by_path[path] ||= { data: "", metadata: metadata }
213
+ chunks_by_path[path][:data] += data
214
+ end
215
+
216
+ # Save each file
217
+ results = []
218
+ chunks_by_path.each do |path, file_info|
219
+ File.binwrite(path, file_info[:data])
220
+
221
+ file_data = {
222
+ path: path,
223
+ binary_data: file_info[:data],
224
+ metadata: get_file_metadata(path)
225
+ }
226
+
227
+ results << file_data
228
+ end
229
+
230
+ # Return the first (or only) file result
231
+ result = results.first
232
+
233
+ if self.class.grpc_available?
234
+ Makit::V1::Io::FileResponse.new(file: Makit::V1::Io::File.new(result))
235
+ else
236
+ { file: result }
237
+ end
238
+ end
239
+
240
+ # Download a file in chunks
241
+ def download(request, _unused_call = nil)
242
+ path = request.respond_to?(:path) ? request.path : request[:path]
243
+
244
+ unless File.exist?(path)
245
+ raise Errno::ENOENT, "No such file or directory: #{path}"
246
+ end
247
+
248
+ content = File.binread(path)
249
+ chunk_size = 1024 # 1KB chunks
250
+ chunks = []
251
+
252
+ (0...content.length).step(chunk_size) do |i|
253
+ chunk_data = content[i, chunk_size]
254
+ is_final = (i + chunk_size >= content.length)
255
+
256
+ chunk = {
257
+ path: path,
258
+ data: chunk_data,
259
+ metadata: is_final ? get_file_metadata(path) : nil,
260
+ is_final: is_final
261
+ }
262
+
263
+ chunks << chunk
264
+ end
265
+
266
+ if self.class.grpc_available?
267
+ chunks.map { |c| Makit::V1::Io::FileChunk.new(c) }
268
+ else
269
+ chunks
270
+ end
271
+ end
272
+
273
+ private
274
+
275
+ def get_file_metadata(path)
276
+ stat = File.stat(path)
277
+
278
+ {
279
+ mime_type: get_mime_type(path),
280
+ size_bytes: stat.size,
281
+ created_at: stat.birthtime.iso8601,
282
+ modified_at: stat.mtime.iso8601,
283
+ owner: stat.uid.to_s,
284
+ is_directory: stat.directory?,
285
+ checksum: calculate_checksum(path)
286
+ }
287
+ end
288
+
289
+ def get_mime_type(path)
290
+ mime_type = MIME::Types.type_for(path).first
291
+ mime_type ? mime_type.content_type : "application/octet-stream"
292
+ end
293
+
294
+ def calculate_checksum(path)
295
+ Digest::SHA256.file(path).hexdigest
296
+ end
297
+
298
+ def binary_file?(path)
299
+ # Simple heuristic: check if file contains null bytes or high bytes
300
+ File.open(path, "rb") do |file|
301
+ chunk = file.read(1024)
302
+ return true if chunk.nil? || chunk.include?("\x00") || chunk.bytes.any? { |b| b > 127 }
303
+ end
304
+ false
305
+ rescue
306
+ true # If we can't read it, assume it's binary
307
+ end
308
+
309
+ def should_ignore_file?(file_path, gitignore_patterns)
310
+ return false if gitignore_patterns.empty?
311
+
312
+ relative_path = file_path
313
+ gitignore_patterns.any? do |pattern|
314
+ File.fnmatch(pattern, relative_path) || File.fnmatch(pattern, File.basename(relative_path))
315
+ end
316
+ end
317
+
318
+ def build_file_data(file_path, include_metadata, include_content)
319
+ file_data = { path: file_path }
320
+
321
+ if include_content
322
+ if binary_file?(file_path)
323
+ file_data[:binary_data] = File.binread(file_path)
324
+ else
325
+ file_data[:text_lines] = File.readlines(file_path).map(&:chomp)
326
+ end
327
+ end
328
+
329
+ if include_metadata
330
+ file_data[:metadata] = get_file_metadata(file_path)
331
+ end
332
+
333
+ file_data
334
+ end
335
+ end
336
+ end
337
+ end