kettle-family 1.2.22 → 1.2.23

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: 8352dd3e0095e5bdec1437a5e0098a50e375f64362ffa592aa01aa056d48172b
4
- data.tar.gz: 947519ebac1a4a0ad9c468efd5defaaddcec2feabdaff037e3470e3c19a0cc2c
3
+ metadata.gz: c7444a3d6ecfad0866aae1d481043a9daebb360a185d628ffff7250dc46d8cf4
4
+ data.tar.gz: dbfa71d55c722830254b219d60b98e330918fc853e5d76f026c90e1b6929d971
5
5
  SHA512:
6
- metadata.gz: bd6b1cf2bbef2d4cabdcb2c8fadaf2a71c4437b1fbef55c5d2ae716997dc69276ac1dd3cc95a2d8b457a5599754169f7d004bbe38ba52c2afdbae3b41e04d5b9
7
- data.tar.gz: 2e6ea899f8012f72619f58fb1cefb1495511b8a3252d5e7c8be57c1f204ed92b55439e1d0c10278b57f4793bab8043d572ba38eed9b7b0b1f218f6468ab54997
6
+ metadata.gz: a10e4a4db9fe4bc4769c45757a93767286b19de7657b09b4e6cac32877510f5a46342c9a566dd35776d3ed26ac91e4b7c688deaf646107e8d162873331ed64de
7
+ data.tar.gz: dbb1cab4664586a13210038ac5e48a8fb8be2b7e5ec6022cc516f40f58da2c9587c7f3d884b6acc31c7afd89fc01b4b1e9d15b8296bff2986e037b9d5474f6c7
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,36 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.2.23] - 2026-08-02
34
+
35
+ - TAG: [v1.2.23][1.2.23t]
36
+ - COVERAGE: 93.91% -- 4658/4960 lines in 31 files
37
+ - BRANCH COVERAGE: 76.23% -- 1822/2390 branches in 31 files
38
+ - 29.31% documented
39
+
40
+ ### Changed
41
+
42
+ - Budget kettle-jem template workers across concurrent family members.
43
+
44
+ ### Fixed
45
+
46
+ - Remove trailing blank lines when retiring legacy appraisal source declarations during templating.
47
+
48
+ - Allow direct --only member selections to proceed when unrelated member Gemfiles are temporarily unparsable.
49
+
50
+ - kettle-jem-template-20260802-001 - Devcontainer JSON files now merge as JSONC,
51
+ preserving comments and trailing commas during template updates.
52
+
53
+ - Preserve the completed generated kettle-jem lockfile when restoring an autostash conflicts only on that file.
54
+
55
+ - Mise-managed template members now retain PATH when launching nested Bundler commands.
56
+
57
+ - Local kettle-jem template commands now bypass login-shell PATH resets.
58
+
59
+ - Nested Bundler commands now retain the active Ruby executable paths during autostashed templating.
60
+
61
+ - Autostashed mise configuration remains available while templating runs.
62
+
33
63
  ## [1.2.22] - 2026-08-01
34
64
 
35
65
  - TAG: [v1.2.22][1.2.22t]
@@ -440,10 +470,6 @@ Please file a bug if you notice a violation of semantic versioning.
440
470
 
441
471
  ### Added
442
472
 
443
- - kettle-jem-template-20260729-005 - Gemspec metadata now publishes this
444
- project's RubyForum tag as `mailing_list_uri`, and support docs link to the
445
- tagged RubyForum community alongside Discord.
446
-
447
473
  ### Fixed
448
474
 
449
475
  - Family release changelog phases now pass `--yes` to configured
@@ -451,25 +477,16 @@ Please file a bug if you notice a violation of semantic versioning.
451
477
  behavior and keeping publish runs non-interactive unless `--no-accept` is
452
478
  used.
453
479
 
454
- - kettle-jem-template-20260728-003 - Generated dep-heads workflows now run
455
- TruffleRuby jobs with current RubyGems and Bundler, avoiding setup failures
456
- before the test suite starts.
457
480
  - kettle-jem-template-20260728-004 - Generated dep-heads workflows now use the
458
481
  setup-ruby Bundler install path for direct appraisal Gemfiles, avoiding rv
459
482
  lockfile parser failures on Git and path dependencies.
460
483
  - kettle-jem-template-20260728-005 - VersionGem bootstrap now creates the
461
484
  missing canonical version spec when a project only has shim namespace version
462
485
  specs.
