makit 0.0.158 → 0.0.161

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ef7398a9e375d733986e441439fe559ef13ccf112013bcf17c453099a963232
4
- data.tar.gz: 60467798aa0a1d29a802a9f164e4b91852f35824f3f9d8f24b3b9b85c80a967a
3
+ metadata.gz: 816fae3f93930ba4fd38d5862f5b716ef7f367f0b15718e3cbef6b825415fd8f
4
+ data.tar.gz: 21e8e83a05ef9ce2ba3748a8ed824b85eb6e436693354ea8ccd414bddce4d0e6
5
5
  SHA512:
6
- metadata.gz: f73335164b833168eaf3e11770aa3f5eba1a7d563a5a8d98dd7e93e5962826e8e71a4f39c337e6e5eca8c1a9f50cec83f0f9b34c7c14c02cb14ceb0b8e7961c6
7
- data.tar.gz: 9198e9f1b17101964bc405bad3deb9e253a00d71c3d89935f35cf194dbc082fbeb52e362ff8498975c5544ba04b540b64cc3e32683d514abaa34e42d49c5743b
6
+ metadata.gz: 0f80e9b880100024ccc5411292a206bd5ecd504c86f57ed0c2c90ba9c3a1e1d849f0ccc1044b00f5eedd1b2cf81d6a2c12565becabfee7426da2144a7e924402
7
+ data.tar.gz: a3af55207d9e4e018c15f1659cc3dc4ff1727b4d9098da7c35f36ccae2745ad051828166be517e57e98c02c4fd79b94c91924764002127b6f4db485d015b90d3
data/lib/makit/git/cli.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "open3"
4
+
3
5
  # This module provides classes for the Makit gem.
4
6
  module Makit
5
7
  class Git
@@ -22,11 +24,15 @@ module Makit
22
24
  return unless Repository.git_repo? && !Repository.detached
23
25
 
24
26
  "git pull".try
27
+ # Fetch tags explicitly to ensure we have the latest remote tag information
28
+ "git fetch --tags origin".try
25
29
  "git push origin".try
26
30
 
27
31
  # Check which tags need to be pushed (exist locally but not remotely)
28
32
  local_tags = `git tag -l`.strip.split("\n").reject(&:empty?)
29
- remote_tags_output = `git ls-remote --tags origin 2>/dev/null`.strip
33
+ # Use Open3 for cross-platform stderr handling (Windows doesn't support 2>/dev/null)
34
+ remote_tags_output, _stderr, _status = Open3.capture3("git", "ls-remote", "--tags", "origin")
35
+ remote_tags_output = remote_tags_output.strip
30
36
  remote_tags = if remote_tags_output.empty?
31
37
  []
32
38
  else
@@ -43,9 +49,28 @@ module Makit
43
49
  if tags_to_push.empty?
44
50
  Makit::Logging.default_logger.info("All tags already exist on remote, skipping tag push")
45
51
  else
46
- Makit::Logging.default_logger.info("Pushing #{tags_to_push.length} tag(s) to remote: #{tags_to_push.join(', ')}")
47
- tags_to_push.each do |tag|
48
- "git push origin #{tag}".try
52
+ # Limit to 5 tags per sync to avoid overwhelming the remote
53
+ tags_to_push_limited = tags_to_push.first(5)
54
+ remaining_count = tags_to_push.length - tags_to_push_limited.length
55
+
56
+ if remaining_count.positive?
57
+ Makit::Logging.default_logger.info("Pushing #{tags_to_push_limited.length} of #{tags_to_push.length} tag(s) to remote (limited to 5 per sync): #{tags_to_push_limited.join(', ')}")
58
+ Makit::Logging.default_logger.info("#{remaining_count} tag(s) will be pushed in subsequent sync operations")
59
+ else
60
+ Makit::Logging.default_logger.info("Pushing #{tags_to_push_limited.length} tag(s) to remote: #{tags_to_push_limited.join(', ')}")
61
+ end
62
+
63
+ tags_to_push_limited.each do |tag|
64
+ # Double-check that tag doesn't exist on remote before pushing
65
+ # This prevents pushing tags that were added to remote between the ls-remote check and now
66
+ # Use Open3 for cross-platform stderr handling (Windows doesn't support 2>/dev/null)
67
+ remote_tag_check, _stderr, _status = Open3.capture3("git", "ls-remote", "--tags", "origin", "refs/tags/#{tag}")
68
+ remote_tag_check = remote_tag_check.strip
69
+ if remote_tag_check.empty?
70
+ "git push origin #{tag}".try
71
+ else
72
+ Makit::Logging.default_logger.info("Tag #{tag} already exists on remote, skipping push")
73
+ end
49
74
  end
