kettle-gha-pins 0.2.1 → 0.3.0

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: 133a74b904371a5a48a6fe9bc0483ae4c3925b99167f394b452386b0a69664fd
4
- data.tar.gz: d43ede8e0f4a3f3a1400b489e46d077d5744a6c4b0b5231b46b2de4c60951814
3
+ metadata.gz: 4b35d635c79b4982830fe80cda9f003e3cb5b6ad146fe1f35a2d9826852953ef
4
+ data.tar.gz: 2ca635cc1ee80d6bbb1511d0371b880b199dfe8bf1a0f0265a0ec465d60345c7
5
5
  SHA512:
6
- metadata.gz: c59c539a7107ea0b9f69464c06656ad317c1515099861e995c022036af1c6cf0c3e0e4227c58f97262f32d2b1b4fd2f4a536eec305c0f5ca93623116ad56eceb
7
- data.tar.gz: 278b5a77866045c05a677a5e426359a7c34afe2556523a92ae042680a27e6e013e928124e42b0b26a8acc55d446370020e445114eb145c6bc77084cef1045a42
6
+ metadata.gz: 623fd4769e4597bff617136531ffbc403c9f8252ba67a7cbf8fa86a7304aa0e3e4d1e20a77020ca07dfdc72207b59c67f11fb4480291a59e163c2389cf8c5f93
7
+ data.tar.gz: 4dec38a332e0ed5a447f585f3d0cf41338aba8e1d6a0af5beb51a5116ceb12bb6ee4b9e43d02979d189b14fc1c1cb3cc3737c09160f931d999b0ed66c3fe01be
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,31 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.3.0] - 2026-07-23
34
+
35
+ - TAG: [v0.3.0][0.3.0t]
36
+ - COVERAGE: 98.92% -- 915/925 lines in 8 files
37
+ - BRANCH COVERAGE: 90.93% -- 371/408 branches in 8 files
38
+ - 22.77% documented
39
+
40
+ ### Changed
41
+
42
+ - `kettle-gha-pins` now defaults `--upgrade` to `major`, making
43
+ `kettle-gha-pins --check` fail for any unapplied GitHub Actions pin update
44
+ unless callers choose a narrower upgrade level.
45
+ - `kettle-gha-pins --check` now supports `--cooldown-days` and
46
+ `KETTLE_GHA_PINS_COOLDOWN_DAYS` so projects can warn on freshly released
47
+ action version upgrades before enforcing them.
48
+
49
+ - Action-resolution progress now uses `tty-progressbar` multi-line bars so
50
+ cached, live, and skipped counters no longer overwrite each other on TTYs.
51
+
52
+ ### Fixed
53
+
54
+ - GitHub Actions pin cache writes now persist tag SHA refs from release version
55
+ metadata, so repeated runs can reuse cached action resolution instead of
56
+ rechecking freshly cached actions live.
57
+
33
58
  ## [0.2.1] - 2026-07-22
34
59
 
35
60
  - TAG: [v0.2.1][0.2.1t]
@@ -91,7 +116,9 @@ Please file a bug if you notice a violation of semantic versioning.
91
116
  - kettle-jem-template-20260720-005 - Generated README Support & Community rows
92
117
  now include a RubyForum help badge.
93
118
 
94
- [Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.2.1...HEAD
119
+ [Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.0...HEAD
120
+ [0.3.0]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.2.1...v0.3.0
121
+ [0.3.0t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.0
95
122
  [0.2.1]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.2.0...v0.2.1
96
123
  [0.2.1t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.2.1
97
124
  [0.2.0]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -35,17 +35,18 @@ For ordinary workflow pin maintenance, install this gem and run:
35
35
 
36
36
  ```console
37
37
  kettle-gha-pins --check
38
- kettle-gha-pins --write --upgrade patch
38
+ kettle-gha-pins --write --upgrade major
39
39
  ```
40
40
 
41
41
  ## 💡 Info you can shake a stick at
42
42
 
43
43
  | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
44
44
  |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
45
- | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
46
- | Works with Truffle Ruby | [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf]|
45
+ | Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎jruby-9.2-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎jruby-9.3-wf] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎jruby-9.4-wf] [![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
46
+ | Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.0 Compat][💎truby-23.0i]][🚎truby-23.0-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎truby-23.1-wf] <br/> [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf]|
47
47
  | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
48
- | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
48
+ | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎ruby-3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎ruby-3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
49
+ | Works with MRI Ruby 2 | [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎ruby-2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎ruby-2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎ruby-2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎ruby-2.7-wf]|
49
50
  | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Get help from RubyForum][✉️ruby-forum-img]][✉️ruby-forum] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