463
- - kettle-jem-template-20260729-001 - Generated JRuby 9.4 workflows now use the
464
- legacy manual bundle install path, avoiding setup-time Bundler full-index
465
- failures against `gem.coop`.
466
486
  - kettle-jem-template-20260729-002 - VersionGem bootstrap now preserves
467
487
  and templates dedicated `version_gem.rb` entrypoints even when the gemspec
468
488
  dependency is intentionally omitted, and generated anonymous-loader specs
469
489
  cover both `version.rb` and `version_gem.rb`.
470
- - kettle-jem-template-20260729-003 - Old-Ruby gems below the VersionGem runtime
471
- floor now get managed minimal `version.rb` files and anonymous-loader version
472
- specs without adding `version_gem`.
473
490
 
474
491
  ## [1.2.3] - 2026-07-28
475
492
 
@@ -691,11 +708,6 @@ Please file a bug if you notice a violation of semantic versioning.
691
708
 
692
709
  ### Changed
693
710
 
694
- - kettle-jem-template-20260725-001 - Generated JRuby and TruffleRuby workflow
695
- files now run when pull request head branches start with `feature/release`,
696
- so release CI monitoring does not report intentionally skipped engine
697
- workflows as failures.
698
-
699
711
  ## [1.1.5] - 2026-07-25
700
712
 
701
713
  - TAG: [v1.1.5][1.1.5t]
@@ -879,8 +891,6 @@ Please file a bug if you notice a violation of semantic versioning.
879
891
 
880
892
  - `kettle-family release-state` now reports the checked-out branch and displays
881
893
  release distance as local/remote ahead and behind counts.
882
- - kettle-jem-template-20260720-001 - Generated READMEs can now render
883
- template-managed corporate sponsor logos from project or family config.
884
894
  - kettle-jem-template-20260720-002 - Generated development Gemfiles now use the
885
895
  released `tree_sitter_language_pack` gem 1.13.3 or newer by default.
886
896
  - kettle-jem-template-20260720-003 - Generated StructuredMerge Git diff driver
@@ -967,8 +977,6 @@ Please file a bug if you notice a violation of semantic versioning.
967
977
  the next `kettle-jem` release depends on unreleased `kettle-family` fixes.
968
978
  - Raised the runtime Ruby floor to 4.0.0.
969
979
 
970
- - kettle-jem-template-20260716-001 - Shim gemspec manifests now include
971
- `LICENSE.md` instead of nonexistent `LICENSE.txt`.
972
980
  - kettle-jem-template-20260716-002 - Generated gemspec manifests now ship fewer
973
981
  repository-only files by default to reduce downstream distro packaging churn.
974
982
 
@@ -1652,7 +1660,9 @@ Please file a bug if you notice a violation of semantic versioning.
1652
1660
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
1653
1661
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
1654
1662
 