50
75
  end
51
76
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open3"
5
+
6
+ module Makit
7
+ module NugetCache
8
+ class Error < StandardError; end
9
+
10
+ # Purges a package (and optionally a version) from the NuGet global packages cache.
11
+ #
12
+ # Examples:
13
+ # Makit::NugetCache.purge(package_id: "Newtonsoft.Json")
14
+ # Makit::NugetCache.purge(package_id: "Newtonsoft.Json", version: "13.0.3")
15
+ #
16
+ # Returns a Hash with keys:
17
+ # :global_packages_dir, :package_dir, :target_dir, :purged, :reason (if not purged)
18
+ #
19
+ def self.purge(package_id:, version: nil, dotnet: "dotnet", dry_run: false)
20
+ raise ArgumentError, "package_id is required" if package_id.to_s.strip.empty?
21
+
22
+ global_dir = resolve_global_packages_dir(dotnet: dotnet)
23
+ package_dir = File.join(global_dir, normalize_package_id(package_id))
24
+
25
+ target_dir =
26
+ if version && !version.to_s.strip.empty?
27
+ File.join(package_dir, version.to_s.strip)
28
+ else
29
+ package_dir
30
+ end
31
+
32
+ result = {
33
+ global_packages_dir: global_dir,
34
+ package_dir: package_dir,
35
+ target_dir: target_dir,
36
+ purged: false
37
+ }
38
+
39
+ unless Dir.exist?(target_dir)
40
+ result[:reason] = "Target directory does not exist"
41
+ return result
42
+ end
43
+
44
+ if dry_run
45
+ result[:reason] = "dry_run"
46
+ return result
47
+ end
48
+
49
+ assert_within!(target_dir, global_dir)
50
+
51
+ FileUtils.rm_rf(target_dir)
52
+ result[:purged] = true
53
+ result
54
+ rescue Error, ArgumentError => e
55
+ result ||= {}
56
+ result[:purged] = false
57
+ result[:reason] = e.message
58
+ result
59
+ end
60
+
61
+ # -----------------------
62
+ # Internals
63
+ # -----------------------
64
+
65
+ def self.normalize_package_id(package_id)
66
+ # NuGet global-packages directory uses lowercase package IDs on disk
67
+ package_id.to_s.strip.downcase
68
+ end
69
+ private_class_method :normalize_package_id
70
+
71
+ def self.resolve_global_packages_dir(dotnet:)
72
+ stdout, _stderr, status =
73
+ Open3.capture3(dotnet.to_s, "nuget", "locals", "global-packages", "--list")
74
+
75
+ if status.success?
76
+ path = parse_global_packages_dir(stdout)
77
+ return path if path && !path.empty?
78
+ end
79
+
80
+ # Fallback 1: NUGET_PACKAGES environment variable
81
+ env_path = ENV["NUGET_PACKAGES"]
82
+ return File.expand_path(env_path) if env_path && !env_path.strip.empty?
83
+
84
+ # Fallback 2: conventional default
85
+ home = Dir.home
86
+ raise Error, "Unable to resolve home directory" if home.nil? || home.strip.empty?
87
+
88
+ File.join(home, ".nuget", "packages")
89
+ rescue Errno::ENOENT
90
+ # dotnet not available; fall back to env/default
91
+ env_path = ENV["NUGET_PACKAGES"]
92
+ return File.expand_path(env_path) if env_path && !env_path.strip.empty?
93
+
94
+ File.join(Dir.home, ".nuget", "packages")
95
+ end
96
+ private_class_method :resolve_global_packages_dir
97
+
98
+ def self.parse_global_packages_dir(output)
99
+ line = output.to_s.lines.find { |l| l.strip.start_with?("global-packages:") }
100
+ return nil unless line
101
+
102
+ path = line.split("global-packages:", 2).last.to_s.strip
103
+ return nil if path.empty?
104
+
105
+ File.expand_path(path)
106
+ end
107
+ private_class_method :parse_global_packages_dir
108
+
109
+ def self.assert_within!(target_dir, root_dir)
110
+ target = File.expand_path(target_dir)
111
+ root = File.expand_path(root_dir)
112
+
113
+ if root.strip.empty? || root == File::SEPARATOR
114
+ raise Error, "Refusing to purge: invalid global packages directory '#{root_dir}'"
115
+ end
116
+
117
+ unless target.start_with?(root.end_with?(File::SEPARATOR) ? root : "#{root}#{File::SEPARATOR}")
118
+ raise Error, "Refusing to purge '#{target}': not within global packages dir '#{root}'"
119
+ end
120
+ end
121
+ private_class_method :assert_within!
122
+ end
123
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Makit
6
+ module PortUtility
7
+ class Error < StandardError; end
8
+
9
+ # Ensures a port is free by killing any process using it.
10
+ #
11
+ # Examples:
12
+ # Makit::PortUtility.ensure_port_free(5261)
13
+ # Makit::PortUtility.ensure_port_free(5261, verbose: true)
14
+ #
15
+ # Returns true if the port is now free, false if it couldn't be freed.
16
+ #
17
+ def self.ensure_port_free(port, verbose: false)
18
+ raise ArgumentError, "port must be a positive integer" unless port.is_a?(Integer) && port > 0
19
+
20
+ return true unless port_in_use?(port)
21
+
22
+ puts "Port #{port} is in use. Attempting to free it..." if verbose
23
+
24
+ killed = kill_process_on_port(port, verbose: verbose)
25
+
26
+ if killed
27
+ # Wait a moment for the port to be released
28
+ sleep(1)
29
+ # Verify port is now free
30
+ unless port_in_use?(port)
31
+ puts "Port #{port} is now free." if verbose
32
+ return true
33
+ else
34
+ puts "Warning: Port #{port} is still in use after killing process." if verbose
35
+ return false
36
+ end
37
+ else
38
+ puts "Warning: Could not kill process on port #{port}." if verbose
39
+ return false
40
+ end
41
+ rescue Error, ArgumentError => e
42
+ puts "Error freeing port #{port}: #{e.message}" if verbose
43
+ false
44
+ end
45
+
46
+ # -----------------------
47
+ # Internals
48
+ # -----------------------
49
+
50
+ def self.port_in_use?(port)
51
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
52
+ # Windows: Use netstat
53
+ stdout, _stderr, status = Open3.capture3("netstat", "-ano")
54
+ return false unless status.success?
55
+ stdout.lines.any? { |line| line.include?(":#{port}") && line.include?("LISTENING") }
56
+ else
57
+ # Unix/macOS: Use lsof
58
+ stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
59
+ status.success? && !stdout.strip.empty?
60
+ end
61
+ rescue Errno::ENOENT
62
+ # Command not found - assume port is not in use
63
+ false
64
+ end
65
+ private_class_method :port_in_use?
66
+
67
+ def self.kill_process_on_port(port, verbose: false)
68
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
69
+ # Windows: Use netstat to find PID, then taskkill
70
+ stdout, _stderr, status = Open3.capture3("netstat", "-ano")
71
+ return false unless status.success?
72
+
73
+ pids = []
74
+ stdout.lines.each do |line|
75
+ if line.include?(":#{port}") && line.include?("LISTENING")
76
+ parts = line.split
77
+ pid = parts.last
78
+ pids << pid if pid && pid.match?(/^\d+$/)
79
+ end
80
+ end
81
+
82
+ killed_any = false
83
+ pids.uniq.each do |pid|
84
+ begin
85
+ stdout, _stderr, status = Open3.capture3("taskkill", "/F", "/PID", pid)
86
+ if status.success?
87
+ puts "Killed process #{pid} on port #{port}" if verbose
88
+ killed_any = true
89
+ end
90
+ rescue Errno::ENOENT
91
+ # taskkill not found
92
+ return false
93
+ end
94
+ end
95
+
96
+ killed_any
97
+ else
98
+ # Unix/macOS: Use lsof to find PID, then kill
99
+ stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
100
+ return false unless status.success?
101
+ return true if stdout.strip.empty? # Port is already free
102
+
103
+ pids = stdout.strip.split("\n").map(&:strip).reject(&:empty?)
104
+ return false if pids.empty?
105
+
106
+ killed_any = false
107
+ pids.each do |pid|
108
+ begin
109
+ stdout, _stderr, status = Open3.capture3("kill", "-9", pid)
110
+ if status.success?
111
+ puts "Killed process #{pid} on port #{port}" if verbose
112
+ killed_any = true
113
+ end
114
+ rescue Errno::ENOENT
115
+ # kill not found
116
+ return false
117
+ end
118
+ end
119
+
120
+ killed_any
121
+ end
122
+ rescue Errno::ENOENT
123
+ # Command not found
124
+ false
125
+ end
126
+ private_class_method :kill_process_on_port
127
+ end
128
+ end
@@ -6,12 +6,15 @@ task :update do
6
6
  # are there any *.sln files?
