kettle-dev 1.0.14 → 1.0.15

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: 61d7bcf42ac9f8dd354de12af2f45c4a000bf0fb038c17534fdb521fd9799cff
4
- data.tar.gz: be95960f8520e6a001376909ecd3d27d9cfc56506a46d770119f11b951f0039f
3
+ metadata.gz: 2bad6b81b9914fe8444ff6d626f42a2b3c567fd4c291f0bb95f47efebf34fb7b
4
+ data.tar.gz: 31921c38c683df7a384e1633a1bd46815cf98629775202dbb9354bc048c6712a
5
5
  SHA512:
6
- metadata.gz: 60ddf9f6624d7bcd57a5b702fd182b8766ba133937c21c93da094738089b832a6b86ceea0528d1c116e42c701c8e2f5b56ba40312fcdd348a0c6bf0a984bbda1
7
- data.tar.gz: 98e13824721dc2b7aa68e01cea45f6289b9c6d980606613e013fb6f238317a299b9c10cc63d4a5a65fdd312a2d9221f5463d2ce3d1a3f43f13ef417e42d0a6d9
6
+ metadata.gz: e8eeeb8887d61cf1fe49bf4113ae1ac37232a85d3171ee24ba089f74dca25bec3f25b980150d72919ba611ca4cf9feab2736cc0ba4b8f25639cc057c19c2acc7
7
+ data.tar.gz: eac8be5a8e37590f83b91759b51d9af0efda35d0d51459a15476a456c44ce6551ddc65e7090393bfefd6fafa79ea0543d44546b64f0226017d67efff378b3901
checksums.yaml.gz.sig CHANGED
Binary file
@@ -39,13 +39,15 @@ jobs:
39
39
  rubygems: "3.3.27"
40
40
  bundler: "2.3.27"
41
41
 
42
- # Ruby 2.4
43
- - ruby: "ruby-2.4"
44
- appraisal: "ruby-2-4"
45
- exec_cmd: "rake test"
46
- gemfile: "Appraisal.root"
47
- rubygems: "3.3.27"
48
- bundler: "2.3.27"
42
+ # Ruby 2.4 testing is disabled because when vcr is required, any version, it raises
43
+ # NoMethodError: undefined method `delete_prefix' for "CONTENT_LENGTH":String
44
+ # # Ruby 2.4
45
+ # - ruby: "ruby-2.4"
46
+ # appraisal: "ruby-2-4"
47
+ # exec_cmd: "rake test"
48
+ # gemfile: "Appraisal.root"
49
+ # rubygems: "3.3.27"
50
+ # bundler: "2.3.27"
49
51
 
50
52
  # Ruby 2.5
51
53
  - ruby: "ruby-2.5"
data/.junie/guidelines.md CHANGED
@@ -57,7 +57,8 @@ This document captures project-specific knowledge to streamline setup, testing,
57
57
  - When adding new code or modifying existing code always add tests to cover the updated behavior, including branches, and different types of expected and unexpected inputs.
58
58
  - Additional test utilities:
59
59
  - rspec-stubbed_env: Use stub_env to control ENV safely within examples.
60
- - timecop-rspec: Time manipulation available, see lib/kettle/test/config/int/rspec/timecop_rspec.
60
+ - timecop-rspec: Time manipulation is available, and is setup by kettle-test.
61
+ - To freeze time use `freeze: Time.new(*args)` tag on an example or group
61
62
  - Running tests (verified)
62
63
  - Full suite (recommended to satisfy coverage thresholds):
63
64
  - bin/rspec
data/.rubocop.yml CHANGED
@@ -11,3 +11,6 @@ Layout/IndentationConsistency:
11
11
 
12
12
  RSpec/ExampleLength:
13
13
  Enabled: false
14
+
15
+ RSpec/MultipleExpectations:
16
+ Enabled: false
data/Appraisals CHANGED
@@ -27,6 +27,7 @@ appraise "unlocked_deps" do
27
27
  eval_gemfile "modular/documentation.gemfile"
28
28
  eval_gemfile "modular/style.gemfile"
29
29
  eval_gemfile "modular/optional.gemfile"
30
+ eval_gemfile "modular/recording/r3/recording.gemfile"
30
31
  end
31
32
 
32
33
  # Used for head (nightly) releases of ruby, truffleruby, and jruby.
@@ -36,6 +37,7 @@ appraise "head" do
36
37
  gem "mutex_m", ">= 0.2"
