dependabot-nix 0.383.0 → 0.384.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: 9400dda6e00c6bfbbf31f20efd478924245fb89da9d1f0dd0d9f5e8256783bba
4
- data.tar.gz: dedd3122805ca51fa7c18bb859383d6ba236c54a8c2e9e42ee20d14eeac02e37
3
+ metadata.gz: cc6760fd535e74086902529169aa58860943e51c3b7210d0a2608b12b5cfdf47
4
+ data.tar.gz: 239b9f0415ca75a7451d184baaa88a50c3e95af54c9a7351f0b08bceb2a6694b
5
5
  SHA512:
6
- metadata.gz: d7db92f0a7b58867248fdb971d96a3c3353a9d613ec05ae1635ac982459ad41662aab7c1ef54e80219886a51eee7b529ebd909c9790c877d496f7f7451d6a535
7
- data.tar.gz: c42a5326b2cfa670d36e5865d5d4c2e40426e7f23665f6c65ed8ae49367672ea13bbd875e5866fd44473e329d7d9f3fc310816f089d5140bac94361e8884b58b
6
+ metadata.gz: 47dd160601292ee0357e80c8011e67c18b5cb42afc361d3df5d0b1e59f444a2c2ae0a53a9e1f18c7471d35d77267e79f80ebc8f40ba9e27fc473acec88f67f98
7
+ data.tar.gz: 020f4cfed1fc8b9ada9e1cf97c806614e07ea27dc8b322614928e403709140d61ffb34d9565bdc34e84ef7b3a91c7a2d42957bd46cb6b4a13aee1cc0d8eb5017
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/nix/versioned_name"
7
+
8
+ module Dependabot
9
+ module Nix
10
+ # A NixOS channel: a VersionedName plus helpers for channel tarball URLs
11
+ # (channels.nixos.org/<channel>/nixexprs.tar.xz).
12
+ class Channel < VersionedName
13
+ extend T::Sig
14
+
15
+ CHANNEL_HOST = "channels.nixos.org"
16
+ DEFAULT_EXTENSION = "xz"
17
+
18
+ # e.g. https://channels.nixos.org/nixos-26.05/nixexprs.tar.xz
19
+ # The suffix is captured so a bump keeps the flake's existing format.
20
+ CHANNEL_URL_PATTERN = %r{
21
+ \Ahttps?://channels\.nixos\.org/
22
+ (?<channel>[a-zA-Z0-9][a-zA-Z0-9._-]*)
23
+ /nixexprs\.tar\.(?<extension>xz|gz|bz2)\z
24
+ }x
25
+
26
+ sig { params(url: T.nilable(String)).returns(T::Boolean) }
27
+ def self.channel_url?(url)
28
+ return false unless url
29
+
30
+ CHANNEL_URL_PATTERN.match?(url)
31
+ end
32
+
33
+ sig { params(url: T.nilable(String)).returns(T.nilable(String)) }
34
+ def self.channel_name_from_url(url)
35
+ return unless url
36
+
37
+ CHANNEL_URL_PATTERN.match(url)&.[](:channel)
38
+ end
39
+
40
+ # The compression suffix (xz, gz, bz2) of a channel tarball URL.
41
+ sig { params(url: T.nilable(String)).returns(T.nilable(String)) }
42
+ def self.extension_from_url(url)
43
+ return unless url
44
+
45
+ CHANNEL_URL_PATTERN.match(url)&.[](:extension)
46
+ end
47
+
48
+ # Preserves the flake's suffix so a bump keeps its compression format.
49
+ sig { params(channel_name: String, extension: String).returns(String) }
50
+ def self.url_for(channel_name, extension: DEFAULT_EXTENSION)
51
+ "https://#{CHANNEL_HOST}/#{channel_name}/nixexprs.tar.#{extension}"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -8,6 +8,7 @@ require "dependabot/dependency"
8
8
  require "dependabot/file_parsers"
9
9
  require "dependabot/file_parsers/base"
10
10
  require "dependabot/shared_helpers"
11
+ require "dependabot/nix/channel"
11
12
  require "dependabot/nix/package_manager"
12
13
 
13
14
  module Dependabot
@@ -15,8 +16,8 @@ module Dependabot
15
16
  class FileParser < Dependabot::FileParsers::Base
16
17
  extend T::Sig
17
18
 
18
- # Source types that are backed by git and can be updated via revision tracking
19
- SUPPORTED_SOURCE_TYPES = T.let(%w(github gitlab sourcehut git).freeze, T::Array[String])
19
+ # Updatable source types: git-backed sources plus NixOS channel tarballs.
20
+ SUPPORTED_SOURCE_TYPES = T.let(%w(github gitlab sourcehut git tarball).freeze, T::Array[String])
20
21
 