7
7
  Dir.glob("**/*.sln").each do |sln_file|
8
8
  "dotnet sln migrate #{sln_file}".run
9
- FileUtils.rm_f(sln_file) if File.exist?(sln_file.gsub(".sln", ".slnx"))
9
+ #FileUtils.rm_f(sln_file) if File.exist?(sln_file.gsub(".sln", ".slnx"))
10
10
  end
11
11
  "dotnet tool update --global dotnet-outdated-tool".run
12
- Dir.glob("**/*.slnx").each do |slnx_file|
13
- "dotnet outdated #{slnx_file} --upgrade:Auto".run
14
- end
12
+ #Dir.glob("**/*.slnx").each do |slnx_file|
13
+ # "dotnet outdated #{slnx_file} --upgrade:Auto".run
14
+ #end
15
+ #Dir.glob("**/*.sln").each do |sln_file|
16
+ # "dotnet outdated #{sln_file} --upgrade:Auto".run
17
+ #end
15
18
  Dir.glob("**/*.csproj").each do |csproj_file|
16
19
  "dotnet outdated #{csproj_file} --upgrade:Auto".run
17
20
  end
data/lib/makit/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Makit
4
4
  # Static version for now to avoid circular dependency issues
5
- #VERSION = "0.0.158"
5
+ #VERSION = "0.0.161"
6
6
 
