kettle-gha-pins 0.3.7 → 0.3.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: e55947f165ea60fdf05e34f18bbd7af157e37c2e0da0c975a721ca6672da0b9f
4
- data.tar.gz: eaaa403c59388d699bc50d0108ff1211d6249933dfd0304a5cd896980a3b646f
3
+ metadata.gz: 40a359f61e73b1b17ee62829c001c9690844f8ccfb944b776ecb21cbd402b382
4
+ data.tar.gz: c5f15b58492474aefb12423903e34e3b4107f8e01dc92d964a0f0ad9c8320408
5
5
  SHA512:
6
- metadata.gz: 24b75d7754e3617cee6eb4178495de33b813e940b65b3a83c64201ad539edcaeb9038cec38b25bafe4f7f9a2510771e4c34d24282043b737b412066580627641
7
- data.tar.gz: afceb46d10b46e25d008a125d3d724f3aec05fc3f468aa388aa0cc899b1f5aae6b64c2271651c74b7fd22da364766740098ec037a385b490453c5cd3a082b06d
6
+ metadata.gz: 786283e4de7a16524b78e7c30552a8622465d559df4edeb8cbfd1808dda7c3cd7c5ad730c19f5d3c35cc328c11cb9829f0fc056f531e787997961bb442ee97ca
7
+ data.tar.gz: c8d901235c8b8ebb5e9aae9fa0918a58965abdd1a61f612a76985df9068ddfa5aaac871e7ace466a2732a0a03821cb3352be232641e692283c4083287b8b4beb
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,28 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.3.9] - 2026-08-05
34
+
35
+ - TAG: [v0.3.9][0.3.9t]
36
+ - COVERAGE: 98.36% -- 961/977 lines in 8 files
37
+ - BRANCH COVERAGE: 89.95% -- 394/438 branches in 8 files
38
+ - 25.71% documented
39
+
40
+ ### Fixed
41
+
42
+ - Emit NDJSON progress events for family orchestration without replaying the full human report.
43
+
44
+ ## [0.3.8] - 2026-08-05
45
+
46
+ - TAG: [v0.3.8][0.3.8t]
47
+ - COVERAGE: 98.34% -- 945/961 lines in 8 files
48
+ - BRANCH COVERAGE: 89.81% -- 388/432 branches in 8 files
49
+ - 25.71% documented
50
+
51
+ ### Fixed
52
+
53
+ - Refresh cached GitHub Action release data when a newer release tag is detected.
54
+
33
55
  ## [0.3.7] - 2026-08-05
34
56
 
35
57
  - TAG: [v0.3.7][0.3.7t]
@@ -260,7 +282,11 @@ Please file a bug if you notice a violation of semantic versioning.
260
282
  - kettle-jem-template-20260720-005 - Generated README Support & Community rows
261
283
  now include a RubyForum help badge.
262
284
 