37
38
  gem "stringio", ">= 3.0"
38
39
  gem "benchmark", "~> 0.4", ">= 0.4.1"
40
+ eval_gemfile "modular/recording/r3/recording.gemfile"
39
41
  end
40
42
 
41
43
  # Used for current releases of ruby, truffleruby, and jruby.
@@ -44,6 +46,7 @@ appraise "current" do
44
46
  gem "erb"
45
47
  gem "mutex_m", ">= 0.2"
46
48
  gem "stringio", ">= 3.0"
49
+ eval_gemfile "modular/recording/r3/recording.gemfile"
47
50
  end
48
51
 
49
52
  appraise "ruby-2-3" do
@@ -51,32 +54,38 @@ appraise "ruby-2-3" do
51
54
  # This means we have no choice but to use the erb that shipped with Ruby 2.3
52
55
  # /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/gems/2.3.0/gems/erb-2.2.2/lib/erb.rb:670:in `prepare_trim_mode': undefined method `match?' for "-":String (NoMethodError)
53
56
  # spec.add_development_dependency("erb", ">= 2.2") # ruby >= 2.3.0, not SemVer, old rubies get dropped in a patch.
57
+ eval_gemfile "modular/recording/r2.3/recording.gemfile"
54
58
  end
55
59
 
56
60
  appraise "ruby-2-4" do
57
61
  gem "erb"
62
+ eval_gemfile "modular/recording/r2.4/recording.gemfile"
58
63
  end
59
64
 
60
65
  appraise "ruby-2-5" do
61
66
  gem "erb"
67
+ eval_gemfile "modular/recording/r2.5/recording.gemfile"
62
68
  end
63
69
 
64
70
  appraise "ruby-2-6" do
65
71
  gem "erb"
66
72
  gem "mutex_m", "~> 0.2"
67
73
  gem "stringio", "~> 3.0"
74
+ eval_gemfile "modular/recording/r2.5/recording.gemfile"
68
75
  end
69
76
 
70
77
  appraise "ruby-2-7" do
71
78
  gem "erb"
72
79
  gem "mutex_m", "~> 0.2"
73
80
  gem "stringio", "~> 3.0"
81
+ eval_gemfile "modular/recording/r2.5/recording.gemfile"
74
82
  end
75
83
 
76
84
  appraise "ruby-3-0" do
77
85
  gem "erb"
78
86
  gem "mutex_m", "~> 0.2"
79
87
  gem "stringio", "~> 3.0"
88
+ eval_gemfile "modular/recording/r3/recording.gemfile"
80
89
  end
81
90
 
82
91
  appraise "ruby-3-1" do
@@ -85,6 +94,7 @@ appraise "ruby-3-1" do
85
94
  gem "erb"
86
95
  gem "mutex_m", "~> 0.2"
87
96
  gem "stringio", "~> 3.0"
97
+ eval_gemfile "modular/recording/r3/recording.gemfile"
88
98
  end
89
99
 
90
100
  appraise "ruby-3-2" do
@@ -93,9 +103,11 @@ appraise "ruby-3-2" do
93
103
  gem "erb"
94
104
  gem "mutex_m", "~> 0.2"
95
105
  gem "stringio", "~> 3.0"
106
+ eval_gemfile "modular/recording/r3/recording.gemfile"
96
107
  end
97
108
 
98
109
  appraise "ruby-3-3" do
110
+ eval_gemfile "modular/recording/r3/recording.gemfile"
99
111
  gem "erb"
100
112
  gem "mutex_m", "~> 0.2"
101
113
  gem "stringio", "~> 3.0"
@@ -115,6 +127,7 @@ appraise "coverage" do
115
127
  gem "stringio", "~> 3.0"
116
128
  eval_gemfile "modular/coverage.gemfile"
117
129
  eval_gemfile "modular/optional.gemfile"
130
+ eval_gemfile "modular/recording/r3/recording.gemfile"
118
131
  end
119
132
 
120
133
  # Only run linter on latest Ruby version (but, in support of oldest supported Ruby version)
data/CHANGELOG.md CHANGED
@@ -24,6 +24,27 @@ Please file a bug if you notice a violation of semantic versioning.
24
24
  ### Fixed
25
25
  ### Security
26
26
 