7
7
  # Version management utilities for various file formats
8
8
  #
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "find"
5
+ require "zip"
6
+
7
+ module Makit
8
+ module ZipUtility
9
+ class Error < StandardError; end
10
+
11
+ # Creates a zip file from a directory, preserving directory structure.
12
+ # Excludes files matching the exclude_pattern from the archive.
13
+ #
14
+ # Examples:
15
+ # Makit::ZipUtility.create_from_directory(
16
+ # source_dir: "artifacts/Musco.Aim3.AspNet.Host",
17
+ # zip_path: "artifacts/Musco.Aim3.AspNet.Host.zip"
18
+ # )
19
+ # Makit::ZipUtility.create_from_directory(
20
+ # source_dir: "artifacts/build",
21
+ # zip_path: "artifacts/build.zip",
22
+ # exclude_pattern: "*.{pdb,log}"
23
+ # )
24
+ #
25
+ # @param source_dir [String] The directory to zip
26
+ # @param zip_path [String] The output zip file path
27
+ # @param exclude_pattern [String] File pattern to exclude (default: "*.pdb")
28
+ # Supports glob patterns like "*.pdb", "*.{pdb,log}", "**/*.tmp"
29
+ # @raise [Error] If source directory doesn't exist or zip creation fails
30
+ def self.create_from_directory(source_dir:, zip_path:, exclude_pattern: "*.pdb")
31
+ source_dir = File.expand_path(source_dir)
32
+ zip_path = File.expand_path(zip_path)
33
+
34
+ unless Dir.exist?(source_dir)
35
+ raise Error, "Source directory does not exist: #{source_dir}"
36
+ end
37
+
38
+ # Delete existing zip if it exists
39
+ File.delete(zip_path) if File.exist?(zip_path)
40
+
41
+ # Use RubyZip gem (cross-platform, no shell commands needed)
42
+ create_zip_with_rubyzip(source_dir, zip_path, exclude_pattern)
43
+
44
+ unless File.exist?(zip_path)
45
+ raise Error, "Failed to create zip file: #{zip_path}"
46
+ end
47
+
48
+ zip_path
49
+ end
50
+
51
+ # -----------------------
52
+ # Internals
53
+ # -----------------------
54
+
55
+ def self.create_zip_with_rubyzip(source_dir, zip_path, exclude_pattern)
56
+ source_dir_normalized = File.expand_path(source_dir)
57
+
58
+ ::Zip::File.open(zip_path, ::Zip::File::CREATE) do |zipfile|
59
+ Find.find(source_dir_normalized) do |file_path|
60
+ next unless File.file?(file_path)
61
+
62
+ # Get relative path from source directory (normalize separators)
63
+ relative_path = file_path.sub(/^#{Regexp.escape(source_dir_normalized)}[\/\\]?/, "")
64
+ # Normalize to forward slashes for zip file entries (zip standard)
65
+ relative_path = relative_path.gsub("\\", "/")
66
+
67
+ # Skip if file matches exclude pattern
68
+ next if should_exclude?(relative_path, exclude_pattern)
69
+
70
+ # Add file to zip with relative path
71
+ zipfile.add(relative_path, file_path)
72
+ end
73
+ end
74
+ rescue ::Zip::Error => e
75
+ raise Error, "Failed to create zip file: #{e.message}"
76
+ rescue StandardError => e
77
+ raise Error, "Unexpected error creating zip file: #{e.message}"
78
+ end
79
+ private_class_method :create_zip_with_rubyzip
80
+
81
+ def self.should_exclude?(file_path, exclude_pattern)
82
+ return false if exclude_pattern.nil? || exclude_pattern.strip.empty?
83
+
84
+ # Support multiple patterns separated by commas
85
+ patterns = exclude_pattern.split(",").map(&:strip)
86
+
87
+ patterns.any? do |pattern|
88
+ # Use File.fnmatch for glob pattern matching
89
+ # Match against just the filename or full relative path
90
+ filename = File.basename(file_path)
91
+ File.fnmatch?(pattern, filename, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
92
+ File.fnmatch?(pattern, file_path, File::FNM_PATHNAME | File::FNM_DOTMATCH)
93
+ end
94
+ end
95
+ private_class_method :should_exclude?
96
+ end
97
+ end
data/lib/makit.rb CHANGED
@@ -88,6 +88,9 @@ require_relative "makit/mp/string_mp"
88
88
  require_relative "makit/git"
89
89
  require_relative "makit/protoc"
90
90
  require_relative "makit/zip"
91
+ require_relative "makit/nuget_cache"
92
+ require_relative "makit/port_utility"
93
+ require_relative "makit/zip_utility"
91
94
 
92
95
  # Load services modules
93
96
  # Note: RepositoryManager intermediate classes removed after Phase 4 cleanup
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: makit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.158
4
+ version: 0.0.161
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lou Parslow
@@ -341,9 +341,11 @@ files:
341
341
  - lib/makit/mp/project_mp.rb
342
342
  - lib/makit/mp/string_mp.rb
343
343
  - lib/makit/nuget.rb
344
+ - lib/makit/nuget_cache.rb
344
345
  - lib/makit/podman/podman.rb
345
346
  - lib/makit/podman/podman_service_impl.rb
346
347
  - lib/makit/port.rb
348
+ - lib/makit/port_utility.rb
347
349
  - lib/makit/process.rb
348
350
  - lib/makit/protoc.rb
349
351
  - lib/makit/rake.rb
@@ -404,6 +406,7 @@ files:
404
406
  - lib/makit/wix.rb
405
407
  - lib/makit/yaml.rb
406
408
  - lib/makit/zip.rb
409
+ - lib/makit/zip_utility.rb
407
410
  homepage: https://github.com/yourusername/makit
408
411
  licenses:
409
412
  - MIT