21
22
  SUPPORTED_LOCK_VERSION = 7
22
23
 
@@ -134,9 +135,28 @@ module Dependabot
134
135
  source_type = locked.fetch("type", nil)
135
136
  return unless SUPPORTED_SOURCE_TYPES.include?(source_type)
136
137
 
138
+ # Skip inputs pinned to a bare commit SHA: no branch or tag to track.
139
+ return if revision_pinned?(original)
140
+
137
141
  rev = locked.fetch("rev", nil)
138
142
  return unless rev
139
143
 
144
+ if source_type == "tarball"
145
+ build_tarball_dependency(input_name, original, rev)
146
+ else
147
+ build_git_dependency(input_name, locked, original, rev)
148
+ end
149
+ end
150
+
151
+ sig do
152
+ params(
153
+ input_name: String,
154
+ locked: T::Hash[String, T.untyped],
155
+ original: T::Hash[String, T.untyped],
156
+ rev: String
157
+ ).returns(T.nilable(Dependabot::Dependency))
158
+ end
159
+ def build_git_dependency(input_name, locked, original, rev)
140
160
  url = build_url(locked)
141
161
  return unless url
142
162
 
@@ -155,6 +175,38 @@ module Dependabot
155
175
  )
156
176
  end
157
177
 
178
+ sig { params(original: T::Hash[String, T.untyped]).returns(T::Boolean) }
179
+ def revision_pinned?(original)
180
+ !original.fetch("rev", nil).nil?
181
+ end
182
+
183
+ # Channel tarballs track a channel in the URL (e.g. nixos-26.05), not a git
184
+ # ref. Non-channel tarballs aren't updatable, so they're skipped.
185
+ sig do
186
+ params(
187
+ input_name: String,
188
+ original: T::Hash[String, T.untyped],
189
+ rev: String
190
+ ).returns(T.nilable(Dependabot::Dependency))
191
+ end
192
+ def build_tarball_dependency(input_name, original, rev)
193
+ url = original.fetch("url", nil)
194
+ channel = Channel.channel_name_from_url(url)
195
+ return unless channel
196
+
197
+ Dependency.new(
198
+ name: input_name,
199
+ version: rev,
200
+ package_manager: "nix",
201
+ requirements: [{
202
+ requirement: nil,
203
+ file: "flake.lock",
204
+ source: { type: "tarball", url: url, branch: nil, ref: channel },
205
+ groups: []
206
+ }]
207
+ )
208
+ end
209
+
158
210
  sig { params(locked: T::Hash[String, T.untyped]).returns(T.nilable(String)) }
159
211
  def build_url(locked)
160
212
  case locked["type"]
@@ -3,6 +3,8 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
 
6
+ require "dependabot/nix/channel"
7
+
6
8
  module Dependabot
7
9
  module Nix
8
10
  # Parses flake.nix content to locate input URL declarations and extract
@@ -62,35 +64,9 @@ module Dependabot
62
64
 
63
65
  url_str = match[:url]
64
66
 
65
- # Try shorthand scheme first (github:, gitlab:, sourcehut:)
66
- url_match = FLAKE_URL_PATTERN.match(url_str)
67
- if url_match
68
- return InputUrl.new(
69
- full_url: url_str,
70
- scheme: T.must(url_match[:scheme]),
71
- owner: T.must(url_match[:owner]),
72
- repo: T.must(url_match[:repo]),
73
- ref: url_match[:ref],
74
- query: url_match[:query],
75
- match_start: match[:url_start],
76
- match_end: match[:url_end]
77
- )
78
- end
79
-
80
- # Try indirect/registry shorthand (e.g. nixpkgs/nixos-24.11)
81
- indirect_match = INDIRECT_URL_PATTERN.match(url_str)
82
- return unless indirect_match
83
-
84
- InputUrl.new(
85
- full_url: url_str,
86
- scheme: "indirect",
87
- owner: T.must(indirect_match[:id]),
88
- repo: "",
89
- ref: indirect_match[:ref],
90
- query: nil,
91
- match_start: match[:url_start],
92
- match_end: match[:url_end]
93
- )
67
+ build_shorthand_input(url_str, match) ||
68
+ build_tarball_input(url_str, match) ||
69
+ build_indirect_input(url_str, match)
94
70
  end
95
71
 
96
72
  sig { params(new_ref: String).returns(T.nilable(String)) }