1655
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.22...HEAD
1663
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.23...HEAD
1664
+ [1.2.23]: https://github.com/kettle-dev/kettle-family/compare/v1.2.22...v1.2.23
1665
+ [1.2.23t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.23
1656
1666
  [1.2.22]: https://github.com/kettle-dev/kettle-family/compare/v1.2.21...v1.2.22
1657
1667
  [1.2.22t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.22
1658
1668
  [1.2.21]: https://github.com/kettle-dev/kettle-family/compare/v1.2.20...v1.2.21
data/README.md CHANGED
@@ -828,7 +828,7 @@ Thanks for RTFM. ☺️
828
828
  [📌gitmoji]: https://gitmoji.dev
829
829
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
830
830
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
831
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.928-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
831
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.960-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
832
832
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
833
833
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
834
834
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -69,9 +69,22 @@ module Kettle
69
69
  line_start = source.rindex("\n", node.location.start_offset - 1).to_i + 1
70
70
  line_end = source.index("\n", node.location.end_offset) || source.length
71
71
  line_end += 1 if line_end < source.length
72
+ if final_standalone_call?(source, node, line_start, line_end)
73
+ # Remove the preceding separator and normalize EOF when the retired
74
+ # declaration is the final standalone line in the file.
75
+ line_start = source[0...line_start].rstrip.length
76
+ return Kettle::Dev::VersionBump.file_edit(path, source, line_start, source.length, "\n")
77
+ end
78
+
72
79
  Kettle::Dev::VersionBump.file_edit(path, source, line_start, line_end, "")
73
80
  end
74
81
 
82
+ def final_standalone_call?(source, node, line_start, line_end)
83
+ source[line_start...node.location.start_offset].to_s.strip.empty? &&
84
+ source[node.location.end_offset...line_end].to_s.strip.empty? &&
85
+ source[line_end..].to_s.strip.empty?
86
+ end
87
+
75
88
  def legacy_appraisal_call?(node)
76
89
  return false unless node.is_a?(Prism::CallNode) && node.name == :gem
77
90
 
@@ -625,7 +625,11 @@ module Kettle
625
625
  def build_report(command, options)
626
626
  config = Config.load(root: options[:root], path: options[:config])
627
627
  start_at = parse_start_at(options[:start_at])
628
- discovery = Discovery.new(config: config)
628
+ effective_only = default_only_filter(command: command, only: options[:only])
629
+ discovery = Discovery.new(
630
+ config: config,
631
+ release_dependency_member_names: direct_member_only_names(effective_only)
632
+ )
629
633
  members = discovery.members
630
634
  ordered = if command == "install"
631
635
  install_order(members, config)
@@ -634,7 +638,6 @@ module Kettle
634
638
  else
635
639
  Orderer.new(members: members, mode: config.order_mode, hints: config.order_hints).ordered
636
640
  end
637
- effective_only = default_only_filter(command: command, only: options[:only])
638
641
  release_state_results = release_state_results_for_selection(config: config, members: ordered, only: effective_only)
639
642
  selected = Selection.new(members: ordered, release_state_results: release_state_results).apply(only: effective_only, exclude: options[:exclude], start_at: start_at.member)
640
643
  result_members = selected
@@ -683,6 +686,13 @@ module Kettle
683
686
  only
684
687
  end
685
688
 
689
+ def direct_member_only_names(only)
690
+ names = only.to_s.split(",").map(&:strip).reject(&:empty?)
691
+ return nil if names.empty? || names.any? { |name| Selection.status_token?(name) }
692
+
693
+ names
694
+ end
695
+
686
696
  def command_results_for_current_branch(command:, config:, members:, options:, start_at: StartAt.new(nil, nil))
687
697
  return mise_trust_results(config: config, members: members) if command == "mise-trust"
688
698
  return bump_version_results(members: members, options: options, phase: command) if %w[bump bump-version].include?(command)
@@ -586,6 +586,15 @@ module Kettle
586
586
 
587
587
  def process_env(member:, env:)
588
588
  base_env = unbundled_process_env
589
+ # Bundler's unbundled environment intentionally omits activation
590
+ # variables, but it may also omit PATH. When `unsetenv_others` is used
591
+ # for mise-managed members, dropping PATH makes nested commands such
592
+ # as `bundle` unresolvable inside kettle-jem.
593
+ base_env["PATH"] = if base_env.key?("PATH")
594
+ executable_path(base_env["PATH"])
595
+ else
596
+ ENV["PATH"]
597
+ end
589
598
  return base_env.merge(env) unless mise_configured?(member)
590
599
 
591
600
  base_env.merge(sensitive_env(env))
@@ -603,6 +612,11 @@ module Kettle
603
612
  SENSITIVE_ENV_KEYS.include?(key.to_s)
604
613
  end
605
614
 
615
+ def executable_path(path)
616
+ paths = [Gem.bindir, File.dirname(RbConfig.ruby), path, ENV["PATH"]]
617
+ paths.compact.map(&:to_s).flat_map { |value| value.split(File::PATH_SEPARATOR) }.reject(&:empty?).uniq.join(File::PATH_SEPARATOR)
618
+ end
619
+
606
620
  def unbundled_process_env
607
621
  return Bundler.original_env if defined?(Bundler) && Bundler.respond_to?(:original_env)
608
622
  return Bundler.unbundled_env if defined?(Bundler) && Bundler.respond_to?(:unbundled_env)
@@ -5,8 +5,9 @@ require "open3"
5
5
  module Kettle
6
6
  module Family
7
7
  class Discovery
8
- def initialize(config:)
8
+ def initialize(config:, release_dependency_member_names: nil)
9
9
  @config = config
10
+ @release_dependency_member_names = release_dependency_member_names&.map(&:to_s)
10
11
  @warnings = []
11
12
  end
12
13
 
@@ -18,7 +19,7 @@ module Kettle
18
19
 
19
20
  private
20
21
 
21
- attr_reader :config
22
+ attr_reader :config, :release_dependency_member_names
22
23
 
23
24
  def build_members
24
25
  @warnings = []
@@ -51,11 +52,15 @@ module Kettle
51
52
  end
52
53
 
53
54
  def configured_root_members
54
- config.configured_member_roots.map { |root| member_from_gemspec(primary_gemspec(root)) }
55
+ config.configured_member_roots
56
+ .select { |root| selected_configured_root?(root) }
57
+ .map { |root| member_from_gemspec(primary_gemspec(root)) }
55
58
  end
56
59
 
57
60
  def explicit_members
58
- config.explicit_members.map do |entry|
61
+ config.explicit_members.filter_map do |entry|
62
+ next unless selected_configured_root?(entry.fetch("root"))
63
+
59
64
  gemspec_path = if entry["gemspec"]
60
65
  File.expand_path(entry["gemspec"], entry.fetch("root"))
61
66
  else
@@ -65,6 +70,12 @@ module Kettle
65
70
  end
66
71
  end
67
72
 
73
+ def selected_configured_root?(root)
74
+ return true if release_dependency_member_names.nil?
75
+
76
+ release_dependency_member_names.include?(File.basename(File.expand_path(root)))
77
+ end
78
+
68
79
  def primary_gemspec(root)
69
80
  gemspecs = Dir.glob(File.join(root, "*.gemspec"))
70
81
  raise Error, "no gemspec found in #{root}" if gemspecs.empty?
@@ -104,18 +115,23 @@ module Kettle
104
115
  version_file: version_file(root, spec.name),
105
116
  version: spec.version.to_s,
106
117
  dependencies: dependencies,
107
- release_dependencies: release_dependencies(root: root, spec: spec, dependencies: dependencies),
118
+ release_dependencies: release_dependencies(
119
+ root: root,
120
+ spec: spec,
121
+ dependencies: dependencies,
122
+ include_gemfile_dependencies: release_dependency_member_names.nil? || release_dependency_member_names.include?(spec.name)
123
+ ),
108
124
  required_ruby_version: required_ruby_version(spec),
109
125
  licenses: licenses(spec),
110
126
  authors: authors(spec)
111
127
  )
112
128
  end
113
129
 
114
- def release_dependencies(root:, spec:, dependencies:)
130
+ def release_dependencies(root:, spec:, dependencies:, include_gemfile_dependencies:)
115
131
  (
116
132
  dependencies +
117
133
  spec.development_dependencies.map(&:name) +
118
- gemfile_dependencies(root)
134
+ (include_gemfile_dependencies ? gemfile_dependencies(root) : [])
119
135
  ).uniq.sort
120
136
  end
121
137
 
@@ -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.2.22"
8
+ VERSION = "1.2.23"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -237,22 +237,25 @@ module Kettle
237
237
  phase: "template_autostash_restore",
238
238
  command: ["git", "stash", "pop", stash.fetch(:ref)]
239
239
  )
