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