makit 0.0.157 → 0.0.158
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 +4 -4
- data/README.md +41 -41
- data/exe/makit +5 -5
- data/lib/makit/apache.rb +28 -28
- data/lib/makit/auto.rb +48 -48
- data/lib/makit/azure/blob_storage.rb +257 -257
- data/lib/makit/azure/cli.rb +284 -284
- data/lib/makit/cli/base.rb +17 -17
- data/lib/makit/cli/build_commands.rb +500 -500
- data/lib/makit/cli/generators/base_generator.rb +74 -74
- data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
- data/lib/makit/cli/generators/generator_factory.rb +49 -49
- data/lib/makit/cli/generators/node_generator.rb +50 -50
- data/lib/makit/cli/generators/ruby_generator.rb +77 -77
- data/lib/makit/cli/generators/rust_generator.rb +50 -50
- data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
- data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
- data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
- data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -41
- data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
- data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
- data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
- data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
- data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
- data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
- data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
- data/lib/makit/cli/main.rb +78 -78
- data/lib/makit/cli/pipeline_commands.rb +311 -311
- data/lib/makit/cli/project_commands.rb +868 -868
- data/lib/makit/cli/repository_commands.rb +661 -661
- data/lib/makit/cli/strategy_commands.rb +207 -207
- data/lib/makit/cli/utility_commands.rb +521 -521
- data/lib/makit/commands/factory.rb +359 -359
- data/lib/makit/commands/middleware/base.rb +73 -73
- data/lib/makit/commands/middleware/cache.rb +248 -248
- data/lib/makit/commands/middleware/command_logger.rb +312 -312
- data/lib/makit/commands/middleware/validator.rb +269 -269
- data/lib/makit/commands/request.rb +316 -316
- data/lib/makit/commands/result.rb +323 -323
- data/lib/makit/commands/runner.rb +386 -386
- data/lib/makit/commands/strategies/base.rb +171 -171
- data/lib/makit/commands/strategies/child_process.rb +162 -162
- data/lib/makit/commands/strategies/factory.rb +136 -136
- data/lib/makit/commands/strategies/synchronous.rb +139 -139
- data/lib/makit/commands.rb +50 -50
- data/lib/makit/configuration/dotnet_project.rb +48 -48
- data/lib/makit/configuration/gitlab_helper.rb +61 -61
- data/lib/makit/configuration/project.rb +292 -292
- data/lib/makit/configuration/rakefile_helper.rb +43 -43
- data/lib/makit/configuration/step.rb +34 -34
- data/lib/makit/configuration/timeout.rb +74 -74
- data/lib/makit/configuration.rb +21 -21
- data/lib/makit/content/default_gitignore.rb +7 -7
- data/lib/makit/content/default_gitignore.txt +225 -225
- data/lib/makit/content/default_rakefile.rb +13 -13
- data/lib/makit/content/gem_rakefile.rb +16 -16
- data/lib/makit/context.rb +1 -1
- data/lib/makit/data.rb +49 -49
- data/lib/makit/directories.rb +170 -170
- data/lib/makit/directory.rb +262 -262
- data/lib/makit/docs/files.rb +89 -89
- data/lib/makit/docs/rake.rb +102 -102
- data/lib/makit/dotnet/cli.rb +69 -69
- data/lib/makit/dotnet/project.rb +217 -217
- data/lib/makit/dotnet/solution.rb +38 -38
- data/lib/makit/dotnet/solution_classlib.rb +239 -239
- data/lib/makit/dotnet/solution_console.rb +264 -264
- data/lib/makit/dotnet/solution_maui.rb +354 -354
- data/lib/makit/dotnet/solution_wasm.rb +275 -275
- data/lib/makit/dotnet/solution_wpf.rb +304 -304
- data/lib/makit/dotnet.rb +102 -102
- data/lib/makit/email.rb +90 -90
- data/lib/makit/environment.rb +142 -142
- data/lib/makit/examples/runner.rb +370 -370
- data/lib/makit/exceptions.rb +45 -45
- data/lib/makit/fileinfo.rb +32 -32
- data/lib/makit/files.rb +43 -43
- data/lib/makit/gems.rb +40 -40
- data/lib/makit/git/cli.rb +78 -54
- data/lib/makit/git/repository.rb +100 -100
- data/lib/makit/git.rb +104 -104
- data/lib/makit/gitlab/pipeline.rb +857 -857
- data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
- data/lib/makit/gitlab_runner.rb +59 -59
- data/lib/makit/humanize.rb +218 -218
- data/lib/makit/indexer.rb +47 -47
- data/lib/makit/io/filesystem.rb +111 -111
- data/lib/makit/io/filesystem_service_impl.rb +337 -337
- data/lib/makit/lint.rb +212 -212
- data/lib/makit/logging/configuration.rb +309 -309
- data/lib/makit/logging/format_registry.rb +84 -84
- data/lib/makit/logging/formatters/base.rb +39 -39
- data/lib/makit/logging/formatters/console_formatter.rb +140 -140
- data/lib/makit/logging/formatters/json_formatter.rb +65 -65
- data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
- data/lib/makit/logging/formatters/text_formatter.rb +64 -64
- data/lib/makit/logging/log_request.rb +119 -119
- data/lib/makit/logging/logger.rb +199 -199
- data/lib/makit/logging/sinks/base.rb +91 -91
- data/lib/makit/logging/sinks/console.rb +72 -72
- data/lib/makit/logging/sinks/file_sink.rb +92 -92
- data/lib/makit/logging/sinks/structured.rb +123 -123
- data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
- data/lib/makit/logging.rb +578 -578
- data/lib/makit/markdown.rb +75 -75
- data/lib/makit/mp/basic_object_mp.rb +17 -17
- data/lib/makit/mp/command_mp.rb +13 -13
- data/lib/makit/mp/command_request.mp.rb +17 -17
- data/lib/makit/mp/project_mp.rb +199 -199
- data/lib/makit/mp/string_mp.rb +205 -205
- data/lib/makit/nuget.rb +74 -74
- data/lib/makit/podman/podman.rb +458 -458
- data/lib/makit/podman/podman_service_impl.rb +1081 -1081
- data/lib/makit/port.rb +32 -32
- data/lib/makit/process.rb +377 -377
- data/lib/makit/protoc.rb +112 -112
- data/lib/makit/rake/cli.rb +196 -196
- data/lib/makit/rake/trace_controller.rb +174 -174
- data/lib/makit/rake.rb +81 -81
- data/lib/makit/ruby/cli.rb +185 -185
- data/lib/makit/ruby.rb +25 -25
- data/lib/makit/rubygems.rb +137 -0
- data/lib/makit/secrets/azure_key_vault.rb +322 -322
- data/lib/makit/secrets/azure_secrets.rb +183 -183
- data/lib/makit/secrets/local_secrets.rb +72 -72
- data/lib/makit/secrets/secrets_manager.rb +105 -105
- data/lib/makit/secrets.rb +16 -16
- data/lib/makit/serializer.rb +130 -130
- data/lib/makit/services/builder.rb +186 -186
- data/lib/makit/services/error_handler.rb +226 -226
- data/lib/makit/services/repository_manager.rb +367 -367
- data/lib/makit/services/validator.rb +112 -112
- data/lib/makit/setup/classlib.rb +101 -101
- data/lib/makit/setup/gem.rb +268 -268
- data/lib/makit/setup/pages.rb +11 -11
- data/lib/makit/setup/razorclasslib.rb +101 -101
- data/lib/makit/setup/runner.rb +54 -54
- data/lib/makit/setup.rb +5 -5
- data/lib/makit/show.rb +110 -110
- data/lib/makit/storage.rb +126 -126
- data/lib/makit/symbols.rb +175 -175
- data/lib/makit/task_info.rb +130 -130
- data/lib/makit/tasks/at_exit.rb +15 -15
- data/lib/makit/tasks/build.rb +22 -22
- data/lib/makit/tasks/bump.rb +7 -7
- data/lib/makit/tasks/clean.rb +13 -13
- data/lib/makit/tasks/configure.rb +10 -10
- data/lib/makit/tasks/format.rb +10 -10
- data/lib/makit/tasks/hook_manager.rb +443 -443
- data/lib/makit/tasks/info.rb +368 -368
- data/lib/makit/tasks/init.rb +49 -49
- data/lib/makit/tasks/integrate.rb +60 -56
- data/lib/makit/tasks/pull_incoming.rb +13 -13
- data/lib/makit/tasks/secrets.rb +7 -7
- data/lib/makit/tasks/setup.rb +16 -16
- data/lib/makit/tasks/sync.rb +14 -17
- data/lib/makit/tasks/tag.rb +27 -27
- data/lib/makit/tasks/task_monkey_patch.rb +81 -81
- data/lib/makit/tasks/test.rb +22 -22
- data/lib/makit/tasks/update.rb +18 -18
- data/lib/makit/tasks/version.rb +6 -6
- data/lib/makit/tasks.rb +24 -24
- data/lib/makit/test_cache.rb +239 -239
- data/lib/makit/tree.rb +37 -37
- data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
- data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
- data/lib/makit/v1/makit.v1_pb.rb +35 -35
- data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
- data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
- data/lib/makit/version.rb +661 -503
- data/lib/makit/version_util.rb +21 -21
- data/lib/makit/wix.rb +95 -95
- data/lib/makit/yaml.rb +29 -29
- data/lib/makit/zip.rb +17 -17
- data/lib/makit copy.rb +44 -44
- data/lib/makit.rb +115 -114
- metadata +3 -2
data/lib/makit/version.rb
CHANGED
|
@@ -1,503 +1,661 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Makit
|
|
4
|
-
# Static version for now to avoid circular dependency issues
|
|
5
|
-
#VERSION = "0.0.
|
|
6
|
-
|
|
7
|
-
# Version management utilities for various file formats
|
|
8
|
-
#
|
|
9
|
-
# This class provides methods for detecting, extracting, and updating version
|
|
10
|
-
# numbers in various file formats including .csproj, .wxs, .yml, .gemspec,
|
|
11
|
-
# .nuspec, and .toml files.
|
|
12
|
-
#
|
|
13
|
-
# == Semantic Versioning
|
|
14
|
-
#
|
|
15
|
-
# This class follows Semantic Versioning (SemVer) specification (https://semver.org/).
|
|
16
|
-
# Version numbers use the format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
|
|
17
|
-
#
|
|
18
|
-
# === Version Number Components
|
|
19
|
-
#
|
|
20
|
-
# * MAJOR: Incremented for incompatible API changes that break backward compatibility.
|
|
21
|
-
# Example: 1.0.0 -> 2.0.0 indicates breaking changes.
|
|
22
|
-
#
|
|
23
|
-
# * MINOR: Incremented for new functionality added in a backward-compatible manner.
|
|
24
|
-
# Example: 1.0.0 -> 1.1.0 indicates new features that don't break existing code.
|
|
25
|
-
#
|
|
26
|
-
# * PATCH: Incremented for backward-compatible bug fixes.
|
|
27
|
-
# Example: 1.0.0 -> 1.0.1 indicates bug fixes only.
|
|
28
|
-
#
|
|
29
|
-
# === Pre-release Versions
|
|
30
|
-
#
|
|
31
|
-
# Pre-release versions can be appended with a hyphen and identifier(s):
|
|
32
|
-
# * 1.0.0-alpha.1 (alpha release)
|
|
33
|
-
# * 1.0.0-beta.2 (beta release)
|
|
34
|
-
# * 1.0.0-rc.1 (release candidate)
|
|
35
|
-
# * 1.0.0-preview (preview release)
|
|
36
|
-
#
|
|
37
|
-
# Pre-release versions have lower precedence than normal versions:
|
|
38
|
-
# * 1.0.0-alpha.1 < 1.0.0
|
|
39
|
-
# * 1.0.0-beta.1 < 1.0.0-rc.1
|
|
40
|
-
#
|
|
41
|
-
# === Version Comparison
|
|
42
|
-
#
|
|
43
|
-
# Versions are compared using semantic versioning rules:
|
|
44
|
-
# * Compare MAJOR first, then MINOR, then PATCH
|
|
45
|
-
# * Pre-release versions are always less than normal versions
|
|
46
|
-
# * Numeric identifiers are compared numerically
|
|
47
|
-
# * Non-numeric identifiers are compared lexicographically
|
|
48
|
-
#
|
|
49
|
-
# Examples:
|
|
50
|
-
# * 1.0.0 < 2.0.0 (major version difference)
|
|
51
|
-
# * 1.0.0 < 1.1.0 (minor version difference)
|
|
52
|
-
# * 1.0.0 < 1.0.1 (patch version difference)
|
|
53
|
-
# * 1.0.0-alpha < 1.0.0 (pre-release is less than release)
|
|
54
|
-
#
|
|
55
|
-
# === Usage in This Class
|
|
56
|
-
#
|
|
57
|
-
# * {parse} - Parses a version string into MAJOR, MINOR, PATCH, and suffix components
|
|
58
|
-
# * {get_highest_version} - Compares versions using semantic versioning rules to find the highest
|
|
59
|
-
# * {version} - Reads the current version from the gemspec file
|
|
60
|
-
# * {get_version_from_file} - Extracts version from various file formats
|
|
61
|
-
# * {set_version_in_file} - Updates version in various file formats
|
|
62
|
-
#
|
|
63
|
-
class Version
|
|
64
|
-
|
|
65
|
-
# Attempt to detect the version from the SSOT (Single Source of Truth) version file
|
|
66
|
-
# Uses the same logic as Makit::Version.info to find version files in priority order:
|
|
67
|
-
# *.gemspec, Directory.Build.props, Cargo.toml, package.json, pyproject.toml, pom.xml
|
|
68
|
-
# Falls back to makit.gemspec only if we're in the makit gem directory
|
|
69
|
-
# @return [String] The version string, or "0.0.0" if no version file is found
|
|
70
|
-
# @raise [RuntimeError] If a version file is found but version cannot be detected
|
|
71
|
-
def self.version
|
|
72
|
-
# Try to find version file in project root using SSOT logic (same as Makit::Version.info)
|
|
73
|
-
project_root = begin
|
|
74
|
-
require_relative "directories" unless defined?(Makit::Directories)
|
|
75
|
-
Makit::Directories::PROJECT_ROOT
|
|
76
|
-
rescue NameError, LoadError
|
|
77
|
-
find_project_root(Dir.pwd)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Use SSOT version file detection (supports multiple file types)
|
|
81
|
-
if project_root && Dir.exist?(project_root)
|
|
82
|
-
version_file = find_ssot_version_file(project_root)
|
|
83
|
-
if version_file && File.exist?(version_file)
|
|
84
|
-
version = extract_version_from_ssot_file(version_file)
|
|
85
|
-
return version if version
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Fallback to makit.gemspec (for the makit gem itself)
|
|
90
|
-
# Check if makit.gemspec exists relative to this file
|
|
91
|
-
makit_gemspec = File.join(File.dirname(__FILE__), "..", "..", "makit.gemspec")
|
|
92
|
-
makit_gemspec = File.expand_path(makit_gemspec)
|
|
93
|
-
|
|
94
|
-
# Use makit.gemspec if:
|
|
95
|
-
# 1. It exists, AND
|
|
96
|
-
# 2. Either we're in the makit gem directory (project_root matches), OR
|
|
97
|
-
# project_root is nil (running outside a project context, likely from installed gem)
|
|
98
|
-
if File.exist?(makit_gemspec)
|
|
99
|
-
makit_gem_dir = File.dirname(makit_gemspec)
|
|
100
|
-
use_makit_gemspec = if project_root
|
|
101
|
-
# If we have a project root, only use makit.gemspec if we're in the makit gem directory
|
|
102
|
-
File.expand_path(project_root) == File.expand_path(makit_gem_dir)
|
|
103
|
-
else
|
|
104
|
-
# If no project root, check if current directory is makit gem or if makit.gemspec is nearby
|
|
105
|
-
# This handles the case when running from installed gem or outside project context
|
|
106
|
-
Dir.pwd == makit_gem_dir || File.expand_path(Dir.pwd).start_with?(File.expand_path(makit_gem_dir))
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
if use_makit_gemspec
|
|
110
|
-
gemspec_content = File.read(makit_gemspec)
|
|
111
|
-
match = gemspec_content.match(/spec\.version\s*=\s*["']([^"']+)["']/)
|
|
112
|
-
raise "Version not found in gemspec file" if match.nil?
|
|
113
|
-
return match[1]
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# If no version file found, return default version (for non-gem projects)
|
|
118
|
-
"0.0.0"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Parse a semantic version string into its components
|
|
122
|
-
#
|
|
123
|
-
# Parses a version string following Semantic Versioning (SemVer) format:
|
|
124
|
-
# MAJOR.MINOR.PATCH[-PRERELEASE]
|
|
125
|
-
#
|
|
126
|
-
# @param version_string [String] Version string in SemVer format (e.g., "1.2.3" or "1.2.3-alpha.1")
|
|
127
|
-
# @return [Hash] Hash with keys: :major, :minor, :patch, :suffix
|
|
128
|
-
# - :major [Integer] Major version number
|
|
129
|
-
# - :minor [Integer] Minor version number
|
|
130
|
-
# - :patch [Integer] Patch version number
|
|
131
|
-
# - :suffix [String] Pre-release suffix (e.g., "-alpha.1") or empty string
|
|
132
|
-
# @raise [RuntimeError] If version string doesn't match MAJOR.MINOR.PATCH format
|
|
133
|
-
#
|
|
134
|
-
# @example Basic version
|
|
135
|
-
# Makit::Version.parse("1.2.3")
|
|
136
|
-
# # => { major: 1, minor: 2, patch: 3, suffix: "" }
|
|
137
|
-
#
|
|
138
|
-
# @example Version with pre-release suffix
|
|
139
|
-
# Makit::Version.parse("1.2.3-alpha")
|
|
140
|
-
# # => { major: 1, minor: 2, patch: 3, suffix: "-alpha" }
|
|
141
|
-
#
|
|
142
|
-
# @example Invalid format
|
|
143
|
-
# Makit::Version.parse("1.2")
|
|
144
|
-
# # => RuntimeError: Invalid version format: 1.2. Expected format: Major.Minor.Patch
|
|
145
|
-
#
|
|
146
|
-
def self.parse(version_string)
|
|
147
|
-
parts = version_string.split(".")
|
|
148
|
-
if parts.length < 3
|
|
149
|
-
raise "Invalid version format: #{version_string}. Expected format: Major.Minor.Patch"
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
major = parts[0].to_i
|
|
153
|
-
minor = parts[1].to_i
|
|
154
|
-
patch = parts[2].to_i
|
|
155
|
-
|
|
156
|
-
# Handle pre-release suffixes (e.g., "0.1.0-preview" -> patch = 0, suffix = "-preview")
|
|
157
|
-
# Note: If suffix contains dots (e.g., "1.2.3-preview.1"), the split by "." will separate
|
|
158
|
-
# the patch and suffix parts, so only the part before the first "-" in parts[2] is used as patch.
|
|
159
|
-
patch_part = parts[2]
|
|
160
|
-
if patch_part.include?("-")
|
|
161
|
-
patch, suffix = patch_part.split("-", 2)
|
|
162
|
-
patch = patch.to_i
|
|
163
|
-
suffix = "-#{suffix}"
|
|
164
|
-
else
|
|
165
|
-
suffix = ""
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
{ major: major, minor: minor, patch: patch, suffix: suffix }
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Find the highest version from an array of version strings
|
|
172
|
-
#
|
|
173
|
-
# Compares versions using semantic versioning rules (see class documentation).
|
|
174
|
-
# Uses Ruby's Gem::Version for comparison, which follows SemVer specification.
|
|
175
|
-
#
|
|
176
|
-
# @param versions [Array<String>] Array of version strings to compare
|
|
177
|
-
# @return [String] The highest version string using semantic versioning comparison
|
|
178
|
-
#
|
|
179
|
-
# @example
|
|
180
|
-
# Makit::Version.get_highest_version(["1.0.0", "2.0.0", "1.5.0", "1.0.1"])
|
|
181
|
-
# # => "2.0.0"
|
|
182
|
-
#
|
|
183
|
-
# @example Pre-release versions
|
|
184
|
-
# Makit::Version.get_highest_version(["1.0.0-alpha", "1.0.0", "1.0.0-beta"])
|
|
185
|
-
# # => "1.0.0" (pre-release versions are lower than release versions)
|
|
186
|
-
#
|
|
187
|
-
def self.get_highest_version(versions)
|
|
188
|
-
versions.max { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) }
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Extract version number from a file based on its extension
|
|
192
|
-
#
|
|
193
|
-
# Supports multiple file formats:
|
|
194
|
-
# - .csproj files: `<Version>x.y.z</Version>`
|
|
195
|
-
# - .wxs files: `Version="x.y.z"`
|
|
196
|
-
# - .yml files: `VERSION: "x.y.z"`
|
|
197
|
-
#
|
|
198
|
-
# @param path [String] Path to the file containing version information
|
|
199
|
-
# @return [String] The extracted version string
|
|
200
|
-
# @raise [RuntimeError] If file doesn't exist or has unrecognized extension
|
|
201
|
-
def self.get_version_from_file(path)
|
|
202
|
-
raise "file #{path}does not exist" unless File.exist?(path)
|
|
203
|
-
|
|
204
|
-
extension = File.extname(path)
|
|
205
|
-
case extension
|
|
206
|
-
when ".csproj"
|
|
207
|
-
Makit::Version.detect_from_file(path, /<Version>([-\w\d.]+)</)
|
|
208
|
-
when ".wxs"
|
|
209
|
-
Makit::Version.detect_from_file(path, / Version="([\d.]+)"/)
|
|
210
|
-
when ".yml"
|
|
211
|
-
Makit::Version.detect_from_file(path, /VERSION:\s*["']?([\d.]+)["']?/)
|
|
212
|
-
when ".rb"
|
|
213
|
-
Makit::Version.detect_from_file(path, /VERSION = "([\d.]+)"/)
|
|
214
|
-
else
|
|
215
|
-
raise "unrecognized file type"
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Detect version using a regex pattern in a specific file
|
|
220
|
-
#
|
|
221
|
-
# @param filename [String] Path to the file to search
|
|
222
|
-
# @param regex [Regexp] Regular expression pattern to match version
|
|
223
|
-
# @return [String, nil] The extracted version or nil if no match found
|
|
224
|
-
# @raise [RuntimeError] If file doesn't exist
|
|
225
|
-
def self.detect_from_file(filename, regex)
|
|
226
|
-
raise "unable to find version in #{filename}" unless File.exist?(filename)
|
|
227
|
-
|
|
228
|
-
match = File.read(filename).match(regex)
|
|
229
|
-
match.captures[0] if !match.nil? && match.captures.length.positive?
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Update version number in a file based on its extension
|
|
233
|
-
#
|
|
234
|
-
# Supports updating versions in multiple file formats:
|
|
235
|
-
# - .yml files
|
|
236
|
-
# - .gemspec files
|
|
237
|
-
# - .csproj files
|
|
238
|
-
# - .nuspec files
|
|
239
|
-
# - .wxs files
|
|
240
|
-
# - .toml files
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
# @param
|
|
244
|
-
# @
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
#
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
new_text =
|
|
256
|
-
new_text = new_text.gsub(/
|
|
257
|
-
new_text = new_text.gsub(/
|
|
258
|
-
# Handle
|
|
259
|
-
if filename.include?(".
|
|
260
|
-
new_text = new_text.gsub(
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
if filename.include?("
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
new_text =
|
|
268
|
-
end
|
|
269
|
-
# Handle
|
|
270
|
-
if filename.include?("
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
# @
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
# #
|
|
367
|
-
#
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Makit
|
|
4
|
+
# Static version for now to avoid circular dependency issues
|
|
5
|
+
#VERSION = "0.0.158"
|
|
6
|
+
|
|
7
|
+
# Version management utilities for various file formats
|
|
8
|
+
#
|
|
9
|
+
# This class provides methods for detecting, extracting, and updating version
|
|
10
|
+
# numbers in various file formats including .csproj, .wxs, .yml, .gemspec,
|
|
11
|
+
# .nuspec, and .toml files.
|
|
12
|
+
#
|
|
13
|
+
# == Semantic Versioning
|
|
14
|
+
#
|
|
15
|
+
# This class follows Semantic Versioning (SemVer) specification (https://semver.org/).
|
|
16
|
+
# Version numbers use the format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
|
|
17
|
+
#
|
|
18
|
+
# === Version Number Components
|
|
19
|
+
#
|
|
20
|
+
# * MAJOR: Incremented for incompatible API changes that break backward compatibility.
|
|
21
|
+
# Example: 1.0.0 -> 2.0.0 indicates breaking changes.
|
|
22
|
+
#
|
|
23
|
+
# * MINOR: Incremented for new functionality added in a backward-compatible manner.
|
|
24
|
+
# Example: 1.0.0 -> 1.1.0 indicates new features that don't break existing code.
|
|
25
|
+
#
|
|
26
|
+
# * PATCH: Incremented for backward-compatible bug fixes.
|
|
27
|
+
# Example: 1.0.0 -> 1.0.1 indicates bug fixes only.
|
|
28
|
+
#
|
|
29
|
+
# === Pre-release Versions
|
|
30
|
+
#
|
|
31
|
+
# Pre-release versions can be appended with a hyphen and identifier(s):
|
|
32
|
+
# * 1.0.0-alpha.1 (alpha release)
|
|
33
|
+
# * 1.0.0-beta.2 (beta release)
|
|
34
|
+
# * 1.0.0-rc.1 (release candidate)
|
|
35
|
+
# * 1.0.0-preview (preview release)
|
|
36
|
+
#
|
|
37
|
+
# Pre-release versions have lower precedence than normal versions:
|
|
38
|
+
# * 1.0.0-alpha.1 < 1.0.0
|
|
39
|
+
# * 1.0.0-beta.1 < 1.0.0-rc.1
|
|
40
|
+
#
|
|
41
|
+
# === Version Comparison
|
|
42
|
+
#
|
|
43
|
+
# Versions are compared using semantic versioning rules:
|
|
44
|
+
# * Compare MAJOR first, then MINOR, then PATCH
|
|
45
|
+
# * Pre-release versions are always less than normal versions
|
|
46
|
+
# * Numeric identifiers are compared numerically
|
|
47
|
+
# * Non-numeric identifiers are compared lexicographically
|
|
48
|
+
#
|
|
49
|
+
# Examples:
|
|
50
|
+
# * 1.0.0 < 2.0.0 (major version difference)
|
|
51
|
+
# * 1.0.0 < 1.1.0 (minor version difference)
|
|
52
|
+
# * 1.0.0 < 1.0.1 (patch version difference)
|
|
53
|
+
# * 1.0.0-alpha < 1.0.0 (pre-release is less than release)
|
|
54
|
+
#
|
|
55
|
+
# === Usage in This Class
|
|
56
|
+
#
|
|
57
|
+
# * {parse} - Parses a version string into MAJOR, MINOR, PATCH, and suffix components
|
|
58
|
+
# * {get_highest_version} - Compares versions using semantic versioning rules to find the highest
|
|
59
|
+
# * {version} - Reads the current version from the gemspec file
|
|
60
|
+
# * {get_version_from_file} - Extracts version from various file formats
|
|
61
|
+
# * {set_version_in_file} - Updates version in various file formats
|
|
62
|
+
#
|
|
63
|
+
class Version
|
|
64
|
+
|
|
65
|
+
# Attempt to detect the version from the SSOT (Single Source of Truth) version file
|
|
66
|
+
# Uses the same logic as Makit::Version.info to find version files in priority order:
|
|
67
|
+
# *.gemspec, Directory.Build.props, Cargo.toml, package.json, pyproject.toml, pom.xml
|
|
68
|
+
# Falls back to makit.gemspec only if we're in the makit gem directory
|
|
69
|
+
# @return [String] The version string, or "0.0.0" if no version file is found
|
|
70
|
+
# @raise [RuntimeError] If a version file is found but version cannot be detected
|
|
71
|
+
def self.version
|
|
72
|
+
# Try to find version file in project root using SSOT logic (same as Makit::Version.info)
|
|
73
|
+
project_root = begin
|
|
74
|
+
require_relative "directories" unless defined?(Makit::Directories)
|
|
75
|
+
Makit::Directories::PROJECT_ROOT
|
|
76
|
+
rescue NameError, LoadError
|
|
77
|
+
find_project_root(Dir.pwd)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Use SSOT version file detection (supports multiple file types)
|
|
81
|
+
if project_root && Dir.exist?(project_root)
|
|
82
|
+
version_file = find_ssot_version_file(project_root)
|
|
83
|
+
if version_file && File.exist?(version_file)
|
|
84
|
+
version = extract_version_from_ssot_file(version_file)
|
|
85
|
+
return version if version
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fallback to makit.gemspec (for the makit gem itself)
|
|
90
|
+
# Check if makit.gemspec exists relative to this file
|
|
91
|
+
makit_gemspec = File.join(File.dirname(__FILE__), "..", "..", "makit.gemspec")
|
|
92
|
+
makit_gemspec = File.expand_path(makit_gemspec)
|
|
93
|
+
|
|
94
|
+
# Use makit.gemspec if:
|
|
95
|
+
# 1. It exists, AND
|
|
96
|
+
# 2. Either we're in the makit gem directory (project_root matches), OR
|
|
97
|
+
# project_root is nil (running outside a project context, likely from installed gem)
|
|
98
|
+
if File.exist?(makit_gemspec)
|
|
99
|
+
makit_gem_dir = File.dirname(makit_gemspec)
|
|
100
|
+
use_makit_gemspec = if project_root
|
|
101
|
+
# If we have a project root, only use makit.gemspec if we're in the makit gem directory
|
|
102
|
+
File.expand_path(project_root) == File.expand_path(makit_gem_dir)
|
|
103
|
+
else
|
|
104
|
+
# If no project root, check if current directory is makit gem or if makit.gemspec is nearby
|
|
105
|
+
# This handles the case when running from installed gem or outside project context
|
|
106
|
+
Dir.pwd == makit_gem_dir || File.expand_path(Dir.pwd).start_with?(File.expand_path(makit_gem_dir))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if use_makit_gemspec
|
|
110
|
+
gemspec_content = File.read(makit_gemspec)
|
|
111
|
+
match = gemspec_content.match(/spec\.version\s*=\s*["']([^"']+)["']/)
|
|
112
|
+
raise "Version not found in gemspec file" if match.nil?
|
|
113
|
+
return match[1]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# If no version file found, return default version (for non-gem projects)
|
|
118
|
+
"0.0.0"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Parse a semantic version string into its components
|
|
122
|
+
#
|
|
123
|
+
# Parses a version string following Semantic Versioning (SemVer) format:
|
|
124
|
+
# MAJOR.MINOR.PATCH[-PRERELEASE]
|
|
125
|
+
#
|
|
126
|
+
# @param version_string [String] Version string in SemVer format (e.g., "1.2.3" or "1.2.3-alpha.1")
|
|
127
|
+
# @return [Hash] Hash with keys: :major, :minor, :patch, :suffix
|
|
128
|
+
# - :major [Integer] Major version number
|
|
129
|
+
# - :minor [Integer] Minor version number
|
|
130
|
+
# - :patch [Integer] Patch version number
|
|
131
|
+
# - :suffix [String] Pre-release suffix (e.g., "-alpha.1") or empty string
|
|
132
|
+
# @raise [RuntimeError] If version string doesn't match MAJOR.MINOR.PATCH format
|
|
133
|
+
#
|
|
134
|
+
# @example Basic version
|
|
135
|
+
# Makit::Version.parse("1.2.3")
|
|
136
|
+
# # => { major: 1, minor: 2, patch: 3, suffix: "" }
|
|
137
|
+
#
|
|
138
|
+
# @example Version with pre-release suffix
|
|
139
|
+
# Makit::Version.parse("1.2.3-alpha")
|
|
140
|
+
# # => { major: 1, minor: 2, patch: 3, suffix: "-alpha" }
|
|
141
|
+
#
|
|
142
|
+
# @example Invalid format
|
|
143
|
+
# Makit::Version.parse("1.2")
|
|
144
|
+
# # => RuntimeError: Invalid version format: 1.2. Expected format: Major.Minor.Patch
|
|
145
|
+
#
|
|
146
|
+
def self.parse(version_string)
|
|
147
|
+
parts = version_string.split(".")
|
|
148
|
+
if parts.length < 3
|
|
149
|
+
raise "Invalid version format: #{version_string}. Expected format: Major.Minor.Patch"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
major = parts[0].to_i
|
|
153
|
+
minor = parts[1].to_i
|
|
154
|
+
patch = parts[2].to_i
|
|
155
|
+
|
|
156
|
+
# Handle pre-release suffixes (e.g., "0.1.0-preview" -> patch = 0, suffix = "-preview")
|
|
157
|
+
# Note: If suffix contains dots (e.g., "1.2.3-preview.1"), the split by "." will separate
|
|
158
|
+
# the patch and suffix parts, so only the part before the first "-" in parts[2] is used as patch.
|
|
159
|
+
patch_part = parts[2]
|
|
160
|
+
if patch_part.include?("-")
|
|
161
|
+
patch, suffix = patch_part.split("-", 2)
|
|
162
|
+
patch = patch.to_i
|
|
163
|
+
suffix = "-#{suffix}"
|
|
164
|
+
else
|
|
165
|
+
suffix = ""
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
{ major: major, minor: minor, patch: patch, suffix: suffix }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Find the highest version from an array of version strings
|
|
172
|
+
#
|
|
173
|
+
# Compares versions using semantic versioning rules (see class documentation).
|
|
174
|
+
# Uses Ruby's Gem::Version for comparison, which follows SemVer specification.
|
|
175
|
+
#
|
|
176
|
+
# @param versions [Array<String>] Array of version strings to compare
|
|
177
|
+
# @return [String] The highest version string using semantic versioning comparison
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# Makit::Version.get_highest_version(["1.0.0", "2.0.0", "1.5.0", "1.0.1"])
|
|
181
|
+
# # => "2.0.0"
|
|
182
|
+
#
|
|
183
|
+
# @example Pre-release versions
|
|
184
|
+
# Makit::Version.get_highest_version(["1.0.0-alpha", "1.0.0", "1.0.0-beta"])
|
|
185
|
+
# # => "1.0.0" (pre-release versions are lower than release versions)
|
|
186
|
+
#
|
|
187
|
+
def self.get_highest_version(versions)
|
|
188
|
+
versions.max { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Extract version number from a file based on its extension
|
|
192
|
+
#
|
|
193
|
+
# Supports multiple file formats:
|
|
194
|
+
# - .csproj files: `<Version>x.y.z</Version>`
|
|
195
|
+
# - .wxs files: `Version="x.y.z"`
|
|
196
|
+
# - .yml files: `VERSION: "x.y.z"`
|
|
197
|
+
#
|
|
198
|
+
# @param path [String] Path to the file containing version information
|
|
199
|
+
# @return [String] The extracted version string
|
|
200
|
+
# @raise [RuntimeError] If file doesn't exist or has unrecognized extension
|
|
201
|
+
def self.get_version_from_file(path)
|
|
202
|
+
raise "file #{path}does not exist" unless File.exist?(path)
|
|
203
|
+
|
|
204
|
+
extension = File.extname(path)
|
|
205
|
+
case extension
|
|
206
|
+
when ".csproj"
|
|
207
|
+
Makit::Version.detect_from_file(path, /<Version>([-\w\d.]+)</)
|
|
208
|
+
when ".wxs"
|
|
209
|
+
Makit::Version.detect_from_file(path, / Version="([\d.]+)"/)
|
|
210
|
+
when ".yml"
|
|
211
|
+
Makit::Version.detect_from_file(path, /VERSION:\s*["']?([\d.]+)["']?/)
|
|
212
|
+
when ".rb"
|
|
213
|
+
Makit::Version.detect_from_file(path, /VERSION = "([\d.]+)"/)
|
|
214
|
+
else
|
|
215
|
+
raise "unrecognized file type"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Detect version using a regex pattern in a specific file
|
|
220
|
+
#
|
|
221
|
+
# @param filename [String] Path to the file to search
|
|
222
|
+
# @param regex [Regexp] Regular expression pattern to match version
|
|
223
|
+
# @return [String, nil] The extracted version or nil if no match found
|
|
224
|
+
# @raise [RuntimeError] If file doesn't exist
|
|
225
|
+
def self.detect_from_file(filename, regex)
|
|
226
|
+
raise "unable to find version in #{filename}" unless File.exist?(filename)
|
|
227
|
+
|
|
228
|
+
match = File.read(filename).match(regex)
|
|
229
|
+
match.captures[0] if !match.nil? && match.captures.length.positive?
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Update version number in a file based on its extension
|
|
233
|
+
#
|
|
234
|
+
# Supports updating versions in multiple file formats:
|
|
235
|
+
# - .yml files
|
|
236
|
+
# - .gemspec files
|
|
237
|
+
# - .csproj files
|
|
238
|
+
# - .nuspec files
|
|
239
|
+
# - .wxs files
|
|
240
|
+
# - .toml files
|
|
241
|
+
# - Directory.Build.props files (.NET projects)
|
|
242
|
+
#
|
|
243
|
+
# @param filename [String] Path to the file to update
|
|
244
|
+
# @param version [String] New version string to set
|
|
245
|
+
# @return [nil]
|
|
246
|
+
def self.set_version_in_file(filename, version)
|
|
247
|
+
# Handle Directory.Build.props files with special logic
|
|
248
|
+
if filename.include?("Directory.Build.props")
|
|
249
|
+
set_version_in_directory_build_props(filename, version)
|
|
250
|
+
return
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
text = File.read(filename)
|
|
254
|
+
# VERSION = "0.0.138rake" (.rb file)
|
|
255
|
+
new_text = text
|
|
256
|
+
new_text = new_text.gsub(/VERSION:\s?['|"]([.\d]+)['|"]/, "VERSION: \"#{version}\"") if filename.include?(".yml")
|
|
257
|
+
new_text = new_text.gsub(/spec\.version\s*=\s*['"]([^'"]+)['"]/, "spec.version = '#{version}'") if filename.include?(".gemspec")
|
|
258
|
+
# Handle Directory.Build.props and .csproj files (both use <Version> tag)
|
|
259
|
+
if filename.include?("Directory.Build.props") || filename.include?(".csproj")
|
|
260
|
+
new_text = new_text.gsub(/<Version>([-\w\d.]+)</, "<Version>#{version}<")
|
|
261
|
+
end
|
|
262
|
+
new_text = new_text.gsub(/<version>([-\w\d.]+)</, "<version>#{version}<") if filename.include?(".nuspec")
|
|
263
|
+
new_text = new_text.gsub(/ Version="([\d.]+)"/, " Version=\"#{version}\"") if filename.include?(".wxs")
|
|
264
|
+
new_text = new_text.gsub(/VERSION = "([\d.]+)"/, "VERSION = \"#{version}\"") if filename.include?(".rb")
|
|
265
|
+
# Handle Cargo.toml, pyproject.toml, and other .toml files
|
|
266
|
+
if filename.include?(".toml")
|
|
267
|
+
new_text = new_text.gsub(/version\s+=\s+['"]([\w.]+)['"]/, "version=\"#{version}\"")
|
|
268
|
+
end
|
|
269
|
+
# Handle package.json
|
|
270
|
+
if filename.include?("package.json")
|
|
271
|
+
require "json"
|
|
272
|
+
json = JSON.parse(new_text)
|
|
273
|
+
json["version"] = version
|
|
274
|
+
new_text = JSON.pretty_generate(json)
|
|
275
|
+
end
|
|
276
|
+
# Handle pom.xml
|
|
277
|
+
if filename.include?("pom.xml")
|
|
278
|
+
new_text = new_text.gsub(%r{<version>([^<]+)</version>}, "<version>#{version}</version>")
|
|
279
|
+
end
|
|
280
|
+
File.write(filename, new_text) if new_text != text
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Update version number in multiple files matching a glob pattern
|
|
284
|
+
#
|
|
285
|
+
# @param glob_pattern [String] Glob pattern to match files (e.g., '**/*.csproj')
|
|
286
|
+
# @param version [String] New version string to set in all matching files
|
|
287
|
+
# @return [nil]
|
|
288
|
+
def self.set_version_in_files(glob_pattern, version)
|
|
289
|
+
Dir.glob(glob_pattern).each do |filename|
|
|
290
|
+
set_version_in_file(filename, version)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Display version information for the current project
|
|
295
|
+
#
|
|
296
|
+
# Finds the Single Source of Truth (SSOT) version file in the project root
|
|
297
|
+
# and displays the file path and current version value.
|
|
298
|
+
#
|
|
299
|
+
# Searches for common version files in priority order:
|
|
300
|
+
# * *.gemspec (Ruby gems)
|
|
301
|
+
# * Directory.Build.props (.NET projects)
|
|
302
|
+
# * Cargo.toml (Rust projects)
|
|
303
|
+
# * package.json (Node.js projects)
|
|
304
|
+
# * pyproject.toml (Python projects)
|
|
305
|
+
# * pom.xml (Maven/Java projects)
|
|
306
|
+
#
|
|
307
|
+
# If no version file is found, issues a warning and suggests defining a VERSION_FILE
|
|
308
|
+
# constant to manually specify the version file path.
|
|
309
|
+
#
|
|
310
|
+
# @return [nil] Outputs version information to stdout, or returns early with warning if no file found
|
|
311
|
+
# @raise [RuntimeError] If project root cannot be determined or version cannot be extracted from file
|
|
312
|
+
#
|
|
313
|
+
# @example Output format
|
|
314
|
+
# Version File: makit.gemspec
|
|
315
|
+
# Version: 0.0.147
|
|
316
|
+
#
|
|
317
|
+
# @example With VERSION_FILE constant defined
|
|
318
|
+
# VERSION_FILE = "custom/version.txt"
|
|
319
|
+
# Makit::Version.info
|
|
320
|
+
# # Uses the file specified by VERSION_FILE constant
|
|
321
|
+
#
|
|
322
|
+
def self.info
|
|
323
|
+
# Access Directories lazily to avoid circular dependency
|
|
324
|
+
project_root = begin
|
|
325
|
+
require_relative "directories" unless defined?(Makit::Directories)
|
|
326
|
+
Makit::Directories::PROJECT_ROOT
|
|
327
|
+
rescue NameError, LoadError
|
|
328
|
+
# Fallback: try to find project root from current directory
|
|
329
|
+
find_project_root(Dir.pwd)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
raise "Project root not found" if project_root.nil? || !Dir.exist?(project_root)
|
|
333
|
+
|
|
334
|
+
version_file = find_ssot_version_file(project_root)
|
|
335
|
+
|
|
336
|
+
if version_file.nil?
|
|
337
|
+
warn " Warning: No version file found in project root: #{project_root}"
|
|
338
|
+
warn " You may define a constant VERSION_FILE to manually set the version file path"
|
|
339
|
+
return
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Extract version based on file type
|
|
343
|
+
version = extract_version_from_ssot_file(version_file)
|
|
344
|
+
|
|
345
|
+
raise "Version not found in #{version_file}" if version.nil?
|
|
346
|
+
|
|
347
|
+
# Display information with relative path (Windows-safe path handling)
|
|
348
|
+
# Normalize both paths to forward slashes for comparison, then convert back if needed
|
|
349
|
+
normalized_version_file = version_file.gsub(/\\/, "/")
|
|
350
|
+
normalized_project_root = project_root.gsub(/\\/, "/")
|
|
351
|
+
relative_path = normalized_version_file.sub(normalized_project_root + "/", "")
|
|
352
|
+
puts " Version File: #{relative_path}"
|
|
353
|
+
puts " Version: #{version}"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Bump the patch version in the SSOT version file
|
|
357
|
+
#
|
|
358
|
+
# Finds the Single Source of Truth (SSOT) version file in the project root,
|
|
359
|
+
# reads the current version, increments the patch version, and updates the file.
|
|
360
|
+
#
|
|
361
|
+
# @return [String] The new version string after bumping
|
|
362
|
+
# @raise [RuntimeError] If project root cannot be determined, no version file is found,
|
|
363
|
+
# or version cannot be parsed/updated
|
|
364
|
+
#
|
|
365
|
+
# @example
|
|
366
|
+
# # Current version: 1.2.3
|
|
367
|
+
# Makit::Version.bump
|
|
368
|
+
# # => "1.2.4"
|
|
369
|
+
#
|
|
370
|
+
# @example With pre-release suffix
|
|
371
|
+
# # Current version: 1.2.3-alpha
|
|
372
|
+
# Makit::Version.bump
|
|
373
|
+
# # => "1.2.4" (removes pre-release suffix when bumping)
|
|
374
|
+
#
|
|
375
|
+
def self.bump
|
|
376
|
+
# Find the SSOT version file
|
|
377
|
+
project_root = begin
|
|
378
|
+
require_relative "directories" unless defined?(Makit::Directories)
|
|
379
|
+
Makit::Directories::PROJECT_ROOT
|
|
380
|
+
rescue NameError, LoadError
|
|
381
|
+
find_project_root(Dir.pwd)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
raise "Project root not found" if project_root.nil? || !Dir.exist?(project_root)
|
|
385
|
+
|
|
386
|
+
version_file = find_ssot_version_file(project_root)
|
|
387
|
+
if version_file.nil?
|
|
388
|
+
warn " Warning: No version file found in project root: #{project_root}"
|
|
389
|
+
warn " You may define a constant VERSION_FILE to manually set the version file path"
|
|
390
|
+
raise "Cannot bump version: no version file found"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Read current version
|
|
394
|
+
current_version = extract_version_from_ssot_file(version_file)
|
|
395
|
+
raise "Version not found in #{version_file}" if current_version.nil?
|
|
396
|
+
|
|
397
|
+
# Parse and bump patch version
|
|
398
|
+
parsed = parse(current_version)
|
|
399
|
+
new_version = "#{parsed[:major]}.#{parsed[:minor]}.#{parsed[:patch] + 1}"
|
|
400
|
+
|
|
401
|
+
# Update the version file
|
|
402
|
+
set_version_in_file(version_file, new_version)
|
|
403
|
+
|
|
404
|
+
# Verify the update
|
|
405
|
+
updated_version = extract_version_from_ssot_file(version_file)
|
|
406
|
+
if updated_version != new_version
|
|
407
|
+
raise "Version bump failed: expected #{new_version}, got #{updated_version}"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
new_version
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
private
|
|
414
|
+
|
|
415
|
+
# Find the SSOT version file in the project root
|
|
416
|
+
#
|
|
417
|
+
# Checks for VERSION_FILE constant first (if defined), then searches for common version files.
|
|
418
|
+
#
|
|
419
|
+
# @param project_root [String] Path to the project root directory
|
|
420
|
+
# @return [String, nil] Path to the version file, or nil if not found
|
|
421
|
+
def self.find_ssot_version_file(project_root)
|
|
422
|
+
# Normalize project_root for file operations (Ruby's File methods work with forward slashes)
|
|
423
|
+
normalized_root = project_root.gsub(/\\/, "/")
|
|
424
|
+
|
|
425
|
+
# Check for manually defined VERSION_FILE constant first
|
|
426
|
+
if defined?(VERSION_FILE) && !VERSION_FILE.nil?
|
|
427
|
+
version_file = File.expand_path(VERSION_FILE, normalized_root)
|
|
428
|
+
return version_file if File.exist?(version_file)
|
|
429
|
+
warn " Warning: VERSION_FILE constant points to non-existent file: #{VERSION_FILE}"
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Priority order for version files (SSOT)
|
|
433
|
+
version_file_patterns = [
|
|
434
|
+
"*.gemspec", # Ruby gems
|
|
435
|
+
"Directory.Build.props", # .NET projects
|
|
436
|
+
"Cargo.toml", # Rust projects
|
|
437
|
+
"package.json", # Node.js projects
|
|
438
|
+
"pyproject.toml", # Python projects
|
|
439
|
+
"pom.xml" # Maven/Java projects
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
version_file_patterns.each do |pattern|
|
|
443
|
+
matches = Dir.glob(File.join(normalized_root, pattern))
|
|
444
|
+
next if matches.empty?
|
|
445
|
+
|
|
446
|
+
# For gemspec, prefer the one matching the project name or take the first
|
|
447
|
+
version_file = matches.first
|
|
448
|
+
return version_file if version_file
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
nil
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Find project root by looking for common markers
|
|
455
|
+
def self.find_project_root(start_dir)
|
|
456
|
+
current = File.expand_path(start_dir)
|
|
457
|
+
root = File.expand_path("/")
|
|
458
|
+
|
|
459
|
+
while current != root
|
|
460
|
+
markers = ["Rakefile", "rakefile.rb", ".gitignore", ".git"]
|
|
461
|
+
return current if markers.any? { |marker| File.exist?(File.join(current, marker)) }
|
|
462
|
+
current = File.dirname(current)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
nil
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Normalize version string for parsing (strip metadata, handle 3/4-part)
|
|
469
|
+
#
|
|
470
|
+
# Strips build metadata (+abc123), handles 3-part and 4-part versions,
|
|
471
|
+
# and trims whitespace.
|
|
472
|
+
#
|
|
473
|
+
# @param version_string [String] Version string to normalize
|
|
474
|
+
# @return [String] Normalized version string
|
|
475
|
+
#
|
|
476
|
+
# @example
|
|
477
|
+
# normalize_version_for_parsing("0.1.1+abc123") # => "0.1.1"
|
|
478
|
+
# normalize_version_for_parsing("0.1.1.0") # => "0.1.1.0"
|
|
479
|
+
# normalize_version_for_parsing(" 0.1.1 ") # => "0.1.1"
|
|
480
|
+
def self.normalize_version_for_parsing(version_string)
|
|
481
|
+
return nil if version_string.nil?
|
|
482
|
+
|
|
483
|
+
# Trim whitespace
|
|
484
|
+
normalized = version_string.strip
|
|
485
|
+
|
|
486
|
+
# Strip build metadata (e.g., "+abc123")
|
|
487
|
+
normalized = normalized.split("+").first if normalized.include?("+")
|
|
488
|
+
|
|
489
|
+
normalized
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Extract SemVer (3-part) from version string (handles 4-part)
|
|
493
|
+
#
|
|
494
|
+
# If version is 4-part (x.y.z.w), extracts first 3 parts (x.y.z).
|
|
495
|
+
# If 3-part, returns as-is. Strips pre-release and build metadata.
|
|
496
|
+
#
|
|
497
|
+
# @param version_string [String] Version string (3-part or 4-part)
|
|
498
|
+
# @return [String] 3-part SemVer version string
|
|
499
|
+
#
|
|
500
|
+
# @example
|
|
501
|
+
# extract_semver_from_version("0.1.1.0") # => "0.1.1"
|
|
502
|
+
# extract_semver_from_version("0.1.1") # => "0.1.1"
|
|
503
|
+
# extract_semver_from_version("0.1.1-alpha") # => "0.1.1"
|
|
504
|
+
def self.extract_semver_from_version(version_string)
|
|
505
|
+
normalized = normalize_version_for_parsing(version_string)
|
|
506
|
+
return nil if normalized.nil?
|
|
507
|
+
|
|
508
|
+
# Strip pre-release suffix (e.g., "-alpha", "-beta.1")
|
|
509
|
+
normalized = normalized.split("-").first if normalized.include?("-")
|
|
510
|
+
|
|
511
|
+
# Split by dots to check if 4-part
|
|
512
|
+
parts = normalized.split(".")
|
|
513
|
+
|
|
514
|
+
# If 4-part, extract first 3 parts
|
|
515
|
+
if parts.length >= 4
|
|
516
|
+
"#{parts[0]}.#{parts[1]}.#{parts[2]}"
|
|
517
|
+
elsif parts.length == 3
|
|
518
|
+
normalized
|
|
519
|
+
else
|
|
520
|
+
# Invalid format, return as-is (will be caught by parse method)
|
|
521
|
+
normalized
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Convert SemVer to 4-part format
|
|
526
|
+
#
|
|
527
|
+
# Converts "x.y.z" to "x.y.z.0". Handles pre-release suffixes by stripping them.
|
|
528
|
+
#
|
|
529
|
+
# @param version_string [String] SemVer version string (x.y.z)
|
|
530
|
+
# @return [String] 4-part version string (x.y.z.0)
|
|
531
|
+
#
|
|
532
|
+
# @example
|
|
533
|
+
# semver_to_four_part("0.1.2") # => "0.1.2.0"
|
|
534
|
+
# semver_to_four_part("0.1.2-alpha") # => "0.1.2.0"
|
|
535
|
+
def self.semver_to_four_part(version_string)
|
|
536
|
+
# Extract SemVer first (handles pre-release suffixes)
|
|
537
|
+
semver = extract_semver_from_version(version_string)
|
|
538
|
+
return nil if semver.nil?
|
|
539
|
+
|
|
540
|
+
# Ensure it's 3-part, then append .0
|
|
541
|
+
parts = semver.split(".")
|
|
542
|
+
if parts.length == 3
|
|
543
|
+
"#{parts[0]}.#{parts[1]}.#{parts[2]}.0"
|
|
544
|
+
else
|
|
545
|
+
# If already 4-part or invalid, return as-is
|
|
546
|
+
semver
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Update Directory.Build.props file with new version
|
|
551
|
+
#
|
|
552
|
+
# Reads file, updates <Version>, <AssemblyVersion>, and <FileVersion> elements
|
|
553
|
+
# in all PropertyGroups, preserves XML structure, and performs atomic operation.
|
|
554
|
+
#
|
|
555
|
+
# @param filename [String] Path to Directory.Build.props file
|
|
556
|
+
# @param new_version [String] New SemVer version string (x.y.z)
|
|
557
|
+
# @return [nil]
|
|
558
|
+
# @raise [RuntimeError] If <Version> element is missing or update fails
|
|
559
|
+
def self.set_version_in_directory_build_props(filename, new_version)
|
|
560
|
+
# Read file content
|
|
561
|
+
content = File.read(filename)
|
|
562
|
+
|
|
563
|
+
# Validate that <Version> element exists
|
|
564
|
+
unless content.match(%r{<Version>([^<]+)</Version>})
|
|
565
|
+
raise "Directory.Build.props file does not contain <Version> element: #{filename}"
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Extract SemVer from new_version (in case it's 4-part or has metadata)
|
|
569
|
+
semver_version = extract_semver_from_version(new_version)
|
|
570
|
+
raise "Invalid version format: #{new_version}" if semver_version.nil?
|
|
571
|
+
|
|
572
|
+
# Convert to 4-part format for AssemblyVersion and FileVersion
|
|
573
|
+
four_part_version = semver_to_four_part(semver_version)
|
|
574
|
+
raise "Failed to convert version to 4-part format: #{semver_version}" if four_part_version.nil?
|
|
575
|
+
|
|
576
|
+
# Prepare updated content (start with original)
|
|
577
|
+
updated_content = content.dup
|
|
578
|
+
|
|
579
|
+
# Update <Version> element (preserve XML structure)
|
|
580
|
+
# Pattern: captures opening tag, whitespace, content, whitespace, closing tag
|
|
581
|
+
version_pattern = %r{(<Version>)\s*([^<]+?)\s*(</Version>)}
|
|
582
|
+
if updated_content.match(version_pattern)
|
|
583
|
+
updated_content = updated_content.gsub(version_pattern) do |match|
|
|
584
|
+
"#{$1}#{semver_version}#{$3}"
|
|
585
|
+
end
|
|
586
|
+
else
|
|
587
|
+
raise "Failed to update <Version> element in #{filename}"
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# Update <AssemblyVersion> element if present (preserve XML structure)
|
|
591
|
+
assembly_version_pattern = %r{(<AssemblyVersion>)\s*([^<]+?)\s*(</AssemblyVersion>)}
|
|
592
|
+
if updated_content.match(assembly_version_pattern)
|
|
593
|
+
updated_content = updated_content.gsub(assembly_version_pattern) do |match|
|
|
594
|
+
"#{$1}#{four_part_version}#{$3}"
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
# Note: If AssemblyVersion doesn't exist, we don't add it (per FR-006)
|
|
598
|
+
|
|
599
|
+
# Update <FileVersion> element if present (preserve XML structure)
|
|
600
|
+
file_version_pattern = %r{(<FileVersion>)\s*([^<]+?)\s*(</FileVersion>)}
|
|
601
|
+
if updated_content.match(file_version_pattern)
|
|
602
|
+
updated_content = updated_content.gsub(file_version_pattern) do |match|
|
|
603
|
+
"#{$1}#{four_part_version}#{$3}"
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
# Note: If FileVersion doesn't exist, we don't add it (per FR-006)
|
|
607
|
+
|
|
608
|
+
# Atomic write: only write if content changed
|
|
609
|
+
if updated_content != content
|
|
610
|
+
File.write(filename, updated_content)
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Extract version from SSOT file based on file type
|
|
615
|
+
def self.extract_version_from_ssot_file(file_path)
|
|
616
|
+
case File.basename(file_path)
|
|
617
|
+
when /\.gemspec$/
|
|
618
|
+
# Extract from gemspec: spec.version = "x.y.z"
|
|
619
|
+
content = File.read(file_path)
|
|
620
|
+
match = content.match(/spec\.version\s*=\s*["']([^"']+)["']/)
|
|
621
|
+
match ? match[1] : nil
|
|
622
|
+
when "Directory.Build.props"
|
|
623
|
+
# Extract from Directory.Build.props: <Version>x.y.z</Version>
|
|
624
|
+
content = File.read(file_path)
|
|
625
|
+
match = content.match(%r{<Version>([^<]+)</Version>})
|
|
626
|
+
if match
|
|
627
|
+
# Trim whitespace and normalize (strip build metadata)
|
|
628
|
+
normalize_version_for_parsing(match[1])
|
|
629
|
+
else
|
|
630
|
+
nil
|
|
631
|
+
end
|
|
632
|
+
when "Cargo.toml"
|
|
633
|
+
# Extract from Cargo.toml: version = "x.y.z"
|
|
634
|
+
content = File.read(file_path)
|
|
635
|
+
match = content.match(/version\s*=\s*["']([^"']+)["']/)
|
|
636
|
+
match ? match[1] : nil
|
|
637
|
+
when "package.json"
|
|
638
|
+
# Extract from package.json: "version": "x.y.z"
|
|
639
|
+
require "json"
|
|
640
|
+
json = JSON.parse(File.read(file_path))
|
|
641
|
+
json["version"]
|
|
642
|
+
when "pyproject.toml"
|
|
643
|
+
# Extract from pyproject.toml: version = "x.y.z" (in [project] or [tool.poetry] section)
|
|
644
|
+
content = File.read(file_path)
|
|
645
|
+
# Try [project] section first
|
|
646
|
+
match = content.match(/\[project\]\s*version\s*=\s*["']([^"']+)["']/)
|
|
647
|
+
return match[1] if match
|
|
648
|
+
# Try [tool.poetry] section
|
|
649
|
+
match = content.match(/\[tool\.poetry\]\s*version\s*=\s*["']([^"']+)["']/)
|
|
650
|
+
match ? match[1] : nil
|
|
651
|
+
when "pom.xml"
|
|
652
|
+
# Extract from pom.xml: <version>x.y.z</version>
|
|
653
|
+
content = File.read(file_path)
|
|
654
|
+
match = content.match(%r{<version>([^<]+)</version>})
|
|
655
|
+
match ? match[1] : nil
|
|
656
|
+
else
|
|
657
|
+
nil
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
end
|