240
- next restore if restore.ok? || !template_lockfile_only_conflict?(stash.fetch(:member))
240
+ next restore if restore.ok? || !template_generated_lockfile_only_conflict?(stash.fetch(:member))
241
241
 
242
242
  runner.call(
243
243
  member: stash.fetch(:member),
244
- phase: "template_autostash_lockfile_recovery",
244
+ phase: "template_autostash_generated_lockfile_recovery",
245
245
  command: [
246
246
  "sh", "-lc",
247
- "git checkout --ours -- Gemfile.lock && git add -- Gemfile.lock && git stash drop #{Shellwords.escape(stash.fetch(:ref))}"
247
+ "paths=$(git diff --name-only --diff-filter=U); " \
248
+ "git checkout --ours -- $paths && git add -- $paths && " \
249
+ "git stash drop #{Shellwords.escape(stash.fetch(:ref))}"
248
250
  ]
249
251
  )
250
252
  end
251
253
  end
252
254
 
253
- def template_lockfile_only_conflict?(member)
255
+ def template_generated_lockfile_only_conflict?(member)
254
256
  stdout, _stderr, status = Open3.capture3("git", "diff", "--name-only", "--diff-filter=U", chdir: member.root)
255
- status.success? && stdout.lines.map(&:strip).reject(&:empty?) == ["Gemfile.lock"]
257
+ conflicts = stdout.lines.map(&:strip).reject(&:empty?).sort
258
+ status.success? && !conflicts.empty? && conflicts.all? { |path| ["Gemfile.lock", ".structuredmerge/kettle-jem.lock"].include?(path) }
256
259
  end
257
260
 
