Class: Makit::Version
- Inherits:
-
Object
- Object
- Makit::Version
- Defined in:
- lib/makit/version.rb,
lib/makit/version_util.rb
Overview
Version utility class for handling version comparisons
Class Method Summary collapse
-
.bump ⇒ String
Bump the patch version in the SSOT version file.
-
.detect_from_file(filename, regex) ⇒ String?
Detect version using a regex pattern in a specific file.
-
.extract_version_from_ssot_file(file_path) ⇒ Object
Extract version from SSOT file based on file type.
-
.find_project_root(start_dir) ⇒ Object
Find project root by looking for common markers.
-
.find_ssot_version_file(project_root) ⇒ String?
Find the SSOT version file in the project root.
-
.get_highest_version(versions) ⇒ String?
Get the highest version from a list of version strings.
-
.get_version_from_file(path) ⇒ String
Extract version number from a file based on its extension.
-
.info ⇒ nil
Display version information for the current project.
-
.parse(version_string) ⇒ Hash
Parse a semantic version string into its components.
-
.set_version_in_file(filename, version) ⇒ nil
Update version number in a file based on its extension.
-
.set_version_in_files(glob_pattern, version) ⇒ nil
Update version number in multiple files matching a glob pattern.
-
.version ⇒ String
Attempt to detect the version from the SSOT (Single Source of Truth) version file Uses the same logic as Makit::Version.info to find version files in priority order: *.gemspec, Directory.Build.props, Cargo.toml, package.json, pyproject.toml, pom.xml Falls back to makit.gemspec only if we’re in the makit gem directory.
Class Method Details
.bump ⇒ String
Bump the patch version in the SSOT version file
Finds the Single Source of Truth (SSOT) version file in the project root, reads the current version, increments the patch version, and updates the file.
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/makit/version.rb', line 368 def self.bump # Find the SSOT version file project_root = begin require_relative "directories" unless defined?(Makit::Directories) Makit::Directories::PROJECT_ROOT rescue NameError, LoadError find_project_root(Dir.pwd) end raise "Project root not found" if project_root.nil? || !Dir.exist?(project_root) version_file = find_ssot_version_file(project_root) if version_file.nil? warn " Warning: No version file found in project root: #{project_root}" warn " You may define a constant VERSION_FILE to manually set the version file path" raise "Cannot bump version: no version file found" end # Read current version current_version = extract_version_from_ssot_file(version_file) raise "Version not found in #{version_file}" if current_version.nil? # Parse and bump patch version parsed = parse(current_version) new_version = "#{parsed[:major]}.#{parsed[:minor]}.#{parsed[:patch] + 1}" # Update the version file set_version_in_file(version_file, new_version) # Verify the update updated_version = extract_version_from_ssot_file(version_file) if updated_version != new_version raise "Version bump failed: expected #{new_version}, got #{updated_version}" end new_version end |
.detect_from_file(filename, regex) ⇒ String?
Detect version using a regex pattern in a specific file
225 226 227 228 229 230 |
# File 'lib/makit/version.rb', line 225 def self.detect_from_file(filename, regex) raise "unable to find version in #{filename}" unless File.exist?(filename) match = File.read(filename).match(regex) match.captures[0] if !match.nil? && match.captures.length.positive? end |
.extract_version_from_ssot_file(file_path) ⇒ Object
Extract version from SSOT file based on file type
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/makit/version.rb', line 462 def self.extract_version_from_ssot_file(file_path) case File.basename(file_path) when /\.gemspec$/ # Extract from gemspec: spec.version = "x.y.z" content = File.read(file_path) match = content.match(/spec\.version\s*=\s*["']([^"']+)["']/) match ? match[1] : nil when "Directory.Build.props" # Extract from Directory.Build.props: <Version>x.y.z</Version> content = File.read(file_path) match = content.match(%r{<Version>([^<]+)</Version>}) match ? match[1] : nil when "Cargo.toml" # Extract from Cargo.toml: version = "x.y.z" content = File.read(file_path) match = content.match(/version\s*=\s*["']([^"']+)["']/) match ? match[1] : nil when "package.json" # Extract from package.json: "version": "x.y.z" require "json" json = JSON.parse(File.read(file_path)) json["version"] when "pyproject.toml" # Extract from pyproject.toml: version = "x.y.z" (in [project] or [tool.poetry] section) content = File.read(file_path) # Try [project] section first match = content.match(/\[project\]\s*version\s*=\s*["']([^"']+)["']/) return match[1] if match # Try [tool.poetry] section match = content.match(/\[tool\.poetry\]\s*version\s*=\s*["']([^"']+)["']/) match ? match[1] : nil when "pom.xml" # Extract from pom.xml: <version>x.y.z</version> content = File.read(file_path) match = content.match(%r{<version>([^<]+)</version>}) match ? match[1] : nil else nil end end |
.find_project_root(start_dir) ⇒ Object
Find project root by looking for common markers
448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/makit/version.rb', line 448 def self.find_project_root(start_dir) current = File.(start_dir) root = File.("/") while current != root markers = ["Rakefile", "rakefile.rb", ".gitignore", ".git"] return current if markers.any? { |marker| File.exist?(File.join(current, marker)) } current = File.dirname(current) end nil end |
.find_ssot_version_file(project_root) ⇒ String?
Find the SSOT version file in the project root
Checks for VERSION_FILE constant first (if defined), then searches for common version files.
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/makit/version.rb', line 414 def self.find_ssot_version_file(project_root) # Normalize project_root for file operations (Ruby's File methods work with forward slashes) normalized_root = project_root.gsub(/\\/, "/") # Check for manually defined VERSION_FILE constant first if defined?(VERSION_FILE) && !VERSION_FILE.nil? version_file = File.(VERSION_FILE, normalized_root) return version_file if File.exist?(version_file) warn " Warning: VERSION_FILE constant points to non-existent file: #{VERSION_FILE}" end # Priority order for version files (SSOT) version_file_patterns = [ "*.gemspec", # Ruby gems "Directory.Build.props", # .NET projects "Cargo.toml", # Rust projects "package.json", # Node.js projects "pyproject.toml", # Python projects "pom.xml" # Maven/Java projects ] version_file_patterns.each do |pattern| matches = Dir.glob(File.join(normalized_root, pattern)) next if matches.empty? # For gemspec, prefer the one matching the project name or take the first version_file = matches.first return version_file if version_file end nil end |
.get_highest_version(versions) ⇒ String?
Get the highest version from a list of version strings
187 188 189 |
# File 'lib/makit/version.rb', line 187 def self.get_highest_version(versions) versions.max { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) } end |
.get_version_from_file(path) ⇒ String
Extract version number from a file based on its extension
Supports multiple file formats:
-
.csproj files: ‘<Version>x.y.z</Version>`
-
.wxs files: ‘Version=“x.y.z”`
-
.yml files: ‘VERSION: “x.y.z”`
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/makit/version.rb', line 201 def self.get_version_from_file(path) raise "file #{path}does not exist" unless File.exist?(path) extension = File.extname(path) case extension when ".csproj" Makit::Version.detect_from_file(path, /<Version>([-\w\d.]+)</) when ".wxs" Makit::Version.detect_from_file(path, / Version="([\d.]+)"/) when ".yml" Makit::Version.detect_from_file(path, /VERSION:\s*["']?([\d.]+)["']?/) when ".rb" Makit::Version.detect_from_file(path, /VERSION = "([\d.]+)"/) else raise "unrecognized file type" end end |
.info ⇒ nil
Display version information for the current project
Finds the Single Source of Truth (SSOT) version file in the project root and displays the file path and current version value.
Searches for common version files in priority order:
-
*.gemspec (Ruby gems)
-
Directory.Build.props (.NET projects)
-
Cargo.toml (Rust projects)
-
package.json (Node.js projects)
-
pyproject.toml (Python projects)
-
pom.xml (Maven/Java projects)
If no version file is found, issues a warning and suggests defining a VERSION_FILE constant to manually specify the version file path.
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/makit/version.rb', line 315 def self.info # Access Directories lazily to avoid circular dependency project_root = begin require_relative "directories" unless defined?(Makit::Directories) Makit::Directories::PROJECT_ROOT rescue NameError, LoadError # Fallback: try to find project root from current directory find_project_root(Dir.pwd) end raise "Project root not found" if project_root.nil? || !Dir.exist?(project_root) version_file = find_ssot_version_file(project_root) if version_file.nil? warn " Warning: No version file found in project root: #{project_root}" warn " You may define a constant VERSION_FILE to manually set the version file path" return end # Extract version based on file type version = extract_version_from_ssot_file(version_file) raise "Version not found in #{version_file}" if version.nil? # Display information with relative path (Windows-safe path handling) # Normalize both paths to forward slashes for comparison, then convert back if needed normalized_version_file = version_file.gsub(/\\/, "/") normalized_project_root = project_root.gsub(/\\/, "/") relative_path = normalized_version_file.sub(normalized_project_root + "/", "") puts " Version File: #{relative_path}" puts " Version: #{version}" end |
.parse(version_string) ⇒ Hash
Parse a semantic version string into its components
Parses a version string following Semantic Versioning (SemVer) format: MAJOR.MINOR.PATCH
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/makit/version.rb', line 146 def self.parse(version_string) parts = version_string.split(".") if parts.length < 3 raise "Invalid version format: #{version_string}. Expected format: Major.Minor.Patch" end major = parts[0].to_i minor = parts[1].to_i patch = parts[2].to_i # Handle pre-release suffixes (e.g., "0.1.0-preview" -> patch = 0, suffix = "-preview") # Note: If suffix contains dots (e.g., "1.2.3-preview.1"), the split by "." will separate # the patch and suffix parts, so only the part before the first "-" in parts[2] is used as patch. patch_part = parts[2] if patch_part.include?("-") patch, suffix = patch_part.split("-", 2) patch = patch.to_i suffix = "-#{suffix}" else suffix = "" end { major: major, minor: minor, patch: patch, suffix: suffix } end |
.set_version_in_file(filename, version) ⇒ nil
Update version number in a file based on its extension
Supports updating versions in multiple file formats:
-
.yml files
-
.gemspec files
-
.csproj files
-
.nuspec files
-
.wxs files
-
.toml files
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/makit/version.rb', line 245 def self.set_version_in_file(filename, version) text = File.read(filename) # VERSION = "0.0.138rake" (.rb file) new_text = text new_text = new_text.gsub(/VERSION:\s?['|"]([.\d]+)['|"]/, "VERSION: \"#{version}\"") if filename.include?(".yml") new_text = new_text.gsub(/spec\.version\s*=\s*['"]([^'"]+)['"]/, "spec.version = '#{version}'") if filename.include?(".gemspec") # Handle Directory.Build.props and .csproj files (both use <Version> tag) if filename.include?("Directory.Build.props") || filename.include?(".csproj") new_text = new_text.gsub(/<Version>([-\w\d.]+)</, "<Version>#{version}<") end new_text = new_text.gsub(/<version>([-\w\d.]+)</, "<version>#{version}<") if filename.include?(".nuspec") new_text = new_text.gsub(/ Version="([\d.]+)"/, " Version=\"#{version}\"") if filename.include?(".wxs") new_text = new_text.gsub(/VERSION = "([\d.]+)"/, "VERSION = \"#{version}\"") if filename.include?(".rb") # Handle Cargo.toml, pyproject.toml, and other .toml files if filename.include?(".toml") new_text = new_text.gsub(/version\s+=\s+['"]([\w.]+)['"]/, "version=\"#{version}\"") end # Handle package.json if filename.include?("package.json") require "json" json = JSON.parse(new_text) json["version"] = version new_text = JSON.pretty_generate(json) end # Handle pom.xml if filename.include?("pom.xml") new_text = new_text.gsub(%r{<version>([^<]+)</version>}, "<version>#{version}</version>") end File.write(filename, new_text) if new_text != text end |
.set_version_in_files(glob_pattern, version) ⇒ nil
Update version number in multiple files matching a glob pattern
281 282 283 284 285 |
# File 'lib/makit/version.rb', line 281 def self.set_version_in_files(glob_pattern, version) Dir.glob(glob_pattern).each do |filename| set_version_in_file(filename, version) end end |
.version ⇒ String
Attempt to detect the version from the SSOT (Single Source of Truth) version file Uses the same logic as Makit::Version.info to find version files in priority order: *.gemspec, Directory.Build.props, Cargo.toml, package.json, pyproject.toml, pom.xml Falls back to makit.gemspec only if we’re in the makit gem directory
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/makit/version.rb', line 71 def self.version # Try to find version file in project root using SSOT logic (same as Makit::Version.info) project_root = begin require_relative "directories" unless defined?(Makit::Directories) Makit::Directories::PROJECT_ROOT rescue NameError, LoadError find_project_root(Dir.pwd) end # Use SSOT version file detection (supports multiple file types) if project_root && Dir.exist?(project_root) version_file = find_ssot_version_file(project_root) if version_file && File.exist?(version_file) version = extract_version_from_ssot_file(version_file) return version if version end end # Fallback to makit.gemspec (for the makit gem itself) # Check if makit.gemspec exists relative to this file makit_gemspec = File.join(File.dirname(__FILE__), "..", "..", "makit.gemspec") makit_gemspec = File.(makit_gemspec) # Use makit.gemspec if: # 1. It exists, AND # 2. Either we're in the makit gem directory (project_root matches), OR # project_root is nil (running outside a project context, likely from installed gem) if File.exist?(makit_gemspec) makit_gem_dir = File.dirname(makit_gemspec) use_makit_gemspec = if project_root # If we have a project root, only use makit.gemspec if we're in the makit gem directory File.(project_root) == File.(makit_gem_dir) else # If no project root, check if current directory is makit gem or if makit.gemspec is nearby # This handles the case when running from installed gem or outside project context Dir.pwd == makit_gem_dir || File.(Dir.pwd).start_with?(File.(makit_gem_dir)) end if use_makit_gemspec gemspec_content = File.read(makit_gemspec) match = gemspec_content.match(/spec\.version\s*=\s*["']([^"']+)["']/) raise "Version not found in gemspec file" if match.nil? return match[1] end end # If no version file found, return default version (for non-gem projects) "0.0.0" end |