@@ -117,6 +93,60 @@ module Dependabot
117
93
  sig { returns(String) }
118
94
  attr_reader :input_name
119
95
 
96
+ # Shorthand scheme URLs: github:, gitlab:, sourcehut:
97
+ sig { params(url_str: String, match: T::Hash[Symbol, T.untyped]).returns(T.nilable(InputUrl)) }
98
+ def build_shorthand_input(url_str, match)
99
+ url_match = FLAKE_URL_PATTERN.match(url_str)
100
+ return unless url_match
101
+
102
+ InputUrl.new(
103
+ full_url: url_str,
104
+ scheme: T.must(url_match[:scheme]),
105
+ owner: T.must(url_match[:owner]),
106
+ repo: T.must(url_match[:repo]),
107
+ ref: url_match[:ref],
108
+ query: url_match[:query],
109
+ match_start: match[:url_start],
110
+ match_end: match[:url_end]
111
+ )
112
+ end
113
+
114
+ # NixOS channel tarball URL, e.g. channels.nixos.org/nixos-26.05/nixexprs.tar.xz
115
+ sig { params(url_str: String, match: T::Hash[Symbol, T.untyped]).returns(T.nilable(InputUrl)) }
116
+ def build_tarball_input(url_str, match)
117
+ channel = Channel.channel_name_from_url(url_str)
118
+ return unless channel
119
+
120
+ InputUrl.new(
121
+ full_url: url_str,
122
+ scheme: "tarball",
123
+ owner: "",
124
+ repo: "",
125
+ ref: channel,
126
+ query: nil,
127
+ match_start: match[:url_start],
128
+ match_end: match[:url_end]
129
+ )
130
+ end
131
+
132
+ # Indirect/registry shorthand (e.g. nixpkgs/nixos-24.11)
133
+ sig { params(url_str: String, match: T::Hash[Symbol, T.untyped]).returns(T.nilable(InputUrl)) }
134
+ def build_indirect_input(url_str, match)
135
+ indirect_match = INDIRECT_URL_PATTERN.match(url_str)
136
+ return unless indirect_match
137
+
138
+ InputUrl.new(
139
+ full_url: url_str,
140
+ scheme: "indirect",
141
+ owner: T.must(indirect_match[:id]),
142
+ repo: "",
143
+ ref: indirect_match[:ref],
144
+ query: nil,
145
+ match_start: match[:url_start],
146
+ match_end: match[:url_end]
147
+ )
148
+ end
149
+
120
150
  # Returns a hash with :url, :url_start, :url_end if found, or nil.
121
151
  sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
122
152
  def find_url_match
@@ -180,8 +210,12 @@ module Dependabot
180
210
 
181
211
  sig { params(input_url: InputUrl, new_ref: String).returns(String) }
182
212
  def build_updated_url(input_url, new_ref)
183
- if input_url.scheme == "indirect"
213
+ case input_url.scheme
214
+ when "indirect"
184
215
  "#{input_url.owner}/#{new_ref}"
216
+ when "tarball"
217
+ old_channel = T.must(input_url.ref)
218
+ input_url.full_url.sub("/#{old_channel}/", "/#{new_ref}/")
185
219
  else
186
220
  base = "#{input_url.scheme}:#{input_url.owner}/#{input_url.repo}/#{new_ref}"
187
221
  input_url.query ? "#{base}?#{input_url.query}" : base
@@ -0,0 +1,44 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/nix/requirement"
7
+
8
+ module Dependabot
9
+ module Nix
10
+ # Tests YY.MM version strings against Dependabot ignore conditions.
11
+ class IgnoreFilter
12
+ extend T::Sig
13
+
14
+ sig { params(ignored_versions: T::Array[String]).void }
15
+ def initialize(ignored_versions)
16
+ @ignored_versions = ignored_versions
17
+ @requirements = T.let(nil, T.nilable(T::Array[Gem::Requirement]))
18
+ end
19
+
20
+ sig { params(version_str: T.nilable(String)).returns(T::Boolean) }
21
+ def ignored?(version_str)
22
+ return false unless version_str
23
+ return false if requirements.empty?
24
+
25
+ gem_version = Gem::Version.new(version_str)
26
+ requirements.any? { |req| req.satisfied_by?(gem_version) }
27
+ end
28
+
29
+ private
30
+
31
+ sig { returns(T::Array[String]) }
32
+ attr_reader :ignored_versions
33
+
34
+ sig { returns(T::Array[Gem::Requirement]) }
35
+ def requirements
36
+ @requirements ||= ignored_versions.flat_map do |req|
37
+ Dependabot::Nix::Requirement.requirements_array(req)
38
+ rescue Gem::Requirement::BadRequirementError
39
+ []
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -5,18 +5,24 @@ require "sorbet-runtime"
5
5
 
