kettle-dev 2.2.25 → 2.3.0

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: 5e5b1b17ea5254ca2a656ec1a10a984b5f8170359507c9a7c56c46b42ab3ca0b
4
- data.tar.gz: cc447fb1871394c8473e2464fe8dd56d8b554274b1e08bdb2ae5461b92030b77
3
+ metadata.gz: 12ded2228617ffab7dbd8c9d6712dbae7ce229cb86e243c49a357f49e2777702
4
+ data.tar.gz: b2db64901a1d1e8bbaebfcfdc2b12c6c5bb2d67ee970a5216a7e5f2279b3b352
5
5
  SHA512:
6
- metadata.gz: 0a0ae789338feaaae14007e02ebdb520da4d819d14783f82cf27b2d3e400f6818eefb4be5d5d485fee5cea9cd086b7387b50c0fb8d7f63e64a37214b238f6437
7
- data.tar.gz: 7a6dc01a712098bcc218652f83c219bc9e0551be7d492bec5bcd1fc0f82dbcf7b196f0fafa324a7c934c410136df41108ce22ecf449fa1d475299e3bd9a866ed
6
+ metadata.gz: 28a7ecbc70b68c1f4b430234b006c11643f280b0474c2f4f9627d1896f7cbe8d46e702587528ce4fdec63b730aef6dce72c6ed62dec7cee916ffb5a6b9737084
7
+ data.tar.gz: 53305c2ed6b3cbf3c66c20f453a90524b28a0af396acc285a905fe6336fdfbd35d98ec21ed503fa4b856ce1324098efe228751b4422c9a01eb49103333cb0d01
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,19 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.3.0] - 2026-07-02
34
+
35
+ - TAG: [v2.3.0][2.3.0t]
36
+ - COVERAGE: 91.95% -- 4216/4585 lines in 35 files
37
+ - BRANCH COVERAGE: 73.19% -- 1660/2268 branches in 35 files
38
+ - 58.02% documented
39
+
40
+ ### Added
41
+
42
+ - `Kettle::Dev::VersionBump` now exposes the reusable engine behind
43
+ `kettle-bump`, so other tools can share version detection, target resolution,
44
+ Prism edits, and write behavior without shelling out or duplicating logic.
45
+
33
46
  ## [2.2.25] - 2026-07-01
34
47
 
35
48
  - TAG: [v2.2.25][2.2.25t]
@@ -2396,7 +2409,9 @@ Please file a bug if you notice a violation of semantic versioning.
2396
2409
  - Selecting will run the selected workflow via `act`
2397
2410
  - This may move to its own gem in the future.
2398
2411
 
