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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 272212c97732ca16915b15849051f070a1886d2c45d4ea55d839185cc6db7e52
4
- data.tar.gz: '08bb932c0d13e1567c1fd4ee3e5696d454f9f16a494a1c5c82864e3b580a831d'
3
+ metadata.gz: e9c9f3d7625a5dae78bd27ceda81c4237775381bf4f16fcc2bedb5b419637248
4
+ data.tar.gz: 73d2e5ffe67938d6aee937f8db6e8be9d253dd65dd964e34b4270929769e1b94
5
5
  SHA512:
6
- metadata.gz: 5733eacb7b6c037f40657513242fc133908c00746ad99bf78bbf1c6df956c7ae5b976d5568ad37ac703b1f4aa76404885203c14122d8b584d51dbeae813edf23
7
- data.tar.gz: 3e7c6aa05a68187fcbd443e4f758d88308d4b3aebc24e4f82d032fe301a5273d9ad2e5c4c13d72d24bc8c8730c187bff1c13fd0990a75e99548e3d77ed43b612
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.13...HEAD
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.597-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
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
@@ -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
@@ -922,7 +922,7 @@ module Kettle
922
922
  end
923
923
 
924
924
  def process_alive?(pid)
925
- Process.kill(0, pid)
925
+ Process.kill(0, Integer(pid, 10))
926
926
  true
927
927
  rescue Errno::ESRCH
928
928
  false
@@ -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
- visible_results.reject(&:ok?).map do |result|
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.13"
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
- @member_labels[key] = label
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
- @member_labels[key] = label
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
- @member_labels[key] = label
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
- @member_labels[key] = label
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.13
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.13
489
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.13/CHANGELOG.md
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.13
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