makit 0.0.164 → 0.0.166

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