kettle-family 0.1.31 → 0.1.32
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 +28 -1
- data/README.md +1 -1
- data/lib/kettle/family/cli.rb +469 -201
- data/lib/kettle/family/report.rb +132 -1
- data/lib/kettle/family/selection.rb +12 -1
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +7 -0
- data.tar.gz.sig +0 -0
- metadata +32 -4
- 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: ab7db3c6a2cfe0e5c9456e14e0d7b643ee341da154f591c5de6b81d09a423a65
|
|
4
|
+
data.tar.gz: 9b1b9ecbc828efccfc1d51327a9a4efe7b69b2726500cabdca5943c6ca742f4e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e95a38962f4f0f4dada8de8adf59e9c2910ef9c7984df519f474fda1167e86523a6bb54d87382da9dd7e231bbd44df08955fed36e916aeb28acf078354cfc28
|
|
7
|
+
data.tar.gz: 28c0f4e0592ef36165f5a4bfe62a82fb8c13c8ee2ff5a22ad28dddad84a4b5003a84d5272f84eeb936d920f2fc2a135d426e54ca8d0b48a84203b6e2cefc33d9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,31 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [0.1.32] - 2026-07-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v0.1.32][0.1.32t]
|
|
36
|
+
- COVERAGE: 95.46% -- 2227/2333 lines in 21 files
|
|
37
|
+
- BRANCH COVERAGE: 75.96% -- 714/940 branches in 21 files
|
|
38
|
+
- 29.82% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `kettle-family` now supports `--exclude` anywhere member selection is
|
|
43
|
+
available, selecting all members except the comma-separated exclusions.
|
|
44
|
+
- `kettle-family` now uses command-specific option parsing and help powered by
|
|
45
|
+
`command_kit`, keeping naked help focused on global options.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- `kettle-family` reports a final summary for every command, including selected
|
|
50
|
+
release members left pending when parallel release execution stops after a
|
|
51
|
+
failure.
|
|
52
|
+
|
|
53
|
+
- `kettle-family release --execute` runs release members sequentially on
|
|
54
|
+
TruffleRuby to avoid a TruffleRuby 24.2 internal `ENV.replace` crash from
|
|
55
|
+
`Bundler.with_unbundled_env` inside parallel release threads
|
|
56
|
+
([truffleruby/truffleruby#4352](https://github.com/truffleruby/truffleruby/issues/4352)).
|
|
57
|
+
|
|
33
58
|
## [0.1.31] - 2026-06-30
|
|
34
59
|
|
|
35
60
|
- TAG: [v0.1.31][0.1.31t]
|
|
@@ -547,7 +572,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
547
572
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
548
573
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
549
574
|
|
|
550
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.
|
|
575
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.32...HEAD
|
|
576
|
+
[0.1.32]: https://github.com/kettle-dev/kettle-family/compare/v0.1.31...v0.1.32
|
|
577
|
+
[0.1.32t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.32
|
|
551
578
|
[0.1.31]: https://github.com/kettle-dev/kettle-family/compare/v0.1.30...v0.1.31
|
|
552
579
|
[0.1.31t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.31
|
|
553
580
|
[0.1.30]: https://github.com/kettle-dev/kettle-family/compare/v0.1.29...v0.1.30
|
data/README.md
CHANGED
|
@@ -592,7 +592,7 @@ Thanks for RTFM. ☺️
|
|
|
592
592
|
[📌gitmoji]: https://gitmoji.dev
|
|
593
593
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
594
594
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
595
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.
|
|
595
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.333-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
596
596
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
597
597
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
598
598
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -1,192 +1,498 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "command_kit"
|
|
4
|
+
require "command_kit/commands"
|
|
3
5
|
require "fileutils"
|
|
4
6
|
require "optparse"
|
|
5
7
|
|
|
6
8
|
module Kettle
|
|
7
9
|
module Family
|
|
8
|
-
class CLI
|
|
10
|
+
class CLI < CommandKit::Command
|
|
11
|
+
include CommandKit::Commands
|
|
12
|
+
|
|
9
13
|
COMMANDS = %w[discover plan report metadata check test lint docs template gha-sha-pins bup bupb bex install bump-version add-changelog release push pull up branch-lanes release-state].freeze
|
|
10
14
|
WORKFLOW_COMMANDS = %w[check test lint docs template gha-sha-pins bup bupb bex release push pull up].freeze
|
|
11
15
|
|
|
16
|
+
command_name "kettle-family"
|
|
17
|
+
usage "[options] COMMAND [ARGS...]"
|
|
18
|
+
description "Coordinate related Ruby gems as one family."
|
|
19
|
+
|
|
20
|
+
option :root, value: {type: String, usage: "PATH"}, desc: "Workspace or family root"
|
|
21
|
+
option :config, value: {type: String, usage: "PATH"}, desc: "Family config path"
|
|
22
|
+
option :json, desc: "Print JSON report to stdout"
|
|
23
|
+
option :report, value: {type: String, usage: "PATH"}, desc: "Write JSON report to PATH"
|
|
24
|
+
|
|
12
25
|
def self.call(argv, out: $stdout, err: $stderr)
|
|
13
|
-
|
|
26
|
+
main(argv, stdout: out, stderr: err)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module SharedOptions
|
|
30
|
+
def self.included(base)
|
|
31
|
+
base.option :root, value: {type: String, usage: "PATH"}, desc: "Workspace or family root"
|
|
32
|
+
base.option :config, value: {type: String, usage: "PATH"}, desc: "Family config path"
|
|
33
|
+
base.option :json, desc: "Print JSON report to stdout"
|
|
34
|
+
base.option :report, value: {type: String, usage: "PATH"}, desc: "Write JSON report to PATH"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module SelectionOptions
|
|
39
|
+
def self.included(base)
|
|
40
|
+
base.option :only, value: {type: String, usage: "MEMBERS"}, desc: "Select comma-separated members"
|
|
41
|
+
base.option :exclude, value: {type: String, usage: "MEMBERS"}, desc: "Exclude comma-separated members"
|
|
42
|
+
base.option :start_at, long: "--start-at", value: {type: String, usage: "MEMBER[@BRANCH]"}, desc: "Select from member through the end of order"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ExecutionOptions
|
|
47
|
+
def self.included(base)
|
|
48
|
+
base.option :execute, desc: "Execute external workflow commands"
|
|
49
|
+
base.option :dry_run, long: "--dry-run", desc: "Plan external workflow commands without running them" do
|
|
50
|
+
options[:execute] = false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module CommitOptions
|
|
56
|
+
def self.included(base)
|
|
57
|
+
base.option :commit, desc: "Allow workflow commands that change files to commit"
|
|
58
|
+
base.option :no_commit, long: "--no-commit", desc: "Skip automatic commits after mutating workflow commands" do
|
|
59
|
+
options[:commit] = false
|
|
60
|
+
end
|
|
61
|
+
base.option :allow_dirty, long: "--allow-dirty", desc: "Reserved for compatibility; member repos manage their own commit safety"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module WorkflowOptions
|
|
66
|
+
def self.included(base)
|
|
67
|
+
base.option :debug, desc: "Preserve debug environment for workflow commands"
|
|
68
|
+
base.option :jobs, value: {type: Integer, usage: "N"}, desc: "Parallel jobs for supported executed workflows"
|
|
69
|
+
base.option :env, value: {type: String, usage: "KEY=VALUE"}, desc: "Override an environment variable for each member workflow command" do |value|
|
|
70
|
+
parse_env_override(value, workflow_env)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module ReturningMain
|
|
76
|
+
def main(argv = [])
|
|
77
|
+
args = parse_options(argv)
|
|
78
|
+
return 1 unless valid_argument_count?(args)
|
|
79
|
+
|
|
80
|
+
run(*args)
|
|
81
|
+
rescue SystemExit => error
|
|
82
|
+
error.status
|
|
83
|
+
rescue Error, OptionParser::ParseError => error
|
|
84
|
+
stderr.puts("kettle-family: #{error.message}")
|
|
85
|
+
1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def valid_argument_count?(args)
|
|
91
|
+
required_args = self.class.arguments.each_value.count(&:required?)
|
|
92
|
+
optional_args = self.class.arguments.each_value.count(&:optional?)
|
|
93
|
+
has_repeats_arg = self.class.arguments.each_value.any?(&:repeats?)
|
|
94
|
+
return true if args.length >= required_args && (has_repeats_arg || args.length <= (required_args + optional_args))
|
|
95
|
+
|
|
96
|
+
message = if args.length < required_args
|
|
97
|
+
"insufficient number of arguments"
|
|
98
|
+
else
|
|
99
|
+
"unexpected argument(s): #{args[(required_args + optional_args)..].join(" ")}"
|
|
100
|
+
end
|
|
101
|
+
stderr.puts("kettle-family: #{message}")
|
|
102
|
+
help_usage
|
|
103
|
+
false
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_parse_error(error)
|
|
107
|
+
raise error
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class BaseCommand < CommandKit::Command
|
|
112
|
+
prepend ReturningMain
|
|
113
|
+
|
|
114
|
+
include SharedOptions
|
|
115
|
+
include SelectionOptions
|
|
116
|
+
|
|
117
|
+
def initialize(**kwargs)
|
|
118
|
+
super
|
|
119
|
+
@workflow_env = {}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
attr_reader :workflow_env
|
|
125
|
+
|
|
126
|
+
def family_options(overrides = {})
|
|
127
|
+
{
|
|
128
|
+
root: options[:root] || Dir.pwd,
|
|
129
|
+
config: options[:config],
|
|
130
|
+
only: options[:only],
|
|
131
|
+
exclude: options[:exclude],
|
|
132
|
+
start_at: options[:start_at],
|
|
133
|
+
json: truthy_option?(:json),
|
|
134
|
+
report: options[:report],
|
|
135
|
+
execute: truthy_option?(:execute),
|
|
136
|
+
debug: truthy_option?(:debug),
|
|
137
|
+
jobs: options[:jobs],
|
|
138
|
+
workflow_env: workflow_env,
|
|
139
|
+
changelog_section: nil,
|
|
140
|
+
changelog_entry: nil,
|
|
141
|
+
check: truthy_option?(:check),
|
|
142
|
+
from_version: nil,
|
|
143
|
+
gha_sha_pins_upgrade: "patch",
|
|
144
|
+
publish: false,
|
|
145
|
+
release_start_step: nil,
|
|
146
|
+
release_skip_steps: nil,
|
|
147
|
+
release_local_ci: false,
|
|
148
|
+
release_continue_ci_failures: false,
|
|
149
|
+
accept: true,
|
|
150
|
+
tag: false,
|
|
151
|
+
push: false,
|
|
152
|
+
commit: !options.key?(:commit) || options[:commit],
|
|
153
|
+
allow_dirty: truthy_option?(:allow_dirty),
|
|
154
|
+
target_version: nil,
|
|
155
|
+
bup_args: [],
|
|
156
|
+
bex_args: []
|
|
157
|
+
}.merge(overrides)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def run_family(command, overrides = {})
|
|
161
|
+
Kettle::Family::CLI.new(stdout: stdout, stderr: stderr).run_command(command, family_options(overrides))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def truthy_option?(name)
|
|
165
|
+
options.key?(name) && !!options[name]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def parse_env_override(value, env)
|
|
169
|
+
key, env_value = value.split("=", 2)
|
|
170
|
+
raise OptionParser::InvalidArgument, "--env requires KEY=VALUE" if key.to_s.empty? || env_value.nil?
|
|
171
|
+
raise OptionParser::InvalidArgument, "invalid environment variable name #{key.inspect}" unless key.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
|
|
172
|
+
|
|
173
|
+
env[key] = env_value
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def parse_gha_sha_pins_upgrade(value)
|
|
177
|
+
normalized = value.to_s.downcase
|
|
178
|
+
return normalized if %w[major minor patch].include?(normalized)
|
|
179
|
+
|
|
180
|
+
raise OptionParser::InvalidArgument, "--upgrade must be one of: major, minor, patch"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def unexpected_arguments!(args)
|
|
184
|
+
raise OptionParser::InvalidArgument, "unexpected argument(s): #{args.join(" ")}" unless args.empty?
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Discover < BaseCommand
|
|
189
|
+
command_name "discover"
|
|
190
|
+
usage "[options]"
|
|
191
|
+
description "Discover family members and print selected order."
|
|
192
|
+
|
|
193
|
+
def run(*args)
|
|
194
|
+
unexpected_arguments!(args)
|
|
195
|
+
run_family("discover")
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
class Plan < Discover
|
|
200
|
+
command_name "plan"
|
|
201
|
+
description "Alias for discover while execution workflows are built."
|
|
202
|
+
|
|
203
|
+
def run(*args)
|
|
204
|
+
unexpected_arguments!(args)
|
|
205
|
+
run_family("plan")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class ReportCommand < Discover
|
|
210
|
+
command_name "report"
|
|
211
|
+
description "Print family discovery and configuration report."
|
|
212
|
+
|
|
213
|
+
def run(*args)
|
|
214
|
+
unexpected_arguments!(args)
|
|
215
|
+
run_family("report")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class Metadata < BaseCommand
|
|
220
|
+
command_name "metadata"
|
|
221
|
+
usage "[options]"
|
|
222
|
+
description "Print version, Ruby floor, license, and author metadata."
|
|
223
|
+
|
|
224
|
+
def run(*args)
|
|
225
|
+
unexpected_arguments!(args)
|
|
226
|
+
run_family("metadata")
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class BranchLanes < BaseCommand
|
|
231
|
+
command_name "branch-lanes"
|
|
232
|
+
usage "[options]"
|
|
233
|
+
description "Audit configured branch lanes."
|
|
234
|
+
|
|
235
|
+
def run(*args)
|
|
236
|
+
unexpected_arguments!(args)
|
|
237
|
+
run_family("branch-lanes")
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class ReleaseState < BaseCommand
|
|
242
|
+
command_name "release-state"
|
|
243
|
+
usage "[options]"
|
|
244
|
+
description "Report changelog release state for family members."
|
|
245
|
+
|
|
246
|
+
def run(*args)
|
|
247
|
+
unexpected_arguments!(args)
|
|
248
|
+
run_family("release-state")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class WorkflowCommand < BaseCommand
|
|
253
|
+
include ExecutionOptions
|
|
254
|
+
include WorkflowOptions
|
|
255
|
+
include CommitOptions
|
|
256
|
+
|
|
257
|
+
def run(*args)
|
|
258
|
+
unexpected_arguments!(args)
|
|
259
|
+
run_family(self.class.command_name)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
class Check < WorkflowCommand
|
|
264
|
+
command_name "check"
|
|
265
|
+
usage "[options]"
|
|
266
|
+
description "Run internal read-only readiness checks."
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class Test < WorkflowCommand
|
|
270
|
+
command_name "test"
|
|
271
|
+
usage "[options]"
|
|
272
|
+
description "Plan or execute configured test command per member."
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
class Lint < WorkflowCommand
|
|
276
|
+
command_name "lint"
|
|
277
|
+
usage "[options]"
|
|
278
|
+
description "Plan or execute configured lint command per member."
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class Docs < WorkflowCommand
|
|
282
|
+
command_name "docs"
|
|
283
|
+
usage "[options]"
|
|
284
|
+
description "Plan or execute configured docs command per member."
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
class Template < WorkflowCommand
|
|
288
|
+
command_name "template"
|
|
289
|
+
usage "[options]"
|
|
290
|
+
description "Plan or execute kettle-jem templating per member."
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
class GhaShaPins < WorkflowCommand
|
|
294
|
+
command_name "gha-sha-pins"
|
|
295
|
+
usage "[options]"
|
|
296
|
+
description "Plan or execute kettle-gha-sha-pins per member."
|
|
297
|
+
|
|
298
|
+
option :check, desc: "Check whether SHA pins would need edits"
|
|
299
|
+
option :upgrade, value: {type: String, usage: "LEVEL"}, desc: "SHA pin upgrade strategy: major, minor, patch" do |value|
|
|
300
|
+
options[:upgrade] = parse_gha_sha_pins_upgrade(value)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def run(*args)
|
|
304
|
+
unexpected_arguments!(args)
|
|
305
|
+
run_family("gha-sha-pins", gha_sha_pins_upgrade: options[:upgrade] || "patch")
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
class Bup < WorkflowCommand
|
|
310
|
+
command_name "bup"
|
|
311
|
+
usage "[options] [GEM]"
|
|
312
|
+
description "Plan or execute bundle update --all, or bundle update GEM."
|
|
313
|
+
argument :gems, required: false, repeats: true, usage: "GEM", desc: "Gem name(s) to update"
|
|
314
|
+
|
|
315
|
+
def run(*bup_args)
|
|
316
|
+
run_family("bup", bup_args: bup_args)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
class Bupb < WorkflowCommand
|
|
321
|
+
command_name "bupb"
|
|
322
|
+
usage "[options]"
|
|
323
|
+
description "Plan or execute bundle update --bundler."
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
class Bex < WorkflowCommand
|
|
327
|
+
command_name "bex"
|
|
328
|
+
usage "[options] -- COMMAND [ARGS...]"
|
|
329
|
+
description "Plan or execute bundle exec COMMAND per member."
|
|
330
|
+
argument :command, required: false, repeats: true, usage: "COMMAND [ARGS...]", desc: "Command and arguments to run through bundle exec"
|
|
331
|
+
|
|
332
|
+
def run(*bex_args)
|
|
333
|
+
raise Error, "bex requires COMMAND [ARGS]" if bex_args.empty?
|
|
334
|
+
|
|
335
|
+
run_family("bex", bex_args: bex_args)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
class Install < BaseCommand
|
|
340
|
+
include ExecutionOptions
|
|
341
|
+
|
|
342
|
+
command_name "install"
|
|
343
|
+
usage "[options]"
|
|
344
|
+
description "Build and install selected local family gems."
|
|
345
|
+
|
|
346
|
+
option :jobs, value: {type: Integer, usage: "N"}, desc: "Parallel jobs for executed installs"
|
|
347
|
+
|
|
348
|
+
def run(*args)
|
|
349
|
+
unexpected_arguments!(args)
|
|
350
|
+
run_family("install")
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
class BumpVersion < BaseCommand
|
|
355
|
+
include ExecutionOptions
|
|
356
|
+
include CommitOptions
|
|
357
|
+
|
|
358
|
+
command_name "bump-version"
|
|
359
|
+
usage "[options] VERSION|major|minor|patch|pre"
|
|
360
|
+
description "Check, plan, or execute family version alignment."
|
|
361
|
+
argument :target_version, required: false, usage: "VERSION|major|minor|patch|pre", desc: "Version or bump target"
|
|
362
|
+
|
|
363
|
+
option :check, desc: "Check whether version bumps would need edits"
|
|
364
|
+
option :from, value: {type: String, usage: "VERSION"}, desc: "Require selected members to currently match VERSION"
|
|
365
|
+
|
|
366
|
+
def run(target_version = nil)
|
|
367
|
+
raise Error, "bump-version requires VERSION, major, minor, patch, or pre" unless target_version
|
|
368
|
+
|
|
369
|
+
run_family("bump-version", target_version: target_version, from_version: options[:from])
|
|
370
|
+
end
|
|
14
371
|
end
|
|
15
372
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
373
|
+
class AddChangelog < BaseCommand
|
|
374
|
+
include ExecutionOptions
|
|
375
|
+
|
|
376
|
+
command_name "add-changelog"
|
|
377
|
+
usage "[options]"
|
|
378
|
+
description "Add an entry to an existing Unreleased changelog section."
|
|
379
|
+
|
|
380
|
+
option :section, value: {type: String, usage: "NAME"}, desc: "Changelog section"
|
|
381
|
+
option :entry, value: {type: String, usage: "TEXT"}, desc: "Changelog entry"
|
|
382
|
+
|
|
383
|
+
def run(*args)
|
|
384
|
+
unexpected_arguments!(args)
|
|
385
|
+
run_family("add-changelog", changelog_section: options[:section], changelog_entry: options[:entry])
|
|
386
|
+
end
|
|
20
387
|
end
|
|
21
388
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
389
|
+
class Release < BaseCommand
|
|
390
|
+
include ExecutionOptions
|
|
391
|
+
include WorkflowOptions
|
|
392
|
+
include CommitOptions
|
|
25
393
|
|
|
26
|
-
|
|
394
|
+
command_name "release"
|
|
395
|
+
usage "[options]"
|
|
396
|
+
description "Plan or execute release build/publish phases."
|
|
27
397
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
398
|
+
option :publish, desc: "Use publish release command instead of build command"
|
|
399
|
+
option :build_only, long: "--build-only", desc: "Use build release command" do
|
|
400
|
+
options[:publish] = false
|
|
401
|
+
end
|
|
402
|
+
option :start_step, long: "--start-step", value: {type: Integer, usage: "N"}, desc: "Pass start_step=N through to kettle-release commands"
|
|
403
|
+
option :skip_steps, long: "--skip-steps", value: {type: String, usage: "LIST"}, desc: "Pass skip_steps=LIST through to kettle-release commands"
|
|
404
|
+
option :local_ci, long: "--local-ci", desc: "Pass --local-ci through to kettle-release commands"
|
|
405
|
+
option :continue_ci_failures, long: "--continue-ci-failures", desc: "Set K_RELEASE_CI_CONTINUE=true for release commands"
|
|
406
|
+
option :accept, desc: "Answer yes to confirmation prompts in interactive commands"
|
|
407
|
+
option :no_accept, long: "--no-accept", desc: "Wait for user input at confirmation prompts" do
|
|
408
|
+
options[:accept] = false
|
|
409
|
+
end
|
|
410
|
+
option :tag, desc: "Add release tag phase"
|
|
411
|
+
option :push, desc: "Add release push phase"
|
|
412
|
+
|
|
413
|
+
def run(*args)
|
|
414
|
+
unexpected_arguments!(args)
|
|
415
|
+
run_family(
|
|
416
|
+
"release",
|
|
417
|
+
publish: truthy_option?(:publish),
|
|
418
|
+
release_start_step: options[:start_step],
|
|
419
|
+
release_skip_steps: options[:skip_steps],
|
|
420
|
+
release_local_ci: truthy_option?(:local_ci),
|
|
421
|
+
release_continue_ci_failures: truthy_option?(:continue_ci_failures),
|
|
422
|
+
accept: !options.key?(:accept) || options[:accept],
|
|
423
|
+
tag: truthy_option?(:tag),
|
|
424
|
+
push: truthy_option?(:push)
|
|
425
|
+
)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
32
428
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
429
|
+
class Push < WorkflowCommand
|
|
430
|
+
command_name "push"
|
|
431
|
+
usage "[options]"
|
|
432
|
+
description "Plan or execute git push per member."
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
class Pull < WorkflowCommand
|
|
436
|
+
command_name "pull"
|
|
437
|
+
usage "[options]"
|
|
438
|
+
description "Plan or execute git pull --rebase per member."
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
class Up < WorkflowCommand
|
|
442
|
+
command_name "up"
|
|
443
|
+
usage "[options]"
|
|
444
|
+
description "Plan or execute git pull --rebase then git push per member."
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
command Discover
|
|
448
|
+
command Plan
|
|
449
|
+
command "report", ReportCommand
|
|
450
|
+
command Metadata
|
|
451
|
+
command Check
|
|
452
|
+
command Test
|
|
453
|
+
command Lint
|
|
454
|
+
command Docs
|
|
455
|
+
command Template
|
|
456
|
+
command GhaShaPins
|
|
457
|
+
command Bup
|
|
458
|
+
command Bupb
|
|
459
|
+
command Bex
|
|
460
|
+
command Install
|
|
461
|
+
command BumpVersion
|
|
462
|
+
command AddChangelog
|
|
463
|
+
command Release
|
|
464
|
+
command Push
|
|
465
|
+
command Pull
|
|
466
|
+
command Up
|
|
467
|
+
command BranchLanes
|
|
468
|
+
command ReleaseState
|
|
469
|
+
|
|
470
|
+
prepend ReturningMain
|
|
471
|
+
|
|
472
|
+
def run(command = nil, *argv)
|
|
473
|
+
return invoke(command, *argv) if command
|
|
474
|
+
|
|
475
|
+
help
|
|
476
|
+
0
|
|
477
|
+
end
|
|
36
478
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
479
|
+
def on_unknown_command(name, _argv = [])
|
|
480
|
+
stderr.puts("kettle-family: unknown command #{name.inspect}")
|
|
481
|
+
1
|
|
482
|
+
end
|
|
41
483
|
|
|
484
|
+
def run_command(command, options)
|
|
42
485
|
report = build_report(command, options)
|
|
43
486
|
write_report(report, options)
|
|
44
|
-
|
|
487
|
+
stdout.puts(options[:json] ? report.to_json : report.to_text)
|
|
45
488
|
report.success? ? 0 : 1
|
|
46
489
|
rescue Error, OptionParser::ParseError => error
|
|
47
|
-
|
|
490
|
+
stderr.puts("kettle-family: #{error.message}")
|
|
48
491
|
1
|
|
49
492
|
end
|
|
50
493
|
|
|
51
494
|
private
|
|
52
495
|
|
|
53
|
-
attr_reader :argv, :out, :err
|
|
54
|
-
|
|
55
|
-
def help
|
|
56
|
-
out.puts(<<~HELP)
|
|
57
|
-
kettle-family: #{Kettle::Family::VERSION}
|
|
58
|
-
|
|
59
|
-
Usage: kettle-family COMMAND [options]
|
|
60
|
-
kettle-family bump-version VERSION|major|minor|patch|pre [options]
|
|
61
|
-
kettle-family bup [GEM] [options]
|
|
62
|
-
kettle-family bex [options] -- COMMAND [ARGS]
|
|
63
|
-
|
|
64
|
-
Commands:
|
|
65
|
-
discover Discover family members and print selected order
|
|
66
|
-
plan Alias for discover while execution workflows are built
|
|
67
|
-
report Print family discovery and configuration report
|
|
68
|
-
metadata Print version, Ruby floor, license, and author metadata
|
|
69
|
-
check Run internal read-only readiness checks
|
|
70
|
-
test Plan or execute configured test command per member
|
|
71
|
-
lint Plan or execute configured lint command per member
|
|
72
|
-
docs Plan or execute configured docs command per member
|
|
73
|
-
template Plan or execute kettle-jem templating per member
|
|
74
|
-
gha-sha-pins Plan or execute kettle-gha-sha-pins per member
|
|
75
|
-
bup Plan or execute bundle update --all, or bundle update GEM
|
|
76
|
-
bupb Plan or execute bundle update --bundler
|
|
77
|
-
bex Plan or execute bundle exec COMMAND per member
|
|
78
|
-
install Build and install selected local family gems
|
|
79
|
-
bump-version Check, plan, or execute family version alignment
|
|
80
|
-
add-changelog Add an entry to an existing Unreleased changelog section
|
|
81
|
-
release Plan or execute release build/publish phases
|
|
82
|
-
push Plan or execute git push per member
|
|
83
|
-
pull Plan or execute git pull --rebase per member
|
|
84
|
-
up Plan or execute git pull --rebase then git push per member
|
|
85
|
-
release-state Report changelog release state for family members
|
|
86
|
-
|
|
87
|
-
Options:
|
|
88
|
-
--root PATH Workspace or family root (default: current directory)
|
|
89
|
-
--config PATH Family config path
|
|
90
|
-
--only MEMBERS Select comma-separated members
|
|
91
|
-
--start-at NAME Select from member through the end of order; use MEMBER@BRANCH for branch stacks
|
|
92
|
-
--json Print JSON report to stdout
|
|
93
|
-
--report PATH Write JSON report to PATH
|
|
94
|
-
--execute Execute external workflow commands
|
|
95
|
-
--dry-run Plan external workflow commands without running them (default)
|
|
96
|
-
--debug Preserve debug environment for workflow commands
|
|
97
|
-
--jobs N Parallel jobs for executed family templating, release, or install
|
|
98
|
-
--env KEY=VALUE Override an environment variable for each member workflow command
|
|
99
|
-
--section NAME Changelog section for add-changelog
|
|
100
|
-
--entry TEXT Changelog entry for add-changelog
|
|
101
|
-
--check Check whether bump-version or gha-sha-pins would need edits
|
|
102
|
-
--from VERSION Require selected members to currently match VERSION
|
|
103
|
-
--upgrade LEVEL GitHub Actions SHA pin upgrade strategy: major, minor, patch
|
|
104
|
-
--publish Use publish release command instead of build command
|
|
105
|
-
--build-only Use build release command (default)
|
|
106
|
-
--start-step N Pass start_step=N through to kettle-release commands
|
|
107
|
-
--skip-steps LIST Pass skip_steps=LIST through to kettle-release commands
|
|
108
|
-
--local-ci Pass --local-ci through to kettle-release commands
|
|
109
|
-
--continue-ci-failures
|
|
110
|
-
Set K_RELEASE_CI_CONTINUE=true for release commands
|
|
111
|
-
--accept Answer yes to confirmation prompts in interactive commands (default)
|
|
112
|
-
--no-accept Wait for user input at confirmation prompts
|
|
113
|
-
--tag Add release tag phase
|
|
114
|
-
--push Add release push phase
|
|
115
|
-
--commit Allow workflow commands that change files to commit (default)
|
|
116
|
-
--no-commit Skip automatic commits after mutating workflow commands
|
|
117
|
-
--allow-dirty Reserved for compatibility; member repos manage their own commit safety
|
|
118
|
-
--help Print this help
|
|
119
|
-
HELP
|
|
120
|
-
0
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def parse_options(allow_remainder: false)
|
|
124
|
-
options = {
|
|
125
|
-
root: Dir.pwd,
|
|
126
|
-
config: nil,
|
|
127
|
-
only: nil,
|
|
128
|
-
start_at: nil,
|
|
129
|
-
json: false,
|
|
130
|
-
report: nil,
|
|
131
|
-
execute: false,
|
|
132
|
-
debug: false,
|
|
133
|
-
jobs: nil,
|
|
134
|
-
workflow_env: {},
|
|
135
|
-
changelog_section: nil,
|
|
136
|
-
changelog_entry: nil,
|
|
137
|
-
check: false,
|
|
138
|
-
from_version: nil,
|
|
139
|
-
gha_sha_pins_upgrade: "patch",
|
|
140
|
-
publish: false,
|
|
141
|
-
release_start_step: nil,
|
|
142
|
-
release_skip_steps: nil,
|
|
143
|
-
release_local_ci: false,
|
|
144
|
-
release_continue_ci_failures: false,
|
|
145
|
-
accept: true,
|
|
146
|
-
tag: false,
|
|
147
|
-
push: false,
|
|
148
|
-
commit: true,
|
|
149
|
-
allow_dirty: false
|
|
150
|
-
}
|
|
151
|
-
OptionParser.new do |parser|
|
|
152
|
-
parser.on("--root PATH") { |value| options[:root] = value }
|
|
153
|
-
parser.on("--config PATH") { |value| options[:config] = value }
|
|
154
|
-
parser.on("--only MEMBER") { |value| options[:only] = value }
|
|
155
|
-
parser.on("--start-at MEMBER[@BRANCH]") { |value| options[:start_at] = value }
|
|
156
|
-
parser.on("--json") { options[:json] = true }
|
|
157
|
-
parser.on("--report PATH") { |value| options[:report] = value }
|
|
158
|
-
parser.on("--execute") { options[:execute] = true }
|
|
159
|
-
parser.on("--dry-run") { options[:execute] = false }
|
|
160
|
-
parser.on("--debug") { options[:debug] = true }
|
|
161
|
-
parser.on("--jobs N", Integer) { |value| options[:jobs] = value }
|
|
162
|
-
parser.on("--env KEY=VALUE") { |value| parse_env_override(value, options[:workflow_env]) }
|
|
163
|
-
parser.on("--section NAME") { |value| options[:changelog_section] = value }
|
|
164
|
-
parser.on("--entry TEXT") { |value| options[:changelog_entry] = value }
|
|
165
|
-
parser.on("--check") { options[:check] = true }
|
|
166
|
-
parser.on("--from VERSION") { |value| options[:from_version] = value }
|
|
167
|
-
parser.on("--upgrade LEVEL") { |value| options[:gha_sha_pins_upgrade] = parse_gha_sha_pins_upgrade(value) }
|
|
168
|
-
parser.on("--publish") { options[:publish] = true }
|
|
169
|
-
parser.on("--build-only") { options[:publish] = false }
|
|
170
|
-
parser.on("--start-step N", Integer) { |value| options[:release_start_step] = value }
|
|
171
|
-
parser.on("--skip-steps LIST") { |value| options[:release_skip_steps] = value }
|
|
172
|
-
parser.on("--local-ci") { options[:release_local_ci] = true }
|
|
173
|
-
parser.on("--continue-ci-failures") { options[:release_continue_ci_failures] = true }
|
|
174
|
-
parser.on("--accept") { options[:accept] = true }
|
|
175
|
-
parser.on("--no-accept") { options[:accept] = false }
|
|
176
|
-
parser.on("--tag") { options[:tag] = true }
|
|
177
|
-
parser.on("--push") { options[:push] = true }
|
|
178
|
-
parser.on("--commit") { options[:commit] = true }
|
|
179
|
-
parser.on("--no-commit") { options[:commit] = false }
|
|
180
|
-
parser.on("--allow-dirty") { options[:allow_dirty] = true }
|
|
181
|
-
parser.on("--help") { options[:help] = true }
|
|
182
|
-
end.parse!(argv)
|
|
183
|
-
return options if allow_remainder
|
|
184
|
-
|
|
185
|
-
raise OptionParser::InvalidArgument, "unexpected argument(s): #{argv.join(" ")}" unless argv.empty?
|
|
186
|
-
|
|
187
|
-
options
|
|
188
|
-
end
|
|
189
|
-
|
|
190
496
|
def build_report(command, options)
|
|
191
497
|
config = Config.load(root: options[:root], path: options[:config])
|
|
192
498
|
start_at = parse_start_at(options[:start_at])
|
|
@@ -198,12 +504,8 @@ module Kettle
|
|
|
198
504
|
else
|
|
199
505
|
Orderer.new(members: members, mode: config.order_mode, hints: config.order_hints).ordered
|
|
200
506
|
end
|
|
201
|
-
selected = Selection.new(members: ordered).apply(only: options[:only], start_at: start_at.member)
|
|
202
|
-
result_members =
|
|
203
|
-
ordered
|
|
204
|
-
else
|
|
205
|
-
selected
|
|
206
|
-
end
|
|
507
|
+
selected = Selection.new(members: ordered).apply(only: options[:only], exclude: options[:exclude], start_at: start_at.member)
|
|
508
|
+
result_members = selected
|
|
207
509
|
results = command_results(command: command, config: config, members: result_members, options: options, start_at: start_at)
|
|
208
510
|
Report.new(
|
|
209
511
|
family_name: config.family_name,
|
|
@@ -266,31 +568,12 @@ module Kettle
|
|
|
266
568
|
).results
|
|
267
569
|
end
|
|
268
570
|
|
|
269
|
-
def parse_bup_args(command)
|
|
270
|
-
return [] unless command == "bup"
|
|
271
|
-
|
|
272
|
-
args = []
|
|
273
|
-
args << argv.shift while argv.first && !argv.first.start_with?("-")
|
|
274
|
-
args
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def parse_bex_args_with_separator(command)
|
|
278
|
-
return [] unless command == "bex"
|
|
279
|
-
|
|
280
|
-
separator_index = argv.index("--")
|
|
281
|
-
return [] unless separator_index
|
|
282
|
-
|
|
283
|
-
args = argv[(separator_index + 1)..] || []
|
|
284
|
-
argv.slice!(separator_index..)
|
|
285
|
-
args
|
|
286
|
-
end
|
|
287
|
-
|
|
288
571
|
def progress_io(command, options)
|
|
289
572
|
return nil unless command == "template"
|
|
290
573
|
return nil unless options[:execute]
|
|
291
574
|
return nil if options[:json]
|
|
292
575
|
|
|
293
|
-
|
|
576
|
+
stdout
|
|
294
577
|
end
|
|
295
578
|
|
|
296
579
|
def branch_target_command?(command, config)
|
|
@@ -380,21 +663,6 @@ module Kettle
|
|
|
380
663
|
)
|
|
381
664
|
end
|
|
382
665
|
|
|
383
|
-
def parse_env_override(value, env)
|
|
384
|
-
key, env_value = value.split("=", 2)
|
|
385
|
-
raise OptionParser::InvalidArgument, "--env requires KEY=VALUE" if key.to_s.empty? || env_value.nil?
|
|
386
|
-
raise OptionParser::InvalidArgument, "invalid environment variable name #{key.inspect}" unless key.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
|
|
387
|
-
|
|
388
|
-
env[key] = env_value
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
def parse_gha_sha_pins_upgrade(value)
|
|
392
|
-
normalized = value.to_s.downcase
|
|
393
|
-
return normalized if %w[major minor patch].include?(normalized)
|
|
394
|
-
|
|
395
|
-
raise OptionParser::InvalidArgument, "--upgrade must be one of: major, minor, patch"
|
|
396
|
-
end
|
|
397
|
-
|
|
398
666
|
def parse_start_at(value)
|
|
399
667
|
return StartAt.new(nil, nil) unless value
|
|
400
668
|
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -5,6 +5,26 @@ require "json"
|
|
|
5
5
|
module Kettle
|
|
6
6
|
module Family
|
|
7
7
|
class Report
|
|
8
|
+
MEMBER_RESULT_COMMANDS = %w[
|
|
9
|
+
add-changelog
|
|
10
|
+
bex
|
|
11
|
+
bump-version
|
|
12
|
+
bup
|
|
13
|
+
bupb
|
|
14
|
+
check
|
|
15
|
+
docs
|
|
16
|
+
gha-sha-pins
|
|
17
|
+
install
|
|
18
|
+
lint
|
|
19
|
+
pull
|
|
20
|
+
push
|
|
21
|
+
release
|
|
22
|
+
release-state
|
|
23
|
+
template
|
|
24
|
+
test
|
|
25
|
+
up
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
8
28
|
attr_reader :family_name, :family_mode, :order_mode, :members, :selected_members, :config_path, :command, :results, :branch_lanes, :release_target_branches, :member_release_target_branches, :release_mode
|
|
9
29
|
|
|
10
30
|
def initialize(family_name:, order_mode:, members:, selected_members:, config_path:, family_mode: nil, branch_lanes: {}, release_target_branches: [], member_release_target_branches: {}, release_mode: nil, command: nil, results: [])
|
|
@@ -36,6 +56,7 @@ module Kettle
|
|
|
36
56
|
"release_mode" => release_mode,
|
|
37
57
|
"command" => command,
|
|
38
58
|
"results" => results.map(&:to_h),
|
|
59
|
+
"summary" => summary,
|
|
39
60
|
"resume_hint" => resume_hint
|
|
40
61
|
}
|
|
41
62
|
end
|
|
@@ -61,11 +82,12 @@ module Kettle
|
|
|
61
82
|
end
|
|
62
83
|
append_release_waves(lines)
|
|
63
84
|
append_results(lines)
|
|
85
|
+
append_summary(lines)
|
|
64
86
|
lines.join("\n")
|
|
65
87
|
end
|
|
66
88
|
|
|
67
89
|
def success?
|
|
68
|
-
results.all?(&:ok?)
|
|
90
|
+
results.all?(&:ok?) && summary_pending.empty?
|
|
69
91
|
end
|
|
70
92
|
|
|
71
93
|
private
|
|
@@ -88,6 +110,32 @@ module Kettle
|
|
|
88
110
|
append_template_summary(lines) if command == "template"
|
|
89
111
|
end
|
|
90
112
|
|
|
113
|
+
def append_summary(lines)
|
|
114
|
+
data = summary
|
|
115
|
+
lines << "summary:"
|
|
116
|
+
lines << " outcome: #{data.fetch("outcome")}"
|
|
117
|
+
lines << " selected: #{data.fetch("selected_count")}"
|
|
118
|
+
lines << " results: #{data.fetch("result_count")}"
|
|
119
|
+
lines << " succeeded: #{summary_list(data.fetch("succeeded"))}"
|
|
120
|
+
lines << " skipped: #{summary_list(data.fetch("skipped"))}"
|
|
121
|
+
lines << " failed: #{summary_list(data.fetch("failed").map { |entry| summary_entry(entry) })}"
|
|
122
|
+
lines << " pending: #{summary_list(data.fetch("pending").map { |entry| summary_entry(entry) })}"
|
|
123
|
+
lines << " resume: #{data.fetch("resume_hint")}" if data.fetch("resume_hint")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def summary
|
|
127
|
+
{
|
|
128
|
+
"outcome" => success? ? "success" : "failure",
|
|
129
|
+
"selected_count" => selected_members.length,
|
|
130
|
+
"result_count" => visible_results.length,
|
|
131
|
+
"succeeded" => summary_succeeded,
|
|
132
|
+
"skipped" => summary_skipped,
|
|
133
|
+
"failed" => summary_failed,
|
|
134
|
+
"pending" => summary_pending,
|
|
135
|
+
"resume_hint" => resume_hint
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
91
139
|
def append_release_waves(lines)
|
|
92
140
|
wave_results = results.select { |result| release_wave_result?(result) }
|
|
93
141
|
return if wave_results.empty?
|
|
@@ -102,6 +150,10 @@ module Kettle
|
|
|
102
150
|
result.phase == "release_wave"
|
|
103
151
|
end
|
|
104
152
|
|
|
153
|
+
def visible_results
|
|
154
|
+
results.reject { |result| release_wave_result?(result) }
|
|
155
|
+
end
|
|
156
|
+
|
|
105
157
|
def append_indented_output(lines, output)
|
|
106
158
|
output.to_s.each_line(chomp: true) { |line| lines << " #{line}" }
|
|
107
159
|
end
|
|
@@ -146,6 +198,85 @@ module Kettle
|
|
|
146
198
|
"failed"
|
|
147
199
|
end
|
|
148
200
|
|
|
201
|
+
def member_result_command?
|
|
202
|
+
MEMBER_RESULT_COMMANDS.include?(command)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def selected_names
|
|
206
|
+
selected_members.map(&:name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def selected_member_results
|
|
210
|
+
return {} unless member_result_command?
|
|
211
|
+
|
|
212
|
+
visible_results.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |result, memo|
|
|
213
|
+
next unless selected_names.include?(result.member_name)
|
|
214
|
+
|
|
215
|
+
memo[result.member_name] << result
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def summary_succeeded
|
|
220
|
+
selected_member_results.filter_map do |member_name, member_results|
|
|
221
|
+
next if member_results.empty?
|
|
222
|
+
next if member_results.any? { |result| !result.ok? }
|
|
223
|
+
next if member_results.all?(&:skipped)
|
|
224
|
+
|
|
225
|
+
member_name
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def summary_skipped
|
|
230
|
+
selected_member_results.filter_map do |member_name, member_results|
|
|
231
|
+
next if member_results.empty?
|
|
232
|
+
next unless member_results.all?(&:skipped)
|
|
233
|
+
|
|
234
|
+
member_name
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def summary_failed
|
|
239
|
+
visible_results.reject(&:ok?).map do |result|
|
|
240
|
+
{
|
|
241
|
+
"member" => result.member_name,
|
|
242
|
+
"phase" => result.phase,
|
|
243
|
+
"reason" => result.reason || "command failed"
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def summary_pending
|
|
249
|
+
return [] unless member_result_command?
|
|
250
|
+
return [] if visible_results.empty?
|
|
251
|
+
|
|
252
|
+
ran = selected_member_results.keys
|
|
253
|
+
reason = pending_reason
|
|
254
|
+
(selected_names - ran).map do |member_name|
|
|
255
|
+
{
|
|
256
|
+
"member" => member_name,
|
|
257
|
+
"phase" => command,
|
|
258
|
+
"reason" => reason
|
|
259
|
+
}
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def pending_reason
|
|
264
|
+
if visible_results.any? { |result| !result.ok? }
|
|
265
|
+
"not run after earlier failure"
|
|
266
|
+
else
|
|
267
|
+
"no command result recorded"
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def summary_list(values)
|
|
272
|
+
values.empty? ? "none" : values.join(", ")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def summary_entry(entry)
|
|
276
|
+
reason = entry.fetch("reason")
|
|
277
|
+
"#{entry.fetch("member")} #{entry.fetch("phase")} (#{reason})"
|
|
278
|
+
end
|
|
279
|
+
|
|
149
280
|
def append_metadata_results(lines)
|
|
150
281
|
lines << "metadata:"
|
|
151
282
|
rows = [["gem", "version", "ruby", "licenses", "authors"]]
|
|
@@ -7,9 +7,10 @@ module Kettle
|
|
|
7
7
|
@members = members
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def apply(only: nil, start_at: nil)
|
|
10
|
+
def apply(only: nil, exclude: nil, start_at: nil)
|
|
11
11
|
selected = members
|
|
12
12
|
selected = select_only(selected, only) if only
|
|
13
|
+
selected = select_exclude(selected, exclude) if exclude
|
|
13
14
|
selected = select_start_at(selected, start_at) if start_at
|
|
14
15
|
raise Error, "selection is empty" if selected.empty?
|
|
15
16
|
|
|
@@ -30,6 +31,16 @@ module Kettle
|
|
|
30
31
|
selected.select { |candidate| names.include?(candidate.name) }
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
def select_exclude(selected, exclude)
|
|
35
|
+
names = exclude.split(",").map(&:strip).reject(&:empty?)
|
|
36
|
+
raise Error, "--exclude requires at least one member" if names.empty?
|
|
37
|
+
|
|
38
|
+
unknown = names - members.map(&:name)
|
|
39
|
+
raise Error, "unknown member(s): #{unknown.join(", ")}" unless unknown.empty?
|
|
40
|
+
|
|
41
|
+
selected.reject { |candidate| names.include?(candidate.name) }
|
|
42
|
+
end
|
|
43
|
+
|
|
33
44
|
def select_start_at(selected, start_at)
|
|
34
45
|
index = selected.index { |candidate| candidate.name == start_at }
|
|
35
46
|
raise Error, "unknown member #{start_at.inspect}" unless index
|
|
@@ -394,11 +394,18 @@ module Kettle
|
|
|
394
394
|
end
|
|
395
395
|
|
|
396
396
|
def release_jobs(release_members)
|
|
397
|
+
# TruffleRuby issue: https://github.com/truffleruby/truffleruby/issues/4352
|
|
398
|
+
return 1 if truffleruby?
|
|
399
|
+
|
|
397
400
|
requested = jobs || config.release_jobs
|
|
398
401
|
count = requested ? requested.to_i : [Etc.nprocessors, 4].min
|
|
399
402
|
count.clamp(1, release_members.length)
|
|
400
403
|
end
|
|
401
404
|
|
|
405
|
+
def truffleruby?
|
|
406
|
+
RUBY_ENGINE == "truffleruby"
|
|
407
|
+
end
|
|
408
|
+
|
|
402
409
|
def release_waves(release_members)
|
|
403
410
|
by_name = release_members.to_h { |member| [member.name, member] }
|
|
404
411
|
pending = by_name.keys
|
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.32
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -37,6 +37,34 @@ cert_chain:
|
|
|
37
37
|
-----END CERTIFICATE-----
|
|
38
38
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
39
39
|
dependencies:
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: command_kit
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.6'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.6'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: command_kit-completion
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.1'
|
|
40
68
|
- !ruby/object:Gem::Dependency
|
|
41
69
|
name: tsort
|
|
42
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -311,10 +339,10 @@ licenses:
|
|
|
311
339
|
- AGPL-3.0-only
|
|
312
340
|
metadata:
|
|
313
341
|
homepage_uri: https://kettle-family.galtzo.com
|
|
314
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.
|
|
315
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.
|
|
342
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.32
|
|
343
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.32/CHANGELOG.md
|
|
316
344
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
317
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.
|
|
345
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.32
|
|
318
346
|
funding_uri: https://github.com/sponsors/pboling
|
|
319
347
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
320
348
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|