kettle-gha-pins 0.2.0 → 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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +45 -1
- data/README.md +41 -10
- data/lib/kettle/gha/pins/action_resolver.rb +15 -14
- data/lib/kettle/gha/pins/cache_progress.rb +25 -12
- data/lib/kettle/gha/pins/cli.rb +127 -39
- data/lib/kettle/gha/pins/github_client.rb +17 -4
- data/lib/kettle/gha/pins/persistent_action_cache.rb +18 -8
- data/lib/kettle/gha/pins/version.rb +1 -1
- data/lib/kettle/gha/pins/version_rubric.rb +12 -7
- data.tar.gz.sig +0 -0
- metadata +21 -21
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b35d635c79b4982830fe80cda9f003e3cb5b6ad146fe1f35a2d9826852953ef
|
|
4
|
+
data.tar.gz: 2ca635cc1ee80d6bbb1511d0371b880b199dfe8bf1a0f0265a0ec465d60345c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 623fd4769e4597bff617136531ffbc403c9f8252ba67a7cbf8fa86a7304aa0e3e4d1e20a77020ca07dfdc72207b59c67f11fb4480291a59e163c2389cf8c5f93
|
|
7
|
+
data.tar.gz: 4dec38a332e0ed5a447f585f3d0cf41338aba8e1d6a0af5beb51a5116ceb12bb6ee4b9e43d02979d189b14fc1c1cb3cc3737c09160f931d999b0ed66c3fe01be
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,46 @@ 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
|
+
|
|
58
|
+
## [0.2.1] - 2026-07-22
|
|
59
|
+
|
|
60
|
+
- TAG: [v0.2.1][0.2.1t]
|
|
61
|
+
- COVERAGE: 99.08% -- 858/866 lines in 8 files
|
|
62
|
+
- BRANCH COVERAGE: 92.11% -- 350/380 branches in 8 files
|
|
63
|
+
- 22.22% documented
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- Lowered the generated gemspec Ruby requirement to `>= 2.4.0` to match
|
|
68
|
+
`kettle-dev`, restored Ruby 2.4-compatible runtime syntax, and explicitly
|
|
69
|
+
required the `set` stdlib used by the CLI.
|
|
70
|
+
- Made CLI line rewrites tolerate Psych scalar column differences across Ruby
|
|
71
|
+
versions.
|
|
72
|
+
|
|
33
73
|
## [0.2.0] - 2026-07-22
|
|
34
74
|
|
|
35
75
|
- TAG: [v0.2.0][0.2.0t]
|
|
@@ -76,7 +116,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
76
116
|
- kettle-jem-template-20260720-005 - Generated README Support & Community rows
|
|
77
117
|
now include a RubyForum help badge.
|
|
78
118
|
|
|
79
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.
|
|
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
|
|
122
|
+
[0.2.1]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.2.0...v0.2.1
|
|
123
|
+
[0.2.1t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.2.1
|
|
80
124
|
[0.2.0]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.1.0...v0.2.0
|
|
81
125
|
[0.2.0t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.2.0
|
|
82
126
|
[0.1.0]: https://github.com/kettle-dev/kettle-gha-pins/compare/c633526495c7db0a8721a94a71c3def0f3cc71bb...v0.1.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
|
|
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] |
|
|
@@ -56,8 +57,8 @@ kettle-gha-pins --write --upgrade patch
|
|
|
56
57
|
|
|
57
58
|
### Compatibility
|
|
58
59
|
|
|
59
|
-
Compatible with MRI Ruby
|
|
60
|
-
CI workflows and Appraisals are generated for MRI Ruby
|
|
60
|
+
Compatible with MRI Ruby 2.4.0+, and concordant releases of JRuby, and TruffleRuby.
|
|
61
|
+
CI workflows and Appraisals are generated for MRI Ruby 2.4.0+.
|
|
61
62
|
This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
|
|
62
63
|
may be higher than the gem's runtime compatibility floor when legacy Rubies are
|
|
63
64
|
not practical for the current toolchain.
|
|
@@ -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 `
|
|
151
|
-
|
|
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.
|
|
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
|
|
@@ -558,7 +589,7 @@ Thanks for RTFM. ☺️
|
|
|
558
589
|
| Field | Value |
|
|
559
590
|
|---|---|
|
|
560
591
|
| Package | kettle-gha-pins |
|
|
561
|
-
| Description | 📌
|
|
592
|
+
| Description | 📌 GitHub Actions workflow SHA pin maintenance, including deterministic release-tag parsing, canonicalization, cache-backed GitHub ref resolution, and upgrade planning. |
|
|
562
593
|
| Homepage | https://github.com/kettle-dev/kettle-gha-pins |
|
|
563
594
|
| Source | https://github.com/kettle-dev/kettle-gha-pins |
|
|
564
595
|
| License | `MIT` |
|
|
@@ -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
|
|
@@ -167,32 +168,32 @@ module Kettle
|
|
|
167
168
|
end
|
|
168
169
|
|
|
169
170
|
class << self
|
|
170
|
-
def resolve_action_plan(
|
|
171
|
-
ActionResolver.resolve_action_plan(
|
|
171
|
+
def resolve_action_plan(**kwargs)
|
|
172
|
+
ActionResolver.resolve_action_plan(**kwargs)
|
|
172
173
|
end
|
|
173
174
|
|
|
174
|
-
def determine_upgrade_plan(
|
|
175
|
-
ActionResolver.determine_upgrade_plan(
|
|
175
|
+
def determine_upgrade_plan(**kwargs)
|
|
176
|
+
ActionResolver.determine_upgrade_plan(**kwargs)
|
|
176
177
|
end
|
|
177
178
|
|
|
178
|
-
def matching_version_entry(
|
|
179
|
-
ActionResolver.matching_version_entry(
|
|
179
|
+
def matching_version_entry(versions, current_ref, current_sha, client, repo_ref)
|
|
180
|
+
ActionResolver.matching_version_entry(versions, current_ref, current_sha, client, repo_ref)
|
|
180
181
|
end
|
|
181
182
|
|
|
182
|
-
def version_entry_sha(
|
|
183
|
-
ActionResolver.version_entry_sha(
|
|
183
|
+
def version_entry_sha(entry, client, repo_ref)
|
|
184
|
+
ActionResolver.version_entry_sha(entry, client, repo_ref)
|
|
184
185
|
end
|
|
185
186
|
|
|
186
|
-
def short_sha?(
|
|
187
|
-
ActionResolver.short_sha?(
|
|
187
|
+
def short_sha?(candidate)
|
|
188
|
+
ActionResolver.short_sha?(candidate)
|
|
188
189
|
end
|
|
189
190
|
|
|
190
|
-
def non_sha?(
|
|
191
|
-
ActionResolver.non_sha?(
|
|
191
|
+
def non_sha?(candidate)
|
|
192
|
+
ActionResolver.non_sha?(candidate)
|
|
192
193
|
end
|
|
193
194
|
|
|
194
|
-
def stale_sha?(
|
|
195
|
-
ActionResolver.stale_sha?(
|
|
195
|
+
def stale_sha?(current, latest)
|
|
196
|
+
ActionResolver.stale_sha?(current, latest)
|
|
196
197
|
end
|
|
197
198
|
end
|
|
198
199
|
end
|
|
@@ -1,48 +1,61 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "tty-progressbar"
|
|
4
4
|
|
|
5
5
|
module Kettle
|
|
6
6
|
module Gha
|
|
7
7
|
module Pins
|
|
8
8
|
class CacheProgress
|
|
9
|
-
FORMAT = "
|
|
10
|
-
|
|
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
|
-
@
|
|
18
|
-
@
|
|
19
|
-
@
|
|
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&.
|
|
27
|
+
@cached_bar&.advance
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def live
|
|
30
31
|
@live_count += 1
|
|
31
|
-
@live_bar&.
|
|
32
|
+
@live_bar&.advance
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def skipped
|
|
35
36
|
@skipped_count += 1
|
|
36
|
-
@skipped_bar&.
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
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
|
data/lib/kettle/gha/pins/cli.rb
CHANGED
|
@@ -4,8 +4,11 @@ require "json"
|
|
|
4
4
|
require "open3"
|
|
5
5
|
require "optparse"
|
|
6
6
|
require "pathname"
|
|
7
|
+
require "set"
|
|
8
|
+
require "time"
|
|
7
9
|
|
|
8
10
|
require "psych"
|
|
11
|
+
require "ruby-progressbar"
|
|
9
12
|
require "kettle/gha/pins"
|
|
10
13
|
require_relative "cache_progress"
|
|
11
14
|
|
|
@@ -42,7 +45,13 @@ module Kettle
|
|
|
42
45
|
Kettle::Gha::Pins::VersionRubric.specificity(entry)
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
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?
|
|
46
55
|
@argv = argv
|
|
47
56
|
@err = err
|
|
48
57
|
@options = {
|
|
@@ -57,6 +66,8 @@ module Kettle
|
|
|
57
66
|
user_agent: "kettle-gha-pins",
|
|
58
67
|
upgrade: DEFAULT_UPGRADE_LEVEL,
|
|
59
68
|
cache_path: ENV["KETTLE_GHA_SHA_PINS_CACHE"] || PersistentActionCache.default_path,
|
|
69
|
+
cooldown_days: cooldown_days,
|
|
70
|
+
clock: clock,
|
|
60
71
|
refresh_cache: false,
|
|
61
72
|
reject_patterns: Set.new,
|
|
62
73
|
progress: nil
|
|
@@ -88,6 +99,7 @@ module Kettle
|
|
|
88
99
|
errors: [],
|
|
89
100
|
changed_files: [],
|
|
90
101
|
planned_changes: [],
|
|
102
|
+
cooldown_changes: [],
|
|
91
103
|
outdated_pins: []
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -135,6 +147,7 @@ module Kettle
|
|
|
135
147
|
updates = compute_updates(old_ref, upgrade_plan[:updates][:sha], upgrade_plan[:updates][:reason], repo_ref)
|
|
136
148
|
updates[:new_version] = upgrade_plan[:updates][:version]
|
|
137
149
|
updates[:old_version] = upgrade_plan[:current_version]
|
|
150
|
+
updates[:released_at] = upgrade_plan[:updates][:released_at]
|
|
138
151
|
end
|
|
139
152
|
if updates.nil? && upgrade_plan[:current_version]
|
|
140
153
|
comment_version = version_comment_from_line(text, node[:line], node[:col], parsed_ref[:value])
|
|
@@ -165,6 +178,22 @@ module Kettle
|
|
|
165
178
|
|
|
166
179
|
next unless updates
|
|
167
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
|
+
|
|
168
197
|
replacement = build_replacement_from_line(text, node[:line], node[:col], parsed_ref[:value], updates[:new_ref], updates[:new_version])
|
|
169
198
|
unless replacement
|
|
170
199
|
record_failure(
|
|
@@ -224,6 +253,7 @@ module Kettle
|
|
|
224
253
|
end
|
|
225
254
|
end
|
|
226
255
|
end
|
|
256
|
+
action_progress.stop
|
|
227
257
|
progress_message("Action resolution checks: #{action_progress.cached_count} cached, #{action_progress.live_count} live.") if action_count.positive?
|
|
228
258
|
|
|
229
259
|
print_report(state)
|
|
@@ -257,6 +287,11 @@ module Kettle
|
|
|
257
287
|
end
|
|
258
288
|
@options[:upgrade] = normalized
|
|
259
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
|
|
260
295
|
opt.on("--token VALUE", "GitHub token to increase API rate-limit") do |token|
|
|
261
296
|
@options[:token] = token
|
|
262
297
|
end
|
|
@@ -273,9 +308,11 @@ module Kettle
|
|
|
273
308
|
@options[:progress] = bool
|
|
274
309
|
end
|
|
275
310
|
opt.on("--skip-pattern PATTERN", "Skip workflow paths matching pattern (repeatable)") do |pattern|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
311
|
+
begin
|
|
312
|
+
@options[:reject_patterns] << Regexp.new(pattern)
|
|
313
|
+
rescue RegexpError => e
|
|
314
|
+
Kernel.abort("Invalid --skip-pattern #{pattern.inspect}: #{e.message}")
|
|
315
|
+
end
|
|
279
316
|
end
|
|
280
317
|
opt.on("--[no-]validate", "Validate YAML after editing") do |bool|
|
|
281
318
|
@options[:validate] = bool
|
|
@@ -297,25 +334,27 @@ module Kettle
|
|
|
297
334
|
def load_workflows(paths, state)
|
|
298
335
|
file_progress = progress_bar(title: "Files", total: paths.length)
|
|
299
336
|
paths.each_with_object([]) do |path, workflows|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
337
|
+
begin
|
|
338
|
+
state[:files_scanned] += 1
|
|
339
|
+
begin
|
|
340
|
+
text = File.read(path)
|
|
341
|
+
rescue Errno::EACCES => e
|
|
342
|
+
record_failure(state, path: path, error: "read_error: #{e.message}")
|
|
343
|
+
next
|
|
344
|
+
end
|
|
307
345
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
346
|
+
begin
|
|
347
|
+
parsed = Psych.parse_stream(text)
|
|
348
|
+
rescue Psych::Exception => e
|
|
349
|
+
record_failure(state, path: path, error: "yaml_parse_error: #{e.message}")
|
|
350
|
+
next
|
|
351
|
+
end
|
|
314
352
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
353
|
+
uses_nodes = extract_uses_nodes(parsed, text)
|
|
354
|
+
workflows << {path: path, text: text, uses_nodes: uses_nodes} unless uses_nodes.empty?
|
|
355
|
+
ensure
|
|
356
|
+
file_progress&.increment
|
|
357
|
+
end
|
|
319
358
|
end
|
|
320
359
|
end
|
|
321
360
|
|
|
@@ -328,7 +367,7 @@ module Kettle
|
|
|
328
367
|
old_ref: old_ref,
|
|
329
368
|
upgrade_level: @options[:upgrade]
|
|
330
369
|
)
|
|
331
|
-
if cached
|
|
370
|
+
if cached || (client.respond_to?(:last_versions_cache_hit) && client.last_versions_cache_hit)
|
|
332
371
|
progress.cached
|
|
333
372
|
else
|
|
334
373
|
progress.live
|
|
@@ -469,7 +508,7 @@ module Kettle
|
|
|
469
508
|
action: {
|
|
470
509
|
owner: parts[0],
|
|
471
510
|
repo: parts[1],
|
|
472
|
-
path: (parts.length > 2) ? parts[2
|
|
511
|
+
path: (parts.length > 2) ? parts[2..-1].join("/") : nil,
|
|
473
512
|
ref: ref
|
|
474
513
|
}
|
|
475
514
|
}
|
|
@@ -536,6 +575,31 @@ module Kettle
|
|
|
536
575
|
}
|
|
537
576
|
end
|
|
538
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
|
+
|
|
539
603
|
def extract_scalar_token(raw_text)
|
|
540
604
|
return nil if raw_text.nil? || raw_text.empty?
|
|
541
605
|
|
|
@@ -594,14 +658,10 @@ module Kettle
|
|
|
594
658
|
line_text = text.lines[line]
|
|
595
659
|
return nil if line_text.nil?
|
|
596
660
|
|
|
597
|
-
|
|
598
|
-
return nil if raw.nil?
|
|
599
|
-
|
|
600
|
-
token_info = extract_scalar_token(raw)
|
|
661
|
+
token_info = locate_scalar_token(line_text, col, old_token)
|
|
601
662
|
return nil unless token_info
|
|
602
|
-
return nil unless token_info[:token] == old_token
|
|
603
663
|
|
|
604
|
-
suffix =
|
|
664
|
+
suffix = line_text[token_info[:end]..-1].to_s
|
|
605
665
|
match = suffix.match(VERSION_COMMENT_SUFFIX_RE)
|
|
606
666
|
match && match[:version]
|
|
607
667
|
end
|
|
@@ -610,20 +670,16 @@ module Kettle
|
|
|
610
670
|
line_text = text.lines[line]
|
|
611
671
|
return nil if line_text.nil?
|
|
612
672
|
|
|
613
|
-
|
|
614
|
-
return nil if raw.nil?
|
|
615
|
-
|
|
616
|
-
token_info = extract_scalar_token(raw)
|
|
673
|
+
token_info = locate_scalar_token(line_text, col, old_token)
|
|
617
674
|
return nil unless token_info
|
|
618
|
-
return nil unless token_info[:token] == old_token
|
|
619
675
|
|
|
620
676
|
rendered = render_replacement(old_token, new_ref, token_info[:quote])
|
|
621
677
|
return nil unless rendered
|
|
622
678
|
|
|
623
|
-
span = token_info[:
|
|
679
|
+
span = token_info[:end] - token_info[:start]
|
|
624
680
|
new_scalar = rendered[:quoted]
|
|
625
681
|
if new_version && token_info[:quote] == :plain
|
|
626
|
-
suffix =
|
|
682
|
+
suffix = line_text[token_info[:end]..-1].to_s
|
|
627
683
|
comment = suffix.match(VERSION_COMMENT_REPLACEMENT_RE)
|
|
628
684
|
if comment
|
|
629
685
|
span += comment[0].length
|
|
@@ -632,14 +688,31 @@ module Kettle
|
|
|
632
688
|
end
|
|
633
689
|
|
|
634
690
|
{
|
|
635
|
-
start:
|
|
636
|
-
end:
|
|
691
|
+
start: token_info[:start],
|
|
692
|
+
end: token_info[:start] + span,
|
|
637
693
|
new_scalar: new_scalar,
|
|
638
694
|
new_ref: new_ref,
|
|
639
695
|
old_token: old_token
|
|
640
696
|
}
|
|
641
697
|
end
|
|
642
698
|
|
|
699
|
+
def locate_scalar_token(line_text, col, old_token)
|
|
700
|
+
return nil if col.nil? || col.negative? || col >= line_text.length
|
|
701
|
+
|
|
702
|
+
matches = []
|
|
703
|
+
0.upto(line_text.length - 1) do |index|
|
|
704
|
+
token_info = extract_scalar_token(line_text[index..-1])
|
|
705
|
+
next unless token_info
|
|
706
|
+
next unless token_info[:token] == old_token
|
|
707
|
+
|
|
708
|
+
matches << token_info.merge(
|
|
709
|
+
start: index,
|
|
710
|
+
end: index + token_info[:span]
|
|
711
|
+
)
|
|
712
|
+
end
|
|
713
|
+
matches.min_by { |match| [(match[:start] - col).abs, match[:start]] }
|
|
714
|
+
end
|
|
715
|
+
|
|
643
716
|
def apply_edits(original_text, edits)
|
|
644
717
|
lines = original_text.lines
|
|
645
718
|
grouped = edits.group_by { |entry| entry[:line] }
|
|
@@ -652,7 +725,7 @@ module Kettle
|
|
|
652
725
|
next if line.nil?
|
|
653
726
|
|
|
654
727
|
entries.each do |entry|
|
|
655
|
-
line = line[0...entry[:start]].to_s + entry[:new_scalar] + line[entry[:end]
|
|
728
|
+
line = line[0...entry[:start]].to_s + entry[:new_scalar] + line[entry[:end]..-1].to_s
|
|
656
729
|
end
|
|
657
730
|
updated[line_num] = line
|
|
658
731
|
end
|
|
@@ -681,6 +754,7 @@ module Kettle
|
|
|
681
754
|
|
|
682
755
|
def print_report(state)
|
|
683
756
|
mode = @options[:write] ? "write" : "dry-run"
|
|
757
|
+
cooldown_changes = state.fetch(:cooldown_changes, [])
|
|
684
758
|
if @options[:json]
|
|
685
759
|
payload = {
|
|
686
760
|
mode: mode,
|
|
@@ -692,6 +766,7 @@ module Kettle
|
|
|
692
766
|
failures: state[:failures],
|
|
693
767
|
outdated_pins: state[:outdated_pins],
|
|
694
768
|
changed_files: state[:changed_files].sort,
|
|
769
|
+
cooldown_changes: cooldown_changes.sort_by { |c| [c[:path], c[:line], c[:new_ref]] },
|
|
695
770
|
planned_changes: state[:planned_changes].sort_by { |c| [c[:path], c[:line], c[:new_ref]] },
|
|
696
771
|
errors: state[:errors]
|
|
697
772
|
}
|
|
@@ -708,6 +783,7 @@ module Kettle
|
|
|
708
783
|
lines << " changed_files: #{state[:changed_files].length}"
|
|
709
784
|
lines << " planned_updates: #{state[:updates]}"
|
|
710
785
|
lines << " outdated_pins: #{state[:outdated_pins].length}"
|
|
786
|
+
lines << " cooldown_warnings: #{cooldown_changes.length}"
|
|
711
787
|
lines << " failures: #{state[:failures]}"
|
|
712
788
|
lines << ""
|
|
713
789
|
|
|
@@ -735,6 +811,18 @@ module Kettle
|
|
|
735
811
|
lines << ""
|
|
736
812
|
end
|
|
737
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
|
+
|
|
738
826
|
if state[:planned_changes].empty?
|
|
739
827
|
lines << "Outdated actions: none"
|
|
740
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
|
-
|
|
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)
|
|
99
|
-
|
|
108
|
+
release_dates = {}
|
|
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
|
|
|
105
|
-
tag
|
|
115
|
+
release_dates[tag] = release["published_at"].to_s
|
|
116
|
+
release_dates[tag] = release["created_at"].to_s if release_dates[tag].empty?
|
|
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 =
|
|
12
|
+
VERSION = 3
|
|
13
13
|
|
|
14
14
|
def self.default_path
|
|
15
15
|
state_home = ENV["XDG_STATE_HOME"]
|
|
@@ -41,8 +41,11 @@ module Kettle
|
|
|
41
41
|
return nil if entries.empty?
|
|
42
42
|
return nil if fresh && entries.length != versions.length
|
|
43
43
|
|
|
44
|
-
entries.
|
|
45
|
-
|
|
44
|
+
entries.each_with_object([]) do |entry, memo|
|
|
45
|
+
deserialized = deserialize_version_entry(entry)
|
|
46
|
+
memo << deserialized if deserialized
|
|
47
|
+
end
|
|
48
|
+
.sort_by { |entry| VersionRubric.sort_key(entry) }
|
|
46
49
|
.reverse
|
|
47
50
|
end
|
|
48
51
|
|
|
@@ -52,18 +55,23 @@ module Kettle
|
|
|
52
55
|
|
|
53
56
|
action = data.fetch("actions")[repo_ref] ||= {}
|
|
54
57
|
stored_versions = action["versions"] ||= {}
|
|
58
|
+
refs = action["refs"] ||= {}
|
|
55
59
|
timestamp = @clock.call.utc.iso8601
|
|
56
60
|
|
|
57
61
|
versions.each do |entry|
|
|
58
62
|
version = entry[:version].to_s
|
|
59
63
|
next if version.empty?
|
|
64
|
+
tag = entry[:tag].to_s
|
|
65
|
+
sha = entry[:sha].to_s
|
|
60
66
|
|
|
61
67
|
stored_versions[version] = {
|
|
62
|
-
"tag" =>
|
|
68
|
+
"tag" => tag,
|
|
63
69
|
"version" => version,
|
|
64
|
-
"sha" =>
|
|
70
|
+
"sha" => sha,
|
|
71
|
+
"released_at" => entry[:released_at].to_s,
|
|
65
72
|
"cached_at" => timestamp
|
|
66
73
|
}
|
|
74
|
+
refs[tag] = {"sha" => sha[0, 40], "cached_at" => timestamp} unless tag.empty? || sha.empty?
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
action["targets"] = target_cache(stored_versions.values)
|
|
@@ -142,16 +150,17 @@ module Kettle
|
|
|
142
150
|
tag: entry["tag"].to_s,
|
|
143
151
|
version_obj: parsed,
|
|
144
152
|
version: version,
|
|
145
|
-
sha: entry["sha"].to_s
|
|
153
|
+
sha: entry["sha"].to_s,
|
|
154
|
+
released_at: entry["released_at"].to_s
|
|
146
155
|
}
|
|
147
156
|
end
|
|
148
157
|
|
|
149
158
|
def target_cache(version_entries)
|
|
150
|
-
entries = version_entries.
|
|
159
|
+
entries = version_entries.each_with_object([]) do |entry, memo|
|
|
151
160
|
deserialized = deserialize_version_entry(entry)
|
|
152
161
|
next unless deserialized
|
|
153
162
|
|
|
154
|
-
deserialized.merge(cached_at: entry["cached_at"].to_s)
|
|
163
|
+
memo << deserialized.merge(cached_at: entry["cached_at"].to_s)
|
|
155
164
|
end
|
|
156
165
|
return {} if entries.empty?
|
|
157
166
|
|
|
@@ -170,6 +179,7 @@ module Kettle
|
|
|
170
179
|
"tag" => entry[:tag],
|
|
171
180
|
"version" => entry[:version],
|
|
172
181
|
"sha" => entry[:sha],
|
|
182
|
+
"released_at" => entry[:released_at],
|
|
173
183
|
"cached_at" => entry[:cached_at]
|
|
174
184
|
}
|
|
175
185
|
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 = "
|
|
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,17 +32,22 @@ 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
|
-
releases = release_tags.
|
|
42
|
-
|
|
42
|
+
releases = release_tags.each_with_object([]) do |tag, memo|
|
|
43
|
+
release_entry = entry(tag: tag, sha: tag_shas[tag.to_s], released_at: release_dates[tag.to_s])
|
|
44
|
+
memo << release_entry if release_entry
|
|
45
|
+
end
|
|
46
|
+
tag_versions = tag_shas.each_with_object([]) do |(tag, sha), memo|
|
|
43
47
|
next if released_tags[tag.to_s]
|
|
44
48
|
|
|
45
|
-
entry(tag: tag, sha: sha)
|
|
49
|
+
tag_entry = entry(tag: tag, sha: sha)
|
|
50
|
+
memo << tag_entry if tag_entry
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
sort_versions(canonicalize_equivalent_release_versions(releases + tag_versions)).reverse
|
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.
|
|
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.
|
|
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.
|
|
195
|
+
version: 2.0.14
|
|
182
196
|
- !ruby/object:Gem::Dependency
|
|
183
197
|
name: turbo_tests2
|
|
184
198
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -199,20 +213,6 @@ dependencies:
|
|
|
199
213
|
- - ">="
|
|
200
214
|
- !ruby/object:Gem::Version
|
|
201
215
|
version: 3.2.0
|
|
202
|
-
- !ruby/object:Gem::Dependency
|
|
203
|
-
name: ruby-progressbar
|
|
204
|
-
requirement: !ruby/object:Gem::Requirement
|
|
205
|
-
requirements:
|
|
206
|
-
- - "~>"
|
|
207
|
-
- !ruby/object:Gem::Version
|
|
208
|
-
version: '1.13'
|
|
209
|
-
type: :development
|
|
210
|
-
prerelease: false
|
|
211
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
212
|
-
requirements:
|
|
213
|
-
- - "~>"
|
|
214
|
-
- !ruby/object:Gem::Version
|
|
215
|
-
version: '1.13'
|
|
216
216
|
- !ruby/object:Gem::Dependency
|
|
217
217
|
name: stone_checksums
|
|
218
218
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -281,10 +281,10 @@ licenses:
|
|
|
281
281
|
- MIT
|
|
282
282
|
metadata:
|
|
283
283
|
homepage_uri: https://github.com/kettle-dev/kettle-gha-pins
|
|
284
|
-
source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.
|
|
285
|
-
changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.
|
|
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
|
|
286
286
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-gha-pins/issues
|
|
287
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.
|
|
287
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.0
|
|
288
288
|
funding_uri: https://github.com/sponsors/pboling
|
|
289
289
|
wiki_uri: https://github.com/kettle-dev/kettle-gha-pins/wiki
|
|
290
290
|
news_uri: https://www.railsbling.com/tags/kettle-gha-pins
|
|
@@ -307,7 +307,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
307
307
|
requirements:
|
|
308
308
|
- - ">="
|
|
309
309
|
- !ruby/object:Gem::Version
|
|
310
|
-
version:
|
|
310
|
+
version: 2.4.0
|
|
311
311
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
312
|
requirements:
|
|
313
313
|
- - ">="
|
metadata.gz.sig
CHANGED
|
Binary file
|