263
- [Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.7...HEAD
285
+ [Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.9...HEAD
286
+ [0.3.9]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.8...v0.3.9
287
+ [0.3.9t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.9
288
+ [0.3.8]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.7...v0.3.8
289
+ [0.3.8t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.8
264
290
  [0.3.7]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.6...v0.3.7
265
291
  [0.3.7t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.7
266
292
  [0.3.6]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.5...v0.3.6
data/README.md CHANGED
@@ -563,7 +563,7 @@ Thanks for RTFM. ☺️
563
563
  [📌gitmoji]: https://gitmoji.dev
564
564
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
565
565
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
566
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.941-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
566
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.977-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
567
567
  [🔐security]: https://github.com/kettle-dev/kettle-gha-pins/blob/main/SECURITY.md
568
568
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
569
569
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -115,7 +115,8 @@ module Kettle
115
115
  reason: reason,
116
116
  current_version: current_version,
117
117
  versions: available_versions,
118
- latest_outdated: latest_outdated
118
+ latest_outdated: latest_outdated,
119
+ current_sha: current_sha
119
120
  }
120
121
  end
121
122
 
@@ -59,6 +59,7 @@ module Kettle
59
59
  dry_run: true,
60
60
  token: ENV["GITHUB_TOKEN"] || ENV["GH_TOKEN"],
61
61
  json: false,
62
+ events: false,
62
63
  validate: true,
63
64
  write: false,
64
65
  check: false,
@@ -104,8 +105,10 @@ module Kettle
104
105
  }
105
106
 
106
107
  progress_message("Discovering workflow files under #{display_path(@options[:root])}...")
108
+ emit_event("start", root: @options[:root])
107
109
  workflow_files = discover_workflow_files(@options[:root], @options[:reject_patterns])
108
110
  progress_message("Discovered #{workflow_files.length} workflow file(s).")
111
+ emit_event("workflows", total: workflow_files.length)
109
112
 
110
113
  workflows = load_workflows(workflow_files, state)
111
114
  action_count = workflows.sum { |workflow| workflow[:uses_nodes].count { |node| classify_action_ref(node[:value].to_s) } }
@@ -119,6 +122,7 @@ module Kettle
119
122
  )
120
123
  action_plan_cache = {}
121
124
 
125
+ completed_actions = 0
122
126
  workflows.each do |workflow|
123
127
  path = workflow.fetch(:path)
124
128
  text = workflow.fetch(:text)
@@ -134,6 +138,7 @@ module Kettle
134
138
  action = parsed_ref[:action]
135
139
  repo_ref = "#{action[:owner]}/#{action[:repo]}"
136
140
  old_ref = action[:ref]
141
+ cached_before = action_plan_cache.key?(repo_ref)
137
142
  upgrade_plan = resolve_action_plan(
138
143
  cache: action_plan_cache,
139
144
  client: client,
@@ -141,6 +146,16 @@ module Kettle
141
146
  repo_ref: repo_ref,
142
147
  old_ref: old_ref
143
148
  )
149
+ completed_actions += 1
150
+ emit_event(
151
+ "action",
152
+ action_ref: repo_ref,
153
+ path: path,
154
+ line: node[:line] + 1,
155
+ completed: completed_actions,
156
+ total: action_count,
157
+ cache: (cached_before || (client.respond_to?(:last_versions_cache_hit) && client.last_versions_cache_hit)) ? "hit" : "miss"
158
+ )
144
159
 
145
160
  updates = nil
146
161
  if upgrade_plan[:updates]
@@ -154,7 +169,8 @@ module Kettle
154
169
  comment_update_version = VersionRubric.comment_update_version(
155
170
  comment_version,
156
171
  upgrade_plan[:current_version],
157
- known_versions: upgrade_plan.fetch(:versions, [])
172
+ known_versions: upgrade_plan.fetch(:versions, []),
173
+ resolved_sha: upgrade_plan[:current_sha]
158
174
  )
159
175
  if comment_update_version
160
176
  updates = {
@@ -260,6 +276,15 @@ module Kettle
260
276
  end
261
277
  action_progress.stop
262
278
  progress_message("Action resolution checks: #{action_progress.cached_count} cached, #{action_progress.live_count} live.") if action_count.positive?
279
+ emit_event(
280
+ "summary",
281
+ files_scanned: state[:files_scanned],
282
+ changed_files: state[:changed_files].length,
283
+ updates: state[:updates],
284
+ failures: state[:failures],
285
+ cached: action_progress.cached_count,
286
+ live: action_progress.live_count
287
+ )
263
288
 
264
289
  print_report(state)
265
290
  return 2 unless state[:failures].zero?
@@ -309,6 +334,9 @@ module Kettle
309
334
  opt.on("--json", "Emit JSON report") do
310
335
  @options[:json] = true
311
336
  end
337
+ opt.on("--events", "Emit line-delimited JSON events") do
338
+ @options[:events] = true
339
+ end
312
340
  opt.on("--[no-]progress", "Show progress feedback on STDERR (default: on unless --json)") do |bool|
313
341
  @options[:progress] = bool
314
342
  end
@@ -383,7 +411,13 @@ module Kettle
383
411
  def progress_enabled?
384
412
  return @options[:progress] unless @options[:progress].nil?
385
413
 
386
- !@options[:json]
414
+ !@options[:json] && !@options[:events]
415
+ end
416
+
417
+ def emit_event(action, payload = {})
418
+ return unless @options[:events]
419
+
420
+ puts JSON.generate({"event_version" => 1, "type" => "gha_sha_pins_#{action}", "action" => action}.merge(payload))
387
421
  end
388
422
 
389
423
  def progress_message(message)
@@ -857,8 +891,12 @@ module Kettle
857
891
  lines << "Recommended fix: kettle-gha-pins --write --upgrade #{@options[:upgrade]}"
858
892
  end
859
893
 
860
- puts lines.join("
861
- ")
894
+ output = lines.join("\n")
895
+ if @options[:events]
896
+ @err.puts output
897
+ else
898
+ puts output
899
+ end
862
900
  end
863
901
  end
864
902
  end
@@ -38,9 +38,11 @@ module Kettle
38
38
  unless @refresh_cache
39
39
  cached = @persistent_cache&.versions_for_repo(repo_ref, fresh: true)
40
40
  if cached
41
- @last_versions_cache_hit = true
42
- @release_cache[repo_ref] = cached
43
- return cached
41
+ unless cache_requires_refresh?(repo_ref, cached)
42
+ @last_versions_cache_hit = true
43
+ @release_cache[repo_ref] = cached
44
+ return cached
45
+ end
44
46
  end
45
47
  stale = @persistent_cache&.versions_for_repo(repo_ref, fresh: false)
46
48
  end
@@ -62,13 +64,13 @@ module Kettle
62
64
  cached_versions(repo_ref, stale)
63
65
  end
64
66
 
65
- def commit_sha(repo_ref, ref)
67
+ def commit_sha(repo_ref, ref, refresh: false)
66
68
  return nil if repo_ref.to_s.empty? || ref.to_s.empty?
67
69
 
68
70
  cache_key = "commit:#{repo_ref}:#{ref}"
69
- return @commit_cache[cache_key] if @commit_cache.key?(cache_key)
71
+ return @commit_cache[cache_key] if @commit_cache.key?(cache_key) && !refresh
70
72
 
71
- unless @refresh_cache
73
+ unless @refresh_cache || refresh
72
74
  cached = @persistent_cache&.ref_sha(repo_ref, ref, fresh: true)
73
75
  if cached
74
76
  @commit_cache[cache_key] = cached
@@ -104,6 +106,26 @@ module Kettle
104
106
  versions
105
107
  end
106
108
 
109
+ # Release tags can be added or retargeted inside the cache TTL. Probe
110
+ # the latest release before trusting a cached inventory, and refresh
111
+ # only when that probe identifies a tag or target the cache lacks.
112
+ def cache_requires_refresh?(repo_ref, cached)
113
+ latest = request_json("/repos/#{repo_ref}/releases/latest")
114
+ return false unless latest.is_a?(Hash)
115
+
116
+ tag = latest["tag_name"].to_s
117
+ return false unless VersionRubric.parse(tag)
118
+
119
+ cached_entry = cached.find { |entry| entry[:tag].to_s == tag }
120
+ return true unless cached_entry
121
+
122
+ latest_sha = commit_sha(repo_ref, tag, refresh: true)
123
+ cached_sha = cached_entry[:sha].to_s
124
+ return !latest_sha.to_s.empty? if cached_sha.empty?
125
+
126
+ !latest_sha.to_s.empty? && latest_sha != cached_sha
127
+ end
128
+
107
129
  def build_release_versions(data, tag_shas)
108
130
  release_dates = {}
109
131
  release_tags = data.each_with_object([]) do |release, memo|
@@ -143,7 +165,7 @@ module Kettle
143
165
  when "commit"
144
166
  memo[tag] = sha
145
167
  when "tag"
146
- memo[tag] = nil
168
+ memo[tag] = annotated_tag_commit_sha(repo_ref, sha)
147
169
  end
148
170
  end
149
171
  end
@@ -6,7 +6,7 @@ module Kettle
6
6
  # Version namespace for this gem.
7
7
  module Version
8
8
  # Current gem version.
9
- VERSION = "0.3.7"
9
+ VERSION = "0.3.9"
10
10
  end
11
11
  # Current gem version exposed at the traditional constant location.
12
12
  VERSION = Version::VERSION # Traditional Constant Location
@@ -112,12 +112,17 @@ module Kettle
112
112
  # the comment even when both values parse to version objects).
113
113
  # 2. Normalize equivalent spellings (v2, v2.0, v2.0.0...) to the most
114
114
  # specific canonical form available for that SHA in known_versions.
115
- def comment_update_version(comment_version, resolved_version, known_versions: [])
115
+ def comment_update_version(comment_version, resolved_version, resolved_sha: nil, known_versions: [])
116
116
  comment = parse(comment_version)
117
117
  resolved = parse(resolved_version)
118
118
  return nil unless comment && resolved
119
119
 
120
- canonical = canonical_version_for(resolved_version, known_versions)
120
+ canonical = canonical_version_for(
121
+ resolved_version,
122
+ known_versions,
123
+ resolved_sha: resolved_sha,
124
+ comment_version: comment_version
125
+ )
121
126
  return nil if comment_version.to_s == canonical
122
127
 
123
128
  canonical
@@ -127,18 +132,40 @@ module Kettle
127
132
  versions.select { |entry| entry.fetch(:version_obj, nil) == version_obj }
128
133
  end
129
134
 
130
- # When multiple release spellings resolve to the same SHA, prefer the
131
- # most specific equivalent tag. If there is no known equivalent, fall
132
- # back to the plain resolved version string.
133
- def canonical_version_for(resolved_version, known_versions)
135
+ # Canonical spellings are version-specific and SHA-aware.
136
+ #
137
+ # - For SHA-tracked tags, prefer the most explicit spelling that resolves
138
+ # to the same SHA as the currently resolved target.
139
+ # - If the resolved SHA is known but the currently-commented spellings
140
+ # are tied to a different SHA, keep the in-file spelling when the
141
+ # semantic version is unchanged, so we don't rewrite between aliases that
142
+ # no longer represent the same object.
143
+ # - Otherwise fall back to the resolved version form.
144
+ def canonical_version_for(resolved_version, known_versions, resolved_sha: nil, comment_version: nil)
134
145
  resolved = parse(resolved_version)
135
146
  return resolved_version.to_s unless resolved
136
147
 
137
148
  equivalents = entries_for_semantic_version(known_versions, resolved)
149
+ if !resolved_sha.to_s.empty?
150
+ sha_matched_equivalents = equivalents.select do |entry|
151
+ entry.fetch(:sha).to_s == resolved_sha.to_s
152
+ end
153
+ equivalents = sha_matched_equivalents unless sha_matched_equivalents.empty?
154
+
155
+ if sha_matched_equivalents.empty? &&
156
+ comment_version &&
157
+ parse(comment_version) == resolved
158
+ return strip_leading_v(comment_version)
159
+ end
160
+ end
138
161
  preferred = equivalents.max_by { |entry| sort_key(entry) }
139
162
  preferred ? preferred[:version] : resolved.to_s
140
163
  end
141
164
 
165
+ def strip_leading_v(text)
166
+ text.to_s.sub(/\A[vV]/, "")
167
+ end
168
+
142
169
  def normalize_upgrade_level(level)
143
170
  normalized = level.to_s.downcase
144
171
  VALID_UPGRADE_LEVELS.include?(normalized) ? normalized : DEFAULT_UPGRADE_LEVEL
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-gha-pins
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -301,10 +301,10 @@ licenses:
301
301
  - MIT
302
302
  metadata:
303
303
  homepage_uri: https://github.com/kettle-dev/kettle-gha-pins
304
- source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.3.7
305
- changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.3.7/CHANGELOG.md
304
+ source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.3.9
305
+ changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.3.9/CHANGELOG.md
306
306
  bug_tracker_uri: https://github.com/kettle-dev/kettle-gha-pins/issues
307
- documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.7
307
+ documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.9
308
308
  funding_uri: https://github.com/sponsors/pboling
309
309
  wiki_uri: https://github.com/kettle-dev/kettle-gha-pins/wiki
310
310
  news_uri: https://www.railsbling.com/tags/kettle-gha-pins
metadata.gz.sig CHANGED
Binary file