kettle-dev 2.2.13 → 2.2.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.
data/README.md CHANGED
@@ -10,9 +10,9 @@
10
10
 
11
11
  `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
12
12
 
13
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
13
+ [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
14
14
 
15
- <details>
15
+ <details markdown="1">
16
16
  <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
17
17
 
18
18
  I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
@@ -22,7 +22,7 @@ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-ta
22
22
  ## 🌻 Synopsis <a href="https://discord.gg/3qme4XHNKN"><img alt="Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px.svg" width="8%" align="right"/></a> <a href="https://ruby-toolbox.com"><img alt="ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5" src="https://logos.galtzo.com/assets/images/ruby-lang/avatar-128px.svg" width="8%" align="right"/></a>
23
23
 
24
24
  Kettle::Dev is the development, CI, changelog, and release harness used by
25
- kettle-rb gems. It installs rake tasks when loaded from a project's `Rakefile`,
25
+ kettle-dev gems. It installs rake tasks when loaded from a project's `Rakefile`,
26
26
  and it ships command-line tools for changelog preparation, release automation,
27
27
  multi-forge git remotes, commit-message hooks, and Open Collective README
28
28
  updates.
@@ -42,14 +42,14 @@ require "kettle/dev"
42
42
  ```
43
43
 
44
44
  For RSpec projects, use the matching test harness from