50
51
  | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on GitHub.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
51
52
  | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
@@ -147,8 +148,14 @@ The shared upgrade policies are:
147
148
  - `major` - upgrade to the latest higher release, including major-line tags such
148
149
  as `v2` to `v3`.
149
150
 
150
- Invalid upgrade levels normalize to `patch`, matching the historical
151
- `kettle-gha-sha-pins` default.
151
+ Invalid upgrade levels normalize to `major`, matching the strict default used
152
+ by release checks.
153
+
154
+ `kettle-gha-pins --check --cooldown-days DAYS` reports newly released action
155
+ version upgrades without failing until the selected release has aged past the
156
+ cooldown window. The same default can be supplied with
157
+ `KETTLE_GHA_PINS_COOLDOWN_DAYS`. The default is `0`, so checks remain strict
158
+ unless a project opts into a warning window.
152
159
 
153
160
  `Kettle::Gha::Pins::PersistentActionCache.default_path` intentionally preserves
154
161
  the historical `kettle-gha-sha-pins` cache location so command-line tools can
@@ -466,10 +473,22 @@ Thanks for RTFM. ☺️
466
473
  [🏀codecovi]: https://codecov.io/gh/kettle-dev/kettle-gha-pins/graph/badge.svg
467
474
  [🏀coveralls]: https://coveralls.io/github/kettle-dev/kettle-gha-pins?branch=main
