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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30993355e24373bb5b160eee7028eee8357d502fd1e8843feb0a5d51b9510066
|
|
4
|
+
data.tar.gz: 373aa7fc524757879a45305546cfe4385dc6f8c0bfa365036318a7f9f3101a73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
35
|
+
lockfile = Lockfile.new(T.must(flake_lock.content))
|
|
36
36
|
|
|
37
|
-
lock_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
|
-
|
|
45
|
-
|
|
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}
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
78
|
-
fingerprint:
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
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.
|
|
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
|