45
- [kettle-test](https://github.com/kettle-rb/kettle-test):
45
+ [kettle-test](https://github.com/kettle-dev/kettle-test):
46
46
 
47
47
  ```ruby
48
48
  require "kettle/test/rspec"
49
49
  ```
50
50
 
51
51
  Project setup and template refreshes are now owned by
52
- [kettle-jem](https://github.com/kettle-rb/kettle-jem), not kettle-dev:
52
+ [kettle-jem](https://github.com/kettle-dev/kettle-jem), not kettle-dev:
53
53
 
54
54
  ```console
55
55
  gem install kettle-jem
@@ -77,7 +77,7 @@ bin/kettle-release
77
77
  - Rake task loading from `require "kettle/dev"`.
78
78
  - RuboCop Gradual, Reek, YARD, appraisal, local CI, benchmark, and coverage task wiring.
79
79
  - `kettle-changelog` for moving Unreleased changelog notes into a versioned release section with coverage and documentation stats.
80
- - `kettle-release` for the canonical kettle-rb release flow.
80
+ - `kettle-release` for the canonical kettle-dev release flow.
81
81
  - `kettle-pre-release` for release readiness checks.
82
82
  - `kettle-dvcs` for normalizing GitHub, GitLab, Codeberg, and aggregate remotes.
83
83
  - `kettle-commit-msg` for shared commit-message hook behavior.
@@ -88,9 +88,9 @@ bin/kettle-release
88
88
 
89
89
  | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
90
90
  |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
91
- | Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎jruby-9.2-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎jruby-9.3-wf] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎jruby-9.4-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
92
- | Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.0 Compat][💎truby-23.0i]][🚎truby-23.0-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎truby-23.1-wf] <br/> [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf]|
93
- | Works with MRI Ruby 4 | [![Ruby 4.0 Compat][💎ruby-4.0i]][🚎11-c-wf] [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
91
+ | Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎jruby-9.2-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎jruby-9.3-wf] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎jruby-9.4-wf] [![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
92
+ | Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.0 Compat][💎truby-23.0i]][🚎truby-23.0-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎truby-23.1-wf] <br/> [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf]|
93
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
94
94
  | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎ruby-3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎ruby-3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
95
95
  | Works with MRI Ruby 2 | [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎ruby-2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎ruby-2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎ruby-2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎ruby-2.7-wf]|
96
96
  | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
@@ -109,9 +109,24 @@ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
109
109
  may be higher than the gem's runtime compatibility floor when legacy Rubies are
110
110
  not practical for the current toolchain.
111
111
 
112
- | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
113
- |------------------------------------------------|--------------------------------------------------------|
114
- | 👟 Check it out! | [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
112
+ <a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
113
+
114
+ The _amazing_ test matrix is powered by the kettle-dev stack.
115
+
116
+ <details markdown="1">
117
+ <summary>How kettle-dev manages complexity in tests</summary>
118
+
119
+ | Gem | Source | Role | Daily download rank |
120
+ |-----|--------|------|---------------------|
121
+ | [appraisal2](https://bestgems.org/gems/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [![Daily download rank for appraisal2](https://img.shields.io/gem/rd/appraisal2.svg?style=flat-square)](https://bestgems.org/gems/appraisal2) |
122
+ | [appraisal2-rubocop](https://bestgems.org/gems/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [![Daily download rank for appraisal2-rubocop](https://img.shields.io/gem/rd/appraisal2-rubocop.svg?style=flat-square)](https://bestgems.org/gems/appraisal2-rubocop) |
123
+ | [kettle-jem](https://bestgems.org/gems/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [![Daily download rank for kettle-jem](https://img.shields.io/gem/rd/kettle-jem.svg?style=flat-square)](https://bestgems.org/gems/kettle-jem) |
124
+ | [kettle-soup-cover](https://bestgems.org/gems/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [![Daily download rank for kettle-soup-cover](https://img.shields.io/gem/rd/kettle-soup-cover.svg?style=flat-square)](https://bestgems.org/gems/kettle-soup-cover) |
125
+ | [kettle-test](https://bestgems.org/gems/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [![Daily download rank for kettle-test](https://img.shields.io/gem/rd/kettle-test.svg?style=flat-square)](https://bestgems.org/gems/kettle-test) |
126
+ | [rubocop-lts](https://bestgems.org/gems/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [![Daily download rank for rubocop-lts](https://img.shields.io/gem/rd/rubocop-lts.svg?style=flat-square)](https://bestgems.org/gems/rubocop-lts) |
127
+ | [turbo_tests2](https://bestgems.org/gems/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [![Daily download rank for turbo_tests2](https://img.shields.io/gem/rd/turbo_tests2.svg?style=flat-square)](https://bestgems.org/gems/turbo_tests2) |
128
+
129
+ </details>
115
130
 
116
131
  ### Federated DVCS
117
132
 
@@ -187,7 +202,7 @@ require "kettle/dev"
187
202
 
188
203
  ### RSpec
189
204
 
190
- This gem integrates tightly with [kettle-test](https://github.com/kettle-rb/kettle-test).
205
+ This gem integrates tightly with [kettle-test](https://github.com/kettle-dev/kettle-test).
191
206
 
192
207
  ```ruby
193
208
  require "kettle/test/rspec"
@@ -418,9 +433,11 @@ What it does:
418
433
 
419
434
  - Script: `exe/kettle-bump` (run as `kettle-bump`)
420
435
  - Purpose: Bumps the current single gem's `lib/**/version.rb` before changelog
421
- preparation. It accepts an exact version or `major`, `minor`, or `patch`.
436
+ preparation. It accepts an exact version or `major`, `minor`, `patch`, or
437
+ `pre`.
422
438
  - Usage:
423
439
  - `kettle-bump patch`
440
+ - `kettle-bump pre`
424
441
  - `kettle-bump 1.2.4 --from 1.2.3`
425
442
  - `kettle-bump minor --dry-run`
426
443
  - `kettle-bump patch --check`
@@ -527,7 +544,7 @@ What it does:
527
544
  - Sponsors (Organizations): `<!-- <TAG>-ORGANIZATIONS:START --> … <!-- <TAG>-ORGANIZATIONS:END -->`
528
545
  - Handle resolution:
529
546
  1. `OPENCOLLECTIVE_HANDLE` environment variable, if set
530
- 2. `opencollective.yml` in the project root (e.g., `collective: "kettle-rb"` in this repo)
547
+ 2. `opencollective.yml` in the project root (e.g., `collective: "kettle-dev"` in this repo)
531
548
  - Usage:
532
549
  - `exe/kettle-readme-backers`
533
550
  - `OPENCOLLECTIVE_HANDLE=my-collective exe/kettle-readme-backers`
@@ -593,7 +610,7 @@ Made with [contributors-img][🖐contrib-rocks].
593
610
 
594
611
  Also see GitLab Contributors: [https://gitlab.com/kettle-dev/kettle-dev/-/graphs/main][🚎contributors-gl]
595
612
 
596
- <details>
613
+ <details markdown="1">
597
614
  <summary>⭐️ Star History</summary>
598
615
 
599
616
  <a href="https://star-history.com/kettle-dev/kettle-dev&Date">
@@ -690,12 +707,8 @@ Thanks for RTFM. ☺️
690
707
  [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
691
708
  [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
692
709
  [🖇sponsor]: https://github.com/sponsors/pboling
693
- [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
694
- [🖇polar]: https://polar.sh/pboling
695
710
  [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
696
711
  [🖇kofi]: https://ko-fi.com/pboling
697
- [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
698
- [🖇patreon]: https://patreon.com/galtzo
699
712
  [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
700
713
  [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
701
714
  [🖇buyme]: https://www.buymeacoffee.com/pboling
@@ -785,11 +798,13 @@ Thanks for RTFM. ☺️
785
798
  [🚎jruby-9.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.2.yml
786
799
  [🚎jruby-9.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.3.yml
787
800
  [🚎jruby-9.4-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.4.yml
801
+ [🚎jruby-10.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-10.0.yml
788
802
  [🚎truby-22.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-22.3.yml
789
803
  [🚎truby-23.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.0.yml
790
804
  [🚎truby-23.1-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.1.yml
791
805
  [🚎truby-24.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-24.2.yml
792
806
  [🚎truby-25.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-25.0.yml
807
+ [🚎truby-33.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-33.0.yml
793
808
  [🚎2-cov-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml
794
809
  [🚎2-cov-wfi]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml/badge.svg
795
810
  [🚎3-hd-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/heads.yml
@@ -817,7 +832,6 @@ Thanks for RTFM. ☺️
817
832
  [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
818
833
  [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
819
834
  [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
820
- [💎ruby-4.0i]: https://img.shields.io/badge/Ruby-4.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
821
835
  [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
822
836
  [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
823
837
  [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
@@ -825,10 +839,13 @@ Thanks for RTFM. ☺️
825
839
  [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
826
840
  [💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
827
841
  [💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
842
+ [💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
828
843
  [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
844
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
829
845
  [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
830
846
  [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
831
847
  [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
848
+ [💎jruby-10.0i]: https://img.shields.io/badge/JRuby-10.0-FBE742?style=for-the-badge&logo=ruby&logoColor=red
832
849
  [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
833
850
  [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
834
851
  [🤝gh-issues]: https://github.com/kettle-dev/kettle-dev/issues
@@ -857,7 +874,7 @@ Thanks for RTFM. ☺️
857
874
  [📌gitmoji]: https://gitmoji.dev
858
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
859
876
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
860
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.266-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
877
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.429-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
861
878
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
862
879
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
863
880
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -885,7 +902,7 @@ Thanks for RTFM. ☺️
885
902
  | Package | kettle-dev |
886
903
  | Description | 🍲 Kettle::Dev is a meta tool from kettle-rb to streamline development and testing. Acts as a shim dependency, pulling in many other dependencies, to give you OOTB productivity with a RubyGem, or Ruby app project. Configures a complete set of Rake tasks, for all the libraries is brings in, so they arrive ready to go. Fund overlooked open source projects - bottom of stack, dev/test dependencies: floss-funding.dev |
887
904
  | Homepage | https://github.com/kettle-dev/kettle-dev |
888
- | Source | https://github.com/kettle-dev/kettle-dev/tree/v2.2.4 |
905
+ | Source | https://github.com/kettle-dev/kettle-dev |
889
906
  | License | `AGPL-3.0-only` |
890
- | Funding | https://github.com/sponsors/pboling, https://issuehunt.io/u/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://opencollective.com/kettle-rb, https://patreon.com/galtzo, https://polar.sh/pboling, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-dev, https://www.buymeacoffee.com/pboling |
907
+ | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-dev, https://www.buymeacoffee.com/pboling |
891
908
  <!-- kettle-jem:metadata:end -->
data/exe/kettle-changelog CHANGED
@@ -55,7 +55,7 @@ end
55
55
  begin
56
56
  if ARGV.include?("-h") || ARGV.include?("--help")
57
57
  puts <<~USAGE
58
- Usage: kettle-changelog [--version VERSION] [--update-prep] [--pending-release] [--release-state] [--json] [--no-strict] [--no-coverage-threshold]
58
+ Usage: kettle-changelog [--version VERSION] [--update-prep] [--pending-release] [--release-state] [--add-unreleased-entry --section SECTION --entry TEXT] [--json] [--no-strict] [--no-coverage-threshold]
59
59
 
60
60
  Detects the current version from lib/**/version.rb, the latest live release, and
61
61
  the most recent CHANGELOG.md release section, then prompts to confirm the selected plan:
@@ -70,6 +70,9 @@ begin
70
70
  --pending-release Query whether the changelog has release work pending; exits 0 for yes, 1 for no
71
71
  --release-state,
72
72
  --release-status Print changelog release state, including latest published release and pending sources
73
+ --add-unreleased-entry Add one entry to an existing section under ## [Unreleased]
74
+ --section SECTION Unreleased section to receive the entry (Added, Changed, Deprecated, Removed, Fixed, Security)
75
+ --entry TEXT Changelog entry text; "- " is added when omitted
73
76
  --json Print query output as JSON
74
77
  --no-strict Allow missing coverage and yard data (warnings only, no errors)
75
78
  --no-coverage-threshold Generate coverage without hard-failing below configured thresholds
@@ -128,11 +131,42 @@ begin
128
131
  update_prep = ARGV.delete("--update-prep")
129
132
  pending_release_query = ARGV.delete("--pending-release")
130
133
  release_state_query = ARGV.delete("--release-state") || ARGV.delete("--release-status")
134
+ add_unreleased_entry = ARGV.delete("--add-unreleased-entry") || ARGV.delete("--add-changelog-entry")
131
135
  json_output = ARGV.delete("--json")
132
136
  coverage_threshold_disabled = ARGV.include?("--no-coverage-threshold") || ARGV.include?("--no-coverage-thresholds")
133
137
  coverage_hard = !coverage_threshold_disabled && ENV.fetch("K_CHANGELOG_COVERAGE_HARD", "true").downcase != "false"
134
138
  version_override = extract_version_arg!(ARGV)
135
139
 
140
+ def extract_option_arg!(argv, name)
141
+ value = nil
142
+ if (idx = argv.index(name))
143
+ value = argv[idx + 1]
144
+ Kettle::Dev::ExitAdapter.abort("#{name} requires a value") if value.to_s.empty?
145
+ argv.slice!(idx, 2)
146
+ end
147
+ argv.delete_if do |arg|
148
+ if arg.start_with?("#{name}=")
149
+ value = arg.split("=", 2)[1]
150
+ true
151
+ else
152
+ false
153
+ end
154
+ end
155
+ value
156
+ end
157
+
158
+ if add_unreleased_entry
159
+ section = extract_option_arg!(ARGV, "--section")
160
+ entry = extract_option_arg!(ARGV, "--entry")
161
+ Kettle::Dev::ExitAdapter.abort("--section is required with --add-unreleased-entry") if section.to_s.empty?
162
+ Kettle::Dev::ExitAdapter.abort("--entry is required with --add-unreleased-entry") if entry.to_s.empty?
163
+
164
+ result = Kettle::Dev::ChangelogEntryAdder.new(section: section, entry: entry).run
165
+ message = (result == :changed) ? "CHANGELOG.md updated under Unreleased #{section}." : "CHANGELOG.md already contains that Unreleased #{section} entry."
166
+ puts(json_output ? JSON.pretty_generate({changed: result == :changed, section: section, entry: entry}) : message)
167
+ exit(0)
168
+ end
169
+
136
170
  cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, version: version_override)
137
171
  if release_state_query
138
172
  state = cli.release_state
@@ -6,7 +6,7 @@ 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].freeze
9
+ BUMP_TYPES = %w[major minor patch pre].freeze
10
10
 
11
11
  def initialize(argv = [], out: $stdout, err: $stderr, root: Kettle::Dev::CIHelpers.project_root)
12
12
  @argv = argv.dup
@@ -39,7 +39,7 @@ module Kettle
39
39
  def parse_options
40
40
  options = {mode: :execute, help: false}
41
41
  parser = OptionParser.new do |opts|
42
- opts.banner = "Usage: kettle-bump VERSION|major|minor|patch [options]"
42
+ opts.banner = "Usage: kettle-bump VERSION|major|minor|patch|pre [options]"
43
43
  opts.on("--from VERSION", "Require the current version before bumping") { |value| options[:from] = validate_version(value) }
44
44
  opts.on("--check", "Exit non-zero when the bump would change files") { options[:mode] = :check }
45
45
  opts.on("--dry-run", "Print planned changes without writing files") { options[:mode] = :dry_run }
@@ -51,7 +51,7 @@ module Kettle
51
51
  end
52
52
  parser.parse!(argv)
53
53
  options[:target] = argv.shift
54
- raise Kettle::Dev::Error, "kettle-bump requires VERSION, major, minor, or patch" unless options[:target] || options[:help]
54
+ raise Kettle::Dev::Error, "kettle-bump requires VERSION, major, minor, patch, or pre" unless options[:target] || options[:help]
55
55
  raise Kettle::Dev::Error, "unexpected arguments: #{argv.join(" ")}" unless argv.empty?
56
56
 
57
57
  options
@@ -68,6 +68,8 @@ module Kettle
68
68
  end
69
69
 
70
70
  def bumped_version(type, current_version)
71
+ return bumped_prerelease_version(current_version) if type == "pre"
72
+
71
73
  version = Gem::Version.new(current_version)
72
74
  segments = version.segments
73
75
  unless segments.all? { |segment| segment.is_a?(Integer) }
@@ -85,6 +87,33 @@ module Kettle
85
87
  end
86
88
  end
87
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
+
88
117
  def validate_version(version)
89
118
  Gem::Version.new(version).to_s
90
119
  rescue ArgumentError => error
@@ -14,6 +14,8 @@ module Kettle
14
14
  # includes coverage and YARD stats, and updates link references.
15
15
  class ChangelogCLI
16
16
  UNRELEASED_SECTION_HEADING = "[Unreleased]:"
17
+ CHANGELOG_VERSION_PATTERN = /\d+\.\d+\.\d+(?:[.-][0-9A-Za-z]+)*/
18
+ CHANGELOG_VERSION_PATTERN_SOURCE = CHANGELOG_VERSION_PATTERN.source
17
19
  # Matches a Markdown link-reference definition line, e.g. `[key]: https://...`
18
20
  LINK_REF_DEF_RE = /^\s*\[[^\]]+\]:\s+\S+/
19
21
  # Matches an ATX heading at H4 or deeper (####, #####, ...)
@@ -435,7 +437,7 @@ module Kettle
435
437
 
436
438
  def detect_previous_version(after_text)
437
439
  # after_text begins with the first released section following Unreleased
438
- m = after_text.match(/^## \[(\d+\.\d+\.\d+)\]/)
440
+ m = after_text.match(/^## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]/o)
439
441
  return m[1] if m
440
442
 
441
443
  nil
@@ -445,7 +447,7 @@ module Kettle
445
447
  unreleased_block, before, after = extract_unreleased(changelog)
446
448
  abort("Could not find '## [Unreleased]' section in CHANGELOG.md") if unreleased_block.nil?
447
449
 
448
- release_heading = after.to_s.match(/\A## \[(\d+\.\d+\.\d+)\][^\n]*\n/)
450
+ release_heading = after.to_s.match(/\A## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\][^\n]*\n/o)
449
451
  abort("Could not find a prepared release section after '## [Unreleased]' in CHANGELOG.md") unless release_heading
450
452
 
451
453
  prepared_version = release_heading[1]
@@ -790,16 +792,16 @@ module Kettle
790
792
  non_t_tag_refs = {}
791
793
  lines[scan_start..-1].to_a.each do |l|
792
794
  # Case A: explicit tag ref key like [1.2.3t]: ...
793
- if (m = l.match(/^\[(\d+\.\d+\.\d+)t\]:\s+(\S+)/))
795
+ if (m = l.match(/^\[(#{CHANGELOG_VERSION_PATTERN_SOURCE})t\]:\s+(\S+)/o))
794
796
  t_versions[m[1]] = true
795
797
  next
796
798
  end
797
799
  # Case B: non-t ref that nevertheless points to a tag URL (GitHub or GitLab)
798
- if (m2 = l.match(/^\[(\d+\.\d+\.\d+)\]:\s+(\S+)/))
800
+ if (m2 = l.match(/^\[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]:\s+(\S+)/o))
799
801
  url = m2[2]
800
802
  # Accept only when the URL clearly points to a tag for the SAME version
801
803
  # Support both GitHub and GitLab style tag URLs
802
- if (murl = url.match(%r{/(?:releases/)?tags?/v(\d+\.\d+\.\d+)}i))
804
+ if (murl = url.match(%r{/(?:releases/)?tags?/v(#{CHANGELOG_VERSION_PATTERN_SOURCE})}io))
803
805
  version_in_url = murl[1]
804
806
  if version_in_url == m2[1]
805
807
  non_t_tag_refs[m2[1]] = url
@@ -817,7 +819,7 @@ module Kettle
817
819
  while i < lines.length
818
820
  line = lines[i]
819
821
  # Case 1: Heading contains legacy tag suffix we should convert
820
- m = line.match(/^## \[(\d+\.\d+\.\d+)\](.*)\(\[tag\]\[(\d+\.\d+\.\d+)t\]\)\s*$/i)
822
+ m = line.match(/^## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\](.*)\(\[tag\]\[(#{CHANGELOG_VERSION_PATTERN_SOURCE})t\]\)\s*$/io)
821
823
  if m && m[1] == m[3]
822
824
  ver = m[1]
823
825
  middle = m[2]
@@ -837,7 +839,7 @@ module Kettle
837
839
  end
838
840
 
839
841
  # Case 2: Heading does NOT contain suffix, but a matching tag ref exists; ensure a TAG list item
840
- if (m2 = line.match(/^## \[(\d+\.\d+\.\d+)\](.*)$/))
842
+ if (m2 = line.match(/^## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\](.*)$/o))
841
843
  ver2 = m2[1]
842
844
  # Skip Unreleased heading and non-release headings
843
845
  unless ver2.nil?
@@ -858,7 +860,7 @@ module Kettle
858
860
  # Footer duplication: if we are in the footer block and encounter a non-t tag-ref
859
861
  # without a matching t-ref, emit the t-ref immediately after with the same URL.
860
862
  if i >= scan_start
861
- if (mref = line.match(/^\[(\d+\.\d+\.\d+)\]:\s+(\S+)/))
863
+ if (mref = line.match(/^\[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]:\s+(\S+)/o))
862
864
  vref = mref[1]
863
865
  mref[2]
864
866
  if non_t_tag_refs[vref] && !t_versions[vref]
@@ -949,9 +951,9 @@ module Kettle
949
951
  compares = {}
950
952
  tags = {}
951
953
  by_key.each do |k, v|
952
- if k =~ /^(\d+\.\d+\.\d+)$/
954
+ if k =~ /^(#{CHANGELOG_VERSION_PATTERN_SOURCE})$/o
953
955
  compares[$1] = v
954
- elsif k =~ /^(\d+\.\d+\.\d+)t$/
956
+ elsif k =~ /^(#{CHANGELOG_VERSION_PATTERN_SOURCE})t$/o
955
957
  tags[$1] = v
956
958
  end
957
959
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kettle
4
+ module Dev
5
+ class ChangelogEntryAdder
6
+ SECTIONS = %w[Added Changed Deprecated Removed Fixed Security].freeze
7
+
8
+ def initialize(section:, entry:, root: Kettle::Dev::CIHelpers.project_root)
9
+ @root = root
10
+ @section = section.to_s
11
+ @entry = normalize_entry(entry)
12
+ @changelog_path = File.join(@root, "CHANGELOG.md")
13
+ end
14
+
15
+ def run
16
+ raise Error, "unsupported changelog section #{@section.inspect}" unless SECTIONS.include?(@section)
17
+ raise Error, "missing CHANGELOG.md in #{Kettle::Dev.display_path(@root)}" unless File.file?(@changelog_path)
18
+
19
+ require_markly_crispr!
20
+ source = File.read(@changelog_path)
21
+ context = Ast::Crispr::Markdown::Markly.document_context(content: source, source_label: @changelog_path)
22
+ sections = context.structural_owners(owner_scope: :heading_sections)
23
+ unreleased = find_unreleased_heading!(sections)
24
+ target = find_unreleased_section!(sections, unreleased)
25
+ slice = target_slice(source, target)
26
+ return :unchanged if entry_present?(slice)
27
+
28
+ updated = insert_entry(source, target)
29
+ File.write(@changelog_path, updated)
30
+ :changed
31
+ end
32
+
33
+ private
34
+
35
+ def normalize_entry(entry)
36
+ text = entry.to_s.strip
37
+ raise Error, "changelog entry must not be empty" if text.empty?
38
+
39
+ text.start_with?("- ") ? text : "- #{text}"
40
+ end
41
+
42
+ def require_markly_crispr!
43
+ require "ast/crispr/markdown/markly"
44
+ rescue LoadError => error
45
+ raise Error, "kettle-changelog add-unreleased-entry requires ast-crispr-markdown-markly (#{error.message})"
46
+ end
47
+
48
+ def find_heading!(sections, heading_text:, level:)
49
+ matches = sections.select { |owner| owner.heading_text.to_s.strip == heading_text && owner.level == level }
50
+ raise Error, "expected exactly one #{heading(level, heading_text)} section in CHANGELOG.md, found #{matches.length}" unless matches.length == 1
51
+
52
+ matches.first
53
+ end
54
+
55
+ def find_unreleased_heading!(sections)
56
+ matches = sections.select do |owner|
57
+ owner.level == 2 && owner.heading_source.to_s.strip == "## [Unreleased]"
58
+ end
59
+ raise Error, "expected exactly one ## [Unreleased] section in CHANGELOG.md, found #{matches.length}" unless matches.length == 1
60
+
61
+ matches.first
62
+ end
63
+
64
+ def find_unreleased_section!(sections, unreleased)
65
+ matches = sections.select do |owner|
66
+ owner.heading_text.to_s.strip == @section &&
67
+ owner.level == 3 &&
68
+ owner.location.start_line > unreleased.location.start_line &&
69
+ owner.location.end_line <= unreleased.location.end_line
70
+ end
71
+ raise Error, "expected exactly one #{heading(3, @section)} section under ## [Unreleased] in CHANGELOG.md, found #{matches.length}" unless matches.length == 1
72
+
73
+ matches.first
74
+ end
75
+
76
+ def heading(level, text)
77
+ "#{"#" * level} #{text}"
78
+ end
79
+
80
+ def target_slice(source, target)
81
+ lines = source.lines
82
+ lines[(target.location.start_line - 1)...target.location.end_line].to_a.join
83
+ end
84
+
85
+ def entry_present?(slice)
86
+ slice.lines.any? { |line| line.chomp == @entry }
87
+ end
88
+
89
+ def insert_entry(source, target)
90
+ lines = source.lines
91
+ insertion_index = insertion_index(lines, target)
92
+ payload = entry_payload(lines, insertion_index, target)
93
+ lines.insert(insertion_index, payload)
94
+ lines.join
95
+ end
96
+
97
+ def insertion_index(lines, target)
98
+ first_content_index = target.location.start_line
99
+ index = target.location.end_line
100
+ while index > first_content_index && lines.fetch(index - 1).strip.empty?
101
+ index -= 1
102
+ end
103
+ index
104
+ end
105
+
106
+ def entry_payload(lines, insertion_index, target)
107
+ previous_line = lines[insertion_index - 1].to_s
108
+ next_line = lines[insertion_index].to_s
109
+ before = previous_line.strip.empty? ? "" : "\n"
110
+ after = (next_line.empty? || next_line.strip.empty?) ? "" : "\n"
111
+ "#{before}#{@entry}\n#{after}"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -20,11 +20,11 @@ module Kettle
20
20
  # @return [String] absolute path to the project root
21
21
  def project_root
22
22
  # Too difficult to test every possible branch here, so ignoring
23
- # :nocov:
23
+ # simplecov:disable
24
24
  dir = if defined?(Rake) && Rake&.application&.respond_to?(:original_dir)
25
25
  Rake.application.original_dir
26
26
  end
27
- # :nocov:
27
+ # simplecov:enable
28
28
  dir || Dir.pwd
29
29
  end
30
30