6
6
  require "dependabot/metadata_finders"
7
7
  require "dependabot/metadata_finders/base"
8
+ require "dependabot/nix/channel"
8
9
 
9
10
  module Dependabot
10
11
  module Nix
11
12
  class MetadataFinder < Dependabot::MetadataFinders::Base
12
13
  extend T::Sig
13
14
 
15
+ # Channel tarballs resolve to nixpkgs revisions, so metadata points there.
16
+ NIXPKGS_SOURCE_URL = "https://github.com/NixOS/nixpkgs"
17
+
14
18
  private
15
19
 
16
20
  sig { override.returns(T.nilable(Dependabot::Source)) }
17
21
  def look_up_source
18
- url = dependency.requirements.first&.fetch(:source)&.fetch(:url) ||
19
- dependency.requirements.first&.fetch(:source)&.fetch("url")
22
+ source = dependency.requirements.first&.fetch(:source, nil)
23
+ url = source && (source[:url] || source["url"])
24
+
25
+ return Source.from_url(NIXPKGS_SOURCE_URL) if Channel.channel_url?(url)
20
26
 
21
27
  Source.from_url(url)
22
28
  end
@@ -0,0 +1,126 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/nix/update_checker"
7
+ require "dependabot/nix/channel"
8
+ require "dependabot/nix/ignore_filter"
9
+ require "dependabot/registry_client"
10
+
11
+ module Dependabot
12
+ module Nix
13
+ class UpdateChecker
14
+ # Finds the latest NixOS channel and its revision: channels come from the
15
+ # channels.nixos.org S3 listing, revisions from each channel's git-revision marker.
16
+ class ChannelVersionFinder
17
+ extend T::Sig
18
+
19
+ CHANNELS_BASE_URL = "https://channels.nixos.org"
20
+ CHANNEL_KEY_PATTERN = %r{<Key>([^<]+)</Key>}
21
+ SHA_PATTERN = /\A[0-9a-f]{40}\z/
22
+
23
+ sig do
24
+ params(
25
+ current_channel: String,
26
+ credentials: T::Array[Dependabot::Credential],
27
+ ignored_versions: T::Array[String],
28
+ extension: String
29
+ ).void
30
+ end
31
+ def initialize(current_channel:, credentials:, ignored_versions: [], extension: Channel::DEFAULT_EXTENSION)
32
+ @current_channel = T.let(Channel.new(current_channel), Channel)
33
+ @credentials = credentials
34
+ @ignored_versions = ignored_versions
35
+ @extension = extension
36
+ @available_channels = T.let(nil, T.nilable(T::Array[String]))
37
+ @ignore_filter = T.let(nil, T.nilable(IgnoreFilter))
38
+ end
39
+
40
+ # Newest same-family channel with its revision, or nil (rolling channel,
41
+ # already latest, or revision unresolvable).
42
+ sig { returns(T.nilable(T::Hash[Symbol, String])) }
43
+ def latest_channel
44
+ return unless current_channel.versioned?
45
+
46
+ candidate = newest_candidate
47
+ return unless candidate
48
+
49
+ rev = resolve_revision(candidate.name)
50
+ return unless rev
51
+
52
+ { channel: candidate.name, url: Channel.url_for(candidate.name, extension: extension), commit_sha: rev }
53
+ end
54
+
55
+ # Current channel's revision (refresh path).
56
+ sig { returns(T.nilable(String)) }
57
+ def current_channel_revision
58
+ resolve_revision(current_channel.name)
59
+ end
60
+
61
+ private
62
+
63
+ sig { returns(Channel) }
64
+ attr_reader :current_channel
65
+
66
+ sig { returns(T::Array[Dependabot::Credential]) }
67
+ attr_reader :credentials
68
+
69
+ sig { returns(T::Array[String]) }
70
+ attr_reader :ignored_versions
71
+
72
+ sig { returns(String) }
73
+ attr_reader :extension
74
+
75
+ sig { returns(T.nilable(Channel)) }
76
+ def newest_candidate
77
+ candidates = available_channels
78
+ .map { |name| Channel.new(name) }
79
+ .select { |channel| channel.same_family?(current_channel) }
80
+ .select { |channel| channel.newer_than?(current_channel) }
81
+ .reject { |channel| ignore_filter.ignored?(channel.version_string) }
82
+
83
+ candidates.max_by { |channel| T.must(channel.version) }
84
+ end
85
+
86
+ sig { returns(T::Array[String]) }
87
+ def available_channels
88
+ @available_channels ||= fetch_available_channels
89
+ end
90
+
91
+ sig { returns(T::Array[String]) }
92
+ def fetch_available_channels
93
+ prefix = current_channel.prefix
94
+ return [] unless prefix
95
+
96
+ url = "#{CHANNELS_BASE_URL}/?delimiter=/&list-type=2&prefix=#{prefix}"
97
+ response = Dependabot::RegistryClient.get(url: url)
98
+ return [] unless response.status == 200
99
+
100
+ response.body.to_s.scan(CHANNEL_KEY_PATTERN).flatten
101
+ rescue StandardError => e
102
+ Dependabot.logger.info("Failed to list NixOS channels: #{e.class}: #{e.message}")
103
+ []
104
+ end
105
+
106
+ sig { params(channel: String).returns(T.nilable(String)) }
107
+ def resolve_revision(channel)
108
+ url = "#{CHANNELS_BASE_URL}/#{channel}/git-revision"
109
+ response = Dependabot::RegistryClient.get(url: url)
110
+ return unless response.status == 200
111
+
112
+ rev = response.body.to_s.strip
113
+ rev if rev.match?(SHA_PATTERN)
114
+ rescue StandardError => e
115
+ Dependabot.logger.info("Failed to resolve revision for #{channel}: #{e.class}: #{e.message}")
116
+ nil
117
+ end
118
+
119
+ sig { returns(IgnoreFilter) }
120
+ def ignore_filter
121
+ @ignore_filter ||= IgnoreFilter.new(ignored_versions)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -4,7 +4,8 @@
4
4
  require "sorbet-runtime"
