dependabot-nix 0.393.0 → 0.394.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: '09f3dd2c398997b22338a69338bdd464270f86f03a2e7656f4ddb342a79a813d'
4
- data.tar.gz: d768ac393fc688222704845e790a73c69fcb030b50e31543719a24546fafbaf7
3
+ metadata.gz: 30993355e24373bb5b160eee7028eee8357d502fd1e8843feb0a5d51b9510066
4
+ data.tar.gz: 373aa7fc524757879a45305546cfe4385dc6f8c0bfa365036318a7f9f3101a73
5
5
  SHA512:
6
- metadata.gz: 18250664fddb5c46b30a64c6881632cdce5be47017a0ef03ae3b0531f8b24392917b54e94b5cc03699ce057910da2202a0648ef8b10c635070cc3b822ff46bbe
7
- data.tar.gz: 59099630207f6355731b75ff3e15154d3708d4b6bb5163dc88e1093364d1fce992370b79b88663956c79339f85e418cccabea808c8f080adbd959ed277406d6d
6
+ metadata.gz: 7d7de3c9cc3b6c0bf5a5fea4f3cdcc5ef73aeb80d68baa4da6866cd62e4abde74a400a0a9599338d8569bfe7baf9038bb883bfb5c1a3714b098aa4b9985375e0
7
+ data.tar.gz: 8029256ca587c7d66216d1d902b8155c70b1df3384585d52aef6d7637a509d490d1554edb38920da273ce682c8d6cd9e567ca91908bb6d6b114e2fa75a05dfaf
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "json"
5
4
  require "sorbet-runtime"
6
5
 
7
6
  require "dependabot/dependency"
@@ -9,6 +8,7 @@ require "dependabot/file_parsers"
9
8
  require "dependabot/file_parsers/base"
10
9
  require "dependabot/shared_helpers"
11
10
  require "dependabot/nix/channel"
11
+ require "dependabot/nix/lockfile"
12
12
  require "dependabot/nix/package_manager"
13
13
 
14
14
  module Dependabot
@@ -32,22 +32,17 @@ module Dependabot
32
32
 
33
33
  sig { override.returns(T::Array[Dependabot::Dependency]) }
34
34
  def parse
35
- lock_content = JSON.parse(T.must(flake_lock.content))
35
+ lockfile = Lockfile.new(T.must(flake_lock.content))
36
36
 
37
- lock_version = lock_content["version"]
37
+ lock_version = lockfile.version
38
38
  if lock_version != SUPPORTED_LOCK_VERSION
39
39
  Dependabot.logger.warn(
40
40
  "flake.lock version #{lock_version.inspect} differs from expected #{SUPPORTED_LOCK_VERSION}"
41
41
  )
42
42
  end
43
43
 
44
- root_name = lock_content.fetch("root", "root")
45
- nodes = lock_content.fetch("nodes", {})
46
- root_node = nodes.fetch(root_name, {})
47
- root_inputs = root_node.fetch("inputs", {})
48
-
49
- root_inputs.filter_map do |input_name, node_label|
50
- node = resolve_node(node_label, nodes)
44
+ lockfile.root_input_names.filter_map do |input_name|
45
+ node = lockfile.input_node(input_name)
51
46
  next unless node
52
47
 
53
48
  build_dependency(input_name, node)
@@ -67,60 +62,6 @@ module Dependabot
67
62
 
68
63
  private
69
64
 
