kettle-dev 3.1.7 → 3.1.9

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: 2de1266e7cdbbfd6174c040d8602cf8c3349a423bc301f071ba45da206cba969
4
- data.tar.gz: f93264ee009dbede2c2ff3273a396916a31d51a70b1ac1cab817532e11f396f5
3
+ metadata.gz: 452c1019273fac5fa450b42b6bf0d24fee016a6641971eeb3c5bddcf08efaa64
4
+ data.tar.gz: 4919f5ac1cd4aeb579da6ae378109923cf7fe86c6568b77ab42101e2f8bd4087
5
5
  SHA512:
6
- metadata.gz: 6a10b65136115e119f2b24eea184c114ea09ae969bed38f71005f39f0fe2f1f0ef1a9869449cf70cf895f075c0c01387918b02bafb914237ca3136d2f4ef868f
7
- data.tar.gz: 9e14f9945043f4f104c65e34dae30e11301e84caa63f474555d0befe80a35508d604b331da62306808a927eb881e2a41a66562582896b4145cf22e9051db8c65
6
+ metadata.gz: 58be74c463a06ea48b7571ec6fffa36bcaca0da0eb5e1a7cab2b4d3765f7fe2a4be2932f1cf09bfc496c97a9fa32badcf36182eabcdf58e61f5eb8c7242c9d41
7
+ data.tar.gz: 6e679af06a75e16ff6321f348cae64f8fa8c6884408f4fca7f20c7d5d8b5498bd4cf6bac270c0dc04a84747f3e67dffe7562c89e15d91afc38d11dd7f59e82f6
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,43 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.1.9] - 2026-10-09
34
+
35
+ - TAG: [v3.1.9][3.1.9t]
36
+ - COVERAGE: 91.79% -- 6291/6854 lines in 49 files
37
+ - BRANCH COVERAGE: 75.97% -- 2397/3155 branches in 49 files
38
+ - 51.77% documented
39
+
40
+ ### Changed
41
+
42
+ - [kc] kettle-jem/prepare: updated 5 project files:
43
+ - dependencies (5)
44
+
45
+ - [kc] kettle-jem/template: updated 3 project files:
46
+ - code and tests (2)
47
+ - other (1)
48
+
49
+ ### Fixed
50
+
51
+ - [kc] bundler-pin-no-prerelease: Release step 1 (`update_bundler_and_commit!`) and `appraisal:update` pin the Bundler version when running `bundle update --bundler` instead of leaving it bare. A bare `--bundler` resolves to whatever RubyGems reports as bundler's "latest", which includes prereleases: bundler published `4.1.0.beta1` as latest, so a bare update installed the beta and rewrote `BUNDLED WITH` to it. That beta vendors its own copy of rubygems' URI constants, and loading it against rubygems' copy emits a wall of `already initialized constant Gem::URI::...` warnings on stderr, which broke any spec shelling out to an executable and asserting clean stderr — failing a later release at its coverage step. Pinning matches `LockfileReset#lockfile_command`, which already documents this hazard.
52
+
53
+ ## [3.1.8] - 2026-10-07
54
+
55
+ - TAG: [v3.1.8][3.1.8t]
56
+ - COVERAGE: 91.65% -- 6276/6848 lines in 49 files
57
+ - BRANCH COVERAGE: 75.90% -- 2390/3149 branches in 49 files
58
+ - 51.77% documented
59
+
60
+ ### Added
61
+
62
+ - `Kettle::Dev::RubyGemsVersions.fetch` accepts a `source:` keyword so callers can query a RubyGems-compatible private registry such as gem.coop instead of only rubygems.org. The version cache is now namespaced by source host, so a private registry lagging rubygems.org cannot serve stale answers for the other. The default source keeps the historical bare gem-name cache key, so existing caches remain valid.
63
+
64
+ - `Kettle::Dev::RubyGemsVersions.published_version_numbers(gem_name, source:)` returns the version numbers a registry serves for a gem, or nil when the registry could not be consulted. Callers must treat nil as "unknown" rather than "unpublished" so an offline run is not blocked; an empty list, by contrast, means the registry was reached and serves no versions.
65
+
66
+ - [kc] lockfile-registry-specs: `Kettle::Dev::LockfileReset.registry_gem_specs_from_source` returns GEM-section specs as `{remote:, name:, version:}` so a caller can attribute each pinned version to the registry it was resolved from, which is what makes it possible to detect a locally built and installed gem leaking into a lockfile: bundler resolves against the local gem dir, so an ordinary `bundle install` can pin a version no registry serves, and the resulting entry is a normal GEM-section line carrying a valid checksum taken from the installed spec, so nothing in the lockfile text distinguishes it and "is this version published?" can only be answered against the remote that section recorded. `gem_specs_from_source` is now a thin wrapper over it and keeps returning `[[name, version]]` unchanged.
67
+
68
+ - `Kettle::Dev::RubyGemsVersions.published_version_numbers` accepts a `version:` keyword and forwards it to `fetch` as a cache-bust hint, so the on-disk release marker that `kettle-release` writes after publishing busts the cache for exactly that gem and version instead of for every gem released within the marker TTL. Without it a caller cannot distinguish "published moments ago in this very run" from "released sometime in the last month". Also adds `RubyGemsVersions.recently_released?(gem_name, version)`, which reports whether the marker records that exact gem and version as published within the TTL, treating a missing or corrupt marker as false rather than raising.
69
+
33
70
  ## [3.1.7] - 2026-10-04