2399
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.25...HEAD
2412
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.0...HEAD
2413
+ [2.3.0]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.25...v2.3.0
2414
+ [2.3.0t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.3.0
2400
2415
  [2.2.25]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.24...v2.2.25
2401
2416
  [2.2.25t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.2.25
2402
2417
  [2.2.24]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.23...v2.2.24
data/README.md CHANGED
@@ -874,7 +874,7 @@ Thanks for RTFM. ☺️
874
874
  [📌gitmoji]: https://gitmoji.dev
875
875
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
876
876
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
877
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.567-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
877
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.585-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
878
878
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
879
879
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
880
880
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -6,8 +6,6 @@ module Kettle
6
6
  module Dev
7
7
  # CLI for bumping the current project's gem version before changelog prep.
8
8
  class BumpCLI
9
- BUMP_TYPES = %w[major minor patch pre].freeze
10
-
11
9
  def initialize(argv = [], out: $stdout, err: $stderr, root: Kettle::Dev::CIHelpers.project_root)
12
10
  @argv = argv.dup
13
11
  @out = out
@@ -19,16 +17,15 @@ module Kettle
19
17
  options = parse_options
20
18
  return 0 if options.fetch(:help)
21
19
 
22
- current_version = Kettle::Dev::Versioning.detect_version(root)
23
- target_version = resolve_target_version(options.fetch(:target), current_version)
24
- edits = collect_edits(
25
- current_version: current_version,
26
- target_version: target_version,
20
+ bump = Kettle::Dev::VersionBump.new(
21
+ root: root,
22
+ target_version: options.fetch(:target),
27
23
  from_version: options[:from]
28
24
  )
29
- write_edits(edits) if options.fetch(:mode) == :execute
25
+ edits = bump.edits
26
+ Kettle::Dev::VersionBump.write_edits(edits) if options.fetch(:mode) == :execute
30
27
 
31
- out.puts(summary(edits: edits, current_version: current_version, target_version: target_version, mode: options.fetch(:mode)))
28
+ out.puts(summary(edits: edits, current_version: bump.current_version, target_version: bump.target_version, mode: options.fetch(:mode)))
32
29
  (options.fetch(:mode) == :check && edits.any?) ? 1 : 0
33
30
  end
34
31
 
@@ -59,168 +56,8 @@ module Kettle
59
56
  raise Kettle::Dev::Error, error.message
60
57
  end
61
58
 
62
- def resolve_target_version(target, current_version)
63
- if BUMP_TYPES.include?(target)
64
- bumped_version(target, current_version)
65
- else
66
- validate_version(target)
67
- end
68
- end
69
-
70
- def bumped_version(type, current_version)
71
- return bumped_prerelease_version(current_version) if type == "pre"
72
-
73
- version = Gem::Version.new(current_version)
74
- segments = version.segments
75
- unless segments.all? { |segment| segment.is_a?(Integer) }
76
- raise Kettle::Dev::Error, "cannot #{type}-bump non-numeric version #{current_version.inspect}"
77
- end
78
-
79
- major, minor, patch = (segments + [0, 0, 0])[0, 3]
80
- case type
81
- when "major"
82
- "#{major + 1}.0.0"
83
- when "minor"
84
- "#{major}.#{minor + 1}.0"
85
- when "patch"
86
- "#{major}.#{minor}.#{patch + 1}"
87
- end
88
- end
89
-
90
- def bumped_prerelease_version(current_version)
91
- version = Gem::Version.new(current_version)
92
- segments = version.segments
93
- prerelease_index = segments.index { |segment| !segment.is_a?(Integer) }
94
- unless prerelease_index
95
- raise Kettle::Dev::Error, "cannot pre-bump version without prerelease segment #{current_version.inspect}"
96
- end
97
-
98
- release_core = segments[0...prerelease_index].join(".")
99
- prerelease_suffix = prerelease_suffix_for(current_version, release_core)
100
- "#{release_core}.#{prerelease_suffix.next}"
101
- end
102
-
103
- def prerelease_suffix_for(current_version, release_core)
104
- prefix = "#{release_core}."
105
- return string_tail(current_version, prefix.length) if current_version.start_with?(prefix)
106
-
107
- canonical_version = Gem::Version.new(current_version).to_s
108
- return string_tail(canonical_version, prefix.length) if canonical_version.start_with?(prefix)
109
-
110
- raise Kettle::Dev::Error, "cannot find prerelease segment in version #{current_version.inspect}"
111
- end
112
-
113
- def string_tail(value, offset)
114
- value[offset, value.length - offset]
115
- end
116
-
117
59
  def validate_version(version)
118
- Gem::Version.new(version).to_s
119
- rescue ArgumentError => error
120
- raise Kettle::Dev::Error, "invalid version #{version.inspect}: #{error.message}"
121
- end
122
-
123
- def collect_edits(current_version:, target_version:, from_version:)
124
- if from_version && current_version != from_version
125
- raise Kettle::Dev::Error, "current version is #{current_version}, not --from #{from_version}"
126
- end
127
-
128
- version_file_edits(target_version) + gemspec_version_edits(current_version, target_version)
129
- end
130
-
131
- def version_file_edits(target_version)
132
- Kettle::Dev::Versioning.version_file_candidates(root).filter_map do |path|
133
- source = File.read(path)
134
- node = version_string_node(source, path)
135
- current = node.unescaped
136
- next if current == target_version
137
-
138
- replacement = quote_like(node.location.slice, target_version)
139
- file_edit(path, source, node.location.start_offset, node.location.end_offset, replacement)
140
- end
141
- end
142
-
143
- def gemspec_version_edits(current_version, target_version)
144
- gemspec_path = gemspec_path_for_bump
145
- return [] unless gemspec_path
146
-
147
- source = File.read(gemspec_path)
148
- parse_result = parse_source(source, gemspec_path)
149
- each_node(parse_result.value).filter_map do |node|
150
- next unless node.is_a?(Prism::CallNode) && node.name == :version=
151
-
152
- version_node = node.arguments&.arguments&.first
153
- next unless version_node.is_a?(Prism::StringNode)
154
- next unless version_node.unescaped == current_version
155
- next if version_node.unescaped == target_version
156
-
157
- replacement = quote_like(version_node.location.slice, target_version)
158
- file_edit(gemspec_path, source, version_node.location.start_offset, version_node.location.end_offset, replacement)
159
- end
160
- end
161
-
162
- def gemspec_path_for_bump
163
- paths = Dir.glob(File.join(root, "*.gemspec")).sort
164
- return nil if paths.empty?
165
- raise Kettle::Dev::Error, "multiple gemspecs found; kettle-bump supports single gems only" if paths.length > 1
166
-
167
- paths.first
168
- end
169
-
170
- def version_string_node(source, path)
171
- parse_result = parse_source(source, path)
172
- constant = each_node(parse_result.value).find do |node|
173
- node.is_a?(Prism::ConstantWriteNode) && node.name == :VERSION && node.value.is_a?(Prism::StringNode)
174
- end
175
- raise Kettle::Dev::Error, "could not find string VERSION constant in #{path}" unless constant
176
-
177
- constant.value
178
- end
179
-
180
- def parse_source(source, path)
181
- require_prism
182
- parse_result = Prism.parse(source)
183
- raise Kettle::Dev::Error, "could not parse #{path}" unless parse_result.success?
184
-
185
- parse_result
186
- end
187
-
188
- def require_prism
189
- return if defined?(Prism)
190
-
191
- require "prism"
192
- rescue LoadError => error
193
- raise Kettle::Dev::Error, "kettle-bump requires Prism; install the prism gem or run on Ruby 3.3+ (#{error.message})"
194
- end
195
-
196
- def each_node(root)
197
- return enum_for(__method__, root) unless block_given?
198
-
199
- queue = [root]
200
- until queue.empty?
201
- node = queue.shift
202
- yield node
203
- queue.concat(node.child_nodes.compact) if node.respond_to?(:child_nodes)
204
- end
205
- end
206
-
207
- def file_edit(path, source, start_offset, end_offset, replacement)
208
- {path: path, source: source, start_offset: start_offset, end_offset: end_offset, replacement: replacement}
209
- end
210
-
211
- def quote_like(original, value)
212
- quote = original.start_with?("'") ? "'" : '"'
213
- "#{quote}#{value}#{quote}"
214
- end
215
-
216
- def write_edits(edits)
217
- edits.group_by { |edit| edit.fetch(:path) }.each_value do |file_edits|
218
- source = file_edits.first.fetch(:source)
219
- file_edits.sort_by { |edit| -edit.fetch(:start_offset) }.each do |edit|
220
- source[edit.fetch(:start_offset)...edit.fetch(:end_offset)] = edit.fetch(:replacement)
221
- end
222
- File.write(file_edits.first.fetch(:path), source)
223
- end
60
+ Kettle::Dev::VersionBump.validate_version(version)
224
61
  end
225
62
 
226
63
  def summary(edits:, current_version:, target_version:, mode:)
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Dev
5
5
  module Version
6
- VERSION = "2.2.25"
6
+ VERSION = "2.3.0"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kettle
4
+ module Dev
5
+ # Reusable engine behind kettle-bump.
6
+ class VersionBump
7
+ BUMP_TYPES = %w[major minor patch pre].freeze
8
+
9
+ class << self
10
+ def resolve_target_version(target, current_version)
11
+ target = target.to_s
12
+ if BUMP_TYPES.include?(target)
13
+ bumped_version(target, current_version)
14
+ else
15
+ validate_version(target)
16
+ end
17
+ end
18
+
19
+ def validate_version(version)
20
+ Gem::Version.new(version).to_s
21
+ rescue ArgumentError => error
22
+ raise Kettle::Dev::Error, "invalid version #{version.inspect}: #{error.message}"
23
+ end
24
+
25
+ def bumped_version(type, current_version)
26
+ return bumped_prerelease_version(current_version) if type == "pre"
27
+
28
+ version = Gem::Version.new(current_version)
29
+ segments = version.segments
30
+ unless segments.all? { |segment| segment.is_a?(Integer) }
31
+ raise Kettle::Dev::Error, "cannot #{type}-bump non-numeric version #{current_version.inspect}"
32
+ end
33
+
34
+ major, minor, patch = (segments + [0, 0, 0])[0, 3]
35
+ case type
36
+ when "major"
37
+ "#{major + 1}.0.0"
38
+ when "minor"
39
+ "#{major}.#{minor + 1}.0"
40
+ when "patch"
41
+ "#{major}.#{minor}.#{patch + 1}"
42
+ end
43
+ end
44
+
45
+ def bumped_prerelease_version(current_version)
46
+ version = Gem::Version.new(current_version)
47
+ segments = version.segments
48
+ prerelease_index = segments.index { |segment| !segment.is_a?(Integer) }
49
+ unless prerelease_index
50
+ raise Kettle::Dev::Error, "cannot pre-bump version without prerelease segment #{current_version.inspect}"
51
+ end
52
+
53
+ release_core = segments[0...prerelease_index].join(".")
54
+ prerelease_suffix = prerelease_suffix_for(current_version, release_core)
55
+ "#{release_core}.#{prerelease_suffix.next}"
56
+ end
57
+
58
+ def prerelease_suffix_for(current_version, release_core)
59
+ prefix = "#{release_core}."
60
+ return string_tail(current_version, prefix.length) if current_version.start_with?(prefix)
61
+
62
+ canonical_version = Gem::Version.new(current_version).to_s
63
+ return string_tail(canonical_version, prefix.length) if canonical_version.start_with?(prefix)
64
+
65
+ raise Kettle::Dev::Error, "cannot find prerelease segment in version #{current_version.inspect}"
66
+ end
67
+
68
+ def string_tail(value, offset)
69
+ value[offset, value.length - offset]
70
+ end
71
+
72
+ def version_string_node(source, path)
73
+ parse_result = parse_source(source, path)
74
+ constant = each_node(parse_result.value).find do |node|
75
+ node.is_a?(Prism::ConstantWriteNode) && node.name == :VERSION && node.value.is_a?(Prism::StringNode)
76
+ end
77
+ raise Kettle::Dev::Error, "could not find string VERSION constant in #{path}" unless constant
78
+
79
+ constant.value
80
+ end
81
+
82
+ def parse_source(source, path)
83
+ require_prism
84
+ parse_result = Prism.parse(source)
85
+ raise Kettle::Dev::Error, "could not parse #{path}" unless parse_result.success?
86
+
87
+ parse_result
88
+ end
89
+
90
+ def require_prism
91
+ return if defined?(Prism)
92
+
93
+ require "prism"
94
+ rescue LoadError => error
95
+ raise Kettle::Dev::Error, "kettle-bump requires Prism; install the prism gem or run on Ruby 3.3+ (#{error.message})"
96
+ end
97
+
98
+ def each_node(root)
99
+ return enum_for(__method__, root) unless block_given?
100
+
101
+ queue = [root]
102
+ until queue.empty?
103
+ node = queue.shift
104
+ yield node
105
+ queue.concat(node.child_nodes.compact) if node.respond_to?(:child_nodes)
106
+ end
107
+ end
108
+
109
+ def file_edit(path, source, start_offset, end_offset, replacement)
110
+ {path: path, source: source, start_offset: start_offset, end_offset: end_offset, replacement: replacement}
111
+ end
112
+
113
+ def quote_like(original, value)
114
+ quote = original.start_with?("'") ? "'" : '"'
115
+ "#{quote}#{value}#{quote}"
116
+ end
117
+
118
+ def write_edits(edits)
119
+ edits.group_by { |edit| edit.fetch(:path) }.each_value do |file_edits|
120
+ source = file_edits.first.fetch(:source)
121
+ file_edits.sort_by { |edit| -edit.fetch(:start_offset) }.each do |edit|
122
+ source[edit.fetch(:start_offset)...edit.fetch(:end_offset)] = edit.fetch(:replacement)
123
+ end
124
+ File.write(file_edits.first.fetch(:path), source)
125
+ end
126
+ end
127
+ end
128
+
129
+ def initialize(root:, target_version:, current_version: nil, from_version: nil)
130
+ @root = root
131
+ @current_version = current_version || Kettle::Dev::Versioning.detect_version(root)
132
+ @target_version = self.class.resolve_target_version(target_version, @current_version)
133
+ @from_version = self.class.validate_version(from_version) if from_version
134
+ end
135
+
136
+ attr_reader :root, :current_version, :target_version, :from_version
137
+
138
+ def edits
139
+ validate_from_version
140
+ version_file_edits + gemspec_version_edits
141
+ end
142
+
143
+ def write!
144
+ self.class.write_edits(edits)
145
+ end
146
+
147
+ private
148
+
149
+ def validate_from_version
150
+ return unless from_version && current_version != from_version
151
+
152
+ raise Kettle::Dev::Error, "current version is #{current_version}, not --from #{from_version}"
153
+ end
154
+
155
+ def version_file_edits
156
+ Kettle::Dev::Versioning.version_file_candidates(root).filter_map do |path|
157
+ source = File.read(path)
158
+ node = self.class.version_string_node(source, path)
159
+ current = node.unescaped
160
+ next if current == target_version
161
+
162
+ replacement = self.class.quote_like(node.location.slice, target_version)
163
+ self.class.file_edit(path, source, node.location.start_offset, node.location.end_offset, replacement)
164
+ end
165
+ end
166
+
167
+ def gemspec_version_edits
168
+ gemspec_path = gemspec_path_for_bump
169
+ return [] unless gemspec_path
170
+
171
+ source = File.read(gemspec_path)
172
+ parse_result = self.class.parse_source(source, gemspec_path)
173
+ self.class.each_node(parse_result.value).filter_map do |node|
174
+ next unless node.is_a?(Prism::CallNode) && node.name == :version=
175
+
176
+ version_node = node.arguments&.arguments&.first
177
+ next unless version_node.is_a?(Prism::StringNode)
178
+ next unless version_node.unescaped == current_version
179
+ next if version_node.unescaped == target_version
180
+
181
+ replacement = self.class.quote_like(version_node.location.slice, target_version)
182
+ self.class.file_edit(gemspec_path, source, version_node.location.start_offset, version_node.location.end_offset, replacement)
183
+ end
184
+ end
185
+
186
+ def gemspec_path_for_bump
187
+ paths = Dir.glob(File.join(root, "*.gemspec")).sort
188
+ return nil if paths.empty?
189
+ raise Kettle::Dev::Error, "multiple gemspecs found; kettle-bump supports single gems only" if paths.length > 1
190
+
191
+ paths.first
192
+ end
193
+ end
194
+ end
195
+ end
data/lib/kettle/dev.rb CHANGED
@@ -47,6 +47,7 @@ module Kettle
47
47
  autoload :ReleaseCLI, "kettle/dev/release_cli"
48
48
  autoload :PreReleaseCLI, "kettle/dev/pre_release_cli"
49
49
  autoload :Version, "kettle/dev/version"
50
+ autoload :VersionBump, "kettle/dev/version_bump"
50
51
  autoload :Versioning, "kettle/dev/versioning"
51
52
 
52
53
  # Nested tasks namespace with autoloaded task modules
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.25
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -339,6 +339,7 @@ files:
339
339
  - lib/kettle/dev/tasks.rb
340
340
  - lib/kettle/dev/tasks/ci_task.rb
341
341
  - lib/kettle/dev/version.rb
342
+ - lib/kettle/dev/version_bump.rb
342
343
  - lib/kettle/dev/versioning.rb
343
344
  - lib/kettle/emoji_regex.rb
344
345
  - sig/kettle-dev.rbs
@@ -375,10 +376,10 @@ licenses:
375
376
  - AGPL-3.0-only
376
377
  metadata:
377
378
  homepage_uri: https://kettle-dev.galtzo.com
378
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.2.25
379
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.2.25/CHANGELOG.md
379
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.3.0
380
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.3.0/CHANGELOG.md
380
381
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
381
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.2.25
382
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.3.0
382
383
  funding_uri: https://github.com/sponsors/pboling
383
384
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
384
385
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file