kettle-dev 2.5.14 → 2.5.16
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 +64 -1
- data/README.md +23 -22
- data/exe/kettle-changelog +8 -2
- data/exe/kettle-gh-release +80 -10
- data/lib/kettle/dev/changelog_cli.rb +159 -14
- data/lib/kettle/dev/changelog_entry_adder.rb +2 -2
- data/lib/kettle/dev/pre_release_cli.rb +39 -0
- data/lib/kettle/dev/release_cli.rb +125 -8
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +18 -18
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8abbbf030dfd2052366ffd4f0fbbd27bc42973a9e0b71b960a391184df41793f
|
|
4
|
+
data.tar.gz: d1f091974575358acbdd459c9bae2083a7a799c2ca9263604d5f3777aac0de59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c348fce5b060edfc8b87e3a51c07e60fece4ee9060faf14b50063b0e70fa20ba1c2e4e4068e58b12c16815e78a65efbe9cc5047e854632d54c29758ba407b9a
|
|
7
|
+
data.tar.gz: a4271b730c428703b39a019c466f123b47625a5e2c912f87216e8d4d4942cf4bcaf2a4651fd5f52e9397cca15d41d240a0704328d651f22f58b0165b9f624a88
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,65 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [2.5.16] - 2026-08-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.5.16][2.5.16t]
|
|
36
|
+
- COVERAGE: 91.43% -- 5291/5787 lines in 46 files
|
|
37
|
+
- BRANCH COVERAGE: 75.66% -- 2002/2646 branches in 46 files
|
|
38
|
+
- 50.41% documented
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- kettle-jem-template-20260801-001 - Generated README gem dashboard links now
|
|
43
|
+
use ClickGems instead of BestGems.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- Pre-release image validation now permits a current-repository workflow badge when its workflow exists locally and awaits the release push.
|
|
48
|
+
|
|
49
|
+
- Release changelog coverage runs now enforce the checked-in coverage workflow line and branch thresholds before pushing.
|
|
50
|
+
|
|
51
|
+
- kettle-jem-template-20260801-002 - Generated RSpec helpers now normalize
|
|
52
|
+
managed configuration block bindings structurally, preventing mixed block
|
|
53
|
+
parameter names from producing invalid configuration after a merge.
|
|
54
|
+
- kettle-jem-template-20260801-003 - Generated project metadata and
|
|
55
|
+
documentation now normalize configured underscore hostnames to valid
|
|
56
|
+
hyphenated hostnames.
|
|
57
|
+
- kettle-jem-template-20260801-004 - Generated organization README logos now
|
|
58
|
+
use GitHub's stable organization avatar endpoint instead of assuming a
|
|
59
|
+
matching Galtzo-hosted asset exists.
|
|
60
|
+
|
|
61
|
+
- Mark modern changelog coverage-threshold tooling specs pending on legacy Ruby Appraisals.
|
|
62
|
+
|
|
63
|
+
## [2.5.15] - 2026-08-01
|
|
64
|
+
|
|
65
|
+
- TAG: [v2.5.15][2.5.15t]
|
|
66
|
+
- COVERAGE: 91.55% -- 5266/5752 lines in 46 files
|
|
67
|
+
- BRANCH COVERAGE: 75.74% -- 1986/2622 branches in 46 files
|
|
68
|
+
- 50.41% documented
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- Add verified GitHub Release backfill checks and NDJSON support to kettle-gh-release.
|
|
73
|
+
|
|
74
|
+
- Add a changelog reformat mode for historical release metadata repair.
|
|
75
|
+
|
|
76
|
+
- Add historical changelog reformat and GitHub release reconciliation checks.
|
|
77
|
+
|
|
78
|
+
- Add an explicit historical release-section backfill mode for tagged releases.
|
|
79
|
+
|
|
80
|
+
- Add kettle-gh-release update mode for existing GitHub Release notes.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Allow the changelog entry writer to add to malformed Unreleased sections with duplicate headings.
|
|
85
|
+
|
|
86
|
+
- Honor K_CHANGELOG_PATH when adding an Unreleased changelog entry.
|
|
87
|
+
|
|
88
|
+
- Canonicalize legacy release headings and remove redundant historical placeholders during reformat.
|
|
89
|
+
|
|
90
|
+
- Roll back unpublished prepared changelog sections when version.rb returns to the published version.
|
|
91
|
+
|
|
33
92
|
## [2.5.14] - 2026-07-31
|
|
34
93
|
|
|
35
94
|
- TAG: [v2.5.14][2.5.14t]
|
|
@@ -3173,7 +3232,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3173
3232
|
- Selecting will run the selected workflow via `act`
|
|
3174
3233
|
- This may move to its own gem in the future.
|
|
3175
3234
|
|
|
3176
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.
|
|
3235
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.16...HEAD
|
|
3236
|
+
[2.5.16]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.15...v2.5.16
|
|
3237
|
+
[2.5.16t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.16
|
|
3238
|
+
[2.5.15]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.14...v2.5.15
|
|
3239
|
+
[2.5.15t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.15
|
|
3177
3240
|
[2.5.14]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.13...v2.5.14
|
|
3178
3241
|
[2.5.14t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.14
|
|
3179
3242
|
[2.5.13]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.12...v2.5.13
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by
|
|
1
|
+
<a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by GitHub" src="https://github.com/kettle-dev.png?size=192" width="14%" align="right"/></a>
|
|
2
2
|
|
|
3
3
|
# 🍲 Kettle::Dev
|
|
4
4
|
|
|
5
|
-
[![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only][📄license-img]][📄license] [![
|
|
5
|
+
[![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only][📄license-img]][📄license] [![Total downloads][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf]
|
|
6
6
|
|
|
7
7
|
`if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know on Discord][✉️discord-invite] or [RubyForum][✉️ruby-forum], as I may have missed the notification.
|
|
8
8
|
|
|
@@ -107,11 +107,12 @@ out to `kettle-release` for each member that needs publishing.
|
|
|
107
107
|
|
|
108
108
|
### Compatibility
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
The gemspec supports installation on MRI Ruby 2.4.0+ and concordant releases
|
|
111
|
+
of JRuby and TruffleRuby so dependent gems can retain their own legacy runtime
|
|
112
|
+
floors. Kettle Dev's command-line and developer tooling is developed and
|
|
113
|
+
supported on current Ruby releases. CI/Appraisal jobs still exercise the legacy
|
|
114
|
+
installation floor, while specs that require current tooling are explicitly
|
|
115
|
+
pending there via `rspec-pending_for`.
|
|
115
116
|
|
|
116
117
|
<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>
|
|
117
118
|
|
|
@@ -120,15 +121,15 @@ The _amazing_ test matrix is powered by the kettle-dev stack.
|
|
|
120
121
|
<details markdown="1">
|
|
121
122
|
<summary>How kettle-dev manages complexity in tests</summary>
|
|
122
123
|
|
|
123
|
-
| Gem | Source | Role |
|
|
124
|
+
| Gem | Source | Role | Total downloads |
|
|
124
125
|
|-----|--------|------|---------------------|
|
|
125
|
-
| [appraisal2](https://
|
|
126
|
-
| [appraisal2-rubocop](https://
|
|
127
|
-
| [kettle-jem](https://
|
|
128
|
-
| [kettle-soup-cover](https://
|
|
129
|
-
| [kettle-test](https://
|
|
130
|
-
| [rubocop-lts](https://
|
|
131
|
-
| [turbo_tests2](https://
|
|
126
|
+
| [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [](https://clickgems.clickhouse.com/dashboard/appraisal2) |
|
|
127
|
+
| [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
|
|
128
|
+
| [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
|
|
129
|
+
| [kettle-soup-cover](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
|
|
130
|
+
| [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [](https://clickgems.clickhouse.com/dashboard/kettle-test) |
|
|
131
|
+
| [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
|
|
132
|
+
| [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
|
|
132
133
|
|
|
133
134
|
</details>
|
|
134
135
|
|
|
@@ -849,14 +850,14 @@ Thanks for RTFM. ☺️
|
|
|
849
850
|
[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
|
|
850
851
|
[✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
|
|
851
852
|
[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
|
|
852
|
-
[✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https
|
|
853
|
-
[✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https
|
|
853
|
+
[✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
|
|
854
|
+
[✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
|
|
854
855
|
[✉️ruby-forum]: https://www.rubyforum.org/tag/kettle-dev
|
|
855
856
|
|
|
856
857
|
[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
|
|
857
858
|
[⛳️gem-namespace]: https://github.com/kettle-dev/kettle-dev
|
|
858
859
|
[⛳️namespace-img]: https://img.shields.io/badge/namespace-Kettle::Dev-3C2D2D.svg?style=square&logo=ruby&logoColor=white
|
|
859
|
-
[⛳️gem-name]: https://
|
|
860
|
+
[⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/kettle-dev
|
|
860
861
|
[⛳️name-img]: https://img.shields.io/badge/name-kettle--dev-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
|
|
861
862
|
[⛳️tag-img]: https://img.shields.io/github/tag/kettle-dev/kettle-dev.svg
|
|
862
863
|
[⛳️tag]: https://github.com/kettle-dev/kettle-dev/releases
|
|
@@ -905,9 +906,9 @@ Thanks for RTFM. ☺️
|
|
|
905
906
|
[📜gh-wiki]: https://github.com/kettle-dev/kettle-dev/wiki
|
|
906
907
|
[📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
|
|
907
908
|
[📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
|
|
908
|
-
[👽dl-rank]: https://
|
|
909
|
-
[👽dl-ranki]: https://img.shields.io/gem/
|
|
910
|
-
[👽version]: https://
|
|
909
|
+
[👽dl-rank]: https://clickgems.clickhouse.com/dashboard/kettle-dev
|
|
910
|
+
[👽dl-ranki]: https://img.shields.io/gem/dt/kettle-dev.svg
|
|
911
|
+
[👽version]: https://clickgems.clickhouse.com/dashboard/kettle-dev
|
|
911
912
|
[👽versioni]: https://img.shields.io/gem/v/kettle-dev.svg
|
|
912
913
|
[🏀qlty-mnt]: https://qlty.sh/gh/kettle-dev/projects/kettle-dev
|
|
913
914
|
[🏀qlty-mnti]: https://qlty.sh/gh/kettle-dev/projects/kettle-dev/maintainability.svg
|
|
@@ -1005,7 +1006,7 @@ Thanks for RTFM. ☺️
|
|
|
1005
1006
|
[📌gitmoji]: https://gitmoji.dev
|
|
1006
1007
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1007
1008
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1008
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-5.
|
|
1009
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-5.787-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1009
1010
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
1010
1011
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1011
1012
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/exe/kettle-changelog
CHANGED
|
@@ -63,7 +63,7 @@ end
|
|
|
63
63
|
|
|
64
64
|
if ARGV.include?("-h") || ARGV.include?("--help")
|
|
65
65
|
puts <<~USAGE
|
|
66
|
-
Usage: kettle-changelog [--version VERSION] [--yes] [--update-prep] [--pending-release] [--release-state] [--refresh-cache] [--add-unreleased-entry --section SECTION --entry TEXT] [--json|--events[=TYPES]] [--no-strict] [--no-coverage-threshold]
|
|
66
|
+
Usage: kettle-changelog [--version VERSION] [--yes] [--update-prep|--reformat|--backfill-released-version VERSION] [--pending-release] [--release-state] [--refresh-cache] [--add-unreleased-entry --section SECTION --entry TEXT] [--json|--events[=TYPES]] [--no-strict] [--no-coverage-threshold]
|
|
67
67
|
|
|
68
68
|
Detects the current version from lib/**/version.rb, the latest live release, and
|
|
69
69
|
the most recent CHANGELOG.md release section, then prompts to confirm the selected plan:
|
|
@@ -76,6 +76,9 @@ if ARGV.include?("-h") || ARGV.include?("--help")
|
|
|
76
76
|
--version VERSION Use this version instead of detecting VERSION from lib/**/version.rb
|
|
77
77
|
--yes Auto-approve the selected release plan
|
|
78
78
|
--update-prep Force updating the most recent prepared release section in place
|
|
79
|
+
--reformat Normalize CHANGELOG.md structure without release-state planning
|
|
80
|
+
--backfill-released-version VERSION
|
|
81
|
+
Add a tagged historical section without moving Unreleased entries
|
|
79
82
|
--pending-release Query whether the changelog has release work pending; exits 0 for yes, 1 for no
|
|
80
83
|
--release-state,
|
|
81
84
|
--release-status Print changelog release state, including latest published release and pending sources
|
|
@@ -159,6 +162,7 @@ begin
|
|
|
159
162
|
# Determine if strict mode is enabled (default: true)
|
|
160
163
|
strict_mode = !ARGV.include?("--no-strict") && ENV.fetch("K_CHANGELOG_STRICT", "true").downcase != "false"
|
|
161
164
|
update_prep = ARGV.delete("--update-prep")
|
|
165
|
+
reformat_only = ARGV.delete("--reformat")
|
|
162
166
|
yes = !!ARGV.delete("--yes")
|
|
163
167
|
pending_release_query = ARGV.delete("--pending-release")
|
|
164
168
|
release_state_query = ARGV.delete("--release-state") || ARGV.delete("--release-status")
|
|
@@ -189,6 +193,8 @@ begin
|
|
|
189
193
|
value
|
|
190
194
|
end
|
|
191
195
|
|
|
196
|
+
historical_backfill_version = extract_option_arg!(ARGV, "--backfill-released-version")
|
|
197
|
+
|
|
192
198
|
if add_unreleased_entry
|
|
193
199
|
section = extract_option_arg!(ARGV, "--section")
|
|
194
200
|
entry = extract_option_arg!(ARGV, "--entry")
|
|
@@ -202,7 +208,7 @@ begin
|
|
|
202
208
|
end
|
|
203
209
|
|
|
204
210
|
event_stream = events_output ? Kettle::Ndjson.event_stream($stdout, types: event_types) : nil
|
|
205
|
-
cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, version: version_override, refresh_cache: refresh_cache, yes: yes, event_stream: event_stream)
|
|
211
|
+
cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, reformat_only: reformat_only, historical_backfill_version: historical_backfill_version, version: version_override, refresh_cache: refresh_cache, yes: yes, event_stream: event_stream)
|
|
206
212
|
if release_state_query
|
|
207
213
|
state = cli.release_state
|
|
208
214
|
puts(json_output ? JSON.pretty_generate(state) : cli.release_state_table(state))
|
data/exe/kettle-gh-release
CHANGED
|
@@ -39,7 +39,7 @@ end
|
|
|
39
39
|
# Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
|
|
40
40
|
if ARGV.include?("-h") || ARGV.include?("--help")
|
|
41
41
|
puts <<~USAGE
|
|
42
|
-
Usage: #{script_basename} [version=<VERSION>]
|
|
42
|
+
Usage: #{script_basename} [--release-version VERSION|version=<VERSION>] [--check|--update] [--events] [--allow-unpublished]
|
|
43
43
|
|
|
44
44
|
Creates a GitHub release for a gem version.
|
|
45
45
|
|
|
@@ -48,29 +48,99 @@ if ARGV.include?("-h") || ARGV.include?("--help")
|
|
|
48
48
|
failed (e.g., due to token issues or network problems).
|
|
49
49
|
|
|
50
50
|
Arguments:
|
|
51
|
-
version
|
|
52
|
-
|
|
51
|
+
--release-version VERSION # Explicit version (e.g., --release-version 1.2.3)
|
|
52
|
+
version=<VERSION> # Backward-compatible explicit version form
|
|
53
|
+
--check # Verify RubyGems, remote tag, changelog, and token without creating a release
|
|
54
|
+
--update # Update an existing GitHub Release title/body from CHANGELOG.md
|
|
55
|
+
--events # Emit the final result as one NDJSON event
|
|
56
|
+
--allow-unpublished # Skip the RubyGems publication requirement (for intentionally non-RubyGems projects)
|
|
53
57
|
|
|
54
58
|
Environment:
|
|
55
59
|
GITHUB_TOKEN / GH_TOKEN # Required; needs repo:public_repo (classic) or contents:write scope
|
|
56
60
|
DEBUG=true # Print backtraces on errors
|
|
57
61
|
|
|
58
62
|
Examples:
|
|
59
|
-
#{script_basename}
|
|
60
|
-
#{script_basename} version
|
|
61
|
-
|
|
63
|
+
#{script_basename} --check --release-version 2.0.0
|
|
64
|
+
#{script_basename} --release-version 2.0.0
|
|
65
|
+
#{script_basename} version=2.0.0 # backward-compatible form
|
|
62
66
|
USAGE
|
|
63
67
|
exit 0
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
check_only = ARGV.delete("--check")
|
|
71
|
+
update_only = ARGV.delete("--update")
|
|
72
|
+
if check_only && update_only
|
|
73
|
+
warn("#{script_basename}: --check and --update cannot be combined")
|
|
74
|
+
exit 1
|
|
75
|
+
end
|
|
76
|
+
events = ARGV.delete("--events")
|
|
77
|
+
allow_unpublished = ARGV.delete("--allow-unpublished")
|
|
78
|
+
version_arg = ARGV.find { |arg| arg.start_with?("version=") }
|
|
79
|
+
ARGV.delete(version_arg) if version_arg
|
|
80
|
+
explicit_version = version_arg&.split("=", 2)&.last&.strip
|
|
81
|
+
|
|
82
|
+
if (index = ARGV.index("--release-version"))
|
|
83
|
+
explicit_version = ARGV.fetch(index + 1, nil).to_s.strip
|
|
84
|
+
ARGV.slice!(index, 2)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless ARGV.empty?
|
|
88
|
+
warn("#{script_basename}: unexpected argument(s): #{ARGV.join(" ")}")
|
|
89
|
+
exit 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if !explicit_version.nil? && explicit_version.empty?
|
|
93
|
+
warn("#{script_basename}: --release-version requires a version")
|
|
94
|
+
exit 1
|
|
95
|
+
end
|
|
69
96
|
|
|
70
97
|
begin
|
|
71
98
|
cli = Kettle::Dev::ReleaseCLI.new(start_step: 18)
|
|
72
99
|
version = explicit_version || cli.send(:detect_version)
|
|
73
|
-
cli.send(:
|
|
100
|
+
result = cli.send(:github_release_backfill_check, version, require_rubygems: !allow_unpublished)
|
|
101
|
+
|
|
102
|
+
if events
|
|
103
|
+
action = if check_only
|
|
104
|
+
"check"
|
|
105
|
+
elsif update_only
|
|
106
|
+
"update"
|
|
107
|
+
else
|
|
108
|
+
"create"
|
|
109
|
+
end
|
|
110
|
+
puts JSON.generate(
|
|
111
|
+
event_version: 1,
|
|
112
|
+
type: "github_release",
|
|
113
|
+
action: action,
|
|
114
|
+
status: result.fetch("ok") ? "ok" : "failed",
|
|
115
|
+
**result
|
|
116
|
+
)
|
|
117
|
+
elsif result.fetch("ok")
|
|
118
|
+
puts "GitHub release prerequisites satisfied for #{result.fetch("tag")}."
|
|
119
|
+
else
|
|
120
|
+
warn("GitHub release prerequisites failed for #{result.fetch("tag")}:")
|
|
121
|
+
result.fetch("diagnostics").each { |diagnostic| warn(" - #{diagnostic}") }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
exit 1 unless result.fetch("ok")
|
|
125
|
+
exit 0 if check_only
|
|
126
|
+
|
|
127
|
+
created, message = if update_only
|
|
128
|
+
cli.send(:maybe_update_github_release!, version)
|
|
129
|
+
else
|
|
130
|
+
cli.send(:maybe_create_github_release!, version)
|
|
131
|
+
end
|
|
132
|
+
if events
|
|
133
|
+
puts JSON.generate(
|
|
134
|
+
event_version: 1,
|
|
135
|
+
type: "github_release",
|
|
136
|
+
action: update_only ? "update" : "create",
|
|
137
|
+
status: created ? "ok" : "failed",
|
|
138
|
+
version: result.fetch("version"),
|
|
139
|
+
tag: result.fetch("tag"),
|
|
140
|
+
result: message
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
exit(created ? 0 : 1)
|
|
74
144
|
rescue LoadError => e
|
|
75
145
|
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
|
76
146
|
warn(Array(e.backtrace).join("\n")) if ENV["DEBUG"]
|
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require "net/http"
|
|
6
6
|
require "uri"
|
|
7
7
|
require "fileutils"
|
|
8
|
+
require "yaml"
|
|
8
9
|
require "kettle/ndjson"
|
|
9
10
|
|
|
10
11
|
module Kettle
|
|
@@ -27,9 +28,11 @@ module Kettle
|
|
|
27
28
|
# @param strict [Boolean] when true (default), require coverage and yard data; raise errors if unavailable
|
|
28
29
|
# @param enforce_coverage_thresholds [Boolean] when true, fail strict coverage generation below project thresholds
|
|
29
30
|
# @param update_prep [Boolean] when true, update the most recent prepared release section in place
|
|
31
|
+
# @param reformat_only [Boolean] when true, normalize structure without release-state planning
|
|
32
|
+
# @param historical_backfill_version [String, nil] tagged release to document without moving Unreleased entries
|
|
30
33
|
# @param version [String, nil] explicit version override for gems without a literal VERSION constant
|
|
31
34
|
# @param yes [Boolean] when true, approve the selected release plan without prompting
|
|
32
|
-
def initialize(strict: true, enforce_coverage_thresholds: true, update_prep: false, version: nil, root: Kettle::Dev::CIHelpers.project_root, refresh_cache: false, yes: false, event_stream: nil)
|
|
35
|
+
def initialize(strict: true, enforce_coverage_thresholds: true, update_prep: false, reformat_only: false, historical_backfill_version: nil, version: nil, root: Kettle::Dev::CIHelpers.project_root, refresh_cache: false, yes: false, event_stream: nil)
|
|
33
36
|
@root = root
|
|
34
37
|
@changelog_path = resolved_changelog_path
|
|
35
38
|
@coverage_root = resolved_coverage_root
|
|
@@ -37,6 +40,8 @@ module Kettle
|
|
|
37
40
|
@strict = strict
|
|
38
41
|
@enforce_coverage_thresholds = enforce_coverage_thresholds
|
|
39
42
|
@update_prep = update_prep
|
|
43
|
+
@reformat_only = reformat_only
|
|
44
|
+
@historical_backfill_version = Kettle::Dev::Versioning.normalize_explicit_version(historical_backfill_version)
|
|
40
45
|
@version_override = Kettle::Dev::Versioning.normalize_explicit_version(version)
|
|
41
46
|
@refresh_cache = refresh_cache
|
|
42
47
|
@yes = !!yes
|
|
@@ -50,6 +55,17 @@ module Kettle
|
|
|
50
55
|
#
|
|
51
56
|
# @return [void]
|
|
52
57
|
def run
|
|
58
|
+
if @reformat_only
|
|
59
|
+
reformat_changelog!(File.read(@changelog_path))
|
|
60
|
+
emit_changelog_event(action: "reformat", status: "ok", plan: "reformat_only")
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if @historical_backfill_version
|
|
65
|
+
backfill_historical_release!(@historical_backfill_version)
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
53
69
|
version = detect_version
|
|
54
70
|
today = Time.now.strftime("%Y-%m-%d")
|
|
55
71
|
owner, repo = Kettle::Dev::CIHelpers.repo_info
|
|
@@ -67,6 +83,12 @@ module Kettle
|
|
|
67
83
|
return
|
|
68
84
|
end
|
|
69
85
|
|
|
86
|
+
if plan.fetch(:action) == :rollback_prepared_release
|
|
87
|
+
rollback_prepared_release!(changelog, owner, repo, version)
|
|
88
|
+
emit_changelog_event(action: "rollback", status: "ok", version: plan.fetch(:latest_changelog_version), plan: "rollback_prepared_release")
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
|
|
70
92
|
line_cov_line, branch_cov_line = coverage_lines
|
|
71
93
|
yard_line = yard_percent_documented
|
|
72
94
|
|
|
@@ -300,21 +322,23 @@ module Kettle
|
|
|
300
322
|
abort("Aborting: version.rb (#{version}) is lower than the latest released version for this release line (#{latest_target}).")
|
|
301
323
|
end
|
|
302
324
|
|
|
303
|
-
if
|
|
325
|
+
if latest_target == version && latest_changelog_version && Gem::Version.new(latest_changelog_version) > Gem::Version.new(version)
|
|
326
|
+
action = :rollback_prepared_release
|
|
327
|
+
elsif section_exists && latest_changelog_version != version
|
|
304
328
|
abort("Aborting: CHANGELOG.md already contains a #{version} section, but the most recent release section is #{latest_changelog_version || "missing"}.")
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
action = if section_exists && latest_target == version
|
|
308
|
-
if unreleased_block_has_entries?(unreleased_block)
|
|
309
|
-
abort("Aborting: version.rb (#{version}) matches the latest released version for this release line (#{latest_target}); bump version.rb before moving Unreleased entries into a release section.")
|
|
310
|
-
end
|
|
311
|
-
:reformat_only
|
|
312
|
-
elsif section_exists
|
|
313
|
-
:update_prepared_release
|
|
314
|
-
elsif latest_target == version
|
|
315
|
-
abort("Aborting: version.rb (#{version}) matches the latest released version, but CHANGELOG.md does not have #{version} as the most recent release section.")
|
|
316
329
|
else
|
|
317
|
-
|
|
330
|
+
action = if section_exists && latest_target == version
|
|
331
|
+
if unreleased_block_has_entries?(unreleased_block)
|
|
332
|
+
abort("Aborting: version.rb (#{version}) matches the latest released version for this release line (#{latest_target}); bump version.rb before moving Unreleased entries into a release section.")
|
|
333
|
+
end
|
|
334
|
+
:reformat_only
|
|
335
|
+
elsif section_exists
|
|
336
|
+
:update_prepared_release
|
|
337
|
+
elsif latest_target == version
|
|
338
|
+
abort("Aborting: version.rb (#{version}) matches the latest released version, but CHANGELOG.md does not have #{version} as the most recent release section.")
|
|
339
|
+
else
|
|
340
|
+
:new_release
|
|
341
|
+
end
|
|
318
342
|
end
|
|
319
343
|
|
|
320
344
|
{
|
|
@@ -396,6 +420,8 @@ module Kettle
|
|
|
396
420
|
|
|
397
421
|
def reformat_changelog!(changelog)
|
|
398
422
|
updated = convert_heading_tag_suffix_to_list(changelog)
|
|
423
|
+
updated = normalize_legacy_release_headings(updated)
|
|
424
|
+
updated = remove_redundant_historical_placeholders(updated)
|
|
399
425
|
updated = normalize_heading_spacing(updated)
|
|
400
426
|
updated = ensure_footer_spacing(updated)
|
|
401
427
|
updated = updated.rstrip + "\n"
|
|
@@ -403,6 +429,86 @@ module Kettle
|
|
|
403
429
|
puts "CHANGELOG.md reformatted. No new version section added."
|
|
404
430
|
end
|
|
405
431
|
|
|
432
|
+
def normalize_legacy_release_headings(changelog)
|
|
433
|
+
changelog.to_s.lines.map do |line|
|
|
434
|
+
match = line.match(/^##\s+(?!\[)(v?\d+(?:\.\d+)+(?:[.-][0-9A-Za-z]+)?)(\s+-\s+.*)?\s*$/)
|
|
435
|
+
next line unless match
|
|
436
|
+
|
|
437
|
+
version = match[1].delete_prefix("v")
|
|
438
|
+
next line unless Gem::Version.correct?(version)
|
|
439
|
+
|
|
440
|
+
"## [#{version}]#{match[2]}\n"
|
|
441
|
+
end.join
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def remove_redundant_historical_placeholders(changelog)
|
|
445
|
+
lines = changelog.to_s.lines
|
|
446
|
+
sections = changelog_release_sections(lines)
|
|
447
|
+
removals = sections.group_by { |section| section.fetch(:version) }.values.flat_map do |same_version|
|
|
448
|
+
next [] unless same_version.size > 1
|
|
449
|
+
next [] unless same_version.any? { |section| !section.fetch(:placeholder) }
|
|
450
|
+
|
|
451
|
+
same_version.filter_map do |section|
|
|
452
|
+
(section.fetch(:start)...section.fetch(:finish)) if section.fetch(:placeholder)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
return changelog if removals.empty?
|
|
456
|
+
|
|
457
|
+
lines.each_with_index.reject { |_line, index| removals.any? { |range| range.cover?(index) } }.map(&:first).join
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def changelog_release_sections(lines)
|
|
461
|
+
headings = lines.each_index.filter_map do |index|
|
|
462
|
+
match = lines[index].match(/^## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]/o)
|
|
463
|
+
{version: match[1], start: index} if match
|
|
464
|
+
end
|
|
465
|
+
headings.each_with_index.map do |heading, index|
|
|
466
|
+
next_heading = headings[index + 1]
|
|
467
|
+
footer = lines.each_index.find do |line_index|
|
|
468
|
+
line_index > heading.fetch(:start) && lines[line_index].start_with?(UNRELEASED_SECTION_HEADING)
|
|
469
|
+
end
|
|
470
|
+
finish = [next_heading&.fetch(:start), footer, lines.length].compact.min
|
|
471
|
+
body = lines[heading.fetch(:start)...finish].join
|
|
472
|
+
heading.merge(finish: finish, placeholder: body.include?("Historical release notes are unavailable in this changelog."))
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def backfill_historical_release!(version)
|
|
477
|
+
changelog = File.read(@changelog_path)
|
|
478
|
+
abort("CHANGELOG.md already contains a section for #{version}") if release_section_exists?(changelog, version)
|
|
479
|
+
|
|
480
|
+
unreleased_block, before, after = extract_unreleased(changelog)
|
|
481
|
+
abort("Could not find '## [Unreleased]' section in CHANGELOG.md") if unreleased_block.nil?
|
|
482
|
+
|
|
483
|
+
date = tagged_release_date(version)
|
|
484
|
+
owner, repo = Kettle::Dev::CIHelpers.repo_info
|
|
485
|
+
abort("Could not determine GitHub owner/repo from origin remote") unless owner && repo
|
|
486
|
+
|
|
487
|
+
section = <<~MD
|
|
488
|
+
## [#{version}] - #{date}
|
|
489
|
+
- TAG: [v#{version}][#{version}t]
|
|
490
|
+
|
|
491
|
+
### Changed
|
|
492
|
+
|
|
493
|
+
- Historical release notes are unavailable in this changelog.
|
|
494
|
+
|
|
495
|
+
MD
|
|
496
|
+
updated = before + "## [Unreleased]\n" + unreleased_block.rstrip + "\n\n" + section + after.to_s
|
|
497
|
+
updated = update_link_refs(updated, owner, repo, nil, version)
|
|
498
|
+
updated = normalize_heading_spacing(updated).rstrip + "\n"
|
|
499
|
+
File.write(@changelog_path, updated)
|
|
500
|
+
emit_changelog_event(action: "backfill", status: "ok", version: version, plan: "historical_release")
|
|
501
|
+
puts "CHANGELOG.md backfilled with historical v#{version} section. Unreleased entries were preserved."
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def tagged_release_date(version)
|
|
505
|
+
tag = "v#{version}"
|
|
506
|
+
output, status = Open3.capture2("git", "log", "-1", "--format=%as", tag, chdir: @root)
|
|
507
|
+
abort("Local git tag #{tag} is required to backfill a historical release section") unless status.success? && !output.strip.empty?
|
|
508
|
+
|
|
509
|
+
output.strip
|
|
510
|
+
end
|
|
511
|
+
|
|
406
512
|
def release_section_exists?(changelog, version)
|
|
407
513
|
changelog.match?(/^## \[#{Regexp.escape(version)}\]/)
|
|
408
514
|
end
|
|
@@ -562,6 +668,25 @@ module Kettle
|
|
|
562
668
|
puts "CHANGELOG.md updated in place for v#{prepared_version}."
|
|
563
669
|
end
|
|
564
670
|
|
|
671
|
+
def rollback_prepared_release!(changelog, owner, repo, published_version)
|
|
672
|
+
unreleased_block, before, after = extract_unreleased(changelog)
|
|
673
|
+
release_lines = after.to_s.lines
|
|
674
|
+
heading = release_lines.first.to_s.match(/\A## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]/o)
|
|
675
|
+
abort("Could not find a prepared release section after '## [Unreleased]' in CHANGELOG.md") unless heading
|
|
676
|
+
|
|
677
|
+
finish = release_lines.each_index.drop(1).find do |index|
|
|
678
|
+
release_lines[index].start_with?("## [", UNRELEASED_SECTION_HEADING)
|
|
679
|
+
end || release_lines.length
|
|
680
|
+
prepared_version = heading[1]
|
|
681
|
+
merged = merge_release_body_with_unreleased(release_lines[1...finish].join, unreleased_block)
|
|
682
|
+
unreleased = "## [Unreleased]\n" + merged.rstrip + "\n\n"
|
|
683
|
+
tail = release_lines[finish, release_lines.length].to_a.reject { |line| line.start_with?("[#{prepared_version}]:", "[#{prepared_version}t]:") }.join
|
|
684
|
+
updated = before + unreleased + tail
|
|
685
|
+
updated = update_link_refs(updated, owner, repo, nil, published_version)
|
|
686
|
+
File.write(@changelog_path, normalize_heading_spacing(updated).rstrip + "\n")
|
|
687
|
+
puts "CHANGELOG.md rolled back prepared v#{prepared_version} into Unreleased."
|
|
688
|
+
end
|
|
689
|
+
|
|
565
690
|
def merge_release_body_with_unreleased(release_body, unreleased_block)
|
|
566
691
|
existing = strip_release_metadata(release_body)
|
|
567
692
|
incoming = filter_unreleased_sections(unreleased_block)
|
|
@@ -791,11 +916,31 @@ module Kettle
|
|
|
791
916
|
"K_SOUP_COV_MULTI_FORMATTERS" => "false",
|
|
792
917
|
"K_SOUP_COV_OPEN_BIN" => ""
|
|
793
918
|
)
|
|
919
|
+
env.merge!(changelog_coverage_workflow_thresholds)
|
|
794
920
|
gemfile = File.join(@coverage_root, "Gemfile")
|
|
795
921
|
env["BUNDLE_GEMFILE"] = gemfile if File.file?(gemfile)
|
|
796
922
|
env
|
|
797
923
|
end
|
|
798
924
|
|
|
925
|
+
# The changelog coverage run is the final local test gate before a
|
|
926
|
+
# release commit is pushed. Reuse the checked-in CI workflow thresholds
|
|
927
|
+
# rather than silently falling back to kettle-soup-cover defaults.
|
|
928
|
+
def changelog_coverage_workflow_thresholds
|
|
929
|
+
path = ["coverage.yml", "coverage.yaml"]
|
|
930
|
+
.map { |name| File.join(@coverage_root, ".github", "workflows", name) }
|
|
931
|
+
.find { |candidate| File.file?(candidate) }
|
|
932
|
+
return {} unless path
|
|
933
|
+
|
|
934
|
+
workflow = YAML.safe_load_file(path, permitted_classes: [], aliases: false)
|
|
935
|
+
workflow_env = (workflow.is_a?(Hash) && workflow["env"].is_a?(Hash)) ? workflow["env"] : {}
|
|
936
|
+
%w[K_SOUP_COV_MIN_LINE K_SOUP_COV_MIN_BRANCH].each_with_object({}) do |key, thresholds|
|
|
937
|
+
value = workflow_env[key]
|
|
938
|
+
thresholds[key] = value.to_s unless value.nil? || value.to_s.empty?
|
|
939
|
+
end
|
|
940
|
+
rescue Psych::Exception => error
|
|
941
|
+
raise "Unable to read coverage thresholds from #{path}: #{error.message}"
|
|
942
|
+
end
|
|
943
|
+
|
|
799
944
|
def ensure_changelog_coverage_json!
|
|
800
945
|
return if File.file?(@coverage_path)
|
|
801
946
|
|
|
@@ -9,7 +9,7 @@ module Kettle
|
|
|
9
9
|
@root = root
|
|
10
10
|
@section = section.to_s
|
|
11
11
|
@entry = normalize_entry(entry)
|
|
12
|
-
@changelog_path = File.join(@root, "CHANGELOG.md")
|
|
12
|
+
@changelog_path = ENV.fetch("K_CHANGELOG_PATH", File.join(@root, "CHANGELOG.md"))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def run
|
|
@@ -68,7 +68,7 @@ module Kettle
|
|
|
68
68
|
owner.location.start_line > unreleased.location.start_line &&
|
|
69
69
|
owner.location.end_line <= unreleased.location.end_line
|
|
70
70
|
end
|
|
71
|
-
raise Error, "expected
|
|
71
|
+
raise Error, "expected at least one #{heading(3, @section)} section under ## [Unreleased] in CHANGELOG.md, found none" if matches.empty?
|
|
72
72
|
|
|
73
73
|
matches.first
|
|
74
74
|
end
|
|
@@ -4,6 +4,7 @@ require "optparse"
|
|
|
4
4
|
require "English"
|
|
5
5
|
require "json"
|
|
6
6
|
require "fileutils"
|
|
7
|
+
require "open3"
|
|
7
8
|
require "yaml"
|
|
8
9
|
require "uri"
|
|
9
10
|
require "net/http"
|
|
@@ -505,11 +506,49 @@ module Kettle
|
|
|
505
506
|
end
|
|
506
507
|
|
|
507
508
|
def image_url_skipped?(url)
|
|
509
|
+
return true if github_actions_badge_for_local_workflow?(url)
|
|
510
|
+
|
|
508
511
|
@image_url_skip_patterns.any? do |pattern|
|
|
509
512
|
File.fnmatch?(pattern, url, File::FNM_CASEFOLD)
|
|
510
513
|
end
|
|
511
514
|
end
|
|
512
515
|
|
|
516
|
+
# A template run can add a workflow and its README badge in the same
|
|
517
|
+
# commit. Before that commit is pushed, GitHub correctly returns 404 for
|
|
518
|
+
# the badge even though the local workflow will make it valid.
|
|
519
|
+
def github_actions_badge_for_local_workflow?(url)
|
|
520
|
+
uri = URI.parse(url)
|
|
521
|
+
return false unless uri.host == "github.com"
|
|
522
|
+
|
|
523
|
+
segments = uri.path.split("/").reject(&:empty?)
|
|
524
|
+
return false unless segments.length == 6
|
|
525
|
+
return false unless segments[2, 2] == %w[actions workflows] && segments.last == "badge.svg"
|
|
526
|
+
return false unless local_github_repository_slug == segments.first(2).join("/")
|
|
527
|
+
|
|
528
|
+
File.file?(File.join(".github", "workflows", segments[4]))
|
|
529
|
+
rescue URI::InvalidURIError
|
|
530
|
+
false
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def local_github_repository_slug
|
|
534
|
+
return @local_github_repository_slug if defined?(@local_github_repository_slug)
|
|
535
|
+
|
|
536
|
+
remote_url, status = Open3.capture2("git", "config", "--get", "remote.origin.url")
|
|
537
|
+
@local_github_repository_slug = status.success? ? github_repository_slug(remote_url) : nil
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def github_repository_slug(remote_url)
|
|
541
|
+
value = remote_url.to_s.strip
|
|
542
|
+
return value.split(":", 2).last.delete_suffix(".git") if value.start_with?("git@github.com:")
|
|
543
|
+
|
|
544
|
+
uri = URI.parse(value)
|
|
545
|
+
return unless uri.host == "github.com"
|
|
546
|
+
|
|
547
|
+
uri.path.delete_prefix("/").delete_suffix(".git")
|
|
548
|
+
rescue URI::InvalidURIError
|
|
549
|
+
nil
|
|
550
|
+
end
|
|
551
|
+
|
|
513
552
|
def stringify_keys(value)
|
|
514
553
|
case value
|
|
515
554
|
when Hash
|
|
@@ -436,7 +436,8 @@ module Kettle
|
|
|
436
436
|
# 18. create GitHub release (optional)
|
|
437
437
|
if run_step?(18)
|
|
438
438
|
version ||= detect_version
|
|
439
|
-
maybe_create_github_release!(version)
|
|
439
|
+
created, message = maybe_create_github_release!(version)
|
|
440
|
+
abort("GitHub release creation failed: #{message}") if github_release_required? && !created
|
|
440
441
|
end
|
|
441
442
|
|
|
442
443
|
# 19. push tags to remotes (final step)
|
|
@@ -2340,24 +2341,27 @@ module Kettle
|
|
|
2340
2341
|
# Title: v<version>
|
|
2341
2342
|
# Body: the CHANGELOG section for this version, followed by the two link references for this version.
|
|
2342
2343
|
def maybe_create_github_release!(version)
|
|
2343
|
-
token =
|
|
2344
|
+
token = github_token
|
|
2344
2345
|
if token.empty?
|
|
2345
|
-
|
|
2346
|
-
|
|
2346
|
+
message = "GITHUB_TOKEN or GH_TOKEN is not set; skipping GitHub release creation. Set a token with repo:public_repo (classic) or contents:write scope."
|
|
2347
|
+
warn(message)
|
|
2348
|
+
return [false, message]
|
|
2347
2349
|
end
|
|
2348
2350
|
|
|
2349
2351
|
gh_remote = preferred_github_remote
|
|
2350
2352
|
url = remote_url(gh_remote || "origin")
|
|
2351
2353
|
owner, repo = parse_github_owner_repo(url)
|
|
2352
2354
|
unless owner && repo
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
+
message = "GitHub token present but could not determine GitHub owner/repo from remotes. Skipping release creation."
|
|
2356
|
+
warn(message)
|
|
2357
|
+
return [false, message]
|
|
2355
2358
|
end
|
|
2356
2359
|
|
|
2357
2360
|
section, compare_ref, tag_ref = extract_changelog_for_version(version)
|
|
2358
2361
|
unless section
|
|
2359
|
-
|
|
2360
|
-
|
|
2362
|
+
message = "CHANGELOG.md does not contain a section for #{version}. Skipping GitHub release creation."
|
|
2363
|
+
warn(message)
|
|
2364
|
+
return [false, message]
|
|
2361
2365
|
end
|
|
2362
2366
|
|
|
2363
2367
|
body = +""
|
|
@@ -2377,6 +2381,97 @@ module Kettle
|
|
|
2377
2381
|
else
|
|
2378
2382
|
warn("GitHub release creation skipped/failed: #{msg}")
|
|
2379
2383
|
end
|
|
2384
|
+
[ok, msg]
|
|
2385
|
+
end
|
|
2386
|
+
|
|
2387
|
+
def maybe_update_github_release!(version)
|
|
2388
|
+
token = github_token
|
|
2389
|
+
return [false, "GITHUB_TOKEN or GH_TOKEN is not set"] if token.empty?
|
|
2390
|
+
|
|
2391
|
+
owner, repo = parse_github_owner_repo(remote_url(preferred_github_remote || "origin"))
|
|
2392
|
+
return [false, "could not determine GitHub owner/repo from remotes"] unless owner && repo
|
|
2393
|
+
|
|
2394
|
+
body = github_release_body(version)
|
|
2395
|
+
return [false, "CHANGELOG.md does not contain a section for #{version}"] unless body
|
|
2396
|
+
|
|
2397
|
+
github_update_release(owner: owner, repo: repo, token: token, tag: "v#{version}", title: "v#{version}", body: body)
|
|
2398
|
+
end
|
|
2399
|
+
|
|
2400
|
+
def github_release_body(version)
|
|
2401
|
+
section, compare_ref, tag_ref = extract_changelog_for_version(version)
|
|
2402
|
+
return unless section
|
|
2403
|
+
|
|
2404
|
+
body = +section.rstrip
|
|
2405
|
+
body << "\n\n" << compare_ref if compare_ref
|
|
2406
|
+
body << tag_ref if tag_ref
|
|
2407
|
+
footer = extract_release_notes_footer
|
|
2408
|
+
body << "\n" << footer if footer && !footer.strip.empty?
|
|
2409
|
+
body
|
|
2410
|
+
end
|
|
2411
|
+
|
|
2412
|
+
# Validate the immutable inputs required to backfill a GitHub Release.
|
|
2413
|
+
# This must never create a tag or publish a gem as a side effect.
|
|
2414
|
+
def github_release_backfill_check(version, require_rubygems: true)
|
|
2415
|
+
normalized_version = version.to_s.delete_prefix("v")
|
|
2416
|
+
tag = "v#{normalized_version}"
|
|
2417
|
+
diagnostics = []
|
|
2418
|
+
token = github_token
|
|
2419
|
+
diagnostics << "GITHUB_TOKEN is not set" if token.empty?
|
|
2420
|
+
|
|
2421
|
+
gem_name = detect_gem_name
|
|
2422
|
+
if require_rubygems && !rubygems_version_published?(gem_name, normalized_version)
|
|
2423
|
+
diagnostics << "RubyGems.org does not list #{gem_name} #{normalized_version}"
|
|
2424
|
+
end
|
|
2425
|
+
|
|
2426
|
+
remote = preferred_github_remote
|
|
2427
|
+
if remote.nil?
|
|
2428
|
+
diagnostics << "no GitHub remote is configured"
|
|
2429
|
+
elsif !remote_tag_exists?(remote, tag)
|
|
2430
|
+
diagnostics << "remote #{remote.inspect} does not contain tag #{tag}"
|
|
2431
|
+
end
|
|
2432
|
+
|
|
2433
|
+
section, = extract_changelog_for_version(normalized_version)
|
|
2434
|
+
diagnostics << "CHANGELOG.md does not contain a section for #{normalized_version}" unless section
|
|
2435
|
+
|
|
2436
|
+
{
|
|
2437
|
+
"ok" => diagnostics.empty?,
|
|
2438
|
+
"version" => normalized_version,
|
|
2439
|
+
"tag" => tag,
|
|
2440
|
+
"gem_name" => gem_name,
|
|
2441
|
+
"remote" => remote,
|
|
2442
|
+
"diagnostics" => diagnostics
|
|
2443
|
+
}
|
|
2444
|
+
end
|
|
2445
|
+
|
|
2446
|
+
def github_release_token_configured?
|
|
2447
|
+
!github_token.empty?
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
def github_release_required?
|
|
2451
|
+
github_release_token_configured? && truthy_value?(ENV["KETTLE_RELEASE_REQUIRE_GITHUB_RELEASE"])
|
|
2452
|
+
end
|
|
2453
|
+
|
|
2454
|
+
def github_token
|
|
2455
|
+
token = ENV.fetch("GITHUB_TOKEN", "").strip
|
|
2456
|
+
return token unless token.empty?
|
|
2457
|
+
|
|
2458
|
+
ENV.fetch("GH_TOKEN", "").strip
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
def rubygems_version_published?(gem_name, version)
|
|
2462
|
+
versions = Kettle::Dev::RubyGemsVersions.fetch(gem_name, version_hint: version, refresh: true)
|
|
2463
|
+
Array(versions).any? { |entry| entry.is_a?(Hash) && entry["number"].to_s == version.to_s }
|
|
2464
|
+
rescue => error
|
|
2465
|
+
warn("Could not verify RubyGems.org publication for #{gem_name} #{version}: #{error.class}: #{error.message}")
|
|
2466
|
+
false
|
|
2467
|
+
end
|
|
2468
|
+
|
|
2469
|
+
def remote_tag_exists?(remote, tag)
|
|
2470
|
+
_stdout, _stderr, status = Open3.capture3("git", "ls-remote", "--exit-code", "--tags", remote, "refs/tags/#{tag}", chdir: @root)
|
|
2471
|
+
status.success?
|
|
2472
|
+
rescue SystemCallError => error
|
|
2473
|
+
warn("Could not verify remote tag #{tag}: #{error.class}: #{error.message}")
|
|
2474
|
+
false
|
|
2380
2475
|
end
|
|
2381
2476
|
|
|
2382
2477
|
# Returns [section_text, compare_ref_line, tag_ref_line]
|
|
@@ -2465,6 +2560,28 @@ module Kettle
|
|
|
2465
2560
|
rescue => e
|
|
2466
2561
|
[false, "#{e.class}: #{e.message}"]
|
|
2467
2562
|
end
|
|
2563
|
+
|
|
2564
|
+
def github_update_release(owner:, repo:, token:, tag:, title:, body:)
|
|
2565
|
+
lookup = URI("https://api.github.com/repos/#{owner}/#{repo}/releases/tags/#{tag}")
|
|
2566
|
+
get = Net::HTTP::Get.new(lookup)
|
|
2567
|
+
get["Accept"] = "application/vnd.github+json"
|
|
2568
|
+
get["Authorization"] = "token #{token}"
|
|
2569
|
+
get["User-Agent"] = "kettle-dev-release-cli"
|
|
2570
|
+
release = Net::HTTP.start(lookup.host, lookup.port, use_ssl: true) { |http| http.request(get) }
|
|
2571
|
+
return [false, "release #{tag} was not found (HTTP #{release.code})"] unless release.is_a?(Net::HTTPSuccess)
|
|
2572
|
+
|
|
2573
|
+
id = JSON.parse(release.body).fetch("id")
|
|
2574
|
+
uri = URI("https://api.github.com/repos/#{owner}/#{repo}/releases/#{id}")
|
|
2575
|
+
request = Net::HTTP::Patch.new(uri)
|
|
2576
|
+
request["Accept"] = "application/vnd.github+json"
|
|
2577
|
+
request["Authorization"] = "token #{token}"
|
|
2578
|
+
request["User-Agent"] = "kettle-dev-release-cli"
|
|
2579
|
+
request.body = JSON.dump(name: title, body: body)
|
|
2580
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
2581
|
+
response.is_a?(Net::HTTPSuccess) ? [true, "updated"] : [false, "HTTP #{response.code}: #{response.body}"]
|
|
2582
|
+
rescue => e
|
|
2583
|
+
[false, "#{e.class}: #{e.message}"]
|
|
2584
|
+
end
|
|
2468
2585
|
end
|
|
2469
2586
|
end
|
|
2470
2587
|
end
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "2.5.
|
|
8
|
+
VERSION = "2.5.16"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -60,7 +60,7 @@ dependencies:
|
|
|
60
60
|
version: '0.3'
|
|
61
61
|
- - ">="
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.3.
|
|
63
|
+
version: 0.3.5
|
|
64
64
|
type: :runtime
|
|
65
65
|
prerelease: false
|
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -70,7 +70,7 @@ dependencies:
|
|
|
70
70
|
version: '0.3'
|
|
71
71
|
- - ">="
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.3.
|
|
73
|
+
version: 0.3.5
|
|
74
74
|
- !ruby/object:Gem::Dependency
|
|
75
75
|
name: kettle-ndjson
|
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,7 +80,7 @@ dependencies:
|
|
|
80
80
|
version: '0.1'
|
|
81
81
|
- - ">="
|
|
82
82
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: 0.1.
|
|
83
|
+
version: 0.1.10
|
|
84
84
|
type: :runtime
|
|
85
85
|
prerelease: false
|
|
86
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -90,7 +90,7 @@ dependencies:
|
|
|
90
90
|
version: '0.1'
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 0.1.
|
|
93
|
+
version: 0.1.10
|
|
94
94
|
- !ruby/object:Gem::Dependency
|
|
95
95
|
name: kettle-rb
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -100,7 +100,7 @@ dependencies:
|
|
|
100
100
|
version: '0.1'
|
|
101
101
|
- - ">="
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: 0.1.
|
|
103
|
+
version: 0.1.8
|
|
104
104
|
type: :runtime
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -110,7 +110,7 @@ dependencies:
|
|
|
110
110
|
version: '0.1'
|
|
111
111
|
- - ">="
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
|
-
version: 0.1.
|
|
113
|
+
version: 0.1.8
|
|
114
114
|
- !ruby/object:Gem::Dependency
|
|
115
115
|
name: kettle-test
|
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -120,7 +120,7 @@ dependencies:
|
|
|
120
120
|
version: '2.0'
|
|
121
121
|
- - ">="
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 2.0.
|
|
123
|
+
version: 2.0.18
|
|
124
124
|
type: :runtime
|
|
125
125
|
prerelease: false
|
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -130,7 +130,7 @@ dependencies:
|
|
|
130
130
|
version: '2.0'
|
|
131
131
|
- - ">="
|
|
132
132
|
- !ruby/object:Gem::Version
|
|
133
|
-
version: 2.0.
|
|
133
|
+
version: 2.0.18
|
|
134
134
|
- !ruby/object:Gem::Dependency
|
|
135
135
|
name: ruby-progressbar
|
|
136
136
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -256,7 +256,7 @@ dependencies:
|
|
|
256
256
|
version: '3.2'
|
|
257
257
|
- - ">="
|
|
258
258
|
- !ruby/object:Gem::Version
|
|
259
|
-
version: 3.2.
|
|
259
|
+
version: 3.2.4
|
|
260
260
|
type: :development
|
|
261
261
|
prerelease: false
|
|
262
262
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -266,7 +266,7 @@ dependencies:
|
|
|
266
266
|
version: '3.2'
|
|
267
267
|
- - ">="
|
|
268
268
|
- !ruby/object:Gem::Version
|
|
269
|
-
version: 3.2.
|
|
269
|
+
version: 3.2.4
|
|
270
270
|
- !ruby/object:Gem::Dependency
|
|
271
271
|
name: stone_checksums
|
|
272
272
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -276,7 +276,7 @@ dependencies:
|
|
|
276
276
|
version: '1.0'
|
|
277
277
|
- - ">="
|
|
278
278
|
- !ruby/object:Gem::Version
|
|
279
|
-
version: 1.0.
|
|
279
|
+
version: 1.0.8
|
|
280
280
|
type: :development
|
|
281
281
|
prerelease: false
|
|
282
282
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -286,7 +286,7 @@ dependencies:
|
|
|
286
286
|
version: '1.0'
|
|
287
287
|
- - ">="
|
|
288
288
|
- !ruby/object:Gem::Version
|
|
289
|
-
version: 1.0.
|
|
289
|
+
version: 1.0.8
|
|
290
290
|
- !ruby/object:Gem::Dependency
|
|
291
291
|
name: gitmoji-regex
|
|
292
292
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -296,7 +296,7 @@ dependencies:
|
|
|
296
296
|
version: '2.0'
|
|
297
297
|
- - ">="
|
|
298
298
|
- !ruby/object:Gem::Version
|
|
299
|
-
version: 2.0.
|
|
299
|
+
version: 2.0.11
|
|
300
300
|
type: :development
|
|
301
301
|
prerelease: false
|
|
302
302
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -306,7 +306,7 @@ dependencies:
|
|
|
306
306
|
version: '2.0'
|
|
307
307
|
- - ">="
|
|
308
308
|
- !ruby/object:Gem::Version
|
|
309
|
-
version: 2.0.
|
|
309
|
+
version: 2.0.11
|
|
310
310
|
description: "\U0001F372 Kettle::Dev is a meta tool from kettle-dev to streamline
|
|
311
311
|
development and testing. Acts as a shim dependency, pulling in many other dependencies,
|
|
312
312
|
to give you OOTB productivity with a RubyGem, or Ruby app project. Configures a
|
|
@@ -401,10 +401,10 @@ licenses:
|
|
|
401
401
|
- AGPL-3.0-only
|
|
402
402
|
metadata:
|
|
403
403
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
404
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.
|
|
405
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.
|
|
404
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.16
|
|
405
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.16/CHANGELOG.md
|
|
406
406
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
407
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.
|
|
407
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.16
|
|
408
408
|
funding_uri: https://github.com/sponsors/pboling
|
|
409
409
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
410
410
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|