makit 0.0.167 → 0.0.169

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 (179) 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 +187 -187
  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/github_actions.rb +202 -0
  84. data/lib/makit/gitlab/pipeline.rb +857 -857
  85. data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
  86. data/lib/makit/gitlab_runner.rb +59 -59
  87. data/lib/makit/humanize.rb +218 -218
  88. data/lib/makit/indexer.rb +47 -47
  89. data/lib/makit/io/filesystem.rb +111 -111
  90. data/lib/makit/io/filesystem_service_impl.rb +337 -337
  91. data/lib/makit/lint.rb +212 -212
  92. data/lib/makit/logging/configuration.rb +309 -309
  93. data/lib/makit/logging/format_registry.rb +84 -84
  94. data/lib/makit/logging/formatters/base.rb +39 -39
  95. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  96. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  97. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  98. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  99. data/lib/makit/logging/log_request.rb +119 -119
  100. data/lib/makit/logging/logger.rb +199 -199
  101. data/lib/makit/logging/sinks/base.rb +91 -91
  102. data/lib/makit/logging/sinks/console.rb +72 -72
  103. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  104. data/lib/makit/logging/sinks/structured.rb +123 -123
  105. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  106. data/lib/makit/logging.rb +578 -578
  107. data/lib/makit/markdown.rb +75 -75
  108. data/lib/makit/mp/basic_object_mp.rb +17 -17
  109. data/lib/makit/mp/command_mp.rb +13 -13
  110. data/lib/makit/mp/command_request.mp.rb +17 -17
  111. data/lib/makit/mp/project_mp.rb +199 -199
  112. data/lib/makit/mp/string_mp.rb +205 -205
  113. data/lib/makit/nuget.rb +460 -454
  114. data/lib/makit/podman/podman.rb +458 -458
  115. data/lib/makit/podman/podman_service_impl.rb +1081 -1081
  116. data/lib/makit/port.rb +32 -32
  117. data/lib/makit/process.rb +377 -377
  118. data/lib/makit/protoc.rb +112 -112
  119. data/lib/makit/rake/cli.rb +196 -196
  120. data/lib/makit/rake/trace_controller.rb +174 -174
  121. data/lib/makit/rake.rb +81 -81
  122. data/lib/makit/ruby/cli.rb +185 -185
  123. data/lib/makit/ruby.rb +25 -25
  124. data/lib/makit/rubygems.rb +137 -137
  125. data/lib/makit/secrets/azure_key_vault.rb +322 -322
  126. data/lib/makit/secrets/azure_secrets.rb +221 -221
  127. data/lib/makit/secrets/local_secrets.rb +72 -72
  128. data/lib/makit/secrets/secrets_manager.rb +105 -105
  129. data/lib/makit/secrets.rb +96 -96
  130. data/lib/makit/serializer.rb +130 -130
  131. data/lib/makit/services/builder.rb +186 -186
  132. data/lib/makit/services/error_handler.rb +226 -226
  133. data/lib/makit/services/repository_manager.rb +367 -367
  134. data/lib/makit/services/validator.rb +112 -112
  135. data/lib/makit/setup/classlib.rb +101 -101
  136. data/lib/makit/setup/gem.rb +268 -268
  137. data/lib/makit/setup/pages.rb +11 -11
  138. data/lib/makit/setup/razorclasslib.rb +101 -101
  139. data/lib/makit/setup/runner.rb +54 -54
  140. data/lib/makit/setup.rb +5 -5
  141. data/lib/makit/show.rb +110 -110
  142. data/lib/makit/storage.rb +126 -126
  143. data/lib/makit/symbols.rb +175 -175
  144. data/lib/makit/task_info.rb +130 -130
  145. data/lib/makit/tasks/at_exit.rb +15 -15
  146. data/lib/makit/tasks/build.rb +22 -22
  147. data/lib/makit/tasks/bump.rb +7 -7
  148. data/lib/makit/tasks/clean.rb +13 -13
  149. data/lib/makit/tasks/configure.rb +10 -10
  150. data/lib/makit/tasks/format.rb +10 -10
  151. data/lib/makit/tasks/hook_manager.rb +443 -443
  152. data/lib/makit/tasks/info.rb +368 -368
  153. data/lib/makit/tasks/init.rb +49 -49
  154. data/lib/makit/tasks/integrate.rb +60 -60
  155. data/lib/makit/tasks/pull_incoming.rb +13 -13
  156. data/lib/makit/tasks/secrets.rb +7 -7
  157. data/lib/makit/tasks/setup.rb +16 -16
  158. data/lib/makit/tasks/sync.rb +14 -14
  159. data/lib/makit/tasks/tag.rb +27 -27
  160. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  161. data/lib/makit/tasks/test.rb +22 -22
  162. data/lib/makit/tasks/update.rb +21 -21
  163. data/lib/makit/tasks/version.rb +6 -6
  164. data/lib/makit/tasks.rb +24 -24
  165. data/lib/makit/test_cache.rb +239 -239
  166. data/lib/makit/tree.rb +37 -37
  167. data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
  168. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
  169. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  170. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  171. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
  172. data/lib/makit/version.rb +661 -661
  173. data/lib/makit/version_util.rb +21 -21
  174. data/lib/makit/wix.rb +95 -95
  175. data/lib/makit/yaml.rb +29 -29
  176. data/lib/makit/zip.rb +17 -17
  177. data/lib/makit copy.rb +44 -44
  178. data/lib/makit.rb +121 -120
  179. 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