kettle-family 1.3.13 → 1.3.15
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 +31 -1
- data/README.md +1 -1
- data/lib/kettle/family/cli.rb +89 -1
- data/lib/kettle/family/mise_tasks.rb +259 -0
- data/lib/kettle/family/release_state_check.rb +1 -1
- data/lib/kettle/family/report.rb +7 -5
- data/lib/kettle/family/templates/mise-family.tasks.toml +11 -0
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +5 -1
- data/lib/kettle/family/workflow_progress.rb +28 -6
- data/lib/kettle/family.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +86 -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: e9c9f3d7625a5dae78bd27ceda81c4237775381bf4f16fcc2bedb5b419637248
|
|
4
|
+
data.tar.gz: 73d2e5ffe67938d6aee937f8db6e8be9d253dd65dd964e34b4270929769e1b94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c540e99bd58a29da64965936ded31a18b71558c5c1b0fdc6aa87198e8843373f2ecf0c14ce2b70c59cd4d03adb3a70fd6da52052054e61838412acb845f03f6
|
|
7
|
+
data.tar.gz: 2f9c08642978b28576845989cb362a9c9da1d6eed77f82c6aaf4be9a24bd916d4905be6c4079e529539a94af706caaa8ec6d4ada61d972affcfd446539720ec8
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,32 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.3.15] - 2026-10-18
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.3.15][1.3.15t]
|
|
36
|
+
- COVERAGE: 93.39% -- 7228/7740 lines in 44 files
|
|
37
|
+
- BRANCH COVERAGE: 77.69% -- 2883/3711 branches in 44 files
|
|
38
|
+
- 36.88% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Convert registered worktree owner PIDs to integers before checking process liveness on Ruby 4.
|
|
43
|
+
|
|
44
|
+
## [1.3.14] - 2026-10-17
|
|
45
|
+
|
|
46
|
+
- TAG: [v1.3.14][1.3.14t]
|
|
47
|
+
- COVERAGE: 93.36% -- 7226/7740 lines in 44 files
|
|
48
|
+
- BRANCH COVERAGE: 77.69% -- 2883/3711 branches in 44 files
|
|
49
|
+
- 36.88% documented
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- [kc] mise-tasks-command: kettle-family mise-tasks command — ensures the destination root mise.toml includes the kettle-family managed TOML task file ([task_config].includes), creates the managed file from the shipped baseline, warns on task redefinitions (redefinition allowed, root wins), removes duplicates with opt-in --clean, and registers the managed file as template-managed (keep_destination) in the destination kettle-jem config via psych-merge; backed by Kettle::Family::MiseTasks, ast-crispr-toml, and psych-merge
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- Release progress pane renders member lines in execution order with wave-number labels (e.g. 2.kettle-jem), and all report summary lists (succeeded/skipped/failed/pending) are now alphabetical regardless of parallel completion order
|
|
58
|
+
|
|
33
59
|
## [1.3.13] - 2026-10-17
|
|
34
60
|
|
|
35
61
|
- TAG: [v1.3.13][1.3.13t]
|
|
@@ -3038,7 +3064,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3038
3064
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
3039
3065
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
3040
3066
|
|
|
3041
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.
|
|
3067
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.15...HEAD
|
|
3068
|
+
[1.3.15]: https://github.com/kettle-dev/kettle-family/compare/v1.3.14...v1.3.15
|
|
3069
|
+
[1.3.15t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.15
|
|
3070
|
+
[1.3.14]: https://github.com/kettle-dev/kettle-family/compare/v1.3.13...v1.3.14
|
|
3071
|
+
[1.3.14t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.14
|
|
3042
3072
|
[1.3.13]: https://github.com/kettle-dev/kettle-family/compare/v1.3.12...v1.3.13
|
|
3043
3073
|
[1.3.13t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.13
|
|
3044
3074
|
[1.3.12]: https://github.com/kettle-dev/kettle-family/compare/v1.3.11...v1.3.12
|
data/README.md
CHANGED
|
@@ -1225,7 +1225,7 @@ Thanks for RTFM. ☺️
|
|
|
1225
1225
|
[📌gitmoji]: https://gitmoji.dev
|
|
1226
1226
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1227
1227
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1228
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.
|
|
1228
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.740-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1229
1229
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
1230
1230
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1231
1231
|
[📄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
|
@@ -10,7 +10,7 @@ module Kettle
|
|
|
10
10
|
class CLI < CommandKit::Command
|
|
11
11
|
include CommandKit::Commands
|
|
12
12
|
|
|
13
|
-
COMMANDS = %w[version mise-trust discover plan report metadata check clean-blockers clean-installed clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb update-bundler bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
|
|
13
|
+
COMMANDS = %w[version mise-trust mise-tasks discover plan report metadata check clean-blockers clean-installed clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb update-bundler bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
|
|
14
14
|
WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb update-bundler bex release push pull sync up].freeze
|
|
15
15
|
|
|
16
16
|
command_name "kettle-family"
|
|
@@ -264,6 +264,93 @@ module Kettle
|
|
|
264
264
|
end
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
+
class MiseTasks < BaseCommand
|
|
268
|
+
command_name "mise-tasks"
|
|
269
|
+
usage "[options]"
|
|
270
|
+
description "Ensure the root mise.toml includes the kettle-family managed task file; warn on redefinitions."
|
|
271
|
+
|
|
272
|
+
option :task_file, long: "--task-file", value: {type: String, usage: "PATH"},
|
|
273
|
+
desc: "Managed TOML task file name, relative to the root (default: mise-family.tasks.toml)"
|
|
274
|
+
option :clean, long: "--clean", desc: "Remove task definitions from the root mise.toml that duplicate the managed file (opt-in; default warns only)"
|
|
275
|
+
|
|
276
|
+
DEFAULT_TASK_FILE = "mise-family.tasks.toml"
|
|
277
|
+
|
|
278
|
+
def run(*args)
|
|
279
|
+
unexpected_arguments!(args)
|
|
280
|
+
require "fileutils"
|
|
281
|
+
root = File.expand_path(options[:root] || Dir.pwd)
|
|
282
|
+
task_file_name = options[:task_file] || DEFAULT_TASK_FILE
|
|
283
|
+
root_mise = File.join(root, "mise.toml")
|
|
284
|
+
task_file_path = File.join(root, task_file_name)
|
|
285
|
+
|
|
286
|
+
unless File.file?(root_mise)
|
|
287
|
+
stderr.puts("kettle-family: no mise.toml at #{root_mise}")
|
|
288
|
+
return 1
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
unless File.file?(task_file_path)
|
|
292
|
+
baseline = File.join(__dir__, "templates", "mise-family.tasks.toml")
|
|
293
|
+
FileUtils.cp(baseline, task_file_path)
|
|
294
|
+
stdout.puts("created #{task_file_name} from the kettle-family baseline")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
root_content = File.read(root_mise)
|
|
298
|
+
task_file_content = File.file?(task_file_path) ? File.read(task_file_path) : nil
|
|
299
|
+
|
|
300
|
+
result = if options[:clean]
|
|
301
|
+
Kettle::Family::MiseTasks.clean_redefinitions(
|
|
302
|
+
root_content: root_content,
|
|
303
|
+
task_file_content: task_file_content,
|
|
304
|
+
task_file_name: task_file_name
|
|
305
|
+
)
|
|
306
|
+
else
|
|
307
|
+
Kettle::Family::MiseTasks.ensure_include(
|
|
308
|
+
root_content: root_content,
|
|
309
|
+
task_file_name: task_file_name,
|
|
310
|
+
task_file_content: task_file_content
|
|
311
|
+
)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if result.changed
|
|
315
|
+
File.write(root_mise, result.content)
|
|
316
|
+
stdout.puts("updated mise.toml")
|
|
317
|
+
else
|
|
318
|
+
stdout.puts("mise.toml already up to date")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if result.redefinitions.empty?
|
|
322
|
+
stdout.puts("no task redefinitions")
|
|
323
|
+
else
|
|
324
|
+
result.redefinitions.each do |name|
|
|
325
|
+
winner = options[:clean] ? "managed task file" : "root mise.toml (redefinition allowed; use --clean to restore the managed definition)"
|
|
326
|
+
stderr.puts("kettle-family: task #{name.inspect} is defined in both mise.toml and #{task_file_name}; #{winner} wins")
|
|
327
|
+
end
|
|
328
|
+
return 1 if options[:clean] && !result.redefinitions.empty?
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Register the managed file in the destination's kettle-jem config
|
|
332
|
+
# when the project is kettle-jem-templated.
|
|
333
|
+
%w[.structuredmerge/kettle-jem.yml .kettle-jem.yml].each do |config_relative|
|
|
334
|
+
config_path = File.join(root, config_relative)
|
|
335
|
+
next unless File.file?(config_path)
|
|
336
|
+
|
|
337
|
+
registration = Kettle::Family::MiseTasks.register_in_jem_config(
|
|
338
|
+
config_content: File.read(config_path),
|
|
339
|
+
task_file_name: task_file_name
|
|
340
|
+
)
|
|
341
|
+
if registration.changed
|
|
342
|
+
File.write(config_path, registration.content)
|
|
343
|
+
stdout.puts("registered #{task_file_name} in #{config_relative}")
|
|
344
|
+
else
|
|
345
|
+
stdout.puts("#{task_file_name} already registered in #{config_relative}")
|
|
346
|
+
end
|
|
347
|
+
break
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
0
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
267
354
|
class Plan < Discover
|
|
268
355
|
command_name "plan"
|
|
269
356
|
description "Alias for discover while execution workflows are built."
|
|
@@ -659,6 +746,7 @@ module Kettle
|
|
|
659
746
|
command Discover
|
|
660
747
|
command Version
|
|
661
748
|
command MiseTrust
|
|
749
|
+
command MiseTasks
|
|
662
750
|
command Plan
|
|
663
751
|
command "report", ReportCommand
|
|
664
752
|
command Metadata
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ast/crispr"
|
|
4
|
+
require "ast/crispr/toml"
|
|
5
|
+
require "psych/merge"
|
|
6
|
+
|
|
7
|
+
module Kettle
|
|
8
|
+
module Family
|
|
9
|
+
# Slice-1044 family mise tasks primitive: ensure a destination root
|
|
10
|
+
# `mise.toml` references the kettle-family-managed TOML task file via
|
|
11
|
+
# `[task_config] includes`, and report (optionally clean) task
|
|
12
|
+
# redefinitions between the root file and the managed file.
|
|
13
|
+
#
|
|
14
|
+
# Precedence contract (mise): a task defined in the root `mise.toml` wins
|
|
15
|
+
# over the same name from a task file. Redefinition is therefore allowed
|
|
16
|
+
# and only reported; `clean` is always opt-in.
|
|
17
|
+
#
|
|
18
|
+
# All edits go through ast-crispr Actors with the ast-crispr-toml adapter.
|
|
19
|
+
module MiseTasks
|
|
20
|
+
TomlSelectors = Ast::Crispr::Toml::Selectors
|
|
21
|
+
|
|
22
|
+
# changed: the root content was modified
|
|
23
|
+
# include_present: the include reference is (now) present and correct
|
|
24
|
+
# content: the resulting root mise.toml content
|
|
25
|
+
# redefinitions: task names defined in BOTH the root and the managed file
|
|
26
|
+
Result = Struct.new(:changed, :include_present, :content, :redefinitions)
|
|
27
|
+
|
|
28
|
+
# Task names declared in a mise.toml / TOML task file.
|
|
29
|
+
#
|
|
30
|
+
# The two shapes differ:
|
|
31
|
+
# - a root `mise.toml` (`managed: false`, default) namespaces tasks
|
|
32
|
+
# under `[tasks]` / `[tasks.<name>]`;
|
|
33
|
+
# - a TOML task file loaded through `[task_config].includes`
|
|
34
|
+
# (`managed: true`) uses the mise-task schema: each top-level table
|
|
35
|
+
# IS a task (bare name), e.g. `[family-canary]`.
|
|
36
|
+
#
|
|
37
|
+
# @param content [String] TOML source
|
|
38
|
+
# @param managed [Boolean] parse as a managed TOML task file
|
|
39
|
+
# @return [Array<String>]
|
|
40
|
+
def self.task_names(content, managed: false)
|
|
41
|
+
context = Ast::Crispr::Toml.document_context(content: content, source_label: "mise.toml")
|
|
42
|
+
if managed
|
|
43
|
+
tables = context.structural_owners(owner_scope: :tables)
|
|
44
|
+
names = tables.reject { |table| table.name == "task_config" }.map(&:name)
|
|
45
|
+
context.structural_owners(owner_scope: :root_pairs).each do |pair|
|
|
46
|
+
names << pair.name.to_s unless pair.full_path == "includes" || pair.full_path.include?(".")
|
|
47
|
+
end
|
|
48
|
+
return names.compact.reject(&:empty?).uniq
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
names = []
|
|
52
|
+
context.structural_owners(owner_scope: :tables).each do |table|
|
|
53
|
+
next unless table.name == "tasks" || table.name.start_with?("tasks.")
|
|
54
|
+
|
|
55
|
+
# `[tasks.<name>]` sub-tables ARE task definitions; their inner
|
|
56
|
+
# pairs are task configuration, not task names. Only the bare
|
|
57
|
+
# `[tasks]` table carries trivial task entries as pairs.
|
|
58
|
+
if table.name == "tasks"
|
|
59
|
+
table.wrapper.pairs.each { |pair| names << pair.key_name.to_s }
|
|
60
|
+
else
|
|
61
|
+
names << table.name.sub(/\Atasks\./, "")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
context.structural_owners(owner_scope: :root_pairs).each do |pair|
|
|
65
|
+
names << pair.name.to_s if pair.full_path == "tasks"
|
|
66
|
+
end
|
|
67
|
+
names.compact.reject(&:empty?).uniq
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Register the managed task file as kettle-jem template-managed in the
|
|
71
|
+
# destination's kettle-jem config. The registration is a template-side
|
|
72
|
+
# fragment merged into the destination YAML with psych-merge:
|
|
73
|
+
# destination entries always win, so an existing strategy choice is
|
|
74
|
+
# never overridden — only a missing registration is added.
|
|
75
|
+
#
|
|
76
|
+
# Skips silently (changed: false) when the destination has no
|
|
77
|
+
# kettle-jem config: a project that is not kettle-jem-templated has
|
|
78
|
+
# nothing to register.
|
|
79
|
+
#
|
|
80
|
+
# @param config_content [String] destination kettle-jem config YAML
|
|
81
|
+
# @param task_file_name [String] managed task file name
|
|
82
|
+
# @return [Result]-shaped Struct (changed, include_present, content, redefinitions: [])
|
|
83
|
+
def self.register_in_jem_config(config_content:, task_file_name:)
|
|
84
|
+
fragment = <<~YAML
|
|
85
|
+
files:
|
|
86
|
+
#{task_file_name}:
|
|
87
|
+
strategy: keep_destination
|
|
88
|
+
YAML
|
|
89
|
+
|
|
90
|
+
merge_result = Psych::Merge.merge_yaml(
|
|
91
|
+
fragment,
|
|
92
|
+
config_content,
|
|
93
|
+
"yaml",
|
|
94
|
+
add_template_only_nodes: true,
|
|
95
|
+
preference: :destination
|
|
96
|
+
)
|
|
97
|
+
raise Error, Array(merge_result[:diagnostics]).map { |d| d[:message] || d["message"] }.join("; ") unless merge_result[:ok]
|
|
98
|
+
|
|
99
|
+
updated = merge_result[:output]
|
|
100
|
+
Result.new(changed: updated != config_content, include_present: true, content: updated, redefinitions: [])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Task names defined in BOTH the root mise.toml and the managed task file.
|
|
104
|
+
#
|
|
105
|
+
# @param root_content [String] destination mise.toml content
|
|
106
|
+
# @param task_file_content [String, nil] managed task file content
|
|
107
|
+
# @return [Array<String>] sorted redefined task names (empty when task_file_content is nil)
|
|
108
|
+
def self.redefinitions(root_content:, task_file_content:)
|
|
109
|
+
return [] if task_file_content.nil?
|
|
110
|
+
|
|
111
|
+
(task_names(root_content) & task_names(task_file_content, managed: true)).sort
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Ensure the root mise.toml contains `[task_config] includes = ["<task_file_name>"]`.
|
|
115
|
+
#
|
|
116
|
+
# @param root_content [String] the destination's mise.toml content
|
|
117
|
+
# @param task_file_name [String] managed task file name, relative to the config root
|
|
118
|
+
# @param task_file_content [String, nil] managed task file content, for redefinition detection
|
|
119
|
+
# @return [Result]
|
|
120
|
+
def self.ensure_include(root_content:, task_file_name:, task_file_content: nil)
|
|
121
|
+
redefs = redefinitions(root_content: root_content, task_file_content: task_file_content)
|
|
122
|
+
context = Ast::Crispr::Toml.document_context(content: root_content, source_label: "mise.toml")
|
|
123
|
+
|
|
124
|
+
if include_present?(context, task_file_name)
|
|
125
|
+
return Result.new(changed: false, include_present: true, content: root_content, redefinitions: redefs)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
updated = add_include(context, task_file_name)
|
|
129
|
+
Result.new(
|
|
130
|
+
changed: updated != root_content,
|
|
131
|
+
include_present: true,
|
|
132
|
+
content: updated,
|
|
133
|
+
redefinitions: redefinitions(root_content: updated, task_file_content: task_file_content)
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Remove duplicate task definitions from the root mise.toml.
|
|
138
|
+
# Opt-in (`--clean`); never runs implicitly.
|
|
139
|
+
#
|
|
140
|
+
# @param root_content [String] the destination's mise.toml content
|
|
141
|
+
# @param task_file_content [String] managed task file content
|
|
142
|
+
# @param task_file_name [String] managed task file name
|
|
143
|
+
# @return [Result]
|
|
144
|
+
def self.clean_redefinitions(root_content:, task_file_content:, task_file_name:)
|
|
145
|
+
redefs = redefinitions(root_content: root_content, task_file_content: task_file_content)
|
|
146
|
+
content = root_content
|
|
147
|
+
redefs.each do |name|
|
|
148
|
+
# A task can be defined as a `[tasks.<name>]` table block or as a
|
|
149
|
+
# trivial `name = "…"` pair inside `[tasks]`; try both owners.
|
|
150
|
+
[
|
|
151
|
+
TomlSelectors.table(path: "tasks.#{name}"),
|
|
152
|
+
TomlSelectors.pair(path: "tasks.#{name}")
|
|
153
|
+
].each do |target|
|
|
154
|
+
result = Ast::Crispr::Delete.result(
|
|
155
|
+
content: content,
|
|
156
|
+
target: target,
|
|
157
|
+
source_label: "mise.toml"
|
|
158
|
+
)
|
|
159
|
+
content = result.updated_content if result.success? && result.changed
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context = Ast::Crispr::Toml.document_context(content: content, source_label: "mise.toml")
|
|
164
|
+
Result.new(
|
|
165
|
+
changed: content != root_content,
|
|
166
|
+
include_present: include_present?(context, task_file_name),
|
|
167
|
+
content: content,
|
|
168
|
+
redefinitions: redefinitions(root_content: content, task_file_content: task_file_content)
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Kinds to try when deleting a task definition: a `[tasks.<name>]`
|
|
173
|
+
# table block or a `name = "…"` pair inside `[tasks]`.
|
|
174
|
+
TASK_OWNER_KINDS = %i[table pair].freeze
|
|
175
|
+
|
|
176
|
+
class << self
|
|
177
|
+
# Build a successful Result. `include_present` is always true on an
|
|
178
|
+
# ensure_include outcome: a no-op means the reference was already correct.
|
|
179
|
+
def ok_result(content, redefs, changed: false)
|
|
180
|
+
Result.new(changed: changed, include_present: true, content: content, redefinitions: redefs)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def document_context(content)
|
|
186
|
+
Ast::Crispr::Toml.document_context(content: content, source_label: "mise.toml")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def include_present?(context, task_file_name)
|
|
190
|
+
includes_entries(context).any? { |entry| entry == task_file_name.to_s }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# String entries of `task_config.includes`; empty when the table or
|
|
194
|
+
# the pair does not exist.
|
|
195
|
+
def includes_entries(context)
|
|
196
|
+
table = context.structural_owners(owner_scope: :tables).find { |owner| owner.name == "task_config" }
|
|
197
|
+
return [] unless table
|
|
198
|
+
|
|
199
|
+
pair = table.wrapper.pairs.find { |candidate| candidate.key_name.to_s.strip == "includes" }
|
|
200
|
+
return [] unless pair
|
|
201
|
+
|
|
202
|
+
value = pair.value_node
|
|
203
|
+
if value&.array?
|
|
204
|
+
value.elements.map { |element| unquote(element.text) }
|
|
205
|
+
else
|
|
206
|
+
[unquote(value&.text)]
|
|
207
|
+
end.compact
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def unquote(text)
|
|
211
|
+
text.to_s.strip.gsub(/\A["']|["']\z/, "")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def add_include(context, task_file_name)
|
|
215
|
+
content = context.content
|
|
216
|
+
block = "[task_config]\nincludes = [\"#{task_file_name}\"]\n"
|
|
217
|
+
|
|
218
|
+
table = context.structural_owners(owner_scope: :tables).find { |owner| owner.name == "task_config" }
|
|
219
|
+
if table.nil?
|
|
220
|
+
result = Ast::Crispr::Insert.result(
|
|
221
|
+
content: content,
|
|
222
|
+
text: block,
|
|
223
|
+
destination: nil,
|
|
224
|
+
if_missing: :append,
|
|
225
|
+
source_label: "mise.toml"
|
|
226
|
+
)
|
|
227
|
+
return fail_or(result)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
pairs = table.wrapper.pairs
|
|
231
|
+
result =
|
|
232
|
+
if pairs.empty?
|
|
233
|
+
Ast::Crispr::Replace.result(
|
|
234
|
+
content: content,
|
|
235
|
+
target: TomlSelectors.table(path: "task_config"),
|
|
236
|
+
replacement: block.rstrip + "\n",
|
|
237
|
+
source_label: "mise.toml"
|
|
238
|
+
)
|
|
239
|
+
else
|
|
240
|
+
last = pairs.last
|
|
241
|
+
Ast::Crispr::Replace.result(
|
|
242
|
+
content: content,
|
|
243
|
+
target: TomlSelectors.pair(path: "task_config.#{last.key_name}"),
|
|
244
|
+
replacement: "#{last.text}\nincludes = [\"#{task_file_name}\"]\n",
|
|
245
|
+
source_label: "mise.toml"
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
fail_or(result)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def fail_or(result)
|
|
252
|
+
raise Error, result.error.to_s unless result.success?
|
|
253
|
+
|
|
254
|
+
result.updated_content
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -442,11 +442,12 @@ module Kettle
|
|
|
442
442
|
def selected_member_results
|
|
443
443
|
return {} unless member_result_command?
|
|
444
444
|
|
|
445
|
-
visible_results.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |result, memo|
|
|
445
|
+
built = visible_results.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |result, memo|
|
|
446
446
|
next unless selected_names.include?(result.member_name)
|
|
447
447
|
|
|
448
448
|
memo[result.member_name] << result
|
|
449
449
|
end
|
|
450
|
+
built.sort.to_h
|
|
450
451
|
end
|
|
451
452
|
|
|
452
453
|
def summary_succeeded
|
|
@@ -475,7 +476,8 @@ module Kettle
|
|
|
475
476
|
end
|
|
476
477
|
|
|
477
478
|
def summary_failed
|
|
478
|
-
|
|
479
|
+
# Alphabetical summary lists, regardless of parallel completion order.
|
|
480
|
+
visible_results.reject(&:ok?).sort_by.with_index { |result, index| [result.member_name, index] }.map do |result|
|
|
479
481
|
{
|
|
480
482
|
"member" => result.member_name,
|
|
481
483
|
"phase" => result.phase,
|
|
@@ -490,7 +492,7 @@ module Kettle
|
|
|
490
492
|
|
|
491
493
|
ran = summary_ran_member_names
|
|
492
494
|
reason = pending_reason
|
|
493
|
-
(selected_names - ran).map do |member_name|
|
|
495
|
+
(selected_names - ran).sort.map do |member_name|
|
|
494
496
|
{
|
|
495
497
|
"member" => member_name,
|
|
496
498
|
"phase" => command,
|
|
@@ -518,11 +520,11 @@ module Kettle
|
|
|
518
520
|
|
|
519
521
|
return selected_member_results.keys unless command == "release"
|
|
520
522
|
|
|
521
|
-
failed = visible_results.reject(&:ok?).map(&:member_name)
|
|
523
|
+
failed = visible_results.reject(&:ok?).map(&:member_name).uniq.sort
|
|
522
524
|
terminal = selected_member_results.filter_map do |member_name, member_results|
|
|
523
525
|
member_name if member_results.any? { |result| release_terminal_result?(result) }
|
|
524
526
|
end
|
|
525
|
-
(failed + terminal).uniq
|
|
527
|
+
(failed + terminal.sort).uniq
|
|
526
528
|
end
|
|
527
529
|
|
|
528
530
|
def summary_terminal_member_results(member_results)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# kettle-family managed family mise tasks (slice-1044 baseline)
|
|
2
|
+
#
|
|
3
|
+
# This file is owned by kettle-family and is overwritten on template updates.
|
|
4
|
+
# It uses the mise-task schema: each top-level table is a task, addressable
|
|
5
|
+
# from the destination root mise.toml via [task_config] includes.
|
|
6
|
+
#
|
|
7
|
+
# Define family tasks below. Example:
|
|
8
|
+
#
|
|
9
|
+
# [family-lint]
|
|
10
|
+
# description = "Run the family lint gate"
|
|
11
|
+
# run = "mise run lint"
|
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "1.3.
|
|
8
|
+
VERSION = "1.3.15"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -4705,12 +4705,16 @@ module Kettle
|
|
|
4705
4705
|
end
|
|
4706
4706
|
|
|
4707
4707
|
def start_release_progress(release_members)
|
|
4708
|
+
wave_numbers = release_waves(release_members).each_with_index.flat_map do |wave, index|
|
|
4709
|
+
wave.map { |member| [member.name, index + 1] }
|
|
4710
|
+
end.to_h
|
|
4708
4711
|
progress = WorkflowProgress.new(
|
|
4709
4712
|
io: progress_io,
|
|
4710
4713
|
label: release_progress_label,
|
|
4711
4714
|
total: release_members.length,
|
|
4712
4715
|
jobs: release_jobs(release_members),
|
|
4713
|
-
members: release_members
|
|
4716
|
+
members: release_members,
|
|
4717
|
+
waves: wave_numbers
|
|
4714
4718
|
)
|
|
4715
4719
|
progress.start
|
|
4716
4720
|
progress
|
|
@@ -15,7 +15,7 @@ module Kettle
|
|
|
15
15
|
DEFAULT_TERMINAL_WIDTH = 80
|
|
16
16
|
ELLIPSIS = "..."
|
|
17
17
|
|
|
18
|
-
def initialize(io:, label:, total:, jobs:, members: [], enabled: true, clock: nil, heading: nil)
|
|
18
|
+
def initialize(io:, label:, total:, jobs:, members: [], enabled: true, clock: nil, heading: nil, waves: {})
|
|
19
19
|
@io = io
|
|
20
20
|
@label = label
|
|
21
21
|
@total = total.to_i
|
|
@@ -23,11 +23,19 @@ module Kettle
|
|
|
23
23
|
@heading = heading
|
|
24
24
|
@clock = clock || lambda { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
25
25
|
@enabled = enabled && !!io
|
|
26
|
+
# Lines render in the order the caller passes: the task's actual
|
|
27
|
+
# execution order (wave plan for release, dependency order elsewhere).
|
|
28
|
+
# The pane is a schedule view, so it reflects reality, unlike the
|
|
29
|
+
# alphabetical summary lists in the report.
|
|
26
30
|
@line_order = members.map(&:name)
|
|
31
|
+
# When the task runs in waves, prefix each member with its wave number
|
|
32
|
+
# ("2.kettle-jem") so the wave structure is visible instead of the
|
|
33
|
+
# ordering looking arbitrary.
|
|
34
|
+
@waves = waves.to_h.transform_values { |wave| wave.to_i }
|
|
27
35
|
@started = false
|
|
28
36
|
@stopped = false
|
|
29
37
|
@member_totals = {}
|
|
30
|
-
@member_labels = @line_order.to_h { |member_name| [member_name, member_name] }
|
|
38
|
+
@member_labels = @line_order.to_h { |member_name| [member_name, wave_label(member_name)] }
|
|
31
39
|
@member_counts = Hash.new(0)
|
|
32
40
|
@member_started_at = {}
|
|
33
41
|
@member_finished_elapsed = {}
|
|
@@ -60,7 +68,7 @@ module Kettle
|
|
|
60
68
|
return unless @enabled
|
|
61
69
|
|
|
62
70
|
synchronize do
|
|
63
|
-
|
|
71
|
+
store_label(key, label)
|
|
64
72
|
@member_totals[key] = total.to_i
|
|
65
73
|
@member_counts[key] = 0
|
|
66
74
|
@member_started_at[key] ||= monotonic_now
|
|
@@ -77,7 +85,7 @@ module Kettle
|
|
|
77
85
|
return unless @enabled
|
|
78
86
|
|
|
79
87
|
synchronize do
|
|
80
|
-
|
|
88
|
+
store_label(key, label)
|
|
81
89
|
event_mark = mark || (success ? "." : "F")
|
|
82
90
|
increment_member_count(key)
|
|
83
91
|
if @tty
|
|
@@ -94,7 +102,7 @@ module Kettle
|
|
|
94
102
|
return if status.to_s.empty?
|
|
95
103
|
|
|
96
104
|
synchronize do
|
|
97
|
-
|
|
105
|
+
store_label(key, label)
|
|
98
106
|
if @tty
|
|
99
107
|
append_event(key, mark) if mark
|
|
100
108
|
render(key, status: status)
|
|
@@ -108,7 +116,7 @@ module Kettle
|
|
|
108
116
|
return unless @enabled
|
|
109
117
|
|
|
110
118
|
synchronize do
|
|
111
|
-
|
|
119
|
+
store_label(key, label)
|
|
112
120
|
total = @member_totals[key].to_i
|
|
113
121
|
@member_counts[key] = total if total.positive?
|
|
114
122
|
@member_finished_elapsed[key] = elapsed_seconds_by_name(key)
|
|
@@ -173,6 +181,20 @@ module Kettle
|
|
|
173
181
|
|
|
174
182
|
private
|
|
175
183
|
|
|
184
|
+
# "2.kettle-jem" when the member belongs to wave 2, otherwise the bare
|
|
185
|
+
# name. Wave numbers come from the caller's `waves:` mapping.
|
|
186
|
+
def wave_label(member_name)
|
|
187
|
+
wave = @waves[member_name]
|
|
188
|
+
wave ? "#{wave}.#{member_name}" : member_name
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Store a display label, re-applying the wave prefix when the caller
|
|
192
|
+
# passes the bare member name, so updates never strip "2." from
|
|
193
|
+
# "2.kettle-jem". Explicit custom labels are stored verbatim.
|
|
194
|
+
def store_label(key, label)
|
|
195
|
+
@member_labels[key] = (@waves[key] && label == key) ? wave_label(key) : label
|
|
196
|
+
end
|
|
197
|
+
|
|
176
198
|
def render(member_key, status:)
|
|
177
199
|
render_name(member_key, status: status)
|
|
178
200
|
render_tty_block if @tty
|
data/lib/kettle/family.rb
CHANGED
|
@@ -30,6 +30,7 @@ require_relative "family/appraisal_bootstrap"
|
|
|
30
30
|
require_relative "family/release_waves"
|
|
31
31
|
require_relative "family/release_graph"
|
|
32
32
|
require_relative "family/execution_profile"
|
|
33
|
+
require_relative "family/mise_tasks"
|
|
33
34
|
require_relative "family/workflow"
|
|
34
35
|
require_relative "family/discovery"
|
|
35
36
|
require_relative "family/orderer"
|
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: 1.3.
|
|
4
|
+
version: 1.3.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -37,6 +37,46 @@ 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: ast-crispr
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.1'
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 7.1.11
|
|
50
|
+
type: :runtime
|
|
51
|
+
prerelease: false
|
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - "~>"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '7.1'
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 7.1.11
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: ast-crispr-toml
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '7.1'
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 7.1.11
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '7.1'
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 7.1.11
|
|
40
80
|
- !ruby/object:Gem::Dependency
|
|
41
81
|
name: command_kit
|
|
42
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -165,6 +205,46 @@ dependencies:
|
|
|
165
205
|
- - ">="
|
|
166
206
|
- !ruby/object:Gem::Version
|
|
167
207
|
version: 2.0.27
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: psych-merge
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - "~>"
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: '7.1'
|
|
215
|
+
- - ">="
|
|
216
|
+
- !ruby/object:Gem::Version
|
|
217
|
+
version: 7.1.4
|
|
218
|
+
type: :runtime
|
|
219
|
+
prerelease: false
|
|
220
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
221
|
+
requirements:
|
|
222
|
+
- - "~>"
|
|
223
|
+
- !ruby/object:Gem::Version
|
|
224
|
+
version: '7.1'
|
|
225
|
+
- - ">="
|
|
226
|
+
- !ruby/object:Gem::Version
|
|
227
|
+
version: 7.1.4
|
|
228
|
+
- !ruby/object:Gem::Dependency
|
|
229
|
+
name: toml-merge
|
|
230
|
+
requirement: !ruby/object:Gem::Requirement
|
|
231
|
+
requirements:
|
|
232
|
+
- - "~>"
|
|
233
|
+
- !ruby/object:Gem::Version
|
|
234
|
+
version: '7.1'
|
|
235
|
+
- - ">="
|
|
236
|
+
- !ruby/object:Gem::Version
|
|
237
|
+
version: 7.1.11
|
|
238
|
+
type: :runtime
|
|
239
|
+
prerelease: false
|
|
240
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
241
|
+
requirements:
|
|
242
|
+
- - "~>"
|
|
243
|
+
- !ruby/object:Gem::Version
|
|
244
|
+
version: '7.1'
|
|
245
|
+
- - ">="
|
|
246
|
+
- !ruby/object:Gem::Version
|
|
247
|
+
version: 7.1.11
|
|
168
248
|
- !ruby/object:Gem::Dependency
|
|
169
249
|
name: tsort
|
|
170
250
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -457,6 +537,7 @@ files:
|
|
|
457
537
|
- lib/kettle/family/local_install.rb
|
|
458
538
|
- lib/kettle/family/member.rb
|
|
459
539
|
- lib/kettle/family/member_loader.rb
|
|
540
|
+
- lib/kettle/family/mise_tasks.rb
|
|
460
541
|
- lib/kettle/family/nomono_bootstrap.rb
|
|
461
542
|
- lib/kettle/family/orderer.rb
|
|
462
543
|
- lib/kettle/family/paths.rb
|
|
@@ -473,6 +554,7 @@ files:
|
|
|
473
554
|
- lib/kettle/family/secrets.rb
|
|
474
555
|
- lib/kettle/family/secrets_broker.rb
|
|
475
556
|
- lib/kettle/family/selection.rb
|
|
557
|
+
- lib/kettle/family/templates/mise-family.tasks.toml
|
|
476
558
|
- lib/kettle/family/unreleased_gem_cleanup.rb
|
|
477
559
|
- lib/kettle/family/version.rb
|
|
478
560
|
- lib/kettle/family/version_bump.rb
|
|
@@ -485,10 +567,10 @@ licenses:
|
|
|
485
567
|
- AGPL-3.0-only
|
|
486
568
|
metadata:
|
|
487
569
|
homepage_uri: https://kettle-family.galtzo.com
|
|
488
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.
|
|
489
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.
|
|
570
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.15
|
|
571
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.15/CHANGELOG.md
|
|
490
572
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
491
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.
|
|
573
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.15
|
|
492
574
|
funding_uri: https://github.com/sponsors/pboling
|
|
493
575
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
494
576
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|