kettle-dev 2.2.13 → 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 +243 -203
- data/CONTRIBUTING.md +2 -2
- data/FUNDING.md +1 -5
- data/README.md +38 -23
- data/exe/kettle-changelog +35 -1
- 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/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"
|
|
@@ -527,7 +542,7 @@ What it does:
|
|
|
527
542
|
- Sponsors (Organizations): `<!-- <TAG>-ORGANIZATIONS:START --> … <!-- <TAG>-ORGANIZATIONS:END -->`
|
|
528
543
|
- Handle resolution:
|
|
529
544
|
1. `OPENCOLLECTIVE_HANDLE` environment variable, if set
|
|
530
|
-
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)
|
|
531
546
|
- Usage:
|
|
532
547
|
- `exe/kettle-readme-backers`
|
|
533
548
|
- `OPENCOLLECTIVE_HANDLE=my-collective exe/kettle-readme-backers`
|
|
@@ -593,7 +608,7 @@ Made with [contributors-img][🖐contrib-rocks].
|
|
|
593
608
|
|
|
594
609
|
Also see GitLab Contributors: [https://gitlab.com/kettle-dev/kettle-dev/-/graphs/main][🚎contributors-gl]
|
|
595
610
|
|
|
596
|
-
<details>
|
|
611
|
+
<details markdown="1">
|
|
597
612
|
<summary>⭐️ Star History</summary>
|
|
598
613
|
|
|
599
614
|
<a href="https://star-history.com/kettle-dev/kettle-dev&Date">
|
|
@@ -690,12 +705,8 @@ Thanks for RTFM. ☺️
|
|
|
690
705
|
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
|
|
691
706
|
[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
|
|
692
707
|
[🖇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
708
|
[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
|
|
696
709
|
[🖇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
710
|
[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
|
|
700
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
|
|
701
712
|
[🖇buyme]: https://www.buymeacoffee.com/pboling
|
|
@@ -785,11 +796,13 @@ Thanks for RTFM. ☺️
|
|
|
785
796
|
[🚎jruby-9.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.2.yml
|
|
786
797
|
[🚎jruby-9.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.3.yml
|
|
787
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
|
|
788
800
|
[🚎truby-22.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-22.3.yml
|
|
789
801
|
[🚎truby-23.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.0.yml
|
|
790
802
|
[🚎truby-23.1-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.1.yml
|
|
791
803
|
[🚎truby-24.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-24.2.yml
|
|
792
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
|
|
793
806
|
[🚎2-cov-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml
|
|
794
807
|
[🚎2-cov-wfi]: https://github.com/kettle-dev/kettle-dev/actions/workflows/coverage.yml/badge.svg
|
|
795
808
|
[🚎3-hd-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/heads.yml
|
|
@@ -817,7 +830,6 @@ Thanks for RTFM. ☺️
|
|
|
817
830
|
[💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
|
|
818
831
|
[💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
|
|
819
832
|
[💎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
833
|
[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
|
|
822
834
|
[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
|
|
823
835
|
[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
@@ -825,10 +837,13 @@ Thanks for RTFM. ☺️
|
|
|
825
837
|
[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
826
838
|
[💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
827
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
|
|
828
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
|
|
829
843
|
[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
|
|
830
844
|
[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
|
|
831
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
|
|
832
847
|
[💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
|
|
833
848
|
[💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
|
|
834
849
|
[🤝gh-issues]: https://github.com/kettle-dev/kettle-dev/issues
|
|
@@ -857,7 +872,7 @@ Thanks for RTFM. ☺️
|
|
|
857
872
|
[📌gitmoji]: https://gitmoji.dev
|
|
858
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
|
|
859
874
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
860
|
-
[🧮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
|
|
861
876
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
862
877
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
863
878
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -885,7 +900,7 @@ Thanks for RTFM. ☺️
|
|
|
885
900
|
| Package | kettle-dev |
|
|
886
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 |
|
|
887
902
|
| Homepage | https://github.com/kettle-dev/kettle-dev |
|
|
888
|
-
| Source | https://github.com/kettle-dev/kettle-dev
|
|
903
|
+
| Source | https://github.com/kettle-dev/kettle-dev |
|
|
889
904
|
| License | `AGPL-3.0-only` |
|
|
890
|
-
| 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 |
|
|
891
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
|
|
@@ -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
|
|
@@ -32,6 +32,9 @@ module Kettle
|
|
|
32
32
|
COMMENT_REASON = "update_version_comment"
|
|
33
33
|
DEFAULT_UPGRADE_LEVEL = "patch"
|
|
34
34
|
DEFAULT_CACHE_TTL_SECONDS = 24 * 60 * 60
|
|
35
|
+
DEFAULT_HTTP_OPEN_TIMEOUT_SECONDS = 5
|
|
36
|
+
DEFAULT_HTTP_READ_TIMEOUT_SECONDS = 10
|
|
37
|
+
DEFAULT_HTTP_REFRESH_TIMEOUT_SECONDS = 20
|
|
35
38
|
VALID_UPGRADE_LEVELS = %w[major minor patch].freeze
|
|
36
39
|
VERSION_COMMENT_SUFFIX_RE = /\A\s+#\s*v?(?<version>\d+(?:\.\d+\.\d+(?:[-.]?[0-9A-Za-z.-]+)?)?)/
|
|
37
40
|
VERSION_COMMENT_REPLACEMENT_RE = /\A(?<prefix>\s+#\s*)v?\d+(?:\.\d+\.\d+(?:[-.]?[0-9A-Za-z.-]+)?)?/
|
|
@@ -1100,12 +1103,15 @@ module Kettle
|
|
|
1100
1103
|
|
|
1101
1104
|
# Lightweight GitHub API client for commit and release SHA resolution.
|
|
1102
1105
|
class GitHubClient
|
|
1103
|
-
def initialize(token:, api_base:, user_agent:, persistent_cache: nil, refresh_cache: false)
|
|
1106
|
+
def initialize(token:, api_base:, user_agent:, persistent_cache: nil, refresh_cache: false, open_timeout: DEFAULT_HTTP_OPEN_TIMEOUT_SECONDS, read_timeout: DEFAULT_HTTP_READ_TIMEOUT_SECONDS, refresh_timeout: DEFAULT_HTTP_REFRESH_TIMEOUT_SECONDS)
|
|
1104
1107
|
@token = token
|
|
1105
1108
|
@api_base = api_base
|
|
1106
1109
|
@user_agent = user_agent
|
|
1107
1110
|
@persistent_cache = persistent_cache
|
|
1108
1111
|
@refresh_cache = refresh_cache
|
|
1112
|
+
@open_timeout = open_timeout
|
|
1113
|
+
@read_timeout = read_timeout
|
|
1114
|
+
@refresh_timeout = refresh_timeout
|
|
1109
1115
|
@commit_cache = {}
|
|
1110
1116
|
@release_cache = {}
|
|
1111
1117
|
end
|
|
@@ -1114,58 +1120,31 @@ module Kettle
|
|
|
1114
1120
|
return [] if repo_ref.to_s.empty?
|
|
1115
1121
|
return @release_cache[repo_ref] if @release_cache.key?(repo_ref)
|
|
1116
1122
|
|
|
1123
|
+
stale = nil
|
|
1117
1124
|
unless @refresh_cache
|
|
1118
1125
|
cached = @persistent_cache&.versions_for_repo(repo_ref, fresh: true)
|
|
1119
1126
|
if cached
|
|
1120
1127
|
@release_cache[repo_ref] = cached
|
|
1121
1128
|
return cached
|
|
1122
1129
|
end
|
|
1130
|
+
stale = @persistent_cache&.versions_for_repo(repo_ref, fresh: false)
|
|
1123
1131
|
end
|
|
1124
1132
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
return
|
|
1133
|
+
releases = nil
|
|
1134
|
+
Timeout.timeout(@refresh_timeout) do
|
|
1135
|
+
data = request_json("/repos/#{repo_ref}/releases?per_page=100")
|
|
1136
|
+
return cached_versions(repo_ref, stale) unless data.is_a?(Array)
|
|
1129
1137
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
tag_shas = tag_ref_shas(repo_ref)
|
|
1134
|
-
releases = data.filter_map do |release|
|
|
1135
|
-
next unless release.is_a?(Hash)
|
|
1136
|
-
|
|
1137
|
-
tag = release["tag_name"].to_s
|
|
1138
|
-
parsed = parse_release_version_text(tag)
|
|
1139
|
-
next unless parsed
|
|
1138
|
+
tag_shas = tag_ref_shas(repo_ref)
|
|
1139
|
+
return cached_versions(repo_ref, stale) unless tag_shas
|
|
1140
1140
|
|
|
1141
|
-
|
|
1142
|
-
tag: tag,
|
|
1143
|
-
version_obj: parsed,
|
|
1144
|
-
version: parsed.to_s,
|
|
1145
|
-
sha: tag_shas[tag]
|
|
1146
|
-
}
|
|
1141
|
+
releases = build_release_versions(data, tag_shas)
|
|
1147
1142
|
end
|
|
1148
|
-
released_tags = releases.each_with_object({}) { |release, memo| memo[release[:tag]] = true }
|
|
1149
|
-
tag_versions = tag_shas.filter_map do |tag, sha|
|
|
1150
|
-
next if released_tags[tag]
|
|
1151
|
-
|
|
1152
|
-
parsed = parse_release_version_text(tag)
|
|
1153
|
-
next unless parsed
|
|
1154
|
-
|
|
1155
|
-
{
|
|
1156
|
-
tag: tag,
|
|
1157
|
-
version_obj: parsed,
|
|
1158
|
-
version: parsed.to_s,
|
|
1159
|
-
sha: sha
|
|
1160
|
-
}
|
|
1161
|
-
end
|
|
1162
|
-
releases.concat(tag_versions)
|
|
1163
|
-
|
|
1164
|
-
releases.sort_by! { |release| release[:version_obj] }
|
|
1165
|
-
releases.reverse!
|
|
1166
1143
|
@persistent_cache&.write_versions(repo_ref, releases)
|
|
1167
1144
|
@release_cache[repo_ref] = releases
|
|
1168
1145
|
releases
|
|
1146
|
+
rescue Timeout::Error
|
|
1147
|
+
cached_versions(repo_ref, stale)
|
|
1169
1148
|
end
|
|
1170
1149
|
|
|
1171
1150
|
def commit_sha(repo_ref, ref)
|
|
@@ -1203,6 +1182,48 @@ module Kettle
|
|
|
1203
1182
|
|
|
1204
1183
|
private
|
|
1205
1184
|
|
|
1185
|
+
def cached_versions(repo_ref, stale)
|
|
1186
|
+
versions = stale || []
|
|
1187
|
+
@release_cache[repo_ref] = versions
|
|
1188
|
+
versions
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
def build_release_versions(data, tag_shas)
|
|
1192
|
+
releases = data.filter_map do |release|
|
|
1193
|
+
next unless release.is_a?(Hash)
|
|
1194
|
+
|
|
1195
|
+
tag = release["tag_name"].to_s
|
|
1196
|
+
parsed = parse_release_version_text(tag)
|
|
1197
|
+
next unless parsed
|
|
1198
|
+
|
|
1199
|
+
{
|
|
1200
|
+
tag: tag,
|
|
1201
|
+
version_obj: parsed,
|
|
1202
|
+
version: parsed.to_s,
|
|
1203
|
+
sha: tag_shas[tag]
|
|
1204
|
+
}
|
|
1205
|
+
end
|
|
1206
|
+
released_tags = releases.each_with_object({}) { |release, memo| memo[release[:tag]] = true }
|
|
1207
|
+
tag_versions = tag_shas.filter_map do |tag, sha|
|
|
1208
|
+
next if released_tags[tag]
|
|
1209
|
+
|
|
1210
|
+
parsed = parse_release_version_text(tag)
|
|
1211
|
+
next unless parsed
|
|
1212
|
+
|
|
1213
|
+
{
|
|
1214
|
+
tag: tag,
|
|
1215
|
+
version_obj: parsed,
|
|
1216
|
+
version: parsed.to_s,
|
|
1217
|
+
sha: sha
|
|
1218
|
+
}
|
|
1219
|
+
end
|
|
1220
|
+
releases.concat(tag_versions)
|
|
1221
|
+
|
|
1222
|
+
releases.sort_by! { |release| release[:version_obj] }
|
|
1223
|
+
releases.reverse!
|
|
1224
|
+
releases
|
|
1225
|
+
end
|
|
1226
|
+
|
|
1206
1227
|
def parse_release_version_text(value)
|
|
1207
1228
|
normalized = value.to_s.sub(/\A[vV]/, "")
|
|
1208
1229
|
return nil unless normalized.match?(/\A(?:\d+|\d+\.\d+\.\d+(?:[-.]?[0-9A-Za-z.-]+)?)\z/)
|
|
@@ -1214,13 +1235,15 @@ module Kettle
|
|
|
1214
1235
|
|
|
1215
1236
|
def tag_ref_shas(repo_ref)
|
|
1216
1237
|
data = request_json("/repos/#{repo_ref}/git/matching-refs/tags/")
|
|
1217
|
-
return
|
|
1238
|
+
return nil unless data.is_a?(Array)
|
|
1218
1239
|
|
|
1219
1240
|
data.each_with_object({}) do |entry, memo|
|
|
1220
1241
|
ref = entry["ref"].to_s
|
|
1221
1242
|
next unless ref.start_with?("refs/tags/")
|
|
1222
1243
|
|
|
1223
1244
|
tag = ref.sub(%r{\Arefs/tags/}, "")
|
|
1245
|
+
next unless parse_release_version_text(tag)
|
|
1246
|
+
|
|
1224
1247
|
object = entry["object"]
|
|
1225
1248
|
next unless object.is_a?(Hash)
|
|
1226
1249
|
|
|
@@ -1229,8 +1252,7 @@ module Kettle
|
|
|
1229
1252
|
when "commit"
|
|
1230
1253
|
memo[tag] = sha
|
|
1231
1254
|
when "tag"
|
|
1232
|
-
|
|
1233
|
-
memo[tag] = dereferenced_sha if dereferenced_sha
|
|
1255
|
+
memo[tag] = nil
|
|
1234
1256
|
end
|
|
1235
1257
|
end
|
|
1236
1258
|
end
|
|
@@ -1259,9 +1281,7 @@ module Kettle
|
|
|
1259
1281
|
request["X-GitHub-Api-Version"] = "2022-11-28"
|
|
1260
1282
|
request["Authorization"] = "Bearer #{@token}" if @token && !@token.empty?
|
|
1261
1283
|
|
|
1262
|
-
response =
|
|
1263
|
-
http.request(request)
|
|
1264
|
-
end
|
|
1284
|
+
response = http_request(uri, request)
|
|
1265
1285
|
|
|
1266
1286
|
break unless response.code.to_i.between?(300, 399)
|
|
1267
1287
|
redirects -= 1
|
|
@@ -1280,6 +1300,17 @@ module Kettle
|
|
|
1280
1300
|
rescue JSON::ParserError
|
|
1281
1301
|
nil
|
|
1282
1302
|
end
|
|
1303
|
+
rescue IOError, SystemCallError, Net::OpenTimeout, Net::ReadTimeout
|
|
1304
|
+
nil
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
def http_request(uri, request)
|
|
1308
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
1309
|
+
http.use_ssl = uri.scheme == "https"
|
|
1310
|
+
http.open_timeout = @open_timeout
|
|
1311
|
+
http.read_timeout = @read_timeout
|
|
1312
|
+
http.ssl_timeout = @open_timeout if http.respond_to?(:ssl_timeout=)
|
|
1313
|
+
http.start { |connection| connection.request(request) }
|
|
1283
1314
|
end
|
|
1284
1315
|
|
|
1285
1316
|
def uri_encode(value)
|