258
261
  def template_branch_sync_results(branch_members, runner:)
@@ -281,7 +284,12 @@ module Kettle
281
284
 
282
285
  def template_autostash_command(member)
283
286
  label = "kettle-family-template-#{member.name}-#{Process.pid}-#{Time.now.to_i}"
284
- "git stash push --include-untracked --message #{Shellwords.escape(label)} && git stash list -1 --format=%gd"
287
+ "git stash push --include-untracked --message #{Shellwords.escape(label)} && " \
288
+ "ref=$(git stash list -1 --format=%gd) && " \
289
+ "git checkout \"$ref\" -- .tool-versions 2>/dev/null || true; " \
290
+ "for path in mise.toml .mise.toml; do " \
291
+ "if git cat-file -e \"$ref^3:$path\" 2>/dev/null; then git show \"$ref^3:$path\" > \"$path\"; fi; " \
292
+ "done; printf '%s\\n' \"$ref\""
285
293
  end
286
294
 
287
295
  def template_dirty_worktree_result(member, dirty_paths)
@@ -2031,6 +2039,7 @@ module Kettle
2031
2039
  if command == "template"
2032
2040
  env["KETTLE_JEM_TEMPLATE_PROFILE"] = config.template_profile if config.template_profile
2033
2041
  env["KJ_REPOSITORY_TOPOLOGY"] = config.template_repository_topology if config.template_repository_topology
2042
+ env["KETTLE_JEM_THREAD_WORKERS"] ||= template_thread_worker_budget.to_s
2034
2043
  if monorepo_template?
2035
2044
  template_git_lock_path = template_git_commit_lock_path
2036
2045
  env["KETTLE_JEM_GIT_LOCK"] = template_git_lock_path
@@ -2114,12 +2123,17 @@ module Kettle
2114
2123
  prefix = prefix[0...-2] if prefix.last(2) == %w[bundle exec]
2115
2124
  prefix + [RbConfig.ruby, executable] + argv[(index + 1)..]
2116
2125
  else
2117
- replacement = "#{Shellwords.escape(RbConfig.ruby)} #{Shellwords.escape(executable)}"
2118
- text = command_text.to_s
2119
- return text.sub(/\bbundle\s+exec\s+kettle-jem\b/, replacement) if text.match?(/\bbundle\s+exec\s+kettle-jem\b/)
2126
+ argv = Shellwords.split(command_text.to_s)
2127
+ index = argv.index("kettle-jem")
2128
+ return command_text unless index
2129
+ return command_text if argv.any? { |token| %w[&& || ; |].include?(token) }
2120
2130
 
2121
- text.sub(/\bkettle-jem\b/, replacement)
2131
+ prefix = argv[0...index]
2132
+ prefix = prefix[0...-2] if prefix.last(2) == %w[bundle exec]
2133
+ prefix + [RbConfig.ruby, executable] + argv[(index + 1)..]
2122
2134
  end
2135
+ rescue ArgumentError
2136
+ command_text
2123
2137
  end
2124
2138
 
2125
2139
  def local_kettle_jem_executable
@@ -2958,6 +2972,11 @@ module Kettle
2958
2972
  env
2959
2973
  end
2960
2974
 
2975
+ def template_thread_worker_budget
2976
+ member_jobs = template_jobs(members)
2977
+ [1, (Etc.nprocessors / member_jobs) - 1].max
2978
+ end
2979
+
2961
2980
  def normalize_release_lockfiles(member:, runner:, memo:)
2962
2981
  result = runner.call(
2963
2982
  member: member,
data/lib/kettle/family.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "family/version"
4
3
  require_relative "family/member"
5
4
  require_relative "family/command_result"
6
5
  require_relative "family/release_state_result"
@@ -26,6 +25,7 @@ require_relative "family/orderer"
26
25
  require_relative "family/selection"
27
26
  require_relative "family/report"
28
27
  require_relative "family/cli"
28
+ require_relative "family/version"
29
29
 
30
30
  module Kettle
31
31
  module Family
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.2.22
4
+ version: 1.2.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -345,10 +345,10 @@ licenses:
345
345
  - AGPL-3.0-only
346
346
  metadata:
347
347
  homepage_uri: https://kettle-family.galtzo.com
348
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.22
349
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.22/CHANGELOG.md
348
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.23
349
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.23/CHANGELOG.md
350
350
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
351
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.22
351
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.23
352
352
  funding_uri: https://github.com/sponsors/pboling
353
353
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
354
354
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file