kettle-dev 2.2.12 → 2.2.14

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"
@@ -375,7 +390,8 @@ What it does:
375
390
 
376
391
  - Script: `exe/kettle-release` (run as `kettle-release`)
377
392
  - Purpose: guided release helper that:
378
- - Runs `kettle-pre-release` before the numbered release steps on full releases, aborting before release setup if any pre-release gate fails.
393
+ - Runs `kettle-pre-release` as step 0 on full releases, aborting before release setup if any pre-release gate fails.
394
+ - Skips pre-release checks for any `start_step` greater than `0`, so resumptions begin directly at the requested numbered step.
379
395
  - Runs sanity checks (`bin/setup`, `bin/rake`), confirms version/changelog, optionally updates Appraisals, regenerates docs via `bin/rake yard`, commits “🔖 Prepare release vX.Y.Z”.
380
396
  - Optionally runs your CI locally with `act` before any push:
381
397
  - Enable with env: `K_RELEASE_LOCAL_CI="true"` (run automatically) or `K_RELEASE_LOCAL_CI="ask"` (prompt \[Y/n\]).
@@ -385,7 +401,8 @@ What it does:
385
401
  - Exports `SOURCE_DATE_EPOCH`, builds (optionally signed), creates gem checksums, and runs `bundle exec rake release` (prompts for signing key + RubyGems MFA OTP as needed).
386
402
  - Options:
387
403
  - `start_step` map (skip directly to a phase):
388
- 1. Verify Bundler \>= 2.7 (always runs; start at 1 to do everything)
404
+ 0. Run `kettle-pre-release` gates
405
+ 1. Verify Bundler \>= 2.7 (start at 1 to skip pre-release checks)
389
406
  2. Detect version; RubyGems sanity check; confirm CHANGELOG/version; sync copyright years; update badges/headers
390
407
  3. Run bin/setup
391
408
  4. Run bin/rake (default task)
@@ -406,7 +423,8 @@ What it does:
406
423
  19. Push tags to remotes (final)
407
424
  - Examples:
408
425
  - After intermittent CI failure, restart from monitoring: `bundle exec kettle-release start_step=10`
409
- - After fixing a failed pre-release gate, rerun from the top: `bundle exec kettle-release`
426
+ - After fixing a failed pre-release gate, rerun from step 0: `bundle exec kettle-release`
427
+ - After intentionally handling a failed pre-release gate, skip it and start numbered release steps: `bundle exec kettle-release start_step=1`
410
428
  - Tips:
411
429
  - The commit message helper `exe/kettle-commit-msg` prefers project-local `.git-hooks` (then falls back to `~/.git-hooks`).
412
430
  - The goalie file `commit-subjects-goalie.txt` controls when a footer is appended; customize `footer-template.erb.txt` as you like.
@@ -524,7 +542,7 @@ What it does:
524
542
  - Sponsors (Organizations): `<!-- <TAG>-ORGANIZATIONS:START --> … <!-- <TAG>-ORGANIZATIONS:END -->`
525
543
  - Handle resolution:
526
544
  1. `OPENCOLLECTIVE_HANDLE` environment variable, if set
527
- 2. `opencollective.yml` in the project root (e.g., `collective: "kettle-rb"` in this repo)
545
+ 2. `opencollective.yml` in the project root (e.g., `collective: "kettle-dev"` in this repo)
528
546
  - Usage:
529
547
  - `exe/kettle-readme-backers`
530
548
  - `OPENCOLLECTIVE_HANDLE=my-collective exe/kettle-readme-backers`
@@ -590,7 +608,7 @@ Made with [contributors-img][🖐contrib-rocks].
590
608
 