34
71
 
35
72
  - TAG: [v3.1.7][3.1.7t]
@@ -3984,7 +4021,11 @@ Please file a bug if you notice a violation of semantic versioning.
3984
4021
  - Selecting will run the selected workflow via `act`
3985
4022
  - This may move to its own gem in the future.
3986
4023
 
3987
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.7...HEAD
4024
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.9...HEAD
4025
+ [3.1.9]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.8...v3.1.9
4026
+ [3.1.9t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.1.9
4027
+ [3.1.8]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.7...v3.1.8
4028
+ [3.1.8t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.1.8
3988
4029
  [3.1.7]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.6...v3.1.7
3989
4030
  [3.1.7t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.1.7
3990
4031
  [3.1.6]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.5...v3.1.6
data/README.md CHANGED
@@ -1067,7 +1067,7 @@ Thanks for RTFM. ☺️
1067
1067
  [📌gitmoji]: https://gitmoji.dev
1068
1068
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1069
1069
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1070
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.824-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1070
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.848-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1071
1071
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
1072
1072
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1073
1073
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -75,19 +75,42 @@ module Kettle
75
75
  end
76
76
 
77
77
  def gem_specs_from_source(lockfile_source)
78
+ registry_gem_specs_from_source(lockfile_source).map { |spec| [spec.fetch(:name), spec.fetch(:version)] }.uniq
79
+ end
80
+
81
+ # Like gem_specs_from_source, but attributes each spec to the registry
82
+ # remote of the GEM section it appeared in.
83
+ #
84
+ # Remote attribution matters for detecting locally installed but
85
+ # unpublished versions: a lockfile can carry several GEM sections with
86
+ # different remotes, and "is this version published?" can only be
87
+ # answered against the source that was actually used. Bundler resolves
88
+ # a locally installed spec during an ordinary `bundle install`, so the
89
+ # pinned entry looks exactly like a published one — same GEM section,
90
+ # valid checksum — and only a registry query for that remote reveals
91
+ # the version does not exist there.
92
+ #
93
+ # @return [Array<Hash>] entries of {remote:, name:, version:}
94
+ def registry_gem_specs_from_source(lockfile_source)
78
95
  in_gem = false
79
96
  in_specs = false
97
+ remote = nil
80
98
  specs = []
81
99
  lockfile_source.each_line do |line|
82
100
  stripped = line.chomp
83
101
  if stripped == "GEM"
84
102
  in_gem = true
85
103
  in_specs = false
104
+ remote = nil
86
105
  next
87
106
  end
88
107
  next unless in_gem
89
108
  break if !stripped.empty? && stripped == stripped.upcase && !stripped.start_with?(" ")
90
109
 
110
+ if stripped.start_with?(" remote:")
111
+ remote = stripped.delete_prefix(" remote:").strip
112
+ next
113
+ end
91
114
  if stripped == " specs:"
92
115
  in_specs = true
93
116
  next
@@ -96,7 +119,9 @@ module Kettle
96
119
  next unless line.start_with?(" ") && !line.start_with?(" ")
97
120
 
98
121
  parsed = parse_lockfile_spec_line(stripped)
99
- specs << [parsed.fetch(:name), parsed.fetch(:version)] if parsed
122
+ next unless parsed
123
+
124
+ specs << {remote: remote, name: parsed.fetch(:name), version: parsed.fetch(:version)}
100
125
  end
101
126
  specs.uniq
102
127
  end
@@ -6,6 +6,16 @@ begin
6
6
  require "kettle/dev/lockfile_reset"
7
7
 
8
8
  bundle = "bundle"
9
+ # The exact Bundler running this rake, so `bundle update --bundler=VERSION`
10
+ # records it instead of resolving to RubyGems' "latest" bundler, which can be
11
+ # a prerelease. Falls back to the bare flag only if no version is
12
+ # determinable, preserving prior behaviour rather than failing.
13
+ bundler_version = if defined?(Bundler) && Bundler::VERSION
14
+ Bundler::VERSION
15
+ elsif (bundler_spec = Gem.loaded_specs["bundler"])
16
+ bundler_spec.version.to_s
17
+ end
18
+ bundler_version_arg = bundler_version ? "--bundler=#{bundler_version}" : "--bundler"
9
19
  unbundled_env = Kettle::Dev::LockfileReset::UNBUNDLED_ENV_KEYS.each_with_object({}) do |key, env|
10
20
  env[key] = nil
11
21
  end
@@ -144,13 +154,20 @@ begin
144
154
  "--quiet"
145
155
  )
146
156
 
147
- # 2) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle update --bundler
157
+ # 2) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle update --bundler=<version>
158
+ #
159
+ # Pinned to the executing Bundler. A bare `--bundler` resolves to
160
+ # whatever RubyGems reports as bundler's "latest", which includes
161
+ # prereleases, so it can install a beta and record it in BUNDLED WITH;
162
+ # that beta's vendored URI then collides with rubygems' copy and
163
+ # spams stderr. See ReleaseCLI#update_bundler_and_commit! and
164
+ # LockfileReset#lockfile_command for the same hazard.
148
165
  run_command.call(
149
166
  "appraisal:update failed: BUNDLE_GEMFILE=Appraisal.root.gemfile bundle update --bundler",
150
167
  appraisal_env,
151
168
  bundle,
152
169
  "update",
153
- "--bundler"
170
+ bundler_version_arg
154
171
  )
155
172
 
156
173
  # 3) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle install
@@ -1864,14 +1864,26 @@ module Kettle
1864
1864
  # Keep Bundler maintenance separate from the release metadata commit.
1865
1865
  # The project lockfiles are shared development state, so the update must
1866
1866
  # be real and committed rather than hidden behind a temporary lockfile.
1867
+ #
1868
+ # The version is pinned to the Bundler actually executing rather than left
1869
+ # bare. `bundle update --bundler` resolves to whatever RubyGems reports as
1870
+ # bundler's "latest", which includes prereleases: bundler published
1871
+ # 4.1.0.beta1 as latest, so a bare update installed the beta and rewrote
1872
+ # BUNDLED WITH to it. That beta vendors its own copy of rubygems' URI
1873
+ # constants, and loading it against rubygems' copy emits a wall of
1874
+ # "already initialized constant Gem::URI::..." warnings on stderr, which
1875
+ # broke any spec shelling out to the executable and asserting clean stderr
1876
+ # — failing a later release at its coverage step. Pinning matches
1877
+ # LockfileReset#lockfile_command, which already documents this hazard.
1867
1878
  def update_bundler_and_commit!
1868
- run_cmd!(release_project_command("bundle update --bundler"))
1879
+ bundler_version = Shellwords.escape(Bundler::VERSION)
1880
+ run_cmd!(release_project_command("bundle update --bundler=#{bundler_version}"))
1869
1881
 
1870
1882
  appraisal_gemfile = File.join(@root, "Appraisal.root.gemfile")
1871
1883
  if File.file?(appraisal_gemfile)
1872
1884
  appraisal_lockfile = File.join(@root, "Appraisal.root.gemfile.lock")
1873
1885
  appraisal_command = "BUNDLE_GEMFILE=#{Shellwords.escape(appraisal_gemfile)} " \
1874
- "BUNDLE_LOCKFILE=#{Shellwords.escape(appraisal_lockfile)} bundle update --bundler"
1886
+ "BUNDLE_LOCKFILE=#{Shellwords.escape(appraisal_lockfile)} bundle update --bundler=#{bundler_version}"
1875
1887
  run_cmd!(release_project_command(appraisal_command))
1876
1888
  run_cmd!(release_project_command("bundle exec rake appraisal:reset"))
1877
1889
  end
@@ -16,19 +16,27 @@ module Kettle
16
16
  VERSION_CACHE_TTL_SECONDS = 30 * 24 * 60 * 60
17
17
  HTTP_OPEN_TIMEOUT_SECONDS = 5
18
18
  HTTP_READ_TIMEOUT_SECONDS = 10
19
+ DEFAULT_SOURCE = "https://rubygems.org"
19
20
  ENV_REFRESH = "KETTLE_RUBYGEMS_REFRESH"
20
21
  ENV_MARKER_PATH = "KETTLE_RUBYGEMS_CACHE_BUST_PATH"
21
22
  ENV_VERSION_CACHE_PATH = "KETTLE_RUBYGEMS_VERSION_CACHE_PATH"
22
23
  ENV_LEGACY_VERSION_CACHE_PATH = "KETTLE_JEM_DEPS_FLOOR_CACHE"
23
24
 
24
25
  class << self
25
- def fetch(gem_name, version_hint: nil, refresh: false)
26
+ # Query a RubyGems-compatible versions API.
27
+ #
28
+ # +source+ selects which registry to ask, because a private registry
29
+ # such as gem.coop can lag rubygems.org: a version present on one may
30
+ # be absent from the other. Cache entries are keyed by source for the
31
+ # same reason, so the two registries cannot poison each other.
32
+ def fetch(gem_name, version_hint: nil, refresh: false, source: DEFAULT_SOURCE)
26
33
  name = gem_name.to_s
27
- cached = cached_versions(name)
34
+ key = cache_key(name, source)
35
+ cached = cached_versions(key)
28
36
  cache_bust = refresh || env_refresh? || fresh_release_marker?(name, version_hint) || cached_behind_version?(cached, version_hint)
29
37
  return cached if cached && !cache_bust
30
38
 
31
- uri = versions_uri(gem_name, cache_bust: cache_bust)
39
+ uri = versions_uri(gem_name, source: source, cache_bust: cache_bust)
32
40
  request = Net::HTTP::Get.new(uri)
33
41
  if cache_bust
34
42
  request["Cache-Control"] = "no-cache"
@@ -44,13 +52,13 @@ module Kettle
44
52
  http.request(request)
45
53
  end
46
54
  if response.code.to_i == 404
47
- write_versions(name, [])
55
+ write_versions(key, [])
48
56
  return []
49
57
  end
50
58
  return cached unless response.is_a?(Net::HTTPSuccess)
51
59
 
52
60
  data = JSON.parse(response.body)
53
- write_versions(name, data) if data.is_a?(Array)
61
+ write_versions(key, data) if data.is_a?(Array)
54
62
  data
55
63
  rescue => error
56
64
  return cached if cached
@@ -58,6 +66,40 @@ module Kettle
58
66
  raise error
59
67
  end
60
68
 
69
+ # Published version numbers for a gem on one registry, or nil when the
70
+ # registry could not be consulted (caller must treat that as "unknown",
71
+ # never as "unpublished", so offline runs are not blocked).
72
+ #
73
+ # +source+ is matched against the remote a lockfile actually recorded,
74
+ # since a private registry such as gem.coop can lag rubygems.org.
75
+ #
76
+ # +version+ is the version the caller is asking about, forwarded as
77
+ # fetch's version_hint. That makes cache-busting precise: the on-disk
78
+ # release marker written by kettle-release busts the cache only for the
79
+ # exact gem+version just published, instead of for every gem released
80
+ # within the marker TTL. Without it a caller cannot tell "this version
81
+ # was published moments ago in this very run" from "this gem was
82
+ # released at some point in the last month".
83
+ def published_version_numbers(gem_name, source: DEFAULT_SOURCE, version: nil)
84
+ versions = fetch(gem_name, version_hint: version, source: normalize_source(source))
85
+ return nil if versions.nil?
86
+
87
+ versions.filter_map { |entry| entry["number"] if entry.is_a?(Hash) }
88
+ rescue
89
+ nil
90
+ end
91
+
92
+ # Whether kettle-release's on-disk marker says this exact gem+version
93
+ # was published recently enough that cached registry data must not be
94
+ # trusted for it.
95
+ #
96
+ # The marker file is the authority on what this machine just published,
97
+ # so any process-local cache layered on top of the registry must consult
98
+ # it rather than serve a pre-publish answer.
99
+ def recently_released?(gem_name, version = nil)
100
+ fresh_release_marker?(gem_name.to_s, version&.to_s)
101
+ end
102
+
61
103
  def mark_released(gem_name, version)
62
104
  return if gem_name.to_s.empty? || version.to_s.empty?
63
105
 
@@ -99,8 +141,25 @@ module Kettle
99
141
 
100
142
  private
101
143
 
102
- def versions_uri(gem_name, cache_bust:)
103
- uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
144
+ # Cache entries are namespaced by source host so a version list fetched
145
+ # from a private registry cannot be served for rubygems.org, or vice
146
+ # versa. The default source keeps the historical bare gem name as its
147
+ # key so existing caches remain valid.
148
+ def cache_key(gem_name, source)
149
+ normalized = normalize_source(source)
150
+ return gem_name.to_s if normalized == DEFAULT_SOURCE
151
+
152
+ "#{URI(normalized).host}:#{gem_name}"
153
+ rescue URI::InvalidURIError
154
+ "#{normalized}:#{gem_name}"
155
+ end
156
+
157
+ def normalize_source(source)
158
+ source.to_s.sub(%r{/+\z}, "")
159
+ end
160
+
161
+ def versions_uri(gem_name, source:, cache_bust:)
162
+ uri = URI("#{normalize_source(source)}/api/v1/versions/#{gem_name}.json")
104
163
  uri.query = "_kettle_cache_bust=#{Time.now.to_i}" if cache_bust
105
164
  uri
106
165
  end
@@ -145,31 +204,31 @@ module Kettle
145
204
  ENV.fetch(ENV_REFRESH, "").match?(Kettle::Dev::ENV_TRUE_RE)
146
205
  end
147
206
 
148
- def cached_versions(gem_name)
207
+ def cached_versions(cache_key)
149
208
  path = version_cache_path
150
209
  return nil if path.empty?
151
210
 
152
- entry = read_version_cache(path).fetch("versions", {})[gem_name]
211
+ entry = read_version_cache(path).fetch("versions", {})[cache_key]
153
212
  return nil unless entry.is_a?(Hash)
154
213
  return nil unless fresh_version_cache_entry?(entry)
155
214
 
156
215
  Array(entry["entries"])
157
216
  end
158
217
 
159
- def write_versions(gem_name, entries)
218
+ def write_versions(cache_key, entries)
160
219
  path = version_cache_path
161
220
  return if path.empty?
162
221
 
163
222
  data = read_version_cache(path)
164
223
  data["versions"] ||= {}
165
- data["versions"][gem_name] = {
224
+ data["versions"][cache_key] = {
166
225
  "cached_at" => Time.now.utc.iso8601,
167
226
  "entries" => entries
168
227
  }
169
228
  FileUtils.mkdir_p(File.dirname(path))
170
229
  File.write(path, JSON.pretty_generate(data) << "\n")
171
230
  rescue => error
172
- warn("[kettle-dev] could not update RubyGems.org version cache: #{error.class}: #{error.message}") if Kettle::Dev::DEBUGGING
231
+ warn("[kettle-dev] could not update RubyGems version cache: #{error.class}: #{error.message}") if Kettle::Dev::DEBUGGING
173
232
  end
174
233
 
175
234
  def read_version_cache(path)
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "3.1.7"
8
+ VERSION = "3.1.9"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.7
4
+ version: 3.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -276,7 +276,7 @@ dependencies:
276
276
  version: '3.2'
277
277
  - - ">="
278
278
  - !ruby/object:Gem::Version
279
- version: 3.2.13
279
+ version: 3.2.14
280
280
  type: :development
281
281
  prerelease: false
282
282
  version_requirements: !ruby/object:Gem::Requirement
@@ -286,7 +286,7 @@ dependencies:
286
286
  version: '3.2'
287
287
  - - ">="
288
288
  - !ruby/object:Gem::Version
289
- version: 3.2.13
289
+ version: 3.2.14
290
290
  - !ruby/object:Gem::Dependency
291
291
  name: stone_checksums
292
292
  requirement: !ruby/object:Gem::Requirement
@@ -422,10 +422,10 @@ licenses:
422
422
  - AGPL-3.0-only
423
423
  metadata:
424
424
  homepage_uri: https://kettle-dev.galtzo.com
425
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.1.7
426
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.1.7/CHANGELOG.md
425
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.1.9
426
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.1.9/CHANGELOG.md
427
427
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
428
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.1.7
428
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.1.9
429
429
  funding_uri: https://github.com/sponsors/pboling
430
430
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
431
431
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
@@ -1 +1 @@
1
- Z5-w�5��:��7�睹��%ڔ�����0cfe�Y1��w�4��Po+̆�K�,������aM΅n�T�K�V�*E\�͕�u�M�C�� v�0q�D��{@8��|�����@�W� 4�0��qg�G�L��'y��k�a��B��i?���ND�&�n� �lw��M�E�B���ݓ<��#�r��2�������E y�6�����h���M��A(G8 Q��̊�1)=9�|8�Tyo&�3Fc�Y�щ�J�P�D]NA�1bS�Eђ��gJ�n���e5�u�<���d�o uq�&:Ĭ)��]ve�������^����0��S��ۖ�K�J��GZ3��5��}�toe$l��яpb� ������D z
1
+ "���5s_�4+���ٻ\�ڋlb=��*��%�.$/��>���Z���=Q�R%���J�+Z ���2����3��YX