kettle-family 1.3.18 → 1.3.22

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: f6166c5366a3a6175dd73b92d85266730bb16a2598c201785f596245e27eb1f2
4
- data.tar.gz: e1120fcc387e5eca5ff2ae8e14f28e1761003f799c7861e0cd626e92b5497514
3
+ metadata.gz: 96db982e6915d29a69f7d70da67d8bc64cba1ac9d2e5c98cfc6f25a0007a8915
4
+ data.tar.gz: 18df52948a5ed1578f0c1d65d1da40fe93b3c13cd16bec671849651eb3cade02
5
5
  SHA512:
6
- metadata.gz: '0818f112b4a9e37e726d4804db2a1b47ff11d78ae6e8773a5370e8faeba0532e3c1d299cd210f193365717fdf711c85cc687be67c35202ce087d36ca619d5986'
7
- data.tar.gz: 68125b254ed371d6a2f3d721b223d8459514299fbbaa9b0e5f5402f00cf8340b20f599a5b8c6cd5da6b1499306e51d1313cba86dcf643166288eb4b8ab7050a5
6
+ metadata.gz: 543de4d2b9c10fe77cb0ed64231b381bd3c2553def379d4af9d4c84429821f0ddda8f6c300fcad022fe529654d8d11eb496afd3927c2a4d9b83bc5a1d3417d69
7
+ data.tar.gz: 36b14df59d36b187d978391d3e0e4be771f241e8eb816be8e13b01e703b56c957ee51d5545aa8f68c85f3575c1a0227d72b0a69ede2b9b29bacfe2775f3450d8
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,30 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.3.22] - 2026-10-19
34
+
35
+ - TAG: [v1.3.22][1.3.22t]
36
+ - COVERAGE: 93.00% -- 7348/7901 lines in 44 files
37
+ - BRANCH COVERAGE: 77.29% -- 2930/3791 branches in 44 files
38
+ - 37.55% documented
39
+
40
+ ### Fixed
41
+
42
+ - Family releases execute kettle-release from the family root bundle (BUNDLE_GEMFILE points member children at the family root Gemfile), so a stale member worktree lock can never resolve an ancient kettle-release whose fixes silently never load (observed: a member lock pinning kettle-dev 3.1.16 / kettle-family 1.3.11 skipped 3.1.17's task-lockfile materialization and the build failed on kettle-family 1.3.13).
43
+
44
+ ## [1.3.21] - 2026-10-18
45
+
46
+ - TAG: [v1.3.21][1.3.21t]
47
+ - COVERAGE: 92.98% -- 7347/7902 lines in 44 files
48
+ - BRANCH COVERAGE: 77.22% -- 2929/3793 branches in 44 files
49
+ - 37.55% documented
50
+
51
+ ### Added
52
+
53
+ - Managed family mise task suite generated from family config facts via a kettle-jem plugin: comprehensive delegations (test, lint, state, release-state, template, bump, changelog) plus bup/bupb/update-bundler with env wiring derived from family.local_path_env, release.disable_local_path_env, and family.members_root, replacing per-family hand-copied task definitions (and the buggy shell-spawned bundler pinning).
54
+
55
+ - mise-tasks registers the kettle-family plugin in the destination kettle-jem config (psych-merge, destination-wins sequence append), so templating runs load it and materialize the managed task suite with no hand-edited configs.
56
+
33
57
  ## [1.3.18] - 2026-10-18
34
58
 
35
59
  - TAG: [v1.3.18][1.3.18t]
@@ -3097,7 +3121,11 @@ Please file a bug if you notice a violation of semantic versioning.
3097
3121
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
3098
3122
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
3099
3123
 
3100
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.18...HEAD
3124
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.22...HEAD
3125
+ [1.3.22]: https://github.com/kettle-dev/kettle-family/compare/v1.3.21...v1.3.22
3126
+ [1.3.22t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.22
3127
+ [1.3.21]: https://github.com/kettle-dev/kettle-family/compare/v1.3.18...v1.3.21
3128
+ [1.3.21t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.21
3101
3129
  [1.3.18]: https://github.com/kettle-dev/kettle-family/compare/v1.3.17...v1.3.18
3102
3130
  [1.3.18t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.18
3103
3131
  [1.3.17]: https://github.com/kettle-dev/kettle-family/compare/v1.3.16...v1.3.17
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.822-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1228
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-7.901-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
@@ -329,7 +329,9 @@ module Kettle
329
329
  end
330
330
 
331
331
  # Register the managed file in the destination's kettle-jem config
332
- # when the project is kettle-jem-templated.
332
+ # when the project is kettle-jem-templated, and ensure kettle-family
333
+ # itself is listed under `plugins:` so templating runs load it and
334
+ # materialize the managed task suite automatically.
333
335
  %w[.structuredmerge/kettle-jem.yml .kettle-jem.yml].each do |config_relative|
334
336
  config_path = File.join(root, config_relative)
335
337
  next unless File.file?(config_path)
@@ -344,6 +346,16 @@ module Kettle
344
346
  else
345
347
  stdout.puts("#{task_file_name} already registered in #{config_relative}")
346
348
  end
349
+
350
+ plugin_registration = Kettle::Family::MiseTasks.register_plugin_in_jem_config(
351
+ config_content: File.read(config_path)
352
+ )
353
+ if plugin_registration.changed
354
+ File.write(config_path, plugin_registration.content)
355
+ stdout.puts("registered kettle-family plugin in #{config_relative}")
356
+ else
357
+ stdout.puts("kettle-family plugin already registered in #{config_relative}")
358
+ end
347
359
  break
348
360
  end
349
361
 
@@ -966,7 +978,7 @@ module Kettle
966
978
  def command_results_for_current_branch(command:, config:, members:, options:, family_members: members, start_at: StartAt.new(nil, nil), state_event_handler: nil, state_event_tape: nil, release_state_results: nil)
967
979
  return mise_trust_results(config: config, members: members) if command == "mise-trust"
968
980
  return bump_version_results(config: config, members: members, options: options, phase: command, release_state_results: release_state_results) if %w[bump bump-version].include?(command)
969
- return add_changelog_results(members: members, options: options) if command == "add-changelog"
981
+ return add_changelog_results(config: config, members: members, options: options) if command == "add-changelog"
970
982
  return clean_unreleased_results(config: config, members: members, options: options) if command == "clean-unreleased"
971
983
  return clean_blockers_results(members: members, options: options) if command == "clean-blockers"
972
984
  return clean_installed_results(config: config, members: members, options: options) if command == "clean-installed"
@@ -1411,7 +1423,7 @@ module Kettle
1411
1423
  Kettle::Dev::VersionBump.resolve_target_version(options[:target_version].to_s, current.to_s)
1412
1424
  end
1413
1425
 
1414
- def add_changelog_results(members:, options:)
1426
+ def add_changelog_results(config:, members:, options:)
1415
1427
  section = options[:changelog_section].to_s
1416
1428
  entry = options[:changelog_entry].to_s
1417
1429
  raise Error, "add-changelog requires --section" if section.empty?
@@ -1422,12 +1434,27 @@ module Kettle
1422
1434
  memo << runner.call(
1423
1435
  member: member,
1424
1436
  phase: "add-changelog",
1425
- command: [installed_executable("kettle-changelog"), "--add-unreleased-entry", "--section", section, "--entry", entry]
1437
+ command: [installed_executable("kettle-changelog"), "--add-unreleased-entry", "--section", section, "--entry", entry],
1438
+ env: changelog_target_env(config: config, member: member)
1426
1439
  )
1427
1440
  break memo unless memo.last.ok?
1428
1441
  end
1429
1442
  end
1430
1443
 
1444
+ # Changelogs go where the family config says they go: a shared-changelog
1445
+ # family writes its family root changelog, a member-local family writes
1446
+ # each member's own. The env is explicit because kettle-changelog falls
1447
+ # back to the invoking directory's CHANGELOG.md when K_CHANGELOG_PATH is
1448
+ # unset, which is only correct for member-local families by accident.
1449
+ def changelog_target_env(config:, member:)
1450
+ path = if config&.shared_changelog?
1451
+ File.expand_path(config.changelog_path, config.root)
1452
+ else
1453
+ File.join(member.root, "CHANGELOG.md")
1454
+ end
1455
+ {"K_CHANGELOG_PATH" => path}
1456
+ end
1457
+
1431
1458
  def installed_executable(name)
1432
1459
  File.join(Gem.bindir, name)
1433
1460
  end
@@ -19,6 +19,10 @@ module Kettle
19
19
  module MiseTasks
20
20
  TomlSelectors = Ast::Crispr::Toml::Selectors
21
21
 
22
+ # Managed task file name, relative to the family root (the directory
23
+ # containing .kettle-family.yml and the root mise.toml that includes it).
24
+ DEFAULT_TASK_FILE_NAME = "mise-family.tasks.toml"
25
+
22
26
  # changed: the root content was modified
23
27
  # include_present: the include reference is (now) present and correct
24
28
  # content: the resulting root mise.toml content
@@ -100,6 +104,35 @@ module Kettle
100
104
  Result.new(changed: updated != config_content, include_present: true, content: updated, redefinitions: [])
101
105
  end
102
106
 
107
+ # Ensure kettle-family is listed under `plugins:` in the destination's
108
+ # kettle-jem config, so templating runs load it and materialize the
109
+ # managed task suite. Appends to an existing `plugins:` list without
110
+ # disturbing entries already there (sequence items present only in the
111
+ # fragment are added; destination items always win); a no-op when the
112
+ # plugin is already listed.
113
+ #
114
+ # @param config_content [String] destination kettle-jem config YAML
115
+ # @return [Result]-shaped Struct (changed, include_present, content, redefinitions: [])
116
+ def self.register_plugin_in_jem_config(config_content:)
117
+ fragment = <<~YAML
118
+ plugins:
119
+ - kettle-family
120
+ YAML
121
+
122
+ merge_result = Psych::Merge.merge_yaml(
123
+ fragment,
124
+ config_content,
125
+ "yaml",
126
+ add_template_only_nodes: true,
127
+ add_template_only_sequence_items: true,
128
+ preference: :destination
129
+ )
130
+ raise Error, Array(merge_result[:diagnostics]).map { |d| d[:message] || d["message"] }.join("; ") unless merge_result[:ok]
131
+
132
+ updated = merge_result[:output]
133
+ Result.new(changed: updated != config_content, include_present: true, content: updated, redefinitions: [])
134
+ end
135
+
103
136
  # Task names defined in BOTH the root mise.toml and the managed task file.
104
137
  #
105
138
  # @param root_content [String] destination mise.toml content
@@ -254,6 +287,152 @@ module Kettle
254
287
  result.updated_content
255
288
  end
256
289
  end
290
+
291
+ # kettle-jem plugin hook (kettle-jem-appraisals pattern). During
292
+ # templating of a family root, materialize the comprehensive managed
293
+ # task suite from the family config facts and ensure the root mise.toml
294
+ # includes it. Skips non-family roots (member dirs have no
295
+ # .kettle-family.yml).
296
+ #
297
+ # @param context [Hash] kettle-jem plugin context (:project_root)
298
+ def self.materialize_managed_suite(context:)
299
+ root = context[:project_root].to_s
300
+ return if root.empty?
301
+
302
+ root = File.expand_path(root)
303
+ return unless File.file?(File.join(root, ".kettle-family.yml"))
304
+
305
+ config = Config.load(root: root)
306
+ content = ManagedSuite.new(config).content
307
+ content += "\n" unless content.end_with?("\n")
308
+
309
+ task_file_path = File.join(root, DEFAULT_TASK_FILE_NAME)
310
+ File.write(task_file_path, content) unless File.exist?(task_file_path) && File.read(task_file_path) == content
311
+
312
+ root_mise = File.join(root, "mise.toml")
313
+ return unless File.file?(root_mise)
314
+
315
+ result = ensure_include(
316
+ root_content: File.read(root_mise),
317
+ task_file_name: DEFAULT_TASK_FILE_NAME,
318
+ task_file_content: content
319
+ )
320
+ File.write(root_mise, result.content) if result.changed
321
+ rescue Error, SystemCallError => e
322
+ warn("kettle-family: could not materialize managed mise tasks: #{e.class}: #{e.message}")
323
+ end
324
+
325
+ # Comprehensive managed family mise task suite, generated from family
326
+ # config facts. Tasks are thin delegations to kettle-family commands,
327
+ # which resolve changelog targets, local-path env, and members_root
328
+ # from the family config at run time. The bup/bupb env wiring is the
329
+ # one piece of shape baked at generation time, derived from the same
330
+ # config the workflow uses (family.local_path_env,
331
+ # release.disable_local_path_env, family.mode, family.members_root).
332
+ class ManagedSuite
333
+ TASK_DESCRIPTIONS = {
334
+ "test" => "Run each member gem suite with its own bundle",
335
+ "lint" => "Run read-only lint checks across family members",
336
+ "state" => "Report release state for every family member",
337
+ "release-state" => "Report changelog release state for every family member",
338
+ "template" => "Apply kettle-jem templates across family members",
339
+ "bump" => "Bump member gem versions",
340
+ "changelog" => "Add a structured entry to the family changelog",
341
+ "bup" => "Update dependencies and commit lockfiles; pass gems or options after --",
342
+ "bupb" => "Install latest stable Bundler and update family lockfiles; pass options after --",
343
+ "update-bundler" => "Install latest stable Bundler and update family lockfiles; pass options after --"
344
+ }.freeze
345
+
346
+ attr_reader :config
347
+
348
+ def initialize(config)
349
+ @config = config
350
+ end
351
+
352
+ def content
353
+ tasks = {
354
+ "test" => "bundle exec kettle-family test --execute",
355
+ "lint" => "bundle exec kettle-family lint",
356
+ "state" => "bundle exec kettle-family release-state",
357
+ "release-state" => "bundle exec kettle-family release-state",
358
+ "template" => "bundle exec kettle-family template",
359
+ "bump" => "bundle exec kettle-family bump --execute",
360
+ # Targeting is resolved by kettle-family from the family config:
361
+ # shared-changelog families write the root changelog, member-local
362
+ # families write each member's own.
363
+ "changelog" => "bundle exec kettle-family add-changelog --execute"
364
+ }
365
+ tasks["bup"] = "bundle exec kettle-family bup --execute --commit"
366
+ tasks["bupb"] = "bundle exec kettle-family update-bundler --execute --commit"
367
+ tasks["update-bundler"] = tasks["bupb"]
368
+
369
+ neutral = neutralize_assignments
370
+ wiring = env_wiring_flags
371
+ %w[bup bupb].each do |name|
372
+ prefix = neutral.empty? ? "" : "env #{neutral} -- "
373
+ tasks[name] = "#{prefix}#{tasks[name]}#{wiring}"
374
+ end
375
+ tasks["update-bundler"] = tasks["bupb"]
376
+
377
+ render(tasks)
378
+ end
379
+
380
+ private
381
+
382
+ # Neutralize ambient local-path wiring so the command starts from the
383
+ # family config, then hand kettle-family the intended overrides.
384
+ def neutralize_assignments
385
+ pairs = {}
386
+ if (name = config.family_local_path_env_name)
387
+ pairs[name] = "false"
388
+ end
389
+ config.release_disable_local_path_env.each { |name| pairs[name] = "false" }
390
+ pairs.map { |name, value| "#{name}=#{value}" }.join(" ")
391
+ end
392
+
393
+ def env_wiring_flags
394
+ flags = +""
395
+ return flags unless config.family_mode == "monorepo"
396
+ return flags unless (relative = members_root_relative)
397
+
398
+ if (name = config.family_local_path_env_name)
399
+ flags << " --env #{name}={{config_root}}/#{relative}"
400
+ end
401
+ flags << " --env K_JEM_TEMPLATING=true"
402
+ flags
403
+ end
404
+
405
+ def members_root_relative
406
+ require "pathname"
407
+ Pathname.new(config.members_root.to_s)
408
+ .relative_path_from(Pathname.new(config.root.to_s)).to_s
409
+ rescue ArgumentError
410
+ nil
411
+ end
412
+
413
+ def render(tasks)
414
+ out = +<<~HEADER
415
+ # kettle-family managed family mise tasks
416
+ #
417
+ # This file is owned by kettle-family and is overwritten on
418
+ # template updates (and on kettle-jem templating runs for this
419
+ # family). It uses the mise-task schema: each top-level table is a
420
+ # task, addressable from the destination root mise.toml via
421
+ # [task_config] includes.
422
+ #
423
+ # Family-specific tasks (for example a release task with the
424
+ # family's publish flags and secrets provider) belong in the root
425
+ # mise.toml, where they shadow the managed definitions.
426
+ HEADER
427
+ tasks.each do |name, run|
428
+ out << "\n[#{name}]\n"
429
+ out << "description = #{TASK_DESCRIPTIONS.fetch(name).inspect}\n"
430
+ out << "raw = true\n"
431
+ out << "run = #{run.inspect}\n"
432
+ end
433
+ out
434
+ end
435
+ end
257
436
  end
258
437
  end
259
438
  end
@@ -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.18"
8
+ VERSION = "1.3.22"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -3919,15 +3919,17 @@ module Kettle
3919
3919
  local_kettle_dev_gemfile = local_kettle_dev_gemfile_path(local_kettle_dev)
3920
3920
  return local_kettle_dev_gemfile if local_kettle_dev_gemfile
3921
3921
 
3922
- # Release mode: do NOT hand the member child the family-root dev
3923
- # bundle. That bundle resolves the family's own members, which are
3924
- # unreleased by definition while any member is being released — a
3925
- # released-only child bundle cannot materialize it (observed:
3926
- # "Could not find kettle-family-1.3.9 ... in locally installed gems"
3927
- # killed every member's kettle-release). Without BUNDLE_GEMFILE the
3928
- # child resolves kettle-release from its own released dependency.
3929
- return nil unless local_path_env_value?(local_kettle_dev)
3930
-
3922
+ # Execute the release tool from the family root bundle. Each family
3923
+ # has a Gemfile and must be allowed to version-manage its release
3924
+ # tooling there. The member child worktree lockfile is NOT
3925
+ # authoritative for the release tool itself: a stale member lock
3926
+ # resolves an ancient kettle-release whose fixes can never load
3927
+ # (observed: a member lock pinning kettle-dev 3.1.16 and
3928
+ # kettle-family 1.3.11 meant 3.1.17's task-lockfile materialization
3929
+ # never ran, and the build then died with "Could not find
3930
+ # kettle-family-1.3.13 in locally installed gems"). The child env
3931
+ # disables the family's local-path toggles, so the family root
3932
+ # Gemfile resolves its registry declarations — released gems only.
3931
3933
  family_gemfile = File.join(config.root, "Gemfile")
3932
3934
  family_gemfile if File.file?(family_gemfile)
3933
3935
  end
data/lib/kettle/family.rb CHANGED
@@ -42,5 +42,17 @@ require_relative "family/version"
42
42
  module Kettle
43
43
  module Family
44
44
  class Error < StandardError; end
45
+
46
+ # kettle-jem plugin registration (kettle-jem-appraisals pattern). The gem
47
+ # name "kettle-family" resolves to this module as the plugin handle, and
48
+ # kettle-jem calls +register_kettle_jem_plugin(registrar)+ on it when the
49
+ # family opts in via `plugins: [kettle-family]` in its kettle-jem config.
50
+ module_function
51
+
52
+ def register_kettle_jem_plugin(registrar)
53
+ registrar.on_phase("remaining_files", timing: :after) do |context:|
54
+ MiseTasks.materialize_managed_suite(context: context)
55
+ end
56
+ end
45
57
  end
46
58
  end
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.18
4
+ version: 1.3.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -134,7 +134,7 @@ dependencies:
134
134
  version: '3.1'
135
135
  - - ">="
136
136
  - !ruby/object:Gem::Version
137
- version: 3.1.16
137
+ version: 3.1.18
138
138
  type: :runtime
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
@@ -144,7 +144,7 @@ dependencies:
144
144
  version: '3.1'
145
145
  - - ">="
146
146
  - !ruby/object:Gem::Version
147
- version: 3.1.16
147
+ version: 3.1.18
148
148
  - !ruby/object:Gem::Dependency
149
149
  name: kettle-gha-pins
150
150
  requirement: !ruby/object:Gem::Requirement
@@ -567,10 +567,10 @@ licenses:
567
567
  - AGPL-3.0-only
568
568
  metadata:
569
569
  homepage_uri: https://kettle-family.galtzo.com
570
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.18
571
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.18/CHANGELOG.md
570
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.22
571
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.22/CHANGELOG.md
572
572
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
573
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.18
573
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.22
574
574
  funding_uri: https://github.com/sponsors/pboling
575
575
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
576
576
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file