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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +257 -203
- data/CONTRIBUTING.md +2 -2
- data/FUNDING.md +1 -5
- data/README.md +41 -24
- data/exe/kettle-changelog +35 -1
- data/lib/kettle/dev/bump_cli.rb +32 -3
- data/lib/kettle/dev/changelog_cli.rb +12 -10
- data/lib/kettle/dev/changelog_entry_adder.rb +115 -0
- data/lib/kettle/dev/ci_helpers.rb +2 -2
- data/lib/kettle/dev/gha_sha_pins_cli.rb +76 -45
- data/lib/kettle/dev/tasks/ci_task.rb +2 -2
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +11 -10
- metadata.gz.sig +0 -0
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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-
|
|
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`,
|
|
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-
|
|
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.
|
|
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
|
|
905
|
+
| Source | https://github.com/kettle-dev/kettle-dev |
|
|
889
906
|
| License | `AGPL-3.0-only` |
|
|
890
|
-
| Funding | https://github.com/sponsors/pboling, https://
|
|
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
|
data/lib/kettle/dev/bump_cli.rb
CHANGED
|
@@ -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
|
|
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(/^## \[(
|
|
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## \[(
|
|
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(/^\[(
|
|
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(/^\[(
|
|
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(
|
|
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(/^## \[(
|
|
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(/^## \[(
|
|
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(/^\[(
|
|
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 =~ /^(
|
|
954
|
+
if k =~ /^(#{CHANGELOG_VERSION_PATTERN_SOURCE})$/o
|
|
953
955
|
compares[$1] = v
|
|
954
|
-
elsif k =~ /^(
|
|
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
|
-
# :
|
|
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
|
-
# :
|
|
27
|
+
# simplecov:enable
|
|
28
28
|
dir || Dir.pwd
|
|
29
29
|
end
|
|
30
30
|
|