Module: Kettle::Gha::Pins::VersionRubric

Defined in:
lib/kettle/gha/pins/version_rubric.rb

Overview

Shared version ordering and upgrade selection rules for GitHub Action pins.

Constant Summary collapse

VALID_UPGRADE_LEVELS =
%w[major minor patch].freeze
DEFAULT_UPGRADE_LEVEL =
"major"
RELEASE_TAG_RE =
/\A(?:\d+|\d+\.\d+\.\d+(?:[-.]?[0-9A-Za-z.-]+)?)\z/

Class Method Summary collapse

Class Method Details

.allowed_by_level?(current_version, current, entry, level) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/kettle/gha/pins/version_rubric.rb', line 121

def allowed_by_level?(current_version, current, entry, level)
  return true if level == "major"
  return false if major_line?(entry.fetch(:version))
  return false if major_line?(current_version)

  case level
  when "patch"
    entry.fetch(:version_obj).segments[0, 2] == current.segments[0, 2]
  when "minor"
    entry.fetch(:version_obj).segments[0] == current.segments[0]
  else
    true
  end
end

.build_release_versions(release_tags:, tag_shas:, release_dates: {}) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/kettle/gha/pins/version_rubric.rb', line 40

def build_release_versions(release_tags:, tag_shas:, release_dates: {})
  released_tags = release_tags.each_with_object({}) { |tag, memo| memo[tag.to_s] = true }
  releases = release_tags.each_with_object([]) do |tag, memo|
    release_entry = entry(tag: tag, sha: tag_shas[tag.to_s], released_at: release_dates[tag.to_s])
    memo << release_entry if release_entry
  end
  tag_versions = tag_shas.each_with_object([]) do |(tag, sha), memo|
    next if released_tags[tag.to_s]

    tag_entry = entry(tag: tag, sha: sha)
    memo << tag_entry if tag_entry
  end

  sort_versions(canonicalize_equivalent_release_versions(releases + tag_versions)).reverse
end

.canonicalize_equivalent_release_versions(releases) ⇒ Object



56
57
58
59
60
61
# File 'lib/kettle/gha/pins/version_rubric.rb', line 56

def canonicalize_equivalent_release_versions(releases)
  releases.each_with_object([]) do |release, groups|
    group = groups.find { |entries| equivalent_release_tag?(entries.first, release) }
    group ? group << release : groups << [release]
  end.map { |entries| entries.max_by { |entry| sort_key(entry) } }
end

.choose_upgrade_target(current_version, versions, level) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/kettle/gha/pins/version_rubric.rb', line 84

def choose_upgrade_target(current_version, versions, level)
  normalized_level = normalize_upgrade_level(level)
  current = parse(current_version)
  return nil if current.nil?
  return nil if normalized_level != "major" && major_line?(current_version)

  versions.select do |entry|
    entry.fetch(:version_obj, nil).is_a?(Gem::Version) &&
      entry.fetch(:version_obj) > current &&
      (!entry.fetch(:version_obj).prerelease? || current.prerelease?) &&
      allowed_by_level?(current_version, current, entry, normalized_level)
  end.max_by { |entry| sort_key(entry) }
end

.entry(tag:, sha: nil, released_at: nil) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/kettle/gha/pins/version_rubric.rb', line 27

def entry(tag:, sha: nil, released_at: nil)
  version_obj = parse(tag)
  return nil unless version_obj

  {
    tag: tag.to_s,
    version_obj: version_obj,
    version: version_obj.to_s,
    sha: sha,
    released_at: released_at.to_s
  }
end

.equivalent_release_tag?(left, right) ⇒ Boolean

Returns:

  • (Boolean)


114
115
116
117
118
119
# File 'lib/kettle/gha/pins/version_rubric.rb', line 114

def equivalent_release_tag?(left, right)
  left[:version_obj] == right[:version_obj] &&
    left[:sha] &&
    right[:sha] &&
    left[:sha] == right[:sha]
end

.latest_outdated_target(current_version, versions) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/kettle/gha/pins/version_rubric.rb', line 98

def latest_outdated_target(current_version, versions)
  current = parse(current_version)
  return nil if current.nil?

  versions.select do |entry|
    entry.fetch(:version_obj, nil).is_a?(Gem::Version) &&
      entry.fetch(:version_obj) > current &&
      (!entry.fetch(:version_obj).prerelease? || current.prerelease?)
  end.max_by { |entry| sort_key(entry) }
end

.major_line?(value) ⇒ Boolean

Returns:

  • (Boolean)


23
24
25
# File 'lib/kettle/gha/pins/version_rubric.rb', line 23

def major_line?(value)
  value.to_s.match?(/\A\d+\z/)
end

.normalize_upgrade_level(level) ⇒ Object



109
110
111
112
# File 'lib/kettle/gha/pins/version_rubric.rb', line 109

def normalize_upgrade_level(level)
  normalized = level.to_s.downcase
  VALID_UPGRADE_LEVELS.include?(normalized) ? normalized : DEFAULT_UPGRADE_LEVEL
end

.parse(value) ⇒ Object



14
15
16
17
18
19
20
21
# File 'lib/kettle/gha/pins/version_rubric.rb', line 14

def parse(value)
  normalized = value.to_s.sub(/\A[vV]/, "")
  return nil unless normalized.match?(RELEASE_TAG_RE)

  Gem::Version.new(normalized)
rescue ArgumentError
  nil
end

.sort_key(entry) ⇒ Object



67
68
69
# File 'lib/kettle/gha/pins/version_rubric.rb', line 67

def sort_key(entry)
  [entry.fetch(:version_obj), *specificity(entry)]
end

.sort_versions(versions) ⇒ Object



63
64
65
# File 'lib/kettle/gha/pins/version_rubric.rb', line 63

def sort_versions(versions)
  versions.sort_by { |entry| sort_key(entry) }
end

.specificity(entry) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/kettle/gha/pins/version_rubric.rb', line 71

def specificity(entry)
  text = entry.fetch(:tag).to_s.sub(/\A[vV]/, "")
  release_text, suffix = text.split(/[-.](?=[A-Za-z])/, 2)
  numeric_segments = release_text.to_s.split(".").take_while { |part| part.match?(/\A\d+\z/) }

  [
    numeric_segments.length,
    suffix ? 0 : 1,
    text.length,
    entry.fetch(:tag).to_s
  ]
end