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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +258 -203
- data/CONTRIBUTING.md +2 -2
- data/FUNDING.md +1 -5
- data/README.md +44 -26
- data/exe/kettle-changelog +35 -1
- data/exe/kettle-release +9 -7
- data/lib/kettle/dev/changelog_cli.rb +12 -10
- data/lib/kettle/dev/changelog_entry_adder.rb +115 -0
- data/lib/kettle/dev/gha_sha_pins_cli.rb +76 -45
- data/lib/kettle/dev/release_cli.rb +4 -4
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +1 -0
- 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"
|
|
@@ -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`
|
|
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
|
-
|
|
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
|
|
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-
|
|
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.
|
|
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
|
|
903
|
+
| Source | https://github.com/kettle-dev/kettle-dev |
|
|
886
904
|
| License | `AGPL-3.0-only` |
|
|
887
|
-
| Funding | https://github.com/sponsors/pboling, https://
|
|
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=<
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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=
|
|
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 :
|
|
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(/^## \[(
|
|
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
|