27
+ ## [1.0.15] - 2025-08-29
28
+ - TAG: [v1.0.15][1.0.15t]
29
+ - COVERAGE: 98.17% -- 2259/2301 lines in 20 files
30
+ - BRANCH COVERAGE: 81.00% -- 908/1121 branches in 20 files
31
+ - 76.03% documented
32
+ ### Added
33
+ - kettle-release: early validation of identical set of copyright years in README.md and CHANGELOG.md, adds current year if missing, aborts on mismatch
34
+ - kettle-release: update KLOC in README.md
35
+ - kettle-release: update Rakefile.example with version and date
36
+ ### Changed
37
+ - kettle-release: print package name and version released as final line
38
+ - use git adapter to wrap more git commands to make tests easier to build
39
+ - stop testing Ruby 2.4 on CI due to a strange issue with VCR.
40
+ - still testing Ruby 2.3
41
+ ### Fixed
42
+ - include gemfiles/modular/*gemfile.example with packaged gem
43
+ - CI workflow result polling logic revised:
44
+ - includes a delay
45
+ - scopes queries to specific commit SHA
46
+ - prevents false failures from previous runs
47
+
27
48
  ## [1.0.14] - 2025-08-28
28
49
  - TAG: [v1.0.14][1.0.14t]
29
50
  - COVERAGE: 97.70% -- 2125/2175 lines in 20 files
@@ -236,7 +257,7 @@ Please file a bug if you notice a violation of semantic versioning.
236
257
  - Selecting will run the selected workflow via `act`
237
258
  - This may move to its own gem in the future.
238
259
 
239
- [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.0.14...HEAD
260
+ [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.0.15...HEAD
240
261
  [1.0.0]: https://github.com/kettle-rb/kettle-dev/compare/a427c302df09cfe4253a7c8d400333f9a4c1a208...v1.0.0
241
262
  [1.0.0t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.0.0
242
263
  [1.0.1]: https://gitlab.com/kettle-rb/kettle-dev/-/compare/v1.0.0...v1.0.1
@@ -267,3 +288,5 @@ Please file a bug if you notice a violation of semantic versioning.
267
288
  [1.0.13t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.0.13
268
289
  [1.0.14]: https://github.com/kettle-rb/kettle-dev/compare/v1.0.13...v1.0.14
269
290
  [1.0.14t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.0.14
291
+ [1.0.15]: https://github.com/kettle-rb/kettle-dev/compare/v1.0.14...v1.0.15
292
+ [1.0.15t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.0.15
data/Gemfile CHANGED
@@ -25,6 +25,9 @@ end
25
25
  # Code Coverage
26
26
  eval_gemfile "gemfiles/modular/coverage.gemfile"
27
27
 
28
+ # Test HTTP Interaction Recording
29
+ eval_gemfile "gemfiles/modular/recording/r3/recording.gemfile"
30
+
28
31
  # Linting
29
32
  eval_gemfile "gemfiles/modular/style.gemfile"
30
33
 
data/README.md CHANGED
@@ -50,6 +50,28 @@ and concordant releases of JRuby, and TruffleRuby.
50
50
 
51
51
  ## 💡 Info you can shake a stick at
52
52
 
53
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
54
+ |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
55
+ | Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
56
+ | Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
57
+ | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
58
+ | Works with MRI Ruby 2 | <br/> [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
59
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
60
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![Wiki][📜wiki-img]][📜wiki] |
61
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
62
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
63
+ | Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
64
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
65
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
66
+
67
+ ### Compatibility
68
+
69
+ Compatible with Ruby 2.3+, and concordant releases of JRuby, and TruffleRuby.
70
+
71
+ | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 |
72
+ |------------------------------------------------|-------------------------------------------------------------------------------------|
73
+ | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
74
+
53
75
  ### Federated DVCS
54
76
 
55
77
  <details>
@@ -73,7 +95,7 @@ and concordant releases of JRuby, and TruffleRuby.
73
95
 
74
96
  [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
75
97
 
76
- - 💡Subscribe for support guarantees covering _all_ FLOSS dependencies
98
+ - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
77
99
  - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
78
100
  - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
79
101
 
@@ -85,29 +107,19 @@ Alternatively:
85
107
 
86
108
  </details>
87
109
 
88
- | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
89
- |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
90
- | Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
91
- | Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
92
- | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
93
- | Works with MRI Ruby 2 | [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
94
- | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
95
- | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![Wiki][📜wiki-img]][📜wiki] |
96
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
97
- | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
98
- | Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
99
- | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
100
- | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
101
-
102
110
  ## ✨ Installation
103
111
 
104
112
  Install the gem and add to the application's Gemfile by executing:
105
113
 
106
- $ bundle add kettle-dev
114
+ ```console
115
+ bundle add kettle-dev
116
+ ```
107
117
 
108
118
  If bundler is not being used to manage dependencies, install the gem by executing:
109
119
 
110
- $ gem install kettle-dev
120
+ ```console
121
+ gem install kettle-dev
122
+ ```
111
123
 
112
124
  ### 🔒 Secure Installation
113
125
 
@@ -644,10 +656,6 @@ Thanks for RTFM. ☺️
644
656
  [🚎13-🔒️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/locked_deps.yml/badge.svg
645
657
  [🚎14-🔓️-wf]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml
646
658
  [🚎14-🔓️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml/badge.svg
647
- [💎ruby-1.9i]: https://img.shields.io/badge/Ruby-1.9_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
648
- [💎ruby-2.0i]: https://img.shields.io/badge/Ruby-2.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
649
- [💎ruby-2.1i]: https://img.shields.io/badge/Ruby-2.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
650
- [💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
651
659
  [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
652
660
  [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
653
661
  [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
@@ -696,7 +704,7 @@ Thanks for RTFM. ☺️
696
704
  [📌gitmoji]:https://gitmoji.dev
697
705
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20😜%20😍-34495e.svg?style=flat-square
698
706
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
699
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.175-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
707
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.301-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
700
708
  [🔐security]: SECURITY.md
701
709
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
702
710
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/README.md.example CHANGED
@@ -495,7 +495,7 @@ Thanks for RTFM. ☺️
495
495
  [📌gitmoji]:https://gitmoji.dev
496
496
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20😜%20😍-34495e.svg?style=flat-square
497
497
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
498
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.725-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
498
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.301-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
499
499
  [🔐security]: SECURITY.md
500
500
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
501
501
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/Rakefile.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # kettle-dev Rakefile v1.0.0 - 2025-08-23
3
+ # kettle-dev Rakefile v1.0.15 - 2025-08-29
4
4
  # Ruby 2.3 (Safe Navigation) or higher required
5
5
  #
6
6
  # MIT License (see License.txt)
@@ -0,0 +1 @@
1
+ # Optional dependencies are not depended on directly, but may be used if present.
@@ -90,14 +90,24 @@ module Kettle
90
90
  return unless owner && repo
91
91
  b = branch || current_branch
92
92
  return unless b
93
- uri = URI("https://api.github.com/repos/#{owner}/#{repo}/actions/workflows/#{workflow_file}/runs?branch=#{URI.encode_www_form_component(b)}&per_page=1")
93
+ # Scope to the exact commit SHA when available to avoid picking up a previous run on the same branch.
94
+ sha_out, status = Open3.capture2("git", "rev-parse", "HEAD")
95
+ sha = status.success? ? sha_out.strip : nil
96
+ base_url = "https://api.github.com/repos/#{owner}/#{repo}/actions/workflows/#{workflow_file}/runs?branch=#{URI.encode_www_form_component(b)}&per_page=5"
97
+ uri = URI(base_url)
94
98
  req = Net::HTTP::Get.new(uri)
95
99
  req["User-Agent"] = "kettle-dev/ci-helpers"
96
100
  req["Authorization"] = "token #{token}" if token && !token.empty?
97
101
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
98
102
  return unless res.is_a?(Net::HTTPSuccess)
99
103
  data = JSON.parse(res.body)
100
- run = data["workflow_runs"]&.first
104
+ runs = Array(data["workflow_runs"]) || []
105
+ # Try to match by head_sha first; fall back to first run (branch-scoped) if none matches yet.
106
+ run = if sha
107
+ runs.find { |r| r["head_sha"] == sha } || runs.first
108
+ else
109
+ runs.first
110
+ end
101
111
  return unless run
102
112
  {
103
113
  "status" => run["status"],
@@ -70,6 +70,16 @@ module Kettle
70
70
  pbar = if defined?(ProgressBar)
71
71
  ProgressBar.create(title: "CI", total: total, format: "%t %b %c/%C", length: 30)
72
72
  end
73
+ # Small initial delay to allow GitHub to register the newly pushed commit and enqueue workflows.
74
+ # Configurable via K_RELEASE_CI_INITIAL_SLEEP (seconds); defaults to 3s.
75
+ begin
76
+ initial_sleep = begin
77
+ Integer(ENV["K_RELEASE_CI_INITIAL_SLEEP"])
78
+ rescue
79
+ nil
80
+ end
81
+ end
82
+ sleep((initial_sleep && initial_sleep >= 0) ? initial_sleep : 3)
73
83
  idx = 0
74
84
  loop do
75
85
  wf = workflows[idx]
@@ -15,6 +15,19 @@ module Kettle
15
15
  #
16
16
  # Public API is intentionally small and only includes what we need right now.
17
17
  class GitAdapter
18
+ # Execute a git command and capture its stdout and success flag.
19
+ # This is a generic escape hatch used by higher-level code for read-only
20
+ # queries that aren't covered by the explicit adapter API. Tests can stub
21
+ # this method to avoid shelling out.
22
+ # @param args [Array<String>]
23
+ # @return [Array<(String, Boolean)>] [output, success]
24
+ def capture(args)
25
+ out, status = Open3.capture2("git", *args)
26
+ [out.strip, status.success?]
27
+ rescue StandardError
28
+ ["", false]
29
+ end
30
+
18
31
  # Create a new adapter rooted at the current working directory.
19
32
  # @return [void]
20
33
  def initialize
@@ -73,12 +73,26 @@ module Kettle
73
73
  overall = Gem::Version.new(latest_overall)
74
74
  cur_series = cur.segments[0, 2]
75
75
  overall_series = overall.segments[0, 2]
76
+ # Ensure latest_for_series actually matches our current series; ignore otherwise.
77
+ if latest_for_series
78
+ lfs_series = Gem::Version.new(latest_for_series).segments[0, 2]
79
+ latest_for_series = nil unless lfs_series == cur_series
80
+ end
81
+ # Determine the sanity-check target correctly for the current series.
82
+ # If RubyGems has a newer overall series than our current series, only compare
83
+ # against the latest published in our current series. If that cannot be determined
84
+ # (e.g., offline), skip the sanity check rather than treating the overall as target.
76
85
  target = if (cur_series <=> overall_series) == -1
77
86
  latest_for_series
78
87
  else
79
88
  latest_overall
80
89
  end
81
- if target
90
+ # IMPORTANT: Never treat a higher different-series "latest_overall" as a downgrade target.
91
+ # If our current series is behind overall and RubyGems does not report a latest_for_series,
92
+ # then we cannot determine the correct target for this series and should skip the check.
93
+ if (cur_series <=> overall_series) == -1 && target.nil?
94
+ puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
95
+ elsif target
82
96
  bump = Kettle::Dev::Versioning.classify_bump(target, version)
83
97
  case bump
84
98
  when :same
@@ -104,6 +118,23 @@ module Kettle
104
118
  print("> ")
105
119
  ans = Kettle::Dev::InputAdapter.gets&.strip
106
120
  abort("Aborted: please update version.rb and CHANGELOG.md, then re-run.") unless ans&.downcase&.start_with?("y")
121
+
122
+ # Initial validation: Ensure README.md and LICENSE.txt have identical sets of copyright years; also ensure current year present when matched
123
+ validate_copyright_years!
124
+
125
+ # Ensure README KLOC badge reflects current CHANGELOG coverage denominator
126
+ begin
127
+ update_readme_kloc_badge!
128
+ rescue StandardError => e
129
+ warn("Failed to update KLOC badge in README: #{e.class}: #{e.message}")
130
+ end
131
+
132
+ # Update Rakefile.example header banner with current version and date
133
+ begin
134
+ update_rakefile_example_header!(version)
135
+ rescue StandardError => e
136
+ warn("Failed to update Rakefile.example header: #{e.class}: #{e.message}")
137
+ end
107
138
  end
108
139
 
109
140
  # 3. bin/setup
@@ -211,11 +242,222 @@ module Kettle
211
242
  # 18. push tags to remotes (new final step)
212
243
  push_tags! if @start_step <= 18
213
244
 
214
- puts "\nRelease complete. Don't forget to push the checksums commit if needed."
245
+ # Final success message
246
+ begin
247
+ version ||= detect_version
248
+ gem_name = detect_gem_name
249
+ puts "\n🚀 Release #{gem_name} v#{version} Complete 🚀"
250
+ rescue StandardError
251
+ # Fallback if detection fails for any reason
252
+ puts "\n🚀 Release v#{version || "unknown"} Complete 🚀"
253
+ end
215
254
  end
216
255
 
217
256
  private
218
257
 
258
+ # Update the README KLOC badge number based on the denominator in the current version's COVERAGE line in CHANGELOG.md.
259
+ # - Parses the current version section of CHANGELOG.md
260
+ # - Finds a line matching: "- COVERAGE: ... -- <tested>/<total> lines ..."
261
+ # - Computes KLOC = total / 1000.0
262
+ # - Formats with three decimals (e.g., 0.076, 2.175, 10.123)
263
+ # - Rewrites the [🧮kloc-img] badge line in README.md (and README.md.example when present)
264
+ # replacing only the numeric portion after "KLOC-" while preserving other URL params.
265
+ def update_readme_kloc_badge!
266
+ version = detect_version
267
+ # Extract only the current version's section
268
+ section, _compare_ref, _tag_ref = extract_changelog_for_version(version)
269
+ return unless section
270
+
271
+ # Example match: "- COVERAGE: 97.70% -- 2125/2175 lines in 20 files"
272
+ m = section.lines.find { |l| l =~ /-\s*COVERAGE:\s*.+--\s*\d+\/(\d+)\s+lines/i }
273
+ return unless m
274
+ denom = m.match(/-\s*COVERAGE:\s*.+--\s*\d+\/(\d+)\s+lines/i)[1].to_i
275
+ kloc = denom.to_f / 1000.0
276
+ kloc_str = format("%.3f", kloc)
277
+
278
+ update_badge_number_in_file(File.join(@root, "README.md"), kloc_str)
279
+ example_path = File.join(@root, "README.md.example")
280
+ update_badge_number_in_file(example_path, kloc_str) if File.file?(example_path)
281
+ end
282
+
283
+ # Helper to update the [🧮kloc-img] badge in the given file path.
284
+ # Replaces only the numeric portion after "KLOC-" keeping other URL parts intact.
285
+ def update_badge_number_in_file(path, kloc_str)
286
+ return unless File.file?(path)
287
+ content = File.read(path)
288
+ # Match the specific reference line, capture groups around the number
289
+ # Example: [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.175-FFDD67.svg?style=...
290
+ new_content = content.gsub(/(\[🧮kloc-img\]:\s*https?:\/\/img\.shields\.io\/badge\/KLOC-)(\d+(?:\.\d+)?)(-[^\s]*)/, "\\1#{kloc_str}\\3")
291
+ if new_content != content
292
+ File.write(path, new_content)
293
+ end
294
+ end
295
+
296
+ # Update Rakefile.example banner to include current gem version and current date.
297
+ # Looks for a line starting with "# kettle-dev Rakefile v" and replaces version/date.
298
+ def update_rakefile_example_header!(version)
299
+ path = File.join(@root, "Rakefile.example")
300
+ return unless File.file?(path)
301
+ content = File.read(path)
302
+ today = Time.now.strftime("%Y-%m-%d")
303
+ new_line = "# kettle-dev Rakefile v#{version} - #{today}"
304
+ new_content = content.gsub(/^# kettle-dev Rakefile v.*$/, new_line)
305
+ if new_content != content
306
+ File.write(path, new_content)
307
+ end
308
+ end
309
+
310
+ # Validate that README.md and CHANGELOG.md contain identical sets of copyright years.
311
+ # This helps ensure docs are kept in sync when bumping the years.
312
+ # Aborts with a helpful message when they differ.
313
+ def validate_copyright_years!
314
+ require "set"
315
+ readme = File.join(@root, "README.md")
316
+ license = File.join(@root, "LICENSE.txt")
317
+ unless File.file?(readme) && File.file?(license)
318
+ # If either file is missing, skip this check silently (some projects might not have both initially)
319
+ return
320
+ end
321
+
322
+ # Normalize year formatting in both files before comparing
323
+ reformat_copyright_year_lines!(readme)
324
+ reformat_copyright_year_lines!(license)
325
+
326
+ r_years = extract_years_from_file(readme)
327
+ l_years = extract_years_from_file(license)
328
+ if r_years == l_years
329
+ # If they match, ensure the current year is present; if not, inject it into both files.
330
+ current_year = Time.now.year
331
+ unless r_years.include?(current_year)
332
+ # Update both files by appending current year to the set and rewriting the lines canonically
333
+ updated_years = r_years.dup
334
+ updated_years << current_year
335
+ # Write back to both files using canonical collapse formatting
336
+ inject_years_into_file!(readme, updated_years)
337
+ inject_years_into_file!(license, updated_years)
338
+ end
339
+ return
340
+ end
341
+
342
+ abort(<<~MSG)
343
+ Mismatched copyright years between README.md and LICENSE.txt.
344
+ README.md: #{r_years.to_a.sort.join(", ")}
345
+ LICENSE.txt: #{l_years.to_a.sort.join(", ")}
346
+ Please update both files so they contain the identical set of years.
347
+ MSG
348
+ end
349
+
350
+ # Extract a Set of Integer years from the given file.
351
+ # It searches for lines containing the word "Copyright" (case-insensitive),
352
+ # then parses four-digit years and year ranges like "2012-2015" (hyphen or en dash).
353
+ # Returns Set[Integer].
354
+ def extract_years_from_file(path)
355
+ require "set"
356
+ years = Set.new
357
+ content = File.read(path)
358
+ # Only consider lines that look like copyright notices to reduce false positives
359
+ content.each_line do |line|
360
+ next unless line =~ /copyright/i
361
+
362
+ # Expand ranges first (supports hyphen-minus and en dash)
363
+ line.scan(/\b(19\d{2}|20\d{2})\s*[\-–]\s*(19\d{2}|20\d{2})\b/).each do |a, b|
364
+ s = a.to_i
365
+ e = b.to_i
366
+ if e < s
367
+ s, e = e, s
368
+ end
369
+ (s..e).each { |y| years << y }
370
+ end
371
+
372
+ # Then single standalone years
373
+ line.scan(/\b(19\d{2}|20\d{2})\b/).each do |y|
374
+ years << y[0].to_i
375
+ end
376
+ end
377
+ years
378
+ end
379
+
380
+ # Collapse a set/array of years into a canonical, comma-separated string, combining
381
+ # consecutive runs into ranges with a hyphen (YYYY-YYYY) and leaving gaps as commas.
382
+ def collapse_years(enum)
383
+ arr = enum.to_a.map(&:to_i).uniq.sort
384
+ return "" if arr.empty?
385
+ segments = []
386
+ start = arr.first
387
+ prev = start
388
+ arr[1..-1].to_a.each do |y|
389
+ if y == prev + 1
390
+ prev = y
391
+ next
392
+ else
393
+ segments << ((start == prev) ? start.to_s : "#{start}-#{prev}")
394
+ start = prev = y
395
+ end
396
+ end
397
+ segments << ((start == prev) ? start.to_s : "#{start}-#{prev}")
398
+ segments.join(", ")
399
+ end
400
+
401
+ # Inject the provided set of years into copyright lines, rewriting them in canonical form.
402
+ # - Finds lines containing 'copyright' (case-insensitive) and a years blob.
403
+ # - Replaces that blob with the canonical collapsed form of the union of existing years and given years.
404
+ # - If multiple copyright lines, updates each consistently.
405
+ def inject_years_into_file!(path, years_set)
406
+ content = File.read(path)
407
+ changed = false
408
+ canonical_all = collapse_years(years_set)
409
+ new_lines = content.each_line.map do |line|
410
+ unless line =~ /copyright/i
411
+ next line
412
+ end
413
+ m = line.match(/\A(?<pre>.*?copyright[^0-9]*)(?<years>(?:\b(?:19|20)\d{2}\b(?:\s*[\-–]\s*\b(?:19|20)\d{2}\b)?)(?:\s*,\s*\b(?:19|20)\d{2}\b(?:\s*[\-–]\s*\b(?:19|20)\d{2}\b)?)*)(?<post>.*)\z/i)
414
+ unless m
415
+ next line
416
+ end
417
+ new_line = "#{m[:pre]}#{canonical_all}#{m[:post]}"
418
+ changed ||= (new_line != line)
419
+ new_line
420
+ end
421
+ if changed
422
+ File.write(path, new_lines.join)
423
+ end
424
+ end
425
+
426
+ # Rewrite copyright lines in-place to collapse years into canonical ranges.
427
+ # Only modifies lines that contain the word "copyright" (case-insensitive).
428
+ def reformat_copyright_year_lines!(path)
429
+ content = File.read(path)
430
+ changed = false
431
+ new_lines = content.each_line.map do |line|
432
+ unless line =~ /copyright/i
433
+ next line
434
+ end
435
+ # Capture three parts: prefix up to first year, the year blob, and the rest
436
+ m = line.match(/\A(?<pre>.*?copyright[^0-9]*)(?<years>(?:\b(?:19|20)\d{2}\b(?:\s*[\-–]\s*\b(?:19|20)\d{2}\b)?)(?:\s*,\s*\b(?:19|20)\d{2}\b(?:\s*[\-–]\s*\b(?:19|20)\d{2}\b)?)*)(?<post>.*)\z/i)
437
+ unless m
438
+ # No parsable year sequence on this line; leave as-is
439
+ next line
440
+ end
441
+ years_blob = m[:years]
442
+ # Reuse extraction logic on just the years blob
443
+ years = []
444
+ years_blob.scan(/\b(19\d{2}|20\d{2})\s*[\-–]\s*(19\d{2}|20\d{2})\b/).each do |a, b|
445
+ s = a.to_i
446
+ e = b.to_i
447
+ s, e = e, s if e < s
448
+ (s..e).each { |y| years << y }
449
+ end
450
+ years_blob.scan(/\b(19\d{2}|20\d{2})\b/).each { |y| years << y[0].to_i }
451
+ canonical = collapse_years(years)
452
+ new_line = "#{m[:pre]}#{canonical}#{m[:post]}"
453
+ changed ||= (new_line != line)
454
+ new_line
455
+ end
456
+ if changed
457
+ File.write(path, new_lines.join)
458
+ end
459
+ end
460
+
219
461
  def monitor_workflows_after_push!
220
462
  # Delegate to shared CI monitor to keep logic DRY across release flow and rake tasks
221
463
  require "kettle/dev/ci_monitor"
@@ -236,8 +478,9 @@ module Kettle
236
478
  end
237
479
 
238
480
  def git_output(args)
239
- out, status = Open3.capture2("git", *args)
240
- [out.strip, status.success?]
481
+ # Route all git interactions through the GitAdapter so tests can safely mock them
482
+ out, ok = @git.capture(args)
483
+ [out.to_s.strip, !!ok]
241
484
  end
242
485
 
243
486
  def ensure_git_user!
@@ -342,6 +585,7 @@ module Kettle
342
585
  uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
343
586
  res = Net::HTTP.get_response(uri)
344
587
  return [nil, nil] unless res.is_a?(Net::HTTPSuccess)
588
+
345
589
  data = JSON.parse(res.body)
346
590
  versions = data.map { |h| h["number"] }.compact
347
591
  versions.reject! { |v| v.to_s.include?("-pre") || v.to_s.include?(".pre") || v.to_s =~ /[a-zA-Z]/ }
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.0.14"
9
+ VERSION = "1.0.15"
10
10
  end
11
11
  end
12
12
  end
@@ -10,6 +10,7 @@ module Kettle
10
10
  def checkout: (String) -> bool
11
11
  def pull: (String, String) -> bool
12
12
  def fetch: (String, String?) -> bool
13
+ def capture: (Array[String]) -> [String, bool]
13
14
  end
14
15
  end
15
16
  end
@@ -3,6 +3,17 @@ module Kettle
3
3
  class ReleaseCLI
4
4
  def initialize: (?start_step: Integer) -> void
5
5
  def run: () -> void
6
+
7
+ private
8
+
9
+ def update_readme_kloc_badge!: () -> void
10
+ def update_badge_number_in_file: (String path, String kloc_str) -> void
11
+ def update_rakefile_example_header!: (String version) -> void
12
+ def validate_copyright_years!: () -> void
13
+ def extract_years_from_file: (String path) -> ::Set[Integer]
14
+ def collapse_years: (::_ToA[Integer] enum) -> String
15
+ def reformat_copyright_year_lines!: (String path) -> void
16
+ def inject_years_into_file!: (String path, ::Set[Integer] years_set) -> void
6
17
  end
7
18
  end
8
19
  end
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: 1.0.14
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -285,6 +285,7 @@ files:
285
285
  - gemfiles/modular/coverage.gemfile
286
286
  - gemfiles/modular/documentation.gemfile
287
287
  - gemfiles/modular/optional.gemfile
288
+ - gemfiles/modular/optional.gemfile.example
288
289
  - gemfiles/modular/style.gemfile
289
290
  - lib/kettle-dev.rb
290
291
  - lib/kettle/dev.rb
@@ -339,10 +340,10 @@ licenses:
339
340
  - MIT
340
341
  metadata:
341
342
  homepage_uri: https://kettle-dev.galtzo.com/
342
- source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.0.14
343
- changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.0.14/CHANGELOG.md
343
+ source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.0.15
344
+ changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.0.15/CHANGELOG.md
344
345
  bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
345
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.14
346
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.15
346
347
  funding_uri: https://github.com/sponsors/pboling
347
348
  wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
348
349
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file