591
609
  Also see GitLab Contributors: [https://gitlab.com/kettle-dev/kettle-dev/-/graphs/main][🚎contributors-gl]
592
610
 
593
- <details>
611
+ <details markdown="1">
594
612
  <summary>⭐️ Star History</summary>
595
613
 
596
614
  <a href="https://star-history.com/kettle-dev/kettle-dev&Date">
@@ -687,12 +705,8 @@ Thanks for RTFM. ☺️
687
705
  [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
688
706
  [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
689
707
  [🖇sponsor]: https://github.com/sponsors/pboling
690
- [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
691
- [🖇polar]: https://polar.sh/pboling
692
708
  [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
693
709
  [🖇kofi]: https://ko-fi.com/pboling
694
- [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
695
- [🖇patreon]: https://patreon.com/galtzo
696
710
  [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
697
711
  [🖇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
698
712
  [🖇buyme]: https://www.buymeacoffee.com/pboling
@@ -782,11 +796,13 @@ Thanks for RTFM. ☺️
782
796
  [🚎jruby-9.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.2.yml
783
797
  [🚎jruby-9.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.3.yml
784
798
  [🚎jruby-9.4-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.4.yml
799
+ [🚎jruby-10.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-10.0.yml
785
800
  [🚎truby-22.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-22.3.yml
786
801
  [🚎truby-23.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.0.yml
787
802
  [🚎truby-23.1-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.1.yml
788
803
  [🚎truby-24.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-24.2.yml
789
804
  [🚎truby-25.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-25.0.yml
805
+ [🚎truby-33.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-33.0.yml
790
806
  [🚎2-cov-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml
791
807
  [🚎2-cov-wfi]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml/badge.svg
792
808
  [🚎3-hd-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/heads.yml
@@ -814,7 +830,6 @@ Thanks for RTFM. ☺️
814
830
  [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
815
831
  [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
816
832
  [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
817
- [💎ruby-4.0i]: https://img.shields.io/badge/Ruby-4.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
818
833
  [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
819
834
  [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
820
835
  [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
@@ -822,10 +837,13 @@ Thanks for RTFM. ☺️
822
837
  [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
823
838
  [💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
824
839
  [💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
840
+ [💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
825
841
  [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
842
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
826
843
  [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
827
844
  [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
828
845
  [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
846
+ [💎jruby-10.0i]: https://img.shields.io/badge/JRuby-10.0-FBE742?style=for-the-badge&logo=ruby&logoColor=red
829
847
  [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
830
848
  [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
831
849
  [🤝gh-issues]: https://github.com/kettle-dev/kettle-dev/issues
@@ -854,7 +872,7 @@ Thanks for RTFM. ☺️
854
872
  [📌gitmoji]: https://gitmoji.dev
855
873
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
856
874
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
857
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.266-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
875
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.411-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
858
876
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
859
877
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
860
878
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -882,7 +900,7 @@ Thanks for RTFM. ☺️
882
900
  | Package | kettle-dev |
883
901
  | 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 |
884
902
  | Homepage | https://github.com/kettle-dev/kettle-dev |
885
- | Source | https://github.com/kettle-dev/kettle-dev/tree/v2.2.4 |
903
+ | Source | https://github.com/kettle-dev/kettle-dev |
886
904
  | License | `AGPL-3.0-only` |
887
- | 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 |
905
+ | 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 |
888
906
  <!-- 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
data/exe/kettle-release CHANGED
@@ -43,16 +43,17 @@ end
43
43
  # Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
44
44
  if ARGV.include?("-h") || ARGV.include?("--help")
45
45
  puts <<~USAGE
46
- Usage: kettle-release [--version VERSION] [--local-ci] [start_step=<1-19>]
46
+ Usage: kettle-release [--version VERSION] [--local-ci] [start_step=<0-19>]
47
47
 
48
48
  Automates the release flow for a Ruby gem in the host project.
49
49
 
50
- Full releases run `kettle-pre-release` before step 1 and abort if any
51
- pre-release gate fails. Resume with start_step > 1 only after the gate has
52
- passed or after intentionally handling the failure.
50
+ Full releases start at step 0 and run `kettle-pre-release` before the
51
+ numbered release steps. Any start_step greater than 0 skips pre-release
52
+ checks and resumes directly at the requested numbered step.
53
53
 
54
54
  Start steps (use start_step=<n> to begin at that step):
55
- 1. Verify Bundler >= 2.7 (always runs; start at 1 to do everything)
55
+ 0. Run kettle-pre-release gates
56
+ 1. Verify Bundler >= 2.7 (start at 1 to skip pre-release checks)
56
57
  2. Detect version; RubyGems sanity check; confirm CHANGELOG/version; sync copyright years; update badges/headers
57
58
  3. Run bin/setup
58
59
  4. Run bin/rake (default task)
@@ -88,8 +89,9 @@ if ARGV.include?("-h") || ARGV.include?("--help")
88
89
  DEBUG=true # Print backtraces on errors
89
90
 
90
91
  Examples:
91
- kettle-release # full flow from the top (start_step=1)
92
+ kettle-release # full flow from the top (start_step=0)
92
93
  kettle-release --local-ci # run local act CI, publish gem, then push commits/tags
94
+ kettle-release start_step=1 # skip pre-release checks and start numbered release steps
93
95
  kettle-release start_step=10 # resume by monitoring CI for the current branch
94
96
  SKIP_GEM_SIGNING=true kettle-release start_step=14 # build+release without signing
95
97
  USAGE
@@ -117,7 +119,7 @@ end
117
119
 
118
120
  local_ci = ARGV.include?("--local-ci")
119
121
  start_step_arg = ARGV.find { |a| a.start_with?("start_step=") }
120
- start_step = start_step_arg ? start_step_arg.split("=", 2)[1].to_i : 1
122
+ start_step = start_step_arg ? start_step_arg.split("=", 2)[1].to_i : 0
121
123
  version_override = extract_version_arg!(ARGV)
122
124
 
123
125
  begin
@@ -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