70
- # Resolves a node_label to its node in the lock file.
71
- # node_label is either a string (direct reference) or an array ("follows" path
72
- # that must be walked through nested inputs maps).
73
- sig do
74
- params(
75
- node_label: T.any(String, T::Array[String]),
76
- nodes: T::Hash[String, T.untyped]
77
- ).returns(T.nilable(T::Hash[String, T.untyped]))
78
- end
79
- def resolve_node(node_label, nodes)
80
- return nodes[node_label] unless node_label.is_a?(Array)
81
- return nil if node_label.empty?
82
-
83
- # Walk the "follows" path: e.g. ["nixpkgs", "flake-utils"] means
84
- # follow root -> nixpkgs node -> its inputs -> flake-utils
85
- resolved_label = resolve_follows_path(node_label, nodes)
86
- resolved_label ? nodes[resolved_label] : nil
87
- end
88
-
89
- # Walks a "follows" path through nested inputs to find the final node label.
90
- sig do
91
- params(
92
- path: T::Array[String],
93
- nodes: T::Hash[String, T.untyped]
94
- ).returns(T.nilable(String))
95
- end
96
- def resolve_follows_path(path, nodes)
97
- current_node_label = T.let(nil, T.nilable(String))
98
-
99
- path.each_with_index do |segment, index|
100
- # For the first segment, look up in the root's inputs via nodes directly
101
- target = if index.zero?
102
- # The first segment references a top-level node by name
103
- segment
104
- else
105
- # Subsequent segments look up inputs within the current node
106
- node = nodes[T.must(current_node_label)]
107
- return nil unless node.is_a?(Hash)
108
-
109
- inputs = node.fetch("inputs", nil)
110
- return nil unless inputs.is_a?(Hash)
111
-
112
- label = inputs[segment]
113
- return nil unless label.is_a?(String)
114
-
115
- label
116
- end
117
-
118
- current_node_label = target
119
- end
120
-
121
- current_node_label
122
- end
123
-
124
65
  sig do