5
5
 
6
6
  require "dependabot/nix/update_checker"
7
- require "dependabot/nix/requirement"
7
+ require "dependabot/nix/versioned_name"
8
+ require "dependabot/nix/ignore_filter"
8
9
  require "dependabot/git_metadata_fetcher"
9
10
  require "dependabot/git_ref"
10
11
 
@@ -16,14 +17,6 @@ module Dependabot
16
17
  class VersionedBranchFinder
17
18
  extend T::Sig
18
19
 
19
- # Matches branch names with a YY.MM version segment and optional suffix.
20
- # Captures: prefix (including separator), version, and optional suffix.
21
- # Examples: "nixos-24.11" => prefix="nixos-", version="24.11", suffix=nil
22
- # "nixos-24.11-small" => prefix="nixos-", version="24.11", suffix="-small"
23
- # "release-24.11-aarch64" => prefix="release-", version="24.11", suffix="-aarch64"
24
- VERSIONED_BRANCH_PATTERN = /\A(.+[.\-_])(\d{2}\.\d{2})(-[a-zA-Z0-9]+)?\z/
25
- private_constant :VERSIONED_BRANCH_PATTERN
26
-
27
20
  sig do
28
21
  params(
29
22
  current_ref: String,
@@ -42,22 +35,16 @@ module Dependabot
42
35
  # Returns true if the current ref looks like a versioned branch.
43
36
  sig { returns(T::Boolean) }
44
37
  def versioned_branch?
45
- !branch_version_match.nil?
38
+ current_name.versioned?
46
39
  end
47
40
 
48
41
  # Returns the latest versioned branch info or nil if no newer branch exists.
49
42
  # Returns { branch: "nixos-25.05", commit_sha: "abc123" } or nil.
50
43
  sig { returns(T.nilable(T::Hash[Symbol, String])) }
51
44
  def latest_versioned_branch
52
- match = branch_version_match
53
- return unless match
54
-
55
- prefix = match[1]
56
- current_version = parse_version(T.must(match[2]))
57
- return unless current_version
45
+ return unless current_name.versioned?
58
46
 
59
- suffix = match[3] # nil if no suffix, e.g. "-small" if present
60
- find_latest_branch(T.must(prefix), current_version, suffix)
47
+ find_latest_branch
61
48
  end
62
49
 
63
50
  private
@@ -74,25 +61,14 @@ module Dependabot
74
61
  sig { returns(T::Array[String]) }
75
62
  attr_reader :ignored_versions
76
63
 
77
- sig { returns(T.nilable(MatchData)) }
78
- def branch_version_match
79
- @branch_version_match ||= T.let(
80
- VERSIONED_BRANCH_PATTERN.match(current_ref),
81
- T.nilable(MatchData)
82
- )
64
+ sig { returns(VersionedName) }
65
+ def current_name
66
+ @current_name ||= T.let(VersionedName.new(current_ref), T.nilable(VersionedName))
83
67
  end
84
68
 
85
- sig do
86
- params(
87
- prefix: String,
88
- current_version: T::Array[Integer],
89
- suffix: T.nilable(String)
90
- ).returns(T.nilable(T::Hash[Symbol, String]))
91
- end
92
- def find_latest_branch(prefix, current_version, suffix)
93
- candidates = remote_branches.filter_map do |ref|
94
- build_candidate(ref, prefix, current_version, suffix)
95
- end
69
+ sig { returns(T.nilable(T::Hash[Symbol, String])) }
70
+ def find_latest_branch
71
+ candidates = remote_branches.filter_map { |ref| build_candidate(ref) }
96
72
 
97
73
  latest = candidates.max_by { |c| c[:version] }
98
74
  return unless latest
@@ -100,62 +76,19 @@ module Dependabot
100
76
  { branch: latest[:branch].to_s, commit_sha: latest[:commit_sha].to_s }
101
77
  end
102
78
 
103
- sig do
104
- params(
105
- ref: Dependabot::GitRef,
106
- prefix: String,
107
- current_version: T::Array[Integer],
108
- suffix: T.nilable(String)
109
- ).returns(T.nilable(T::Hash[Symbol, T.untyped]))
110
- end
111
- def build_candidate(ref, prefix, current_version, suffix)
112
- branch_match = VERSIONED_BRANCH_PATTERN.match(ref.name)
113
- return unless branch_match
114
- return unless branch_match[1] == prefix
115
- return unless branch_match[3] == suffix
116
-
117
- version_str = T.must(branch_match[2])
118
- version = parse_version(version_str)
119
- return unless version
120
- return unless (version <=> current_version) == 1
121
- return if version_ignored?(version_str)
122
-
123
- { branch: ref.name, commit_sha: ref.commit_sha, version: version }
124
- end
125
-
126
- sig { params(version_str: String).returns(T::Boolean) }
127
- def version_ignored?(version_str)
128
- return false if ignore_requirements.empty?
79
+ sig { params(ref: Dependabot::GitRef).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
80
+ def build_candidate(ref)
81
+ candidate = VersionedName.new(ref.name)
82
+ return unless candidate.same_family?(current_name)
83
+ return unless candidate.newer_than?(current_name)
84
+ return if ignore_filter.ignored?(candidate.version_string)
129
85
 
130
- gem_version = Gem::Version.new(version_str)
131
- ignore_requirements.any? { |req| req.satisfied_by?(gem_version) }
86
+ { branch: ref.name, commit_sha: ref.commit_sha, version: candidate.version }
132
87
  end
133
88
 
134
- # Parses "YY.MM" into [year, month] for comparison.
135
- sig { params(version_str: String).returns(T.nilable(T::Array[Integer])) }
136
- def parse_version(version_str)
137
- parts = version_str.split(".")
138
- return unless parts.length == 2
139
-
140
- year = Integer(T.must(parts[0]), 10)
141
- month = Integer(T.must(parts[1]), 10)
142
- return unless month.between?(1, 12)
143
-
144
- [year, month]
145
- rescue ArgumentError
146
- nil
147
- end
148
-
149
- sig { returns(T::Array[Gem::Requirement]) }
150
- def ignore_requirements
151
- @ignore_requirements ||= T.let(
152
- ignored_versions.flat_map do |req|
153
- Dependabot::Nix::Requirement.requirements_array(req)
154
- rescue Gem::Requirement::BadRequirementError
155
- []
156
- end,
157
- T.nilable(T::Array[Gem::Requirement])
158
- )
89
+ sig { returns(IgnoreFilter) }
90
+ def ignore_filter
91
+ @ignore_filter ||= T.let(IgnoreFilter.new(ignored_versions), T.nilable(IgnoreFilter))
159
92
  end
160
93
 
161
94
  sig { returns(T::Array[Dependabot::GitRef]) }
@@ -7,6 +7,7 @@ require "dependabot/update_checkers"
7
7
  require "dependabot/update_checkers/base"
8
8
  require "dependabot/nix/version"
9
9
  require "dependabot/nix/requirement"
10
+ require "dependabot/nix/channel"
10
11
  require "dependabot/git_commit_checker"
11
12
 
12
13
  module Dependabot
@@ -16,6 +17,7 @@ module Dependabot
16
17
 
17
18
  require_relative "update_checker/latest_version_finder"
18
19
  require_relative "update_checker/versioned_branch_finder"
20
+ require_relative "update_checker/channel_version_finder"
19
21
 
20
22
  sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
21
23
  def latest_version
@@ -38,7 +40,9 @@ module Dependabot
38
40
 
39
41
  sig { override.returns(T::Array[Dependabot::DependencyRequirement]) }
40
42
  def updated_requirements
41
- if ref_pinned_to_version_tag?
43
+ if tarball_channel_input?
44
+ wrap_requirements(updated_requirements_for_channel)
45
+ elsif ref_pinned_to_version_tag?
42
46
  wrap_requirements(updated_requirements_for_tag)
43
47
  elsif ref_is_versioned_branch?
44
48
  wrap_requirements(updated_requirements_for_versioned_branch)
@@ -61,7 +65,9 @@ module Dependabot
61
65
 
62
66
  sig { returns(T.nilable(String)) }
63
67
  def fetch_latest_version
64
- if ref_pinned_to_version_tag?
68
+ if tarball_channel_input?
69
+ fetch_latest_version_for_channel
70
+ elsif ref_pinned_to_version_tag?
65
71
  fetch_latest_version_for_tag
66
72
  elsif ref_is_versioned_branch?
67
73
  fetch_latest_version_for_versioned_branch || fetch_latest_version_for_commit
@@ -70,6 +76,75 @@ module Dependabot
70
76
  end
71
77
  end
72
78
 
79
+ # --- Tarball channel support ---
80
+
81
+ sig { returns(T::Boolean) }
82
+ def tarball_channel_input?
83
+ source = dependency.source_details(allowed_types: ["tarball"])
84
+ return false unless source
85
+
86
+ Channel.channel_url?(source[:url] || source["url"])
87
+ end
88
+
89
+ sig { returns(T.nilable(String)) }
90
+ def fetch_latest_version_for_channel
91
+ latest_channel&.fetch(:commit_sha) || channel_version_finder.current_channel_revision
92
+ end
93
+
94
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
95
+ def updated_requirements_for_channel
96
+ result = latest_channel
97
+ return dependency.requirements unless result
98
+
99
+ dependency.requirements.map do |req|
100
+ source = req[:source]
101
+ next req unless source
102
+
103
+ req.merge(source: source.merge(ref: result[:channel], url: result[:url]))
104
+ end
105
+ end
106
+
107
+ sig { returns(T.nilable(T::Hash[Symbol, String])) }
108
+ def latest_channel
109
+ @latest_channel ||= T.let(
110
+ channel_version_finder.latest_channel,
111
+ T.nilable(T::Hash[Symbol, String])
112
+ )
113
+ end
114
+
115
+ sig { returns(ChannelVersionFinder) }
116
+ def channel_version_finder
117
+ @channel_version_finder ||= T.let(
118
+ ChannelVersionFinder.new(
119
+ current_channel: T.must(tarball_channel_name),
120
+ credentials: credentials,
121
+ ignored_versions: ignored_versions,
122
+ extension: tarball_channel_extension
123
+ ),
124
+ T.nilable(ChannelVersionFinder)
125
+ )
126
+ end
127
+
128
+ sig { returns(T.nilable(String)) }
129
+ def tarball_channel_name
130
+ source = dependency.source_details(allowed_types: ["tarball"])
131
+ return unless source
132
+
133
+ ref = source[:ref] || source["ref"]
134
+ return ref if ref
135
+
136
+ # Fall back to the URL's channel when the source omits a ref.
137
+ Channel.channel_name_from_url(source[:url] || source["url"])
138
+ end
139
+
140
+ # Preserve the flake's existing tarball suffix (xz, gz, bz2) on a bump.
141
+ sig { returns(String) }
142
+ def tarball_channel_extension
143
+ source = dependency.source_details(allowed_types: ["tarball"])
144
+ url = source && (source[:url] || source["url"])
145
+ Channel.extension_from_url(url) || Channel::DEFAULT_EXTENSION
146
+ end
147
+
73
148
  # --- Tag-pinned ref support ---
74
149
 
75
150
  sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
@@ -0,0 +1,88 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module Nix
8
+ # Parses a NixOS-style versioned name (e.g. "nixos-26.05", "nixpkgs-24.11-darwin")
9
+ # into prefix, YY.MM version, and suffix, and compares names within a family.
10
+ class VersionedName
11
+ extend T::Sig
12
+
13
+ # prefix + YY.MM version + optional suffix, e.g. "nixpkgs-26.05-darwin".
14
+ VERSIONED_NAME_PATTERN =
15
+ /\A(?<prefix>.+[.\-_])(?<version>\d{2}\.\d{2})(?<suffix>-[a-zA-Z0-9]+)?\z/
16
+
17
+ sig { params(name: String).void }
18
+ def initialize(name)
19
+ @name = name
20
+ @match = T.let(VERSIONED_NAME_PATTERN.match(name), T.nilable(MatchData))
21
+ end
22
+
23
+ sig { returns(String) }
24
+ attr_reader :name
25
+
26
+ sig { returns(T::Boolean) }
27
+ def versioned?
28
+ !@match.nil?
29
+ end
30
+
31
+ sig { returns(T.nilable(String)) }
32
+ def prefix
33
+ @match&.[](:prefix)
34
+ end
35
+
36
+ sig { returns(T.nilable(String)) }
37
+ def suffix
38
+ @match&.[](:suffix)
39
+ end
40
+
41
+ sig { returns(T.nilable(String)) }
42
+ def version_string
43
+ @match&.[](:version)
44
+ end
45
+
46
+ # YY.MM as a comparable [year, month], or nil.
47
+ sig { returns(T.nilable(T::Array[Integer])) }
48
+ def version
49
+ version_str = version_string
50
+ return unless version_str
51
+
52
+ parse_version(version_str)
53
+ end
54
+
55
+ # Same prefix and suffix, so bumps stay within e.g. nixos-* (not nixos-*-small).
56
+ sig { params(other: VersionedName).returns(T::Boolean) }
57
+ def same_family?(other)
58
+ versioned? && other.versioned? &&
59
+ prefix == other.prefix && suffix == other.suffix
60
+ end
61
+
62
+ sig { params(other: VersionedName).returns(T::Boolean) }
63
+ def newer_than?(other)
64
+ this_version = version
65
+ other_version = other.version
66
+ return false unless this_version && other_version
67
+
68
+ (this_version <=> other_version) == 1
69
+ end
70
+
71
+ private
72
+
73
+ sig { params(version_str: String).returns(T.nilable(T::Array[Integer])) }
74
+ def parse_version(version_str)
75
+ parts = version_str.split(".")
76
+ return unless parts.length == 2
77
+
78
+ year = Integer(T.must(parts[0]), 10)
79
+ month = Integer(T.must(parts[1]), 10)
80
+ return unless month.between?(1, 12)
81
+
82
+ [year, month]
83
+ rescue ArgumentError
84
+ nil
85
+ end
86
+ end
87
+ end
88
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.383.0
4
+ version: 0.384.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.383.0
18
+ version: 0.384.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.383.0
25
+ version: 0.384.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -242,24 +242,28 @@ extensions: []
242
242
  extra_rdoc_files: []
243
243
  files:
244
244
  - lib/dependabot/nix.rb
245
+ - lib/dependabot/nix/channel.rb
245
246
  - lib/dependabot/nix/file_fetcher.rb
246
247
  - lib/dependabot/nix/file_parser.rb
247
248
  - lib/dependabot/nix/file_updater.rb
248
249
  - lib/dependabot/nix/flake_nix_parser.rb
250
+ - lib/dependabot/nix/ignore_filter.rb
249
251
  - lib/dependabot/nix/metadata_finder.rb
250
252
  - lib/dependabot/nix/package/package_details_fetcher.rb
251
253
  - lib/dependabot/nix/package_manager.rb
252
254
  - lib/dependabot/nix/requirement.rb
253
255
  - lib/dependabot/nix/update_checker.rb
256
+ - lib/dependabot/nix/update_checker/channel_version_finder.rb
254
257
  - lib/dependabot/nix/update_checker/latest_version_finder.rb
255
258
  - lib/dependabot/nix/update_checker/versioned_branch_finder.rb
256
259
  - lib/dependabot/nix/version.rb
260
+ - lib/dependabot/nix/versioned_name.rb
257
261
  homepage: https://github.com/dependabot/dependabot-core
258
262
  licenses:
259
263
  - MIT
260
264
  metadata:
261
265
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
262
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.383.0
266
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.384.0
263
267
  rdoc_options: []
264
268
  require_paths:
265
269
  - lib
@@ -274,7 +278,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
278
  - !ruby/object:Gem::Version
275
279
  version: 3.3.0
276
280
  requirements: []
277
- rubygems_version: 3.7.2
281
+ rubygems_version: 4.0.14
278
282
  specification_version: 4
279
283
  summary: Provides Dependabot support for Nix
280
284
  test_files: []