kettle-family 0.1.3 → 0.1.4
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 +21 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +32 -2
- data/lib/kettle/family/changelog_check.rb +19 -7
- data/lib/kettle/family/config.rb +74 -0
- data/lib/kettle/family/readiness_check.rb +72 -6
- data/lib/kettle/family/release_state_check.rb +148 -1
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +25 -8
- data.tar.gz.sig +0 -0
- metadata +12 -12
- 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: b3ec93e3425d765de198975bb390031d33f4a408c31994a18999e3178aec2cee
|
|
4
|
+
data.tar.gz: c22c0cec6c2dc9a387281adb456d7303bc838e687be27b0c69ecc59b7f8038ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7520e01212b4faebaed857c69a7bd0c068be891e8fa79c4ccb7485f9cd8fdd4daa824c5943cc030da765992422a1bf252fe76fde2e68b3b4f0feeaf179beb30a
|
|
7
|
+
data.tar.gz: '0683aae4dfdbbde5b004719856f32b65f7810acd81446145456a7810e4625f85a516f1d53d08d0c1622f65d7d8a2902c0dfb2ff57b35950f19bc76355cdded56'
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,24 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [0.1.4] - 2026-06-16
|
|
34
|
+
|
|
35
|
+
- TAG: [v0.1.4][0.1.4t]
|
|
36
|
+
- COVERAGE: 93.72% -- 1060/1131 lines in 19 files
|
|
37
|
+
- BRANCH COVERAGE: 76.12% -- 322/423 branches in 19 files
|
|
38
|
+
- 40.14% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Added configurable readiness checks, root/shared changelog support, release
|
|
43
|
+
environment overrides, and an optional family changelog release phase for
|
|
44
|
+
monorepo gem families whose members share root release metadata.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Fixed the Ruby 3.2 CI appraisal so root changelog release-state checks have
|
|
49
|
+
Prism available.
|
|
50
|
+
|
|
33
51
|
## [0.1.3] - 2026-06-14
|
|
34
52
|
|
|
35
53
|
- TAG: [v0.1.3][0.1.3t]
|
|
@@ -122,7 +140,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
122
140
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
123
141
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
124
142
|
|
|
125
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.
|
|
143
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.4...HEAD
|
|
144
|
+
[0.1.4]: https://github.com/kettle-dev/kettle-family/compare/v0.1.3...v0.1.4
|
|
145
|
+
[0.1.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.4
|
|
126
146
|
[0.1.3]: https://github.com/kettle-dev/kettle-family/compare/v0.1.2...v0.1.3
|
|
127
147
|
[0.1.3t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.3
|
|
128
148
|
[0.1.2]: https://github.com/kettle-dev/kettle-family/compare/v0.1.1...v0.1.2
|
data/CONTRIBUTING.md
CHANGED
|
@@ -109,14 +109,14 @@ Git diff driver setup
|
|
|
109
109
|
- Git hosting forges generally ignore external diff drivers, so pull request views may still show raw textual diffs even when local `git diff` uses semantic drivers.
|
|
110
110
|
|
|
111
111
|
```console
|
|
112
|
-
K_JEM_TEMPLATING=true
|
|
112
|
+
K_JEM_TEMPLATING=true kettle-jem install
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
Troubleshooting Git diffs
|
|
116
116
|
- Use `git diff --no-ext-diff` to compare against Git's built-in diff output.
|
|
117
117
|
- Use `git diff --no-textconv` when a textconv projection obscures the raw file bytes you need to inspect.
|
|
118
118
|
- If Git reports a missing `smorg-*` executable, rerun `bundle install` and the setup command above, then check `git config --local --get-regexp '^diff\.smorg-'`.
|
|
119
|
-
- To remove managed local entries, run `K_JEM_TEMPLATING=true
|
|
119
|
+
- To remove managed local entries, run `K_JEM_TEMPLATING=true kettle-jem install --undo`; remove global command registrations with `git config --global --unset-all diff.smorg-ruby.command`.
|
|
120
120
|
|
|
121
121
|
For a quick starting point, this repository’s `mise.toml` defines the shared defaults, and `.env.local` can override them locally. Copy `.env.local.example` to `.env.local`, use `KEY=value` lines, and either activate `mise` in your shell or run commands through `mise exec -C /path/to/project -- ...`.
|
|
122
122
|
|
data/README.md
CHANGED
|
@@ -142,6 +142,36 @@ members:
|
|
|
142
142
|
- "**/vendor/**"
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
Monorepo families whose member gems share release metadata from the repository
|
|
146
|
+
root can configure readiness and changelog ownership explicitly:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
check:
|
|
150
|
+
required_files:
|
|
151
|
+
- Gemfile
|
|
152
|
+
- Rakefile
|
|
153
|
+
- README.md
|
|
154
|
+
- LICENSE.md
|
|
155
|
+
required_bins:
|
|
156
|
+
- bin/rake
|
|
157
|
+
- bin/rspec
|
|
158
|
+
root_required_files:
|
|
159
|
+
- CHANGELOG.md
|
|
160
|
+
- SECURITY.md
|
|
161
|
+
|
|
162
|
+
changelog:
|
|
163
|
+
mode: root
|
|
164
|
+
path: CHANGELOG.md
|
|
165
|
+
version_file: gems/tree_haver/lib/tree_haver/version.rb
|
|
166
|
+
|
|
167
|
+
release:
|
|
168
|
+
env:
|
|
169
|
+
KETTLE_RB_DEV: false
|
|
170
|
+
family_changelog:
|
|
171
|
+
enabled: true
|
|
172
|
+
command: bundle exec kettle-changelog
|
|
173
|
+
```
|
|
174
|
+
|
|
145
175
|
For a flat repository that releases from multiple long-lived branches, list the
|
|
146
176
|
release branches under `release.target_branches`. The branch list is processed
|
|
147
177
|
in order. Each branch must be clean enough for `git checkout`, and each branch
|
|
@@ -516,7 +546,7 @@ Thanks for RTFM. ☺️
|
|
|
516
546
|
[📌gitmoji]: https://gitmoji.dev
|
|
517
547
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
518
548
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
519
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
|
549
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-1.131-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
520
550
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
521
551
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
522
552
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -544,7 +574,7 @@ Thanks for RTFM. ☺️
|
|
|
544
574
|
| Package | kettle-family |
|
|
545
575
|
| Description | 👩👩👧👧 Kettle::Family provides scripts and conventions for coordinating related Ruby gems as one family. |
|
|
546
576
|
| Homepage | https://github.com/kettle-dev/kettle-family |
|
|
547
|
-
| Source | https://github.com/kettle-dev/kettle-family
|
|
577
|
+
| Source | https://github.com/kettle-dev/kettle-family |
|
|
548
578
|
| License | `AGPL-3.0-only` |
|
|
549
579
|
| Funding | https://github.com/sponsors/pboling, https://issuehunt.io/u/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://opencollective.com/kettle-rb, https://patreon.com/galtzo, https://polar.sh/pboling, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-family, https://www.buymeacoffee.com/pboling |
|
|
550
580
|
<!-- kettle-jem:metadata:end -->
|
|
@@ -3,25 +3,37 @@
|
|
|
3
3
|
module Kettle
|
|
4
4
|
module Family
|
|
5
5
|
class ChangelogCheck
|
|
6
|
-
def self.call(member:)
|
|
7
|
-
new(member: member).call
|
|
6
|
+
def self.call(member:, config: nil)
|
|
7
|
+
new(member: member, config: config).call
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def initialize(member:)
|
|
10
|
+
def initialize(member:, config: nil)
|
|
11
11
|
@member = member
|
|
12
|
+
@config = config
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def call
|
|
15
16
|
diagnostics = []
|
|
16
|
-
changelog =
|
|
17
|
-
diagnostics << "missing
|
|
18
|
-
diagnostics << "
|
|
17
|
+
changelog = changelog_path
|
|
18
|
+
diagnostics << "missing #{relative_changelog_path}" unless File.file?(changelog)
|
|
19
|
+
diagnostics << "#{relative_changelog_path} missing Unreleased section" if File.file?(changelog) && !File.read(changelog).include?("## [Unreleased]")
|
|
19
20
|
result(diagnostics)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
private
|
|
23
24
|
|
|
24
|
-
attr_reader :member
|
|
25
|
+
attr_reader :member, :config
|
|
26
|
+
|
|
27
|
+
def changelog_path
|
|
28
|
+
config ? config.changelog_full_path(member) : File.join(member.root, "CHANGELOG.md")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def relative_changelog_path
|
|
32
|
+
return "CHANGELOG.md" unless config
|
|
33
|
+
|
|
34
|
+
base = config.shared_changelog? ? config.root : member.root
|
|
35
|
+
changelog_path.delete_prefix("#{base}/")
|
|
36
|
+
end
|
|
25
37
|
|
|
26
38
|
def result(diagnostics)
|
|
27
39
|
CommandResult.new(
|
data/lib/kettle/family/config.rb
CHANGED
|
@@ -90,6 +90,64 @@ module Kettle
|
|
|
90
90
|
fetch_path("commands", name)
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
def check_required_files
|
|
94
|
+
fetch_path("check", "required_files") || ReadinessCheck::REQUIRED_FILES
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_required_bins
|
|
98
|
+
fetch_path("check", "required_bins") || ReadinessCheck::REQUIRED_BINS
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def check_root_required_files
|
|
102
|
+
fetch_path("check", "root_required_files") || []
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def check_member_required_dirs
|
|
106
|
+
fetch_path("check", "member_required_dirs") || []
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def check_forbidden_tracked_member_dirs
|
|
110
|
+
fetch_path("check", "forbidden_tracked_member_dirs") || []
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def check_forbidden_tracked_member_dirs_except
|
|
114
|
+
fetch_path("check", "forbidden_tracked_member_dirs_except") || []
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def check_readme_links
|
|
118
|
+
fetch_path("check", "readme_links") || {}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def changelog_mode
|
|
122
|
+
fetch_path("changelog", "mode") || "member"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def shared_changelog?
|
|
126
|
+
changelog_mode == "root"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def changelog_path
|
|
130
|
+
fetch_path("changelog", "path") || "CHANGELOG.md"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def changelog_version_file
|
|
134
|
+
fetch_path("changelog", "version_file")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def changelog_workdir(_member = nil)
|
|
138
|
+
shared_changelog? ? root : nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def changelog_full_path(member)
|
|
142
|
+
File.expand_path(changelog_path, shared_changelog? ? root : member.root)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def changelog_env
|
|
146
|
+
return {} unless changelog_version_file
|
|
147
|
+
|
|
148
|
+
{"K_CHANGELOG_VERSION_FILE" => changelog_version_file.to_s}
|
|
149
|
+
end
|
|
150
|
+
|
|
93
151
|
def template_command
|
|
94
152
|
fetch_path("template", "command") || command_for("template")
|
|
95
153
|
end
|
|
@@ -118,6 +176,18 @@ module Kettle
|
|
|
118
176
|
fetch_path("release", "publish_command") || command_for("release_publish") || "bundle exec kettle-release"
|
|
119
177
|
end
|
|
120
178
|
|
|
179
|
+
def release_env
|
|
180
|
+
stringify_env(fetch_path("release", "env") || {})
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def release_family_changelog?
|
|
184
|
+
fetch_path("release", "family_changelog", "enabled") == true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def release_family_changelog_command
|
|
188
|
+
fetch_path("release", "family_changelog", "command") || "bundle exec kettle-changelog"
|
|
189
|
+
end
|
|
190
|
+
|
|
121
191
|
def release_tag_command
|
|
122
192
|
fetch_path("release", "tag_command") || command_for("release_tag") || "git tag"
|
|
123
193
|
end
|
|
@@ -160,6 +230,10 @@ module Kettle
|
|
|
160
230
|
value
|
|
161
231
|
end
|
|
162
232
|
end
|
|
233
|
+
|
|
234
|
+
def stringify_env(value)
|
|
235
|
+
stringify_keys(value).to_h { |key, item| [key.to_s, item.to_s] }
|
|
236
|
+
end
|
|
163
237
|
end
|
|
164
238
|
end
|
|
165
239
|
end
|
|
@@ -6,28 +6,33 @@ module Kettle
|
|
|
6
6
|
REQUIRED_FILES = %w[Gemfile Rakefile README.md CHANGELOG.md LICENSE.md].freeze
|
|
7
7
|
REQUIRED_BINS = %w[bin/rake bin/rspec].freeze
|
|
8
8
|
|
|
9
|
-
def self.call(member:)
|
|
10
|
-
new(member: member).call
|
|
9
|
+
def self.call(member:, config: nil)
|
|
10
|
+
new(member: member, config: config).call
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def initialize(member:)
|
|
13
|
+
def initialize(member:, config: nil)
|
|
14
14
|
@member = member
|
|
15
|
+
@config = config
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def call
|
|
18
19
|
diagnostics = []
|
|
19
20
|
diagnostics.concat(missing_required_files)
|
|
20
21
|
diagnostics.concat(missing_required_bins)
|
|
22
|
+
diagnostics.concat(missing_root_required_files)
|
|
23
|
+
diagnostics.concat(missing_member_required_dirs)
|
|
24
|
+
diagnostics.concat(forbidden_tracked_member_dirs)
|
|
25
|
+
diagnostics.concat(missing_readme_links)
|
|
21
26
|
diagnostics.concat(local_path_lockfile_entries)
|
|
22
27
|
result(diagnostics)
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
private
|
|
26
31
|
|
|
27
|
-
attr_reader :member
|
|
32
|
+
attr_reader :member, :config
|
|
28
33
|
|
|
29
34
|
def missing_required_files
|
|
30
|
-
|
|
35
|
+
required_files.filter_map do |path|
|
|
31
36
|
next if File.file?(File.join(member.root, path))
|
|
32
37
|
|
|
33
38
|
"missing required file #{path}"
|
|
@@ -35,7 +40,7 @@ module Kettle
|
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
def missing_required_bins
|
|
38
|
-
|
|
43
|
+
required_bins.filter_map do |path|
|
|
39
44
|
full_path = File.join(member.root, path)
|
|
40
45
|
next if File.file?(full_path) && File.executable?(full_path)
|
|
41
46
|
|
|
@@ -43,6 +48,52 @@ module Kettle
|
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
|
|
51
|
+
def missing_root_required_files
|
|
52
|
+
return [] unless config
|
|
53
|
+
|
|
54
|
+
config.check_root_required_files.filter_map do |path|
|
|
55
|
+
next if File.file?(File.join(config.root, path))
|
|
56
|
+
|
|
57
|
+
"missing root required file #{path}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def missing_member_required_dirs
|
|
62
|
+
return [] unless config
|
|
63
|
+
|
|
64
|
+
config.check_member_required_dirs.filter_map do |path|
|
|
65
|
+
next if Dir.exist?(File.join(member.root, path))
|
|
66
|
+
|
|
67
|
+
"missing required directory #{path}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def forbidden_tracked_member_dirs
|
|
72
|
+
return [] unless config
|
|
73
|
+
return [] if config.check_forbidden_tracked_member_dirs_except.include?(member.name)
|
|
74
|
+
|
|
75
|
+
config.check_forbidden_tracked_member_dirs.filter_map do |path|
|
|
76
|
+
full_path = File.join(member.root, path)
|
|
77
|
+
next unless Dir.exist?(full_path) && tracked_path?(full_path)
|
|
78
|
+
|
|
79
|
+
"forbidden tracked directory #{path}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def missing_readme_links
|
|
84
|
+
return [] unless config
|
|
85
|
+
|
|
86
|
+
readme = File.join(member.root, "README.md")
|
|
87
|
+
return [] unless File.file?(readme)
|
|
88
|
+
|
|
89
|
+
content = File.read(readme)
|
|
90
|
+
config.check_readme_links.filter_map do |label, target|
|
|
91
|
+
next if content.include?("/#{target}") || content.include?("../../#{target}") || content.include?("../#{target}")
|
|
92
|
+
|
|
93
|
+
"README.md missing link to root #{label}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
46
97
|
def local_path_lockfile_entries
|
|
47
98
|
lockfile = File.join(member.root, "Gemfile.lock")
|
|
48
99
|
return [] unless File.file?(lockfile)
|
|
@@ -54,6 +105,21 @@ module Kettle
|
|
|
54
105
|
end
|
|
55
106
|
end
|
|
56
107
|
|
|
108
|
+
def required_files
|
|
109
|
+
config ? config.check_required_files : REQUIRED_FILES
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def required_bins
|
|
113
|
+
config ? config.check_required_bins : REQUIRED_BINS
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def tracked_path?(path)
|
|
117
|
+
return false unless config
|
|
118
|
+
|
|
119
|
+
relative = path.delete_prefix("#{config.root}/")
|
|
120
|
+
system("git", "-C", config.root, "ls-files", "--error-unmatch", relative, out: File::NULL, err: File::NULL)
|
|
121
|
+
end
|
|
122
|
+
|
|
57
123
|
def result(diagnostics)
|
|
58
124
|
CommandResult.new(
|
|
59
125
|
member_name: member.name,
|
|
@@ -16,6 +16,7 @@ module Kettle
|
|
|
16
16
|
|
|
17
17
|
def results
|
|
18
18
|
return branch_results unless release_target_branches.empty?
|
|
19
|
+
return [check_family_changelog] if shared_changelog?
|
|
19
20
|
|
|
20
21
|
members.map { |member| check_member(member) }
|
|
21
22
|
end
|
|
@@ -29,6 +30,11 @@ module Kettle
|
|
|
29
30
|
selected_names = members.map(&:name)
|
|
30
31
|
release_target_branches.each_with_object([]) do |branch, memo|
|
|
31
32
|
with_branch_worktree(root: root, branch: branch) do |worktree_root|
|
|
33
|
+
if shared_changelog?
|
|
34
|
+
memo << check_family_changelog(branch: branch, worktree_root: worktree_root)
|
|
35
|
+
next
|
|
36
|
+
end
|
|
37
|
+
|
|
32
38
|
branch_members = discover_branch_members(worktree_root: worktree_root, selected_names: selected_names)
|
|
33
39
|
memo.concat(branch_members.map { |member| check_member(member, branch: branch) })
|
|
34
40
|
end
|
|
@@ -40,7 +46,7 @@ module Kettle
|
|
|
40
46
|
def check_member(member, branch: nil)
|
|
41
47
|
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
42
48
|
command = release_state_command
|
|
43
|
-
stdout, stderr, status = Open3.capture3(*command, chdir: member
|
|
49
|
+
stdout, stderr, status = Open3.capture3(release_state_env, *command, chdir: release_state_workdir(member))
|
|
44
50
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
45
51
|
success = status.success?
|
|
46
52
|
state = success ? JSON.parse(stdout) : {}
|
|
@@ -53,6 +59,143 @@ module Kettle
|
|
|
53
59
|
[RbConfig.ruby, "-S", "kettle-changelog", "--release-state", "--json"]
|
|
54
60
|
end
|
|
55
61
|
|
|
62
|
+
def check_family_changelog(branch: nil, worktree_root: nil)
|
|
63
|
+
member = family_member(root: worktree_root ? branch_config_root(worktree_root) : config.root)
|
|
64
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
65
|
+
state = family_changelog_state(member.root)
|
|
66
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
67
|
+
result(
|
|
68
|
+
member: member,
|
|
69
|
+
command: ["internal", "release-state", "root-changelog"],
|
|
70
|
+
stdout: "",
|
|
71
|
+
stderr: "",
|
|
72
|
+
status: 0,
|
|
73
|
+
elapsed: elapsed,
|
|
74
|
+
success: true,
|
|
75
|
+
state: state,
|
|
76
|
+
branch: branch
|
|
77
|
+
)
|
|
78
|
+
rescue Error => error
|
|
79
|
+
result(
|
|
80
|
+
member: member,
|
|
81
|
+
command: ["internal", "release-state", "root-changelog"],
|
|
82
|
+
stdout: "",
|
|
83
|
+
stderr: error.message,
|
|
84
|
+
status: 1,
|
|
85
|
+
elapsed: 0.0,
|
|
86
|
+
success: false,
|
|
87
|
+
state: {},
|
|
88
|
+
reason: "release state check failed",
|
|
89
|
+
branch: branch
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def family_member(root:)
|
|
94
|
+
Member.new(
|
|
95
|
+
name: config.family_name,
|
|
96
|
+
root: root,
|
|
97
|
+
gemspec_path: nil,
|
|
98
|
+
version_file: nil,
|
|
99
|
+
version: nil,
|
|
100
|
+
dependencies: []
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def release_state_workdir(member)
|
|
105
|
+
return member.root unless config
|
|
106
|
+
return member.root if config.shared_changelog?
|
|
107
|
+
|
|
108
|
+
config.changelog_workdir(member) || member.root
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def release_state_env
|
|
112
|
+
config ? config.changelog_env : {}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def family_changelog_state(root)
|
|
116
|
+
changelog = File.expand_path(config.changelog_path, root)
|
|
117
|
+
raise Error, "missing root changelog #{config.changelog_path}" unless File.file?(changelog)
|
|
118
|
+
|
|
119
|
+
version = root_changelog_version(root)
|
|
120
|
+
content = File.read(changelog)
|
|
121
|
+
latest_changelog_version = latest_changelog_version(content)
|
|
122
|
+
unreleased_entries = unreleased_entries?(content)
|
|
123
|
+
prepared_release_pending = !version.to_s.empty? && latest_changelog_version == version
|
|
124
|
+
{
|
|
125
|
+
"gem_name" => config.family_name,
|
|
126
|
+
"version" => version,
|
|
127
|
+
"latest_released" => nil,
|
|
128
|
+
"latest_changelog_version" => latest_changelog_version,
|
|
129
|
+
"unreleased_entries" => unreleased_entries,
|
|
130
|
+
"prepared_release_pending" => prepared_release_pending,
|
|
131
|
+
"pending_release" => unreleased_entries || prepared_release_pending
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def root_changelog_version(root)
|
|
136
|
+
version_file = config.changelog_version_file
|
|
137
|
+
return nil unless version_file
|
|
138
|
+
|
|
139
|
+
path = File.expand_path(version_file, root)
|
|
140
|
+
raise Error, "missing changelog version file #{version_file}" unless File.file?(path)
|
|
141
|
+
|
|
142
|
+
version_string_node(File.read(path), path).unescaped
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def version_string_node(source, path)
|
|
146
|
+
require_prism
|
|
147
|
+
parse_result = Prism.parse(source)
|
|
148
|
+
raise Error, "could not parse #{path}" unless parse_result.success?
|
|
149
|
+
|
|
150
|
+
constant = each_node(parse_result.value).find do |node|
|
|
151
|
+
node.is_a?(Prism::ConstantWriteNode) && node.name == :VERSION && node.value.is_a?(Prism::StringNode)
|
|
152
|
+
end
|
|
153
|
+
raise Error, "could not find string VERSION constant in #{path}" unless constant
|
|
154
|
+
|
|
155
|
+
constant.value
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def latest_changelog_version(content)
|
|
159
|
+
content.each_line.filter_map do |line|
|
|
160
|
+
match = line.match(/\A## \[([^\]]+)\]/)
|
|
161
|
+
next unless match
|
|
162
|
+
|
|
163
|
+
version = match[1]
|
|
164
|
+
next if version == "Unreleased"
|
|
165
|
+
|
|
166
|
+
version
|
|
167
|
+
end.first
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def unreleased_entries?(content)
|
|
171
|
+
lines = content.lines
|
|
172
|
+
start = lines.index { |line| line.start_with?("## [Unreleased]") }
|
|
173
|
+
return false unless start
|
|
174
|
+
|
|
175
|
+
following = lines[(start + 1)..] || []
|
|
176
|
+
block = following.take_while { |line| !line.start_with?("## [") }
|
|
177
|
+
block.any? { |line| line.match?(/\S/) && !line.match?(/\A###? /) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def require_prism
|
|
181
|
+
return if defined?(Prism)
|
|
182
|
+
|
|
183
|
+
require "prism"
|
|
184
|
+
rescue LoadError => error
|
|
185
|
+
raise Error, "root changelog release-state requires Prism; install the prism gem or run on a Ruby engine that provides it (#{error.message})"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def each_node(root)
|
|
189
|
+
return enum_for(__method__, root) unless block_given?
|
|
190
|
+
|
|
191
|
+
queue = [root]
|
|
192
|
+
until queue.empty?
|
|
193
|
+
node = queue.shift
|
|
194
|
+
yield node
|
|
195
|
+
queue.concat(node.child_nodes.compact) if node.respond_to?(:child_nodes)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
56
199
|
def result(member:, command:, stdout:, stderr:, status:, elapsed:, success:, state:, reason: nil, branch: nil)
|
|
57
200
|
ReleaseStateResult.new(
|
|
58
201
|
member_name: member.name,
|
|
@@ -91,6 +234,10 @@ module Kettle
|
|
|
91
234
|
config.release_target_branches
|
|
92
235
|
end
|
|
93
236
|
|
|
237
|
+
def shared_changelog?
|
|
238
|
+
config&.shared_changelog?
|
|
239
|
+
end
|
|
240
|
+
|
|
94
241
|
def git_root
|
|
95
242
|
stdout, stderr, status = Open3.capture3("git", "rev-parse", "--show-toplevel", chdir: config.root)
|
|
96
243
|
raise Error, "could not determine git root for #{config.root}: #{stderr}" unless status.success?
|
|
@@ -54,14 +54,14 @@ module Kettle
|
|
|
54
54
|
attr_reader :command, :config, :members, :execute, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :local_ci, :continue_ci_failures
|
|
55
55
|
|
|
56
56
|
def check_results
|
|
57
|
-
members.map { |member| ReadinessCheck.call(member: member) }
|
|
57
|
+
members.map { |member| ReadinessCheck.call(member: member, config: config) }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def release_results
|
|
61
61
|
prompt_for_gem_signing_password if execute && publish && gem_signing_required?
|
|
62
62
|
return branch_target_release_results unless config.release_target_branches.empty?
|
|
63
63
|
|
|
64
|
-
release_member_results(members)
|
|
64
|
+
release_member_results(members, include_family_changelog: true)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def branch_target_release_results
|
|
@@ -72,14 +72,18 @@ module Kettle
|
|
|
72
72
|
break memo unless memo.last.ok?
|
|
73
73
|
|
|
74
74
|
branch_members = rediscovered_selected_members(selected_names)
|
|
75
|
-
memo.concat(release_member_results(branch_members))
|
|
75
|
+
memo.concat(release_member_results(branch_members, include_family_changelog: true))
|
|
76
76
|
break memo unless memo.last&.ok?
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def release_member_results(release_members)
|
|
80
|
+
def release_member_results(release_members, include_family_changelog: false)
|
|
81
81
|
runner = command_runner
|
|
82
|
-
|
|
82
|
+
results = []
|
|
83
|
+
append_family_changelog_result(runner: runner, memo: results) if include_family_changelog
|
|
84
|
+
return results unless results.all?(&:ok?)
|
|
85
|
+
|
|
86
|
+
release_members.each_with_object(results) do |member, memo|
|
|
83
87
|
if skip_already_released?(member)
|
|
84
88
|
memo << already_released_result(member)
|
|
85
89
|
next
|
|
@@ -121,8 +125,19 @@ module Kettle
|
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def append_release_internal_checks(member:, memo:)
|
|
124
|
-
memo << ReadinessCheck.call(member: member)
|
|
125
|
-
memo << ChangelogCheck.call(member: member) if memo.last.ok?
|
|
128
|
+
memo << ReadinessCheck.call(member: member, config: config)
|
|
129
|
+
memo << ChangelogCheck.call(member: member, config: config) if memo.last.ok?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def append_family_changelog_result(runner:, memo:)
|
|
133
|
+
return unless config.release_family_changelog?
|
|
134
|
+
|
|
135
|
+
memo << runner.call(
|
|
136
|
+
member: family_member,
|
|
137
|
+
phase: "family_changelog",
|
|
138
|
+
command: config.release_family_changelog_command,
|
|
139
|
+
env: release_env.merge(config.changelog_env)
|
|
140
|
+
)
|
|
126
141
|
end
|
|
127
142
|
|
|
128
143
|
def release_phase
|
|
@@ -155,7 +170,9 @@ module Kettle
|
|
|
155
170
|
end
|
|
156
171
|
|
|
157
172
|
def release_env
|
|
158
|
-
|
|
173
|
+
env = config.release_env.dup
|
|
174
|
+
env["K_RELEASE_CI_CONTINUE"] = "true" if continue_ci_failures
|
|
175
|
+
env
|
|
159
176
|
end
|
|
160
177
|
|
|
161
178
|
def append_release_git_phases(member:, runner:, memo:)
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-family
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -60,7 +60,7 @@ dependencies:
|
|
|
60
60
|
version: '1.1'
|
|
61
61
|
- - ">="
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
|
-
version: 1.1.
|
|
63
|
+
version: 1.1.12
|
|
64
64
|
type: :runtime
|
|
65
65
|
prerelease: false
|
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -70,7 +70,7 @@ dependencies:
|
|
|
70
70
|
version: '1.1'
|
|
71
71
|
- - ">="
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 1.1.
|
|
73
|
+
version: 1.1.12
|
|
74
74
|
- !ruby/object:Gem::Dependency
|
|
75
75
|
name: kettle-dev
|
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,7 +80,7 @@ dependencies:
|
|
|
80
80
|
version: '2.2'
|
|
81
81
|
- - ">="
|
|
82
82
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: 2.2.
|
|
83
|
+
version: 2.2.9
|
|
84
84
|
type: :development
|
|
85
85
|
prerelease: false
|
|
86
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -90,7 +90,7 @@ dependencies:
|
|
|
90
90
|
version: '2.2'
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 2.2.
|
|
93
|
+
version: 2.2.9
|
|
94
94
|
- !ruby/object:Gem::Dependency
|
|
95
95
|
name: bundler-audit
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -188,7 +188,7 @@ dependencies:
|
|
|
188
188
|
version: '3.1'
|
|
189
189
|
- - ">="
|
|
190
190
|
- !ruby/object:Gem::Version
|
|
191
|
-
version: 3.1.
|
|
191
|
+
version: 3.1.3
|
|
192
192
|
type: :development
|
|
193
193
|
prerelease: false
|
|
194
194
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -198,7 +198,7 @@ dependencies:
|
|
|
198
198
|
version: '3.1'
|
|
199
199
|
- - ">="
|
|
200
200
|
- !ruby/object:Gem::Version
|
|
201
|
-
version: 3.1.
|
|
201
|
+
version: 3.1.3
|
|
202
202
|
- !ruby/object:Gem::Dependency
|
|
203
203
|
name: ruby-progressbar
|
|
204
204
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -242,7 +242,7 @@ dependencies:
|
|
|
242
242
|
version: '2.0'
|
|
243
243
|
- - ">="
|
|
244
244
|
- !ruby/object:Gem::Version
|
|
245
|
-
version: 2.0.
|
|
245
|
+
version: 2.0.2
|
|
246
246
|
type: :development
|
|
247
247
|
prerelease: false
|
|
248
248
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -252,7 +252,7 @@ dependencies:
|
|
|
252
252
|
version: '2.0'
|
|
253
253
|
- - ">="
|
|
254
254
|
- !ruby/object:Gem::Version
|
|
255
|
-
version: 2.0.
|
|
255
|
+
version: 2.0.2
|
|
256
256
|
description: "\U0001F469\U0001F469\U0001F467\U0001F467 Kettle::Family provides
|
|
257
257
|
scripts and conventions for coordinating related Ruby gems as one family."
|
|
258
258
|
email:
|
|
@@ -308,10 +308,10 @@ licenses:
|
|
|
308
308
|
- AGPL-3.0-only
|
|
309
309
|
metadata:
|
|
310
310
|
homepage_uri: https://kettle-family.galtzo.com
|
|
311
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.
|
|
312
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.
|
|
311
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.4
|
|
312
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.4/CHANGELOG.md
|
|
313
313
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
314
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.
|
|
314
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.4
|
|
315
315
|
funding_uri: https://github.com/sponsors/pboling
|
|
316
316
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
317
317
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|