makit 0.0.168 → 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 -202
  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 -458
  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 -121
  179. metadata +2 -2
data/lib/makit/nuget.rb CHANGED
@@ -1,458 +1,460 @@
1
- # frozen_string_literal: true
2
-
3
- require "tmpdir"
4
- require "open3"
5
- require "fileutils"
6
- # This module provides classes for the Makit gem.
7
- module Makit
8
- # This class provide methods for working with the Nuget package cache
9
- #
10
- # Example:
11
- #
12
- # Makit::Directory.cache("Google.Protobuf", "3.27.2")
13
- #
14
- # dotnet nuget locals all --list
15
- # dotnet nuget locals all --clear
16
- #
17
- class NuGet
18
- def self.get_cache_dir(package, version)
19
- File.join(Makit::Directories::NUGET_PACKAGE_CACHE, package, version)
20
- end
21
-
22
- def self.cache(package, version)
23
- # if the package is already cached, there is nothing to do
24
- return if Dir.exist?(get_cache_dir(package, version))
25
-
26
- Dir.mktmpdir do |dir|
27
- # Use the temp directory here
28
- Dir.chdir(dir) do
29
- system("dotnet new classlib -n ClassLib")
30
- Dir.chdir("ClassLib") do
31
- # display a list of directories in the current directory
32
- puts Dir.entries(Dir.pwd)
33
- # display a list of files in the current directory
34
- puts Dir.entries(Dir.pwd)
35
- puts "dotnet add ClassLib.csproj package #{package} --version #{version}"
36
- system("dotnet add ClassLib.csproj package #{package} --version #{version}")
37
- end
38
- end
39
- # The directory and its contents will be removed automatically after the block
40
- end
41
- end
42
-
43
- def self.clear_cache(package, version)
44
- return unless Dir.exist?(get_cache_dir(package, version))
45
-
46
- FileUtils.rm_rf(get_cache_dir(package, version))
47
- end
48
-
49
- # get the latest version of the package
50
- def self.get_latest_version(package)
51
- dir = File.join(Makit::Directories::NUGET_PACKAGE_CACHE, package)
52
- if Dir.exist?(dir)
53
- versions = Dir.entries(dir).select do |entry|
54
- File.directory?(File.join(dir, entry)) && ![".", ".."].include?(entry)
55
- end
56
- highest_version = Makit::Version.get_highest_version(versions)
57
- return highest_version
58
- end
59
- nil
60
- end
61
-
62
- # publish a package to a nuget directory feed
63
- def self.add_package(_package, path)
64
- system("dotnet nuget push #{path} --source #{path}")
65
- end
66
-
67
- def self.publish(package, api_key, source)
68
- "dotnet nuget push #{package} --skip-duplicate --api-key #{api_key} --source #{source}".run
69
- end
70
-
71
- def self.publish_to_directory(nuget_package_path, directory, package_name, version)
72
- target_package_path = "#{directory}/#{package_name}/#{version}/#{package_name}.#{version}.nupkg".downcase
73
- if File.exist?(target_package_path)
74
- puts " #{target_package_path} already exists".colorize(:grey)
75
- else
76
- "dotnet nuget push #{nuget_package_path} --source #{directory}".run
77
- end
78
- end
79
-
80
- # -----------------------
81
- # NuGet Source Management
82
- # -----------------------
83
-
84
- # Lists all configured NuGet sources
85
- #
86
- # @return [Array<Hash>] Array of source hashes with keys: :name, :url, :enabled
87
- def self.list_sources
88
- stdout, stderr, status = Open3.capture3("dotnet", "nuget", "list", "source")
89
-
90
- unless status.success?
91
- Makit::Logging.default_logger.warn("Failed to list NuGet sources: #{stderr}")
92
- return []
93
- end
94
-
95
- sources = []
96
- current_source = nil
97
- lines = stdout.lines
98
-
99
- lines.each_with_index do |line, index|
100
- line_stripped = line.strip
101
- next if line_stripped.empty?
102
-
103
- # Skip header lines
104
- next if line_stripped.start_with?("Registered") || line_stripped.start_with?("---")
105
-
106
- # Parse numbered source lines like:
107
- # " 11. nuget.org [Enabled]"
108
- # " 1. nuget.org [Disabled]"
109
- match = line_stripped.match(/^\s*\d+\.\s+(.+?)(?:\s+\[(Enabled|Disabled)\])?$/)
110
- if match
111
- name_part = match[1]
112
- enabled_str = match[2]
113
-
114
- next if name_part.nil? || name_part.strip.empty?
115
-
116
- name = name_part.strip
117
-
118
- # Default to enabled if not specified
119
- enabled = enabled_str.nil? ? true : (enabled_str == "Enabled")
120
-
121
- # Create new source entry
122
- current_source = { name: name, url: nil, enabled: enabled }
123
- sources << current_source
124
-
125
- # Check next line for URL or path (indented line)
126
- if index + 1 < lines.length
127
- next_line = lines[index + 1]
128
- next_line_stripped = next_line.strip
129
-
130
- # Check if next line is indented (starts with whitespace) and contains URL or path
131
- if next_line.match?(/^\s+/) && !next_line_stripped.empty?
132
- # Check if it's a URL
133
- if next_line_stripped.match?(/^https?:\/\//)
134
- current_source[:url] = next_line_stripped
135
- # Check if it's a file path (Windows drive letter, UNC path, or Unix path)
136
- elsif next_line_stripped.match?(/^[A-Z]:[\\\/]/) || next_line_stripped.match?(/^\\\\/) || next_line_stripped.match?(/^\/[^\/]/)
137
- # For local sources, use the path as the "URL" for comparison purposes
138
- current_source[:url] = next_line_stripped
139
- end
140
- end
141
- end
142
- end
143
- end
144
-
145
- sources
146
- end
147
-
148
- # Checks if a NuGet source with the given name exists
149
- #
150
- # @param name [String] The source name to check
151
- # @return [Boolean] true if the source exists, false otherwise
152
- def self.has_source?(name)
153
- sources = list_sources
154
- sources.any? { |source| source[:name] == name }
155
- end
156
-
157
- # Adds a NuGet source
158
- #
159
- # @param name [String] The source name
160
- # @param url [String] The source URL
161
- # @param username [String, nil] Optional username for authenticated sources
162
- # @param password [String, nil] Optional password for authenticated sources
163
- # @param store_password_in_clear_text [Boolean] Whether to store password in clear text (default: false)
164
- # @return [Boolean] true if successful, false otherwise
165
- def self.add_source(name, url, username: nil, password: nil, store_password_in_clear_text: false)
166
- raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
167
- raise ArgumentError, "url is required" if url.nil? || url.strip.empty?
168
-
169
- # Check if source already exists
170
- if has_source?(name)
171
- Makit::Logging.default_logger.info("NuGet source '#{name}' already exists")
172
- return false
173
- end
174
-
175
- args = ["dotnet", "nuget", "add", "source", url, "--name", name]
176
- args << "--username" << username if username
177
- args << "--password" << password if password
178
- args << "--store-password-in-clear-text" if store_password_in_clear_text
179
-
180
- stdout, stderr, status = Open3.capture3(*args)
181
-
182
- if status.success?
183
- Makit::Logging.default_logger.info("Added NuGet source '#{name}' (#{url})")
184
- true
185
- else
186
- Makit::Logging.default_logger.error("Failed to add NuGet source '#{name}': #{stderr}")
187
- false
188
- end
189
- end
190
-
191
- # Removes a NuGet source
192
- #
193
- # @param name [String] The source name to remove
194
- # @return [Boolean] true if successful, false otherwise
195
- def self.remove_source(name)
196
- raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
197
-
198
- unless has_source?(name)
199
- Makit::Logging.default_logger.info("NuGet source '#{name}' does not exist")
200
- return false
201
- end
202
-
203
- stdout, stderr, status = Open3.capture3("dotnet", "nuget", "remove", "source", name)
204
-
205
- if status.success?
206
- Makit::Logging.default_logger.info("Removed NuGet source '#{name}'")
207
- true
208
- else
209
- Makit::Logging.default_logger.error("Failed to remove NuGet source '#{name}': #{stderr}")
210
- false
211
- end
212
- end
213
-
214
- # Configures a NuGet source (adds if it doesn't exist, updates if URL differs, no-op if already configured)
215
- #
216
- # @param name [String] The source name
217
- # @param url [String] The source URL
218
- # @param username [String, nil] Optional username for authenticated sources
219
- # @param password [String, nil] Optional password for authenticated sources
220
- # @param store_password_in_clear_text [Boolean] Whether to store password in clear text (default: false)
221
- # @return [Boolean] true if successful or already configured, false otherwise
222
- def self.configure_source(name, url, username: nil, password: nil, store_password_in_clear_text: false)
223
- raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
224
- raise ArgumentError, "url is required" if url.nil? || url.strip.empty?
225
-
226
- # Check if source already exists
227
- if has_source?(name)
228
- # Get existing source details
229
- sources = list_sources
230
- existing_source = sources.find { |s| s[:name] == name }
231
-
232
- # If source exists with the same URL, it's already configured correctly (no-op)
233
- if existing_source && existing_source[:url] == url
234
- Makit::Logging.default_logger.info("NuGet source '#{name}' already exists with URL '#{url}', skipping configuration")
235
- return true
236
- end
237
-
238
- # Source exists but URL is different, update it
239
- Makit::Logging.default_logger.info("NuGet source '#{name}' already exists with different URL, updating...")
240
- remove_source(name)
241
- end
242
-
243
- # Add the source
244
- add_source(name, url, username: username, password: password, store_password_in_clear_text: store_password_in_clear_text)
245
- end
246
-
247
- # Migrates packages from one NuGet source to another, matching a pattern
248
- #
249
- # For local directory feeds, packages are found by scanning the directory structure:
250
- # {source_path}/{package_name}/{version}/{package_name}.{version}.nupkg
251
- #
252
- # @param source_name [String] The name of the source to migrate from
253
- # @param package_pattern [String] The package name pattern to match (e.g., "rep*", "MyPackage.*")
254
- # @param destination_name [String] The name of the destination source
255
- # @return [Integer] The number of packages migrated, or -1 if an error occurred
256
- def self.migrate_packages(source_name, package_pattern, destination_name)
257
- raise ArgumentError, "source_name is required" if source_name.nil? || source_name.strip.empty?
258
- raise ArgumentError, "package_pattern is required" if package_pattern.nil? || package_pattern.strip.empty?
259
- raise ArgumentError, "destination_name is required" if destination_name.nil? || destination_name.strip.empty?
260
-
261
- source_name = source_name.strip
262
- package_pattern = package_pattern.strip
263
- destination_name = destination_name.strip
264
-
265
- # Get source information
266
- sources = list_sources
267
- source_info = sources.find { |s| s[:name] == source_name }
268
- destination_info = sources.find { |s| s[:name] == destination_name }
269
-
270
- unless source_info
271
- Makit::Logging.default_logger.error("Source '#{source_name}' not found")
272
- return -1
273
- end
274
-
275
- unless destination_info
276
- Makit::Logging.default_logger.error("Destination source '#{destination_name}' not found")
277
- return -1
278
- end
279
-
280
- source_path = source_info[:url]
281
- destination_path = destination_info[:url]
282
-
283
- # Source must be a local directory feed (not a URL)
284
- unless source_path && !source_path.match?(/^https?:\/\//)
285
- Makit::Logging.default_logger.error("Source '#{source_name}' is not a local directory feed (URL: #{source_path})")
286
- return -1
287
- end
288
-
289
- # Expand and normalize the source path
290
- source_path = File.expand_path(source_path)
291
-
292
- # Destination can be either local directory feed or remote feed (Azure DevOps, NuGet.org, etc.)
293
- is_remote_destination = destination_path && destination_path.match?(/^https?:\/\//)
294
-
295
- # Ensure source directory exists
296
- unless Dir.exist?(source_path)
297
- Makit::Logging.default_logger.error("Source directory does not exist: #{source_path}")
298
- return -1
299
- end
300
-
301
- Makit::Logging.default_logger.info("Searching for packages matching '#{package_pattern}' in source '#{source_name}' (#{source_path})")
302
- Makit::Logging.default_logger.info("Destination: #{destination_name} (#{destination_path}) - #{is_remote_destination ? 'Remote feed' : 'Local directory feed'}")
303
-
304
- # Ensure destination directory exists (only for local feeds)
305
- unless is_remote_destination
306
- FileUtils.mkdir_p(destination_path) unless Dir.exist?(destination_path)
307
- end
308
-
309
- # Find all .nupkg files matching the pattern
310
- # Simply search recursively for all .nupkg files and match package names
311
- matching_packages = []
312
-
313
- # Glob for all .nupkg files recursively in the source directory
314
- Dir.glob(File.join(source_path, "**", "*.nupkg")) do |nupkg_file|
315
- # Extract package name from the file path
316
- # Path structure is typically: {source_path}/{package_name}/{version}/{package_name}.{version}.nupkg
317
- relative_path = nupkg_file.sub(/^#{Regexp.escape(source_path)}[\/\\]?/, "")
318
- path_parts = relative_path.split(/[\/\\]/)
319
-
320
- # Get package name from directory structure (first directory after source_path)
321
- package_name = path_parts.length >= 1 ? path_parts[0] : nil
322
-
323
- # If we can't get it from path, try to extract from filename
324
- if package_name.nil? || package_name.empty?
325
- filename = File.basename(nupkg_file, ".nupkg")
326
- # Try to extract package name by removing version suffix (e.g., "1.2.3" or "1.2.3-beta")
327
- if filename.match?(/^(.+?)\.(\d+\.\d+.*)$/)
328
- package_name = $1
329
- else
330
- package_name = filename
331
- end
332
- end
333
-
334
- # Check if package name matches pattern
335
- if package_name && File.fnmatch?(package_pattern, package_name, File::FNM_DOTMATCH)
336
- # Extract version from path (second directory) or from filename
337
- version = path_parts.length >= 2 ? path_parts[1] : nil
338
-
339
- # If we couldn't extract version from path, try from filename
340
- if version.nil? || version.empty?
341
- filename = File.basename(nupkg_file, ".nupkg")
342
- if filename.match?(/^.+?\.(\d+\.\d+.*)$/)
343
- version = $1
344
- else
345
- version = "unknown"
346
- end
347
- end
348
-
349
- matching_packages << {
350
- package_name: package_name,
351
- version: version,
352
- nupkg_path: nupkg_file
353
- }
354
- end
355
- end
356
-
357
- if matching_packages.empty?
358
- Makit::Logging.default_logger.info("No packages found matching pattern '#{package_pattern}' in source '#{source_name}'")
359
- return 0
360
- end
361
-
362
- Makit::Logging.default_logger.info("Found #{matching_packages.length} package(s) matching pattern '#{package_pattern}' in source '#{source_name}'")
363
-
364
- # Migrate each package
365
- migrated_count = 0
366
- failed_count = 0
367
-
368
- matching_packages.each do |package_info|
369
- package_name = package_info[:package_name]
370
- version = package_info[:version]
371
- nupkg_path = package_info[:nupkg_path]
372
-
373
- Makit::Logging.default_logger.info("Migrating #{package_name}.#{version} from '#{source_name}' to '#{destination_name}'...")
374
-
375
- if is_remote_destination
376
- # For remote feeds (Azure DevOps, NuGet.org, etc.), use dotnet nuget push
377
- # Azure DevOps requires --api-key az (placeholder) and --skip-duplicate
378
- Makit::Logging.default_logger.info(" Pushing #{package_name}.#{version} to remote feed...")
379
-
380
- args = ["dotnet", "nuget", "push", nupkg_path, "--source", destination_path, "--skip-duplicate"]
381
-
382
- # For Azure DevOps feeds, add --api-key az (required placeholder)
383
- if destination_path.include?("dev.azure.com") || destination_path.include?("pkgs.dev.azure.com")
384
- args << "--api-key" << "az"
385
- end
386
-
387
- stdout, stderr, status = Open3.capture3(*args)
388
-
389
- if status.success?
390
- Makit::Logging.default_logger.info(" Successfully migrated #{package_name}.#{version} to remote feed")
391
- migrated_count += 1
392
- else
393
- # Check if it's a duplicate (which is okay with --skip-duplicate)
394
- if stderr.include?("already exists") || stderr.include?("duplicate")
395
- Makit::Logging.default_logger.info(" Package already exists in destination, skipping: #{package_name}.#{version}")
396
- migrated_count += 1
397
- else
398
- Makit::Logging.default_logger.error(" Failed to migrate #{package_name}.#{version}: #{stderr}")
399
- failed_count += 1
400
- end
401
- end
402
- else
403
- # For local directory feeds, use 'nuget add' which automatically organizes packages
404
- # into the hierarchical structure: {packageID}/{version}/packageID.version.nupkg
405
- target_package_dir = File.join(destination_path, package_name.downcase, version)
406
- target_package_path = File.join(target_package_dir, File.basename(nupkg_path).downcase)
407
-
408
- if File.exist?(target_package_path)
409
- Makit::Logging.default_logger.info(" Package already exists in destination, skipping: #{package_name}.#{version}")
410
- migrated_count += 1
411
- next
412
- end
413
-
414
- stdout, stderr, status = Open3.capture3("nuget", "add", nupkg_path, "-Source", destination_path)
415
-
416
- if status.success?
417
- Makit::Logging.default_logger.info(" Successfully migrated and organized #{package_name}.#{version}")
418
- migrated_count += 1
419
- else
420
- # Fallback to dotnet nuget push if nuget.exe is not available
421
- nuget_exe = Makit::Environment.which("nuget")
422
- if nuget_exe.nil? || nuget_exe.empty?
423
- Makit::Logging.default_logger.info(" nuget.exe not found, falling back to dotnet nuget push")
424
-
425
- # Manually organize package into proper directory structure
426
- Makit::Logging.default_logger.info(" Organizing package into directory structure: #{target_package_dir}")
427
- FileUtils.mkdir_p(target_package_dir)
428
- FileUtils.cp(nupkg_path, target_package_path)
429
-
430
- # Push using dotnet nuget push from the organized location
431
- stdout, stderr, status = Open3.capture3("dotnet", "nuget", "push", target_package_path, "--source", destination_path)
432
-
433
- if status.success?
434
- Makit::Logging.default_logger.info(" Successfully migrated and organized #{package_name}.#{version}")
435
- migrated_count += 1
436
- else
437
- Makit::Logging.default_logger.warn(" Package copied to organized structure but dotnet nuget push failed: #{stderr}")
438
- Makit::Logging.default_logger.warn(" Package is available at #{target_package_path} but may need manual indexing")
439
- migrated_count += 1
440
- end
441
- else
442
- Makit::Logging.default_logger.error(" Failed to migrate #{package_name}.#{version}: #{stderr}")
443
- failed_count += 1
444
- end
445
- end
446
- end
447
- end
448
-
449
- if failed_count > 0
450
- Makit::Logging.default_logger.warn("Migration completed with #{failed_count} failure(s). #{migrated_count} package(s) migrated successfully.")
451
- else
452
- Makit::Logging.default_logger.info("Successfully migrated #{migrated_count} package(s) from '#{source_name}' to '#{destination_name}'")
453
- end
454
-
455
- migrated_count
456
- end
457
- end
458
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "open3"
5
+ require "fileutils"
6
+ # This module provides classes for the Makit gem.
7
+ module Makit
8
+ # This class provide methods for working with the Nuget package cache
9
+ #
10
+ # Example:
11
+ #
12
+ # Makit::Directory.cache("Google.Protobuf", "3.27.2")
13
+ #
14
+ # dotnet nuget locals all --list
15
+ # dotnet nuget locals all --clear
16
+ #
17
+ class NuGet
18
+ def self.get_cache_dir(package, version)
19
+ File.join(Makit::Directories::NUGET_PACKAGE_CACHE, package, version)
20
+ end
21
+
22
+ def self.cache(package, version)
23
+ # if the package is already cached, there is nothing to do
24
+ return if Dir.exist?(get_cache_dir(package, version))
25
+
26
+ Dir.mktmpdir do |dir|
27
+ # Use the temp directory here
28
+ Dir.chdir(dir) do
29
+ system("dotnet new classlib -n ClassLib")
30
+ Dir.chdir("ClassLib") do
31
+ # display a list of directories in the current directory
32
+ puts Dir.entries(Dir.pwd)
33
+ # display a list of files in the current directory
34
+ puts Dir.entries(Dir.pwd)
35
+ puts "dotnet add ClassLib.csproj package #{package} --version #{version}"
36
+ system("dotnet add ClassLib.csproj package #{package} --version #{version}")
37
+ end
38
+ end
39
+ # The directory and its contents will be removed automatically after the block
40
+ end
41
+ end
42
+
43
+ def self.clear_cache(package, version)
44
+ return unless Dir.exist?(get_cache_dir(package, version))
45
+
46
+ FileUtils.rm_rf(get_cache_dir(package, version))
47
+ end
48
+
49
+ # get the latest version of the package
50
+ def self.get_latest_version(package)
51
+ dir = File.join(Makit::Directories::NUGET_PACKAGE_CACHE, package)
52
+ if Dir.exist?(dir)
53
+ versions = Dir.entries(dir).select do |entry|
54
+ File.directory?(File.join(dir, entry)) && ![".", ".."].include?(entry)
55
+ end
56
+ highest_version = Makit::Version.get_highest_version(versions)
57
+ return highest_version
58
+ end
59
+ nil
60
+ end
61
+
62
+ # publish a package to a nuget directory feed
63
+ def self.add_package(_package, path)
64
+ system("dotnet nuget push #{path} --source #{path}")
65
+ end
66
+
67
+ def self.publish(package, api_key, source)
68
+ # we do not want the api_key echoed to the console, substitute *****
69
+ puts "dotnet nuget push #{package} --skip-duplicate --api-key ***** --source #{source}"
70
+ puts `dotnet nuget push #{package} --skip-duplicate --api-key #{api_key} --source #{source}`
71
+ end
72
+
73
+ def self.publish_to_directory(nuget_package_path, directory, package_name, version)
74
+ target_package_path = "#{directory}/#{package_name}/#{version}/#{package_name}.#{version}.nupkg".downcase
75
+ if File.exist?(target_package_path)
76
+ puts " #{target_package_path} already exists".colorize(:grey)
77
+ else
78
+ "dotnet nuget push #{nuget_package_path} --source #{directory}".run
79
+ end
80
+ end
81
+
82
+ # -----------------------
83
+ # NuGet Source Management
84
+ # -----------------------
85
+
86
+ # Lists all configured NuGet sources
87
+ #
88
+ # @return [Array<Hash>] Array of source hashes with keys: :name, :url, :enabled
89
+ def self.list_sources
90
+ stdout, stderr, status = Open3.capture3("dotnet", "nuget", "list", "source")
91
+
92
+ unless status.success?
93
+ Makit::Logging.default_logger.warn("Failed to list NuGet sources: #{stderr}")
94
+ return []
95
+ end
96
+
97
+ sources = []
98
+ current_source = nil
99
+ lines = stdout.lines
100
+
101
+ lines.each_with_index do |line, index|
102
+ line_stripped = line.strip
103
+ next if line_stripped.empty?
104
+
105
+ # Skip header lines
106
+ next if line_stripped.start_with?("Registered") || line_stripped.start_with?("---")
107
+
108
+ # Parse numbered source lines like:
109
+ # " 11. nuget.org [Enabled]"
110
+ # " 1. nuget.org [Disabled]"
111
+ match = line_stripped.match(/^\s*\d+\.\s+(.+?)(?:\s+\[(Enabled|Disabled)\])?$/)
112
+ if match
113
+ name_part = match[1]
114
+ enabled_str = match[2]
115
+
116
+ next if name_part.nil? || name_part.strip.empty?
117
+
118
+ name = name_part.strip
119
+
120
+ # Default to enabled if not specified
121
+ enabled = enabled_str.nil? ? true : (enabled_str == "Enabled")
122
+
123
+ # Create new source entry
124
+ current_source = { name: name, url: nil, enabled: enabled }
125
+ sources << current_source
126
+
127
+ # Check next line for URL or path (indented line)
128
+ if index + 1 < lines.length
129
+ next_line = lines[index + 1]
130
+ next_line_stripped = next_line.strip
131
+
132
+ # Check if next line is indented (starts with whitespace) and contains URL or path
133
+ if next_line.match?(/^\s+/) && !next_line_stripped.empty?
134
+ # Check if it's a URL
135
+ if next_line_stripped.match?(/^https?:\/\//)
136
+ current_source[:url] = next_line_stripped
137
+ # Check if it's a file path (Windows drive letter, UNC path, or Unix path)
138
+ elsif next_line_stripped.match?(/^[A-Z]:[\\\/]/) || next_line_stripped.match?(/^\\\\/) || next_line_stripped.match?(/^\/[^\/]/)
139
+ # For local sources, use the path as the "URL" for comparison purposes
140
+ current_source[:url] = next_line_stripped
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ sources
148
+ end
149
+
150
+ # Checks if a NuGet source with the given name exists
151
+ #
152
+ # @param name [String] The source name to check
153
+ # @return [Boolean] true if the source exists, false otherwise
154
+ def self.has_source?(name)
155
+ sources = list_sources
156
+ sources.any? { |source| source[:name] == name }
157
+ end
158
+
159
+ # Adds a NuGet source
160
+ #
161
+ # @param name [String] The source name
162
+ # @param url [String] The source URL
163
+ # @param username [String, nil] Optional username for authenticated sources
164
+ # @param password [String, nil] Optional password for authenticated sources
165
+ # @param store_password_in_clear_text [Boolean] Whether to store password in clear text (default: false)
166
+ # @return [Boolean] true if successful, false otherwise
167
+ def self.add_source(name, url, username: nil, password: nil, store_password_in_clear_text: false)
168
+ raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
169
+ raise ArgumentError, "url is required" if url.nil? || url.strip.empty?
170
+
171
+ # Check if source already exists
172
+ if has_source?(name)
173
+ Makit::Logging.default_logger.info("NuGet source '#{name}' already exists")
174
+ return false
175
+ end
176
+
177
+ args = ["dotnet", "nuget", "add", "source", url, "--name", name]
178
+ args << "--username" << username if username
179
+ args << "--password" << password if password
180
+ args << "--store-password-in-clear-text" if store_password_in_clear_text
181
+
182
+ stdout, stderr, status = Open3.capture3(*args)
183
+
184
+ if status.success?
185
+ Makit::Logging.default_logger.info("Added NuGet source '#{name}' (#{url})")
186
+ true
187
+ else
188
+ Makit::Logging.default_logger.error("Failed to add NuGet source '#{name}': #{stderr}")
189
+ false
190
+ end
191
+ end
192
+
193
+ # Removes a NuGet source
194
+ #
195
+ # @param name [String] The source name to remove
196
+ # @return [Boolean] true if successful, false otherwise
197
+ def self.remove_source(name)
198
+ raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
199
+
200
+ unless has_source?(name)
201
+ Makit::Logging.default_logger.info("NuGet source '#{name}' does not exist")
202
+ return false
203
+ end
204
+
205
+ stdout, stderr, status = Open3.capture3("dotnet", "nuget", "remove", "source", name)
206
+
207
+ if status.success?
208
+ Makit::Logging.default_logger.info("Removed NuGet source '#{name}'")
209
+ true
210
+ else
211
+ Makit::Logging.default_logger.error("Failed to remove NuGet source '#{name}': #{stderr}")
212
+ false
213
+ end
214
+ end
215
+
216
+ # Configures a NuGet source (adds if it doesn't exist, updates if URL differs, no-op if already configured)
217
+ #
218
+ # @param name [String] The source name
219
+ # @param url [String] The source URL
220
+ # @param username [String, nil] Optional username for authenticated sources
221
+ # @param password [String, nil] Optional password for authenticated sources
222
+ # @param store_password_in_clear_text [Boolean] Whether to store password in clear text (default: false)
223
+ # @return [Boolean] true if successful or already configured, false otherwise
224
+ def self.configure_source(name, url, username: nil, password: nil, store_password_in_clear_text: false)
225
+ raise ArgumentError, "name is required" if name.nil? || name.strip.empty?
226
+ raise ArgumentError, "url is required" if url.nil? || url.strip.empty?
227
+
228
+ # Check if source already exists
229
+ if has_source?(name)
230
+ # Get existing source details
231
+ sources = list_sources
232
+ existing_source = sources.find { |s| s[:name] == name }
233
+
234
+ # If source exists with the same URL, it's already configured correctly (no-op)
235
+ if existing_source && existing_source[:url] == url
236
+ Makit::Logging.default_logger.info("NuGet source '#{name}' already exists with URL '#{url}', skipping configuration")
237
+ return true
238
+ end
239
+
240
+ # Source exists but URL is different, update it
241
+ Makit::Logging.default_logger.info("NuGet source '#{name}' already exists with different URL, updating...")
242
+ remove_source(name)
243
+ end
244
+
245
+ # Add the source
246
+ add_source(name, url, username: username, password: password, store_password_in_clear_text: store_password_in_clear_text)
247
+ end
248
+
249
+ # Migrates packages from one NuGet source to another, matching a pattern
250
+ #
251
+ # For local directory feeds, packages are found by scanning the directory structure:
252
+ # {source_path}/{package_name}/{version}/{package_name}.{version}.nupkg
253
+ #
254
+ # @param source_name [String] The name of the source to migrate from
255
+ # @param package_pattern [String] The package name pattern to match (e.g., "rep*", "MyPackage.*")
256
+ # @param destination_name [String] The name of the destination source
257
+ # @return [Integer] The number of packages migrated, or -1 if an error occurred
258
+ def self.migrate_packages(source_name, package_pattern, destination_name)
259
+ raise ArgumentError, "source_name is required" if source_name.nil? || source_name.strip.empty?
260
+ raise ArgumentError, "package_pattern is required" if package_pattern.nil? || package_pattern.strip.empty?
261
+ raise ArgumentError, "destination_name is required" if destination_name.nil? || destination_name.strip.empty?
262
+
263
+ source_name = source_name.strip
264
+ package_pattern = package_pattern.strip
265
+ destination_name = destination_name.strip
266
+
267
+ # Get source information
268
+ sources = list_sources
269
+ source_info = sources.find { |s| s[:name] == source_name }
270
+ destination_info = sources.find { |s| s[:name] == destination_name }
271
+
272
+ unless source_info
273
+ Makit::Logging.default_logger.error("Source '#{source_name}' not found")
274
+ return -1
275
+ end
276
+
277
+ unless destination_info
278
+ Makit::Logging.default_logger.error("Destination source '#{destination_name}' not found")
279
+ return -1
280
+ end
281
+
282
+ source_path = source_info[:url]
283
+ destination_path = destination_info[:url]
284
+
285
+ # Source must be a local directory feed (not a URL)
286
+ unless source_path && !source_path.match?(/^https?:\/\//)
287
+ Makit::Logging.default_logger.error("Source '#{source_name}' is not a local directory feed (URL: #{source_path})")
288
+ return -1
289
+ end
290
+
291
+ # Expand and normalize the source path
292
+ source_path = File.expand_path(source_path)
293
+
294
+ # Destination can be either local directory feed or remote feed (Azure DevOps, NuGet.org, etc.)
295
+ is_remote_destination = destination_path && destination_path.match?(/^https?:\/\//)
296
+
297
+ # Ensure source directory exists
298
+ unless Dir.exist?(source_path)
299
+ Makit::Logging.default_logger.error("Source directory does not exist: #{source_path}")
300
+ return -1
301
+ end
302
+
303
+ Makit::Logging.default_logger.info("Searching for packages matching '#{package_pattern}' in source '#{source_name}' (#{source_path})")
304
+ Makit::Logging.default_logger.info("Destination: #{destination_name} (#{destination_path}) - #{is_remote_destination ? 'Remote feed' : 'Local directory feed'}")
305
+
306
+ # Ensure destination directory exists (only for local feeds)
307
+ unless is_remote_destination
308
+ FileUtils.mkdir_p(destination_path) unless Dir.exist?(destination_path)
309
+ end
310
+
311
+ # Find all .nupkg files matching the pattern
312
+ # Simply search recursively for all .nupkg files and match package names
313
+ matching_packages = []
314
+
315
+ # Glob for all .nupkg files recursively in the source directory
316
+ Dir.glob(File.join(source_path, "**", "*.nupkg")) do |nupkg_file|
317
+ # Extract package name from the file path
318
+ # Path structure is typically: {source_path}/{package_name}/{version}/{package_name}.{version}.nupkg
319
+ relative_path = nupkg_file.sub(/^#{Regexp.escape(source_path)}[\/\\]?/, "")
320
+ path_parts = relative_path.split(/[\/\\]/)
321
+
322
+ # Get package name from directory structure (first directory after source_path)
323
+ package_name = path_parts.length >= 1 ? path_parts[0] : nil
324
+
325
+ # If we can't get it from path, try to extract from filename
326
+ if package_name.nil? || package_name.empty?
327
+ filename = File.basename(nupkg_file, ".nupkg")
328
+ # Try to extract package name by removing version suffix (e.g., "1.2.3" or "1.2.3-beta")
329
+ if filename.match?(/^(.+?)\.(\d+\.\d+.*)$/)
330
+ package_name = $1
331
+ else
332
+ package_name = filename
333
+ end
334
+ end
335
+
336
+ # Check if package name matches pattern
337
+ if package_name && File.fnmatch?(package_pattern, package_name, File::FNM_DOTMATCH)
338
+ # Extract version from path (second directory) or from filename
339
+ version = path_parts.length >= 2 ? path_parts[1] : nil
340
+
341
+ # If we couldn't extract version from path, try from filename
342
+ if version.nil? || version.empty?
343
+ filename = File.basename(nupkg_file, ".nupkg")
344
+ if filename.match?(/^.+?\.(\d+\.\d+.*)$/)
345
+ version = $1
346
+ else
347
+ version = "unknown"
348
+ end
349
+ end
350
+
351
+ matching_packages << {
352
+ package_name: package_name,
353
+ version: version,
354
+ nupkg_path: nupkg_file
355
+ }
356
+ end
357
+ end
358
+
359
+ if matching_packages.empty?
360
+ Makit::Logging.default_logger.info("No packages found matching pattern '#{package_pattern}' in source '#{source_name}'")
361
+ return 0
362
+ end
363
+
364
+ Makit::Logging.default_logger.info("Found #{matching_packages.length} package(s) matching pattern '#{package_pattern}' in source '#{source_name}'")
365
+
366
+ # Migrate each package
367
+ migrated_count = 0
368
+ failed_count = 0
369
+
370
+ matching_packages.each do |package_info|
371
+ package_name = package_info[:package_name]
372
+ version = package_info[:version]
373
+ nupkg_path = package_info[:nupkg_path]
374
+
375
+ Makit::Logging.default_logger.info("Migrating #{package_name}.#{version} from '#{source_name}' to '#{destination_name}'...")
376
+
377
+ if is_remote_destination
378
+ # For remote feeds (Azure DevOps, NuGet.org, etc.), use dotnet nuget push
379
+ # Azure DevOps requires --api-key az (placeholder) and --skip-duplicate
380
+ Makit::Logging.default_logger.info(" Pushing #{package_name}.#{version} to remote feed...")
381
+
382
+ args = ["dotnet", "nuget", "push", nupkg_path, "--source", destination_path, "--skip-duplicate"]
383
+
384
+ # For Azure DevOps feeds, add --api-key az (required placeholder)
385
+ if destination_path.include?("dev.azure.com") || destination_path.include?("pkgs.dev.azure.com")
386
+ args << "--api-key" << "az"
387
+ end
388
+
389
+ stdout, stderr, status = Open3.capture3(*args)
390
+
391
+ if status.success?
392
+ Makit::Logging.default_logger.info(" Successfully migrated #{package_name}.#{version} to remote feed")
393
+ migrated_count += 1
394
+ else
395
+ # Check if it's a duplicate (which is okay with --skip-duplicate)
396
+ if stderr.include?("already exists") || stderr.include?("duplicate")
397
+ Makit::Logging.default_logger.info(" Package already exists in destination, skipping: #{package_name}.#{version}")
398
+ migrated_count += 1
399
+ else
400
+ Makit::Logging.default_logger.error(" Failed to migrate #{package_name}.#{version}: #{stderr}")
401
+ failed_count += 1
402
+ end
403
+ end
404
+ else
405
+ # For local directory feeds, use 'nuget add' which automatically organizes packages
406
+ # into the hierarchical structure: {packageID}/{version}/packageID.version.nupkg
407
+ target_package_dir = File.join(destination_path, package_name.downcase, version)
408
+ target_package_path = File.join(target_package_dir, File.basename(nupkg_path).downcase)
409
+
410
+ if File.exist?(target_package_path)
411
+ Makit::Logging.default_logger.info(" Package already exists in destination, skipping: #{package_name}.#{version}")
412
+ migrated_count += 1
413
+ next
414
+ end
415
+
416
+ stdout, stderr, status = Open3.capture3("nuget", "add", nupkg_path, "-Source", destination_path)
417
+
418
+ if status.success?
419
+ Makit::Logging.default_logger.info(" Successfully migrated and organized #{package_name}.#{version}")
420
+ migrated_count += 1
421
+ else
422
+ # Fallback to dotnet nuget push if nuget.exe is not available
423
+ nuget_exe = Makit::Environment.which("nuget")
424
+ if nuget_exe.nil? || nuget_exe.empty?
425
+ Makit::Logging.default_logger.info(" nuget.exe not found, falling back to dotnet nuget push")
426
+
427
+ # Manually organize package into proper directory structure
428
+ Makit::Logging.default_logger.info(" Organizing package into directory structure: #{target_package_dir}")
429
+ FileUtils.mkdir_p(target_package_dir)
430
+ FileUtils.cp(nupkg_path, target_package_path)
431
+
432
+ # Push using dotnet nuget push from the organized location
433
+ stdout, stderr, status = Open3.capture3("dotnet", "nuget", "push", target_package_path, "--source", destination_path)
434
+
435
+ if status.success?
436
+ Makit::Logging.default_logger.info(" Successfully migrated and organized #{package_name}.#{version}")
437
+ migrated_count += 1
438
+ else
439
+ Makit::Logging.default_logger.warn(" Package copied to organized structure but dotnet nuget push failed: #{stderr}")
440
+ Makit::Logging.default_logger.warn(" Package is available at #{target_package_path} but may need manual indexing")
441
+ migrated_count += 1
442
+ end
443
+ else
444
+ Makit::Logging.default_logger.error(" Failed to migrate #{package_name}.#{version}: #{stderr}")
445
+ failed_count += 1
446
+ end
447
+ end
448
+ end
449
+ end
450
+
451
+ if failed_count > 0
452
+ Makit::Logging.default_logger.warn("Migration completed with #{failed_count} failure(s). #{migrated_count} package(s) migrated successfully.")
453
+ else
454
+ Makit::Logging.default_logger.info("Successfully migrated #{migrated_count} package(s) from '#{source_name}' to '#{destination_name}'")
455
+ end
456
+
457
+ migrated_count
458
+ end
459
+ end
460
+ end