125
66
  params(
126
67
  input_name: String,
@@ -218,7 +159,7 @@ module Dependabot
218
159
  "https://#{host}/#{locked['owner']}/#{locked['repo']}"
219
160
  when "sourcehut"
220
161
  host = locked["host"] || DEFAULT_HOSTS["sourcehut"]
221
- "https://#{host}/~#{locked['owner']}/#{locked['repo']}"
162
+ "https://#{host}/#{locked['owner']}/#{locked['repo']}"
222
163
  when "git"
223
164
  locked["url"]
224
165
  end
@@ -0,0 +1,132 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "uri"
5
+ require "sorbet-runtime"
6
+
7
+ require "dependabot/nix/file_updater"
8
+
9
+ module Dependabot
10
+ module Nix
11
+ class FileUpdater < Dependabot::FileUpdaters::Base
12
+ class FlakeRefBuilder
13
+ extend T::Sig
14
+
15
+ ARCHIVE_TYPES = %w(github gitlab sourcehut).freeze
16
+ ARCHIVE_PATH_KEYS = %w(type owner repo ref rev).freeze
17
+ GIT_URL_KEYS = %w(type url rev).freeze
18
+ LOCKED_ONLY_KEYS = %w(lastModified narHash revCount).freeze
19
+ SCP_STYLE_URL = /\A(?<user>[^@]+)@(?<host>[^:]+):(?<path>.+)\z/
20
+
21
+ sig { params(source: T::Hash[String, Object], revision: String).void }
22
+ def initialize(source:, revision:)
23
+ @source = source
24
+ @revision = revision
25
+ end
26
+
27
+ sig { returns(String) }
28
+ def to_s
29
+ source_type = required_string("type")
30
+
31
+ if ARCHIVE_TYPES.include?(source_type)
32
+ archive_reference(source_type)
33
+ elsif source_type == "git"
34
+ git_reference
35
+ else
36
+ raise ArgumentError, "Nix flake source type #{source_type.inspect} is not supported"
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ sig { returns(T::Hash[String, Object]) }
43
+ attr_reader :source
44
+
45
+ sig { returns(String) }
46
+ attr_reader :revision
47
+
48
+ sig { params(source_type: String).returns(String) }
49
+ def archive_reference(source_type)
50
+ owner = encode_path_segment(required_string("owner"))
51
+ repo = encode_path_segment(required_string("repo"))
52
+ base = "#{source_type}:#{owner}/#{repo}/#{encode_path_segment(revision)}"
53
+
54
+ append_query(base, query_attributes(excluding: ARCHIVE_PATH_KEYS))
55
+ end
56
+
57
+ sig { returns(String) }
58
+ def git_reference
59
+ uri = URI.parse(normalized_git_url(required_string("url")))
60
+ query = URI.decode_www_form(uri.query || "").to_h
61
+ query.merge!(query_attributes(excluding: GIT_URL_KEYS))
62
+ query["rev"] = revision
63
+ uri.query = encoded_query(query)
64
+ uri.to_s
65
+ end
66
+
67
+ sig { params(url: String).returns(String) }
68
+ def normalized_git_url(url)
69
+ return url if url.match?(/\Agit(?:\+[^:]+)?:/)
70
+
71
+ if (match = SCP_STYLE_URL.match(url))
72
+ return "git+ssh://#{match[:user]}@#{match[:host]}/#{match[:path]}"
73
+ end
74
+
75
+ uri = URI.parse(url)
76
+ raise ArgumentError, "git source URL #{url.inspect} must include a scheme" unless uri.scheme
77
+
78
+ "git+#{url}"
79
+ end
80
+
81
+ sig { params(excluding: T::Array[String]).returns(T::Hash[String, String]) }
82
+ def query_attributes(excluding:)
83
+ attributes = T.let({}, T::Hash[String, String])
84
+ source.each do |key, value|
85
+ next if excluding.include?(key) || LOCKED_ONLY_KEYS.include?(key) || value.nil?
86
+
87
+ attributes[key] = query_value(value)
88
+ end
89
+ attributes
90
+ end
91
+
92
+ sig { params(base: String, query: T::Hash[String, String]).returns(String) }
93
+ def append_query(base, query)
94
+ return base if query.empty?
95
+
96
+ "#{base}?#{encoded_query(query)}"
97
+ end
98
+
99
+ sig { params(query: T::Hash[String, String]).returns(String) }
100
+ def encoded_query(query)
101
+ URI.encode_www_form(query.sort)
102
+ end
103
+
104
+ sig { params(value: Object).returns(String) }
105
+ def query_value(value)
106
+ case value
107
+ when true then "1"
108
+ when false then "0"
109
+ when String, Integer then value.to_s
110
+ else
111
+ raise ArgumentError, "Nix flake source attribute value #{value.inspect} is not supported"
112
+ end
113
+ end
114
+
115
+ sig { params(value: String).returns(String) }
116
+ def encode_path_segment(value)
117
+ URI.encode_www_form_component(value)
118
+ .gsub("+", "%20")
119
+ .gsub("%7E", "~")
120
+ end
121
+
122
+ sig { params(key: String).returns(String) }
123
+ def required_string(key)
124
+ value = source[key]
125
+ return value if value.is_a?(String) && !value.empty?
126
+
127
+ raise ArgumentError, "Nix flake source is missing attribute #{key.inspect}"
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,6 +1,7 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "json"
4
5
  require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/errors"
@@ -8,12 +9,15 @@ require "dependabot/file_updaters"
8
9
  require "dependabot/file_updaters/base"
9
10
  require "dependabot/shared_helpers"
10
11
  require "dependabot/nix/flake_nix_parser"
12
+ require "dependabot/nix/lockfile"
11
13
 
12
14
  module Dependabot
13
15
  module Nix
14
16
  class FileUpdater < Dependabot::FileUpdaters::Base
15
17
  extend T::Sig
16
18
 
19
+ require_relative "file_updater/flake_ref_builder"
20
+
17
21
  # Nix's CLI restricts flake input attribute path elements to this regex.
18
22
  # see `flakeIdRegex` in nix/src/libflake/include/nix/flake/flakeref.hh
19
23
  FLAKE_ID_REGEX = /\A[a-zA-Z][a-zA-Z0-9_-]*\z/
@@ -26,6 +30,7 @@ module Dependabot
26
30
  updated_files << updated_file(file: flake_nix, content: updated_flake_nix_content) if updated_flake_nix_content
27
31
 
28
32
  updated_lockfile_content = update_flake_lock(updated_flake_nix_content)
33
+ validate_updated_revision(updated_lockfile_content)
29
34
 
30
35
  if updated_lockfile_content == flake_lock.content
31
36
  raise Dependabot::DependencyFileContentNotChanged,
@@ -53,18 +58,17 @@ module Dependabot
53
58
  return unless old_ref
54
59
  return if old_ref == new_ref
55
60
 
56
- FlakeNixParser.update_input_ref(T.must(flake_nix.content), dependency.name, new_ref)
61
+ updated_content = FlakeNixParser.update_input_ref(T.must(flake_nix.content), dependency.name, new_ref)
62
+ return updated_content if updated_content
63
+
64
+ raise Dependabot::DependencyFileNotResolvable,
65
+ "Cannot update the ref for flake input '#{dependency.name}' in flake.nix"
57
66
  end
58
67
 
59
68
  sig { params(updated_nix_content: T.nilable(String)).returns(String) }
60
69
  def update_flake_lock(updated_nix_content)
61
- unless dependency.name.match?(FLAKE_ID_REGEX)
62
- raise Dependabot::DependencyFileNotResolvable,
63
- "Cannot update flake input '#{dependency.name}': Nix requires input names to start " \
64
- "with a letter and contain only letters, digits, underscores, or hyphens " \
65
- "(pattern: [a-zA-Z][a-zA-Z0-9_-]*). " \
66
- "Rename the input in flake.nix to update it via Dependabot."
67
- end
70
+ lockfile = Lockfile.new(T.must(flake_lock.content))
71
+ input_path = validated_input_path(lockfile)
68
72
 
69
73
  SharedHelpers.in_a_temporary_repo_directory(
70
74
  flake_lock.directory,
@@ -74,14 +78,105 @@ module Dependabot
74
78
  File.write("flake.lock", T.must(flake_lock.content))
75
79
 
76
80
  SharedHelpers.run_shell_command(
77
- "nix flake update #{dependency.name}",
78
- fingerprint: "nix flake update <input_name>"
81
+ update_command(lockfile, input_path),
82
+ fingerprint: command_fingerprint
79
83
  )
80
84
 
81
85
  File.read("flake.lock")
82
86
  end
83
87
  end
84
88
 
89
+ sig { params(lockfile: Lockfile, input_path: String).returns(SharedHelpers::Command) }
90
+ def update_command(lockfile, input_path)
91
+ if git_dependency?
92
+ source = updated_git_source(lockfile)
93
+ revision = dependency.version
94
+ unless source && revision
95
+ raise Dependabot::DependencyFileNotResolvable,
96
+ "Cannot update flake input '#{dependency.name}' because its source metadata " \
97
+ "or target revision is missing"
98
+ end
99
+
100
+ exact_ref = build_exact_flake_ref(source, revision)
101
+ # `nix flake lock` writes the override but keeps the input's original branch or tag.
102
+ ["nix", "flake", "lock", "--override-input", input_path, exact_ref]
103
+ elsif tarball_dependency?
104
+ ["nix", "flake", "update", input_path]
105
+ else
106
+ raise Dependabot::DependencyFileNotResolvable,
107
+ "Cannot update flake input '#{dependency.name}' because its dependency source type is not supported"
108
+ end
109
+ end
110
+
111
+ sig { params(lockfile: Lockfile).returns(T.nilable(T::Hash[String, Object])) }
112
+ def updated_git_source(lockfile)
113
+ original_source = lockfile.original_source(dependency.name)
114
+ return unless original_source
115
+
116
+ source = if original_source["type"] == "indirect"
117
+ lockfile.locked_source(dependency.name)
118
+ else
119
+ original_source
120
+ end
121
+ return unless source
122
+
123
+ ref = new_source_ref
124
+ ref ? source.merge("ref" => ref) : source
125
+ end
126
+
127
+ sig { returns(String) }
128
+ def command_fingerprint
129
+ if git_dependency?
130
+ "nix flake lock --override-input <input_path> <flake_ref>"
131
+ else
132
+ "nix flake update <input_name>"
133
+ end
134
+ end
135
+
136
+ sig { params(source: T::Hash[String, Object], revision: String).returns(String) }
137
+ def build_exact_flake_ref(source, revision)
138
+ FlakeRefBuilder.new(source: source, revision: revision).to_s
139
+ rescue ArgumentError, URI::InvalidURIError => e
140
+ raise Dependabot::DependencyFileNotResolvable,
141
+ "Cannot update flake input '#{dependency.name}': #{e.message}"
142
+ end
143
+
144
+ sig { params(lockfile: Lockfile).returns(String) }
145
+ def validated_input_path(lockfile)
146
+ path = lockfile.input_path(dependency.name)
147
+ unless path&.all? { |segment| segment.match?(FLAKE_ID_REGEX) }
148
+ raise Dependabot::DependencyFileNotResolvable,
149
+ "Cannot update flake input '#{dependency.name}': each Nix input path segment must match " \
150
+ "[a-zA-Z][a-zA-Z0-9_-]*"
151
+ end
152
+
153
+ path.join("/")
154
+ end
155
+
156
+ sig { params(content: String).void }
157
+ def validate_updated_revision(content)
158
+ expected_revision = dependency.version
159
+ actual_revision = Lockfile.new(content).locked_revision(dependency.name)
160
+ return if expected_revision && actual_revision == expected_revision
161
+
162
+ raise Dependabot::DependencyFileNotResolvable,
163
+ "Expected flake input '#{dependency.name}' to lock revision " \
164
+ "#{expected_revision.inspect}, but Nix generated #{actual_revision.inspect}"
165
+ rescue JSON::ParserError => e
166
+ raise Dependabot::DependencyFileNotResolvable,
167
+ "Nix generated an invalid flake.lock for '#{dependency.name}': #{e.message}"
168
+ end
169
+
170
+ sig { returns(T::Boolean) }
171
+ def git_dependency?
172
+ dependency.requirements.first&.source_string("type") == "git"
173
+ end
174
+
175
+ sig { returns(T::Boolean) }
176
+ def tarball_dependency?
177
+ dependency.requirements.first&.source_string("type") == "tarball"
178
+ end
179
+
85
180
  sig { returns(T.nilable(String)) }
86
181
  def new_source_ref
87
182
  dependency.requirements.first&.source_string("ref")
@@ -1,16 +1,14 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "uri"
4
5
  require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/nix/channel"
7
8
 
8
9
  module Dependabot
9
10
  module Nix
10
- # Parses flake.nix content to locate input URL declarations and extract
11
- # their components (scheme, owner, repo, ref). Only handles the shorthand
12
- # URL schemes (github:, gitlab:, sourcehut:) since those are the ones
13
- # where the ref appears inline in the URL string.
11
+ # Parses flake.nix input URL declarations and updates their refs.
14
12
  class FlakeNixParser
15
13
  extend T::Sig
16
14
 
@@ -33,6 +31,9 @@ module Dependabot
33
31
  }x
34
32
  private_constant :INDIRECT_URL_PATTERN
35
33
 
34
+ GENERIC_GIT_SCHEME = /\Agit(?:\+[a-zA-Z][a-zA-Z0-9+.-]*)?\z/
35
+ private_constant :GENERIC_GIT_SCHEME
36
+
36
37
  # Matches an input URL assignment tied to a specific input name.
37
38
  # Covers the common syntactic forms:
38
39
  # inputs.NAME.url = "URL";
@@ -66,9 +67,31 @@ module Dependabot
66
67
 
67
68
  build_shorthand_input(url_str, match) ||
68
69
  build_tarball_input(url_str, match) ||
70
+ build_git_input(url_str, match) ||
69
71
  build_indirect_input(url_str, match)
70
72
  end
71
73
 
74
+ sig { params(url_str: String, match: T::Hash[Symbol, T.untyped]).returns(T.nilable(InputUrl)) }
75
+ def build_git_input(url_str, match)
76
+ uri = URI.parse(url_str)
77
+ return unless uri.scheme&.match?(GENERIC_GIT_SCHEME)
78
+
79
+ ref = URI.decode_www_form(uri.query || "").find { |key, _value| key == "ref" }&.last
80
+
81
+ InputUrl.new(
82
+ full_url: url_str,
83
+ scheme: "git",
84
+ owner: "",
85
+ repo: "",
86
+ ref: ref,
87
+ query: uri.query,
88
+ match_start: match[:url_start],
89
+ match_end: match[:url_end]
90
+ )
91
+ rescue URI::InvalidURIError
92
+ nil
93
+ end
94
+
72
95
  sig { params(new_ref: String).returns(T.nilable(String)) }
73
96
  def update_ref(new_ref)
74
97
  input_url = find
@@ -216,12 +239,23 @@ module Dependabot
216
239
  when "tarball"
217
240
  old_channel = T.must(input_url.ref)
218
241
  input_url.full_url.sub("/#{old_channel}/", "/#{new_ref}/")
242
+ when "git"
243
+ update_git_ref(input_url.full_url, new_ref)
219
244
  else
220
245
  base = "#{input_url.scheme}:#{input_url.owner}/#{input_url.repo}/#{new_ref}"
221
246
  input_url.query ? "#{base}?#{input_url.query}" : base
222
247
  end
223
248
  end
224
249
 
250
+ sig { params(url: String, new_ref: String).returns(String) }
251
+ def update_git_ref(url, new_ref)
252
+ uri = URI.parse(url)
253
+ query = URI.decode_www_form(uri.query || "")
254
+ query.each { |parameter| parameter[1] = new_ref if parameter.first == "ref" }
255
+ uri.query = URI.encode_www_form(query)
256
+ uri.to_s
257
+ end
258
+
225
259
  # Represents a parsed flake input URL from flake.nix
226
260
  class InputUrl < T::Struct
227
261
  const :full_url, String
@@ -0,0 +1,134 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+
7
+ module Dependabot
8
+ module Nix
9
+ class Lockfile
10
+ extend T::Sig
11
+
12
+ Node = T.type_alias { T::Hash[String, Object] }
13
+
14
+ sig { params(content: String).void }
15
+ def initialize(content)
16
+ @data = T.let(T.cast(JSON.parse(content), Node), Node)
17
+ end
18
+
19
+ sig { returns(T.nilable(Integer)) }
20
+ def version
21
+ value = @data["version"]
22
+ value if value.is_a?(Integer)
23
+ end
24
+
25
+ sig { returns(T::Array[String]) }
26
+ def root_input_names
27
+ root_inputs.keys
28
+ end
29
+
30
+ sig { params(input_name: String).returns(T.nilable(T::Array[String])) }
31
+ def input_path(input_name)
32
+ reference = root_inputs[input_name]
33
+ return [input_name] if reference.is_a?(String)
34
+ return unless string_array?(reference)
35
+
36
+ T.cast(reference, T::Array[String])
37
+ end
38
+
39
+ sig { params(input_name: String).returns(T.nilable(Node)) }
40
+ def input_node(input_name)
41
+ label = resolve_reference(root_inputs[input_name], T.let(Set.new, T::Set[String]))
42
+ return unless label
43
+
44
+ node = nodes[label]
45
+ node if node.is_a?(Hash)
46
+ end
47
+
48
+ sig { params(input_name: String).returns(T.nilable(Node)) }
49
+ def original_source(input_name)
50
+ original = input_node(input_name)&.fetch("original", nil)
51
+ original if original.is_a?(Hash)
52
+ end
53
+
54
+ sig { params(input_name: String).returns(T.nilable(Node)) }
55
+ def locked_source(input_name)
56
+ locked = input_node(input_name)&.fetch("locked", nil)
57
+ locked if locked.is_a?(Hash)
58
+ end
59
+
60
+ sig { params(input_name: String).returns(T.nilable(String)) }
61
+ def locked_revision(input_name)
62
+ locked = locked_source(input_name)
63
+ return unless locked
64
+
65
+ revision = locked["rev"]
66
+ revision if revision.is_a?(String)
67
+ end
68
+
69
+ private
70
+
71
+ sig { returns(Node) }
72
+ def nodes
73
+ value = @data["nodes"]
74
+ value.is_a?(Hash) ? value : {}
75
+ end
76
+
77
+ sig { returns(Node) }
78
+ def root_inputs
79
+ root_name = @data["root"]
80
+ root_name = "root" unless root_name.is_a?(String)
81
+
82
+ root_node = nodes[root_name]
83
+ return {} unless root_node.is_a?(Hash)
84
+
85
+ inputs = root_node["inputs"]
86
+ inputs.is_a?(Hash) ? inputs : {}
87
+ end
88
+
89
+ sig { params(reference: Object, visited_paths: T::Set[String]).returns(T.nilable(String)) }
90
+ def resolve_reference(reference, visited_paths)
91
+ return reference if reference.is_a?(String)
92
+ return unless string_array?(reference)
93
+
94
+ path = T.cast(reference, T::Array[String])
95
+ path_key = path.join("\0")
96
+ return if visited_paths.include?(path_key)
97
+
98
+ visited_paths.add(path_key)
99
+ resolve_path(path, visited_paths)
100
+ end
101
+
102
+ sig { params(path: T::Array[String], visited_paths: T::Set[String]).returns(T.nilable(String)) }
103
+ def resolve_path(path, visited_paths)
104
+ first_segment = path.first
105
+ return unless first_segment
106
+
107
+ reference = T.let(root_inputs[first_segment], Object)
108
+
109
+ index = T.let(1, Integer)
110
+ while index < path.length
111
+ segment = path.fetch(index)
112
+ label = resolve_reference(reference, visited_paths)
113
+ return unless label
114
+
115
+ node = nodes[label]
116
+ return unless node.is_a?(Hash)
117
+
118
+ inputs = node["inputs"]
119
+ return unless inputs.is_a?(Hash)
120
+
121
+ reference = T.let(inputs[segment], Object)
122
+ index += 1
123
+ end
124
+
125
+ resolve_reference(reference, visited_paths)
126
+ end
127
+
128
+ sig { params(value: Object).returns(T::Boolean) }
129
+ def string_array?(value)
130
+ value.is_a?(Array) && !value.empty? && value.all?(String)
131
+ end
132
+ end
133
+ end
134
+ 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.393.0
4
+ version: 0.394.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.393.0
18
+ version: 0.394.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.393.0
25
+ version: 0.394.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -169,14 +169,14 @@ dependencies:
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '0.22'
172
+ version: '1.1'
173
173
  type: :development
174
174
  prerelease: false
175
175
  version_requirements: !ruby/object:Gem::Requirement
176
176
  requirements:
177
177
  - - "~>"
178
178
  - !ruby/object:Gem::Version
179
- version: '0.22'
179
+ version: '1.1'
180
180
  - !ruby/object:Gem::Dependency
181
181
  name: turbo_tests
182
182
  requirement: !ruby/object:Gem::Requirement
@@ -246,8 +246,10 @@ files:
246
246
  - lib/dependabot/nix/file_fetcher.rb
247
247
  - lib/dependabot/nix/file_parser.rb
248
248
  - lib/dependabot/nix/file_updater.rb
249
+ - lib/dependabot/nix/file_updater/flake_ref_builder.rb
249
250
  - lib/dependabot/nix/flake_nix_parser.rb
250
251
  - lib/dependabot/nix/ignore_filter.rb
252
+ - lib/dependabot/nix/lockfile.rb
251
253
  - lib/dependabot/nix/metadata_finder.rb
252
254
  - lib/dependabot/nix/package/package_details_fetcher.rb
253
255
  - lib/dependabot/nix/package_manager.rb
@@ -263,7 +265,7 @@ licenses:
263
265
  - MIT
264
266
  metadata:
265
267
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
266
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.393.0
268
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.394.0
267
269
  rdoc_options: []
268
270
  require_paths:
269
271
  - lib