468
475
  [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-dev/kettle-gha-pins/badge.svg?branch=main
476
+ [🚎ruby-2.4-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-2.4.yml
477
+ [🚎ruby-2.5-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-2.5.yml
478
+ [🚎ruby-2.6-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-2.6.yml
479
+ [🚎ruby-2.7-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-2.7.yml
480
+ [🚎ruby-3.0-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-3.0.yml
481
+ [🚎ruby-3.1-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-3.1.yml
469
482
  [🚎ruby-3.2-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-3.2.yml
470
483
  [🚎ruby-3.3-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-3.3.yml
471
484
  [🚎ruby-3.4-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/ruby-3.4.yml
485
+ [🚎jruby-9.2-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/jruby-9.2.yml
486
+ [🚎jruby-9.3-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/jruby-9.3.yml
487
+ [🚎jruby-9.4-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/jruby-9.4.yml
472
488
  [🚎jruby-10.0-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/jruby-10.0.yml
489
+ [🚎truby-22.3-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-22.3.yml
490
+ [🚎truby-23.0-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-23.0.yml
491
+ [🚎truby-23.1-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-23.1.yml
473
492
  [🚎truby-24.2-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-24.2.yml
474
493
  [🚎truby-25.0-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-25.0.yml
475
494
  [🚎truby-33.0-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/truffleruby-33.0.yml
@@ -493,16 +512,28 @@ Thanks for RTFM. ☺️
493
512
  [🚎14-🔓️-wfi]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/unlocked_deps.yml/badge.svg
494
513
  [🚎15-🪪-wf]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/license-eye.yml
495
514
  [🚎15-🪪-wfi]: https://github.com/kettle-dev/kettle-gha-pins/actions/workflows/license-eye.yml/badge.svg
515
+ [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
516
+ [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
517
+ [💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
518
+ [💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
519
+ [💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
520
+ [💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white
496
521
  [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
497
522
  [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
498
523
  [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
499
524
  [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
500
525
  [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
526
+ [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
527
+ [💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
528
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
501
529
  [💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
502
530
  [💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
503
531
  [💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
504
532
  [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
505
533
  [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
534
+ [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
535
+ [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
536
+ [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
506
537
  [💎jruby-10.0i]: https://img.shields.io/badge/JRuby-10.0-FBE742?style=for-the-badge&logo=ruby&logoColor=red
507
538
  [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
508
539
  [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
@@ -532,7 +563,7 @@ Thanks for RTFM. ☺️
532
563
  [📌gitmoji]: https://gitmoji.dev
533
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
534
565
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
535
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.866-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
566
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.925-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
536
567
  [🔐security]: https://github.com/kettle-dev/kettle-gha-pins/blob/main/SECURITY.md
537
568
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
538
569
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -68,6 +68,7 @@ module Kettle
68
68
  updates = {
69
69
  sha: target_sha,
70
70
  version: target[:version],
71
+ released_at: target[:released_at],
71
72
  reason: UPGRADE_REASON
72
73
  }
73
74
  reason ||= UPGRADE_REASON
@@ -1,48 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ruby-progressbar"
3
+ require "tty-progressbar"
4
4
 
5
5
  module Kettle
6
6
  module Gha
7
7
  module Pins
8
8
  class CacheProgress
9
- FORMAT = "%t %b %c/%C"
10
- LENGTH = 30
9
+ FORMAT = "%<title>s [:bar] :current/:total"
10
+ WIDTH = 30
11
11
 
12
12
  def initialize(total:, cached_title:, live_title:, output:, enabled: true, skipped_title: nil)
13
13
  @total = total.to_i
14
14
  @cached_count = 0
15
15
  @live_count = 0
16
16
  @skipped_count = 0
17
- @cached_bar = progress_bar(cached_title, output, enabled)
18
- @live_bar = progress_bar(live_title, output, enabled)
19
- @skipped_bar = skipped_title ? progress_bar(skipped_title, output, enabled) : nil
17
+ @multibar = progress_enabled?(enabled, output) ? TTY::ProgressBar::Multi.new(output: output, width: WIDTH) : nil
18
+ @cached_bar = progress_bar(cached_title)
19
+ @live_bar = progress_bar(live_title)
20
+ @skipped_bar = skipped_title ? progress_bar(skipped_title) : nil
20
21
  end
21
22
 
22
23
  attr_reader :cached_count, :live_count, :skipped_count
23
24
 
24
25
  def cached
25
26
  @cached_count += 1
26
- @cached_bar&.increment
27
+ @cached_bar&.advance
27
28
  end
28
29
 
29
30
  def live
30
31
  @live_count += 1
31
- @live_bar&.increment
32
+ @live_bar&.advance
32
33
  end
33
34
 
34
35
  def skipped
35
36
  @skipped_count += 1
36
- @skipped_bar&.increment
37
+ @skipped_bar&.advance
38
+ end
39
+
40
+ def stop
41
+ @multibar&.stop
37
42
  end
38
43
 
39
44
  private
40
45
 
41
- def progress_bar(title, output, enabled)
42
- return unless enabled
46
+ def progress_enabled?(enabled, output)
47
+ enabled && @total.positive? && output.respond_to?(:tty?) && output.tty?
48
+ end
49
+
50
+ def progress_bar(title)
51
+ return unless @multibar
43
52
  return unless @total.positive?
44
53
 
45
- ProgressBar.create(title: title, total: @total, format: FORMAT, length: LENGTH, output: output)
54
+ @multibar.register(bar_format(title), total: @total)
55
+ end
56
+
57
+ def bar_format(title)
58
+ Kernel.format(FORMAT, title: title)
46
59
  end
47
60
  end
48
61
  end
@@ -5,8 +5,10 @@ require "open3"
5
5
  require "optparse"
6
6
  require "pathname"
7
7
  require "set"
8
+ require "time"
8
9
 
9
10
  require "psych"
11
+ require "ruby-progressbar"
10
12
  require "kettle/gha/pins"
11
13
  require_relative "cache_progress"
12
14
 
@@ -43,7 +45,13 @@ module Kettle
43
45
  Kettle::Gha::Pins::VersionRubric.specificity(entry)
44
46
  end
45
47
 
46
- def initialize(argv, err: $stderr)
48
+ def initialize(argv, err: $stderr, clock: -> { Time.now })
49
+ cooldown_days = begin
50
+ Integer(ENV.fetch("KETTLE_GHA_PINS_COOLDOWN_DAYS", "0"))
51
+ rescue ArgumentError, TypeError
52
+ 0
53
+ end
54
+ cooldown_days = 0 if cooldown_days.negative?
47
55
  @argv = argv
48
56
  @err = err
49
57
  @options = {
@@ -58,6 +66,8 @@ module Kettle
58
66
  user_agent: "kettle-gha-pins",
59
67
  upgrade: DEFAULT_UPGRADE_LEVEL,
60
68
  cache_path: ENV["KETTLE_GHA_SHA_PINS_CACHE"] || PersistentActionCache.default_path,
69
+ cooldown_days: cooldown_days,
70
+ clock: clock,
61
71
  refresh_cache: false,
62
72
  reject_patterns: Set.new,
63
73
  progress: nil
@@ -89,6 +99,7 @@ module Kettle
89
99
  errors: [],
90
100
  changed_files: [],
91
101
  planned_changes: [],
102
+ cooldown_changes: [],
92
103
  outdated_pins: []
93
104
  }
94
105
 
@@ -136,6 +147,7 @@ module Kettle
136
147
  updates = compute_updates(old_ref, upgrade_plan[:updates][:sha], upgrade_plan[:updates][:reason], repo_ref)
137
148
  updates[:new_version] = upgrade_plan[:updates][:version]
138
149
  updates[:old_version] = upgrade_plan[:current_version]
150
+ updates[:released_at] = upgrade_plan[:updates][:released_at]
139
151
  end
140
152
  if updates.nil? && upgrade_plan[:current_version]
141
153
  comment_version = version_comment_from_line(text, node[:line], node[:col], parsed_ref[:value])
@@ -166,6 +178,22 @@ module Kettle
166
178
 
167
179
  next unless updates
168
180
 
181
+ if (cooldown = cooldown_details(updates))
182
+ state[:cooldown_changes] << {
183
+ path: path,
184
+ line: node[:line] + 1,
185
+ old_ref: old_ref,
186
+ old_version: updates[:old_version],
187
+ new_ref: updates[:new_ref],
188
+ new_version: updates[:new_version],
189
+ reason: updates[:reason],
190
+ released_at: cooldown[:released_at],
191
+ cooldown_until: cooldown[:cooldown_until],
192
+ action: repo_ref
193
+ }
194
+ next
195
+ end
196
+
169
197
  replacement = build_replacement_from_line(text, node[:line], node[:col], parsed_ref[:value], updates[:new_ref], updates[:new_version])
170
198
  unless replacement
171
199
  record_failure(
@@ -225,6 +253,7 @@ module Kettle
225
253
  end
226
254
  end
227
255
  end
256
+ action_progress.stop
228
257
  progress_message("Action resolution checks: #{action_progress.cached_count} cached, #{action_progress.live_count} live.") if action_count.positive?
229
258
 
230
259
  print_report(state)
@@ -258,6 +287,11 @@ module Kettle
258
287
  end
259
288
  @options[:upgrade] = normalized
260
289
  end
290
+ opt.on("--cooldown-days DAYS", Integer, "Warn instead of failing --check for new release upgrades newer than DAYS days (default: #{@options[:cooldown_days]})") do |days|
291
+ Kernel.abort("Invalid --cooldown-days value #{days.inspect}; use a non-negative integer") if days.negative?
292
+
293
+ @options[:cooldown_days] = days
294
+ end
261
295
  opt.on("--token VALUE", "GitHub token to increase API rate-limit") do |token|
262
296
  @options[:token] = token
263
297
  end
@@ -333,7 +367,7 @@ module Kettle
333
367
  old_ref: old_ref,
334
368
  upgrade_level: @options[:upgrade]
335
369
  )
336
- if cached
370
+ if cached || (client.respond_to?(:last_versions_cache_hit) && client.last_versions_cache_hit)
337
371
  progress.cached
338
372
  else
339
373
  progress.live
@@ -541,6 +575,31 @@ module Kettle
541
575
  }
542
576
  end
543
577
 
578
+ def cooldown_details(updates)
579
+ return nil unless @options[:check]
580
+ return nil unless @options[:cooldown_days].positive?
581
+ return nil unless updates[:reason] == UPGRADE_REASON
582
+
583
+ released_at = parse_time(updates[:released_at])
584
+ return nil unless released_at
585
+
586
+ cooldown_until = released_at + (@options[:cooldown_days] * 24 * 60 * 60)
587
+ return nil unless @options[:clock].call < cooldown_until
588
+
589
+ {
590
+ released_at: released_at.utc.iso8601,
591
+ cooldown_until: cooldown_until.utc.iso8601
592
+ }
593
+ end
594
+
595
+ def parse_time(value)
596
+ return nil if value.to_s.empty?
597
+
598
+ Time.iso8601(value.to_s)
599
+ rescue ArgumentError
600
+ nil
601
+ end
602
+
544
603
  def extract_scalar_token(raw_text)
545
604
  return nil if raw_text.nil? || raw_text.empty?
546
605
 
@@ -695,6 +754,7 @@ module Kettle
695
754
 
696
755
  def print_report(state)
697
756
  mode = @options[:write] ? "write" : "dry-run"
757
+ cooldown_changes = state.fetch(:cooldown_changes, [])
698
758
  if @options[:json]
699
759
  payload = {
700
760
  mode: mode,
@@ -706,6 +766,7 @@ module Kettle
706
766
  failures: state[:failures],
707
767
  outdated_pins: state[:outdated_pins],
708
768
  changed_files: state[:changed_files].sort,
769
+ cooldown_changes: cooldown_changes.sort_by { |c| [c[:path], c[:line], c[:new_ref]] },
709
770
  planned_changes: state[:planned_changes].sort_by { |c| [c[:path], c[:line], c[:new_ref]] },
710
771
  errors: state[:errors]
711
772
  }
@@ -722,6 +783,7 @@ module Kettle
722
783
  lines << " changed_files: #{state[:changed_files].length}"
723
784
  lines << " planned_updates: #{state[:updates]}"
724
785
  lines << " outdated_pins: #{state[:outdated_pins].length}"
786
+ lines << " cooldown_warnings: #{cooldown_changes.length}"
725
787
  lines << " failures: #{state[:failures]}"
726
788
  lines << ""
727
789
 
@@ -749,6 +811,18 @@ module Kettle
749
811
  lines << ""
750
812
  end
751
813
 
814
+ if cooldown_changes.any?
815
+ lines << "Cooldown warnings (#{cooldown_changes.length}):"
816
+ lines << "Action Current Latest Location Reason CooldownUntil"
817
+ cooldown_changes.sort_by { |c| [c[:action], c[:path], c[:line]] }.each do |change|
818
+ current = change[:old_version] || change[:old_ref]
819
+ latest = change[:new_version] || change[:new_ref]
820
+ location = "#{change[:path]}:#{change[:line]}"
821
+ lines << "#{change[:action]} #{current} #{latest} #{location} #{change[:reason]} #{change[:cooldown_until]}"
822
+ end
823
+ lines << ""
824
+ end
825
+
752
826
  if state[:planned_changes].empty?
753
827
  lines << "Outdated actions: none"
754
828
  else
@@ -21,16 +21,24 @@ module Kettle
21
21
  @refresh_timeout = refresh_timeout
22
22
  @commit_cache = {}
23
23
  @release_cache = {}
24
+ @last_versions_cache_hit = false
24
25
  end
25
26
 
27
+ attr_reader :last_versions_cache_hit
28
+
26
29
  def versions_for_repo(repo_ref)
30
+ @last_versions_cache_hit = false
27
31
  return [] if repo_ref.to_s.empty?
28
- return @release_cache[repo_ref] if @release_cache.key?(repo_ref)
32
+ if @release_cache.key?(repo_ref)
33
+ @last_versions_cache_hit = true
34
+ return @release_cache[repo_ref]
35
+ end
29
36
 
30
37
  stale = nil
31
38
  unless @refresh_cache
32
39
  cached = @persistent_cache&.versions_for_repo(repo_ref, fresh: true)
33
40
  if cached
41
+ @last_versions_cache_hit = true
34
42
  @release_cache[repo_ref] = cached
35
43
  return cached
36
44
  end
@@ -91,23 +99,28 @@ module Kettle
91
99
 
92
100
  def cached_versions(repo_ref, stale)
93
101
  versions = stale || []
102
+ @last_versions_cache_hit = !stale.nil?
94
103
  @release_cache[repo_ref] = versions
95
104
  versions
96
105
  end
97
106
 
98
107
  def build_release_versions(data, tag_shas)
108
+ release_dates = {}
99
109
  release_tags = data.each_with_object([]) do |release, memo|
100
110
  next unless release.is_a?(Hash)
101
111
 
102
112
  tag = release["tag_name"].to_s
103
113
  next unless VersionRubric.parse(tag)
104
114
 
115
+ release_dates[tag] = release["published_at"].to_s
116
+ release_dates[tag] = release["created_at"].to_s if release_dates[tag].empty?
105
117
  memo << tag
106
118
  end
107
119
 
108
120
  VersionRubric.build_release_versions(
109
121
  release_tags: release_tags,
110
- tag_shas: tag_shas
122
+ tag_shas: tag_shas,
123
+ release_dates: release_dates
111
124
  )
112
125
  end
113
126
 
@@ -9,7 +9,7 @@ module Kettle
9
9
  module Pins
10
10
  # Persistent cache of GitHub Action release versions and target SHAs.
11
11
  class PersistentActionCache
12
- VERSION = 2
12
+ VERSION = 3
13
13
 
14
14
  def self.default_path
15
15
  state_home = ENV["XDG_STATE_HOME"]
@@ -45,7 +45,7 @@ module Kettle
45
45
  deserialized = deserialize_version_entry(entry)
46
46
  memo << deserialized if deserialized
47
47
  end
48
- .sort_by { |entry| entry[:version_obj] }
48
+ .sort_by { |entry| VersionRubric.sort_key(entry) }
49
49
  .reverse
50
50
  end
51
51
 
@@ -55,18 +55,23 @@ module Kettle
55
55
 
56
56
  action = data.fetch("actions")[repo_ref] ||= {}
57
57
  stored_versions = action["versions"] ||= {}
58
+ refs = action["refs"] ||= {}
58
59
  timestamp = @clock.call.utc.iso8601
59
60
 
60
61
  versions.each do |entry|
61
62
  version = entry[:version].to_s
62
63
  next if version.empty?
64
+ tag = entry[:tag].to_s
65
+ sha = entry[:sha].to_s
63
66
 
64
67
  stored_versions[version] = {
65
- "tag" => entry[:tag].to_s,
68
+ "tag" => tag,
66
69
  "version" => version,
67
- "sha" => entry[:sha].to_s,
70
+ "sha" => sha,
71
+ "released_at" => entry[:released_at].to_s,
68
72
  "cached_at" => timestamp
69
73
  }
74
+ refs[tag] = {"sha" => sha[0, 40], "cached_at" => timestamp} unless tag.empty? || sha.empty?
70
75
  end
71
76
 
72
77
  action["targets"] = target_cache(stored_versions.values)
@@ -145,7 +150,8 @@ module Kettle
145
150
  tag: entry["tag"].to_s,
146
151
  version_obj: parsed,
147
152
  version: version,
148
- sha: entry["sha"].to_s
153
+ sha: entry["sha"].to_s,
154
+ released_at: entry["released_at"].to_s
149
155
  }
150
156
  end
151
157
 
@@ -173,6 +179,7 @@ module Kettle
173
179
  "tag" => entry[:tag],
174
180
  "version" => entry[:version],
175
181
  "sha" => entry[:sha],
182
+ "released_at" => entry[:released_at],
176
183
  "cached_at" => entry[:cached_at]
177
184
  }
178
185
  end
@@ -4,7 +4,7 @@ module Kettle
4
4
  module Gha
5
5
  module Pins
6
6
  module Version
7
- VERSION = "0.2.1"
7
+ VERSION = "0.3.0"
8
8
  end
9
9
  VERSION = Version::VERSION # Traditional Constant Location
10
10
  end
@@ -6,7 +6,7 @@ module Kettle
6
6
  # Shared version ordering and upgrade selection rules for GitHub Action pins.
7
7
  module VersionRubric
8
8
  VALID_UPGRADE_LEVELS = %w[major minor patch].freeze
9
- DEFAULT_UPGRADE_LEVEL = "patch"
9
+ DEFAULT_UPGRADE_LEVEL = "major"
10
10
  RELEASE_TAG_RE = /\A(?:\d+|\d+\.\d+\.\d+(?:[-.]?[0-9A-Za-z.-]+)?)\z/
11
11
 
12
12
  module_function
@@ -24,7 +24,7 @@ module Kettle
24
24
  value.to_s.match?(/\A\d+\z/)
25
25
  end
26
26
 
27
- def entry(tag:, sha: nil)
27
+ def entry(tag:, sha: nil, released_at: nil)
28
28
  version_obj = parse(tag)
29
29
  return nil unless version_obj
30
30
 
@@ -32,14 +32,15 @@ module Kettle
32
32
  tag: tag.to_s,
33
33
  version_obj: version_obj,
34
34
  version: version_obj.to_s,
35
- sha: sha
35
+ sha: sha,
36
+ released_at: released_at.to_s
36
37
  }
37
38
  end
38
39
 
39
- def build_release_versions(release_tags:, tag_shas:)
40
+ def build_release_versions(release_tags:, tag_shas:, release_dates: {})
40
41
  released_tags = release_tags.each_with_object({}) { |tag, memo| memo[tag.to_s] = true }
41
42
  releases = release_tags.each_with_object([]) do |tag, memo|
42
- release_entry = entry(tag: tag, sha: tag_shas[tag.to_s])
43
+ release_entry = entry(tag: tag, sha: tag_shas[tag.to_s], released_at: release_dates[tag.to_s])
43
44
  memo << release_entry if release_entry
44
45
  end
45
46
  tag_versions = tag_shas.each_with_object([]) do |(tag, sha), memo|
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.13'
54
+ - !ruby/object:Gem::Dependency
55
+ name: tty-progressbar
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.18'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.18'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: version_gem
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -168,7 +182,7 @@ dependencies:
168
182
  version: '2.0'
169
183
  - - ">="
170
184
  - !ruby/object:Gem::Version
171
- version: 2.0.12
185
+ version: 2.0.14
172
186
  type: :development
173
187
  prerelease: false
174
188
  version_requirements: !ruby/object:Gem::Requirement
@@ -178,7 +192,7 @@ dependencies:
178
192
  version: '2.0'
179
193
  - - ">="
180
194
  - !ruby/object:Gem::Version
181
- version: 2.0.12
195
+ version: 2.0.14
182
196
  - !ruby/object:Gem::Dependency
183
197
  name: turbo_tests2
184
198
  requirement: !ruby/object:Gem::Requirement
@@ -267,10 +281,10 @@ licenses:
267
281
  - MIT
268
282
  metadata:
269
283
  homepage_uri: https://github.com/kettle-dev/kettle-gha-pins
270
- source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.2.1
271
- changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.2.1/CHANGELOG.md
284
+ source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.3.0
285
+ changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.3.0/CHANGELOG.md
272
286
  bug_tracker_uri: https://github.com/kettle-dev/kettle-gha-pins/issues
273
- documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.2.1
287
+ documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.0
274
288
  funding_uri: https://github.com/sponsors/pboling
275
289
  wiki_uri: https://github.com/kettle-dev/kettle-gha-pins/wiki
276
290
  news_uri: https://www.railsbling.com/tags/kettle-gha-pins
metadata.gz.sig CHANGED
Binary file