dependabot-julia 0.342.2
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 +7 -0
- data/lib/dependabot/julia/dependency.rb +21 -0
- data/lib/dependabot/julia/file_fetcher.rb +63 -0
- data/lib/dependabot/julia/file_parser.rb +278 -0
- data/lib/dependabot/julia/file_updater.rb +295 -0
- data/lib/dependabot/julia/metadata_finder.rb +99 -0
- data/lib/dependabot/julia/package/package_details_fetcher.rb +129 -0
- data/lib/dependabot/julia/package_manager.rb +71 -0
- data/lib/dependabot/julia/registry_client.rb +340 -0
- data/lib/dependabot/julia/requirement.rb +94 -0
- data/lib/dependabot/julia/update_checker/latest_version_finder.rb +222 -0
- data/lib/dependabot/julia/update_checker/requirements_updater.rb +90 -0
- data/lib/dependabot/julia/update_checker.rb +146 -0
- data/lib/dependabot/julia/version.rb +44 -0
- data/lib/dependabot/julia.rb +65 -0
- metadata +281 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/requirement"
|
|
5
|
+
require "dependabot/utils"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module Julia
|
|
9
|
+
class Requirement < Dependabot::Requirement
|
|
10
|
+
sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Dependabot::Julia::Requirement]) }
|
|
11
|
+
def self.requirements_array(requirement_string)
|
|
12
|
+
# Julia version specifiers can be:
|
|
13
|
+
# - Exact: "1.2.3"
|
|
14
|
+
# - Range: "1.2-1.3", ">=1.0, <2.0"
|
|
15
|
+
# - Caret: "^1.2" (compatible within major version)
|
|
16
|
+
# - Tilde: "~1.2.3" (compatible within minor version)
|
|
17
|
+
# - Wildcard: "*" (any version)
|
|
18
|
+
return [new(">= 0")] if requirement_string.nil? || requirement_string.empty? || requirement_string == "*"
|
|
19
|
+
|
|
20
|
+
# Split by comma for multiple constraints
|
|
21
|
+
constraints = requirement_string.split(",").map(&:strip)
|
|
22
|
+
|
|
23
|
+
constraints.map do |constraint|
|
|
24
|
+
# Handle Julia-specific patterns
|
|
25
|
+
normalized_constraint = normalize_julia_constraint(constraint)
|
|
26
|
+
new(normalized_constraint)
|
|
27
|
+
end
|
|
28
|
+
rescue Gem::Requirement::BadRequirementError
|
|
29
|
+
[new(">= 0")]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { params(requirement_string: String).returns(T::Array[Dependabot::Julia::Requirement]) }
|
|
33
|
+
def self.parse_requirements(requirement_string)
|
|
34
|
+
requirements_array(requirement_string)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { params(version: String).returns(String) }
|
|
38
|
+
def self.normalize_version(version)
|
|
39
|
+
# Remove 'v' prefix if present (common in Julia)
|
|
40
|
+
version = version.sub(/^v/, "") if version.match?(/^v\d/)
|
|
41
|
+
version
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
sig { params(constraint: String).returns(String) }
|
|
45
|
+
def self.normalize_julia_constraint(constraint)
|
|
46
|
+
return normalize_caret_constraint(constraint) if constraint.match?(/^\^(\d+(?:\.\d+)*)/)
|
|
47
|
+
return normalize_tilde_constraint(constraint) if constraint.match?(/^~(\d+(?:\.\d+)*)/)
|
|
48
|
+
return normalize_range_constraint(constraint) if constraint.match?(/^(\d+(?:\.\d+)*)-(\d+(?:\.\d+)*)$/)
|
|
49
|
+
|
|
50
|
+
# Return as-is for standard gem requirements (>=, <=, ==, etc.)
|
|
51
|
+
constraint
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
sig { params(constraint: String).returns(String) }
|
|
55
|
+
private_class_method def self.normalize_caret_constraint(constraint)
|
|
56
|
+
version = T.must(constraint[1..-1])
|
|
57
|
+
parts = version.split(".")
|
|
58
|
+
major = T.must(parts[0])
|
|
59
|
+
return ">= #{version}.0.0, < #{major.to_i + 1}.0.0" if parts.length == 1
|
|
60
|
+
|
|
61
|
+
">= #{version}, < #{major.to_i + 1}.0.0"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
sig { params(constraint: String).returns(String) }
|
|
65
|
+
private_class_method def self.normalize_tilde_constraint(constraint)
|
|
66
|
+
version = T.must(constraint[1..-1])
|
|
67
|
+
parts = version.split(".")
|
|
68
|
+
return ">= #{version}, < #{T.must(parts[0]).to_i + 1}.0.0" unless parts.length >= 2
|
|
69
|
+
|
|
70
|
+
major = T.must(parts[0])
|
|
71
|
+
minor = T.must(parts[1])
|
|
72
|
+
">= #{version}, < #{major}.#{minor.to_i + 1}.0"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { params(constraint: String).returns(String) }
|
|
76
|
+
private_class_method def self.normalize_range_constraint(constraint)
|
|
77
|
+
start_version, end_version = constraint.split("-")
|
|
78
|
+
end_parts = T.must(end_version).split(".")
|
|
79
|
+
|
|
80
|
+
next_minor = if end_parts.length >= 2
|
|
81
|
+
major = T.must(end_parts[0])
|
|
82
|
+
minor = T.must(end_parts[1])
|
|
83
|
+
"#{major}.#{minor.to_i + 1}.0"
|
|
84
|
+
else
|
|
85
|
+
"#{T.must(end_parts[0]).to_i + 1}.0.0"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
">= #{start_version}, < #{next_minor}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
Dependabot::Utils.register_requirement_class("julia", Dependabot::Julia::Requirement)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
require "dependabot/julia/package/package_details_fetcher"
|
|
6
|
+
require "dependabot/julia/version"
|
|
7
|
+
require "dependabot/update_checkers/version_filters"
|
|
8
|
+
|
|
9
|
+
module Dependabot
|
|
10
|
+
module Julia
|
|
11
|
+
class LatestVersionFinder
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
sig do
|
|
15
|
+
params(
|
|
16
|
+
dependency: Dependabot::Dependency,
|
|
17
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
18
|
+
credentials: T::Array[Dependabot::Credential],
|
|
19
|
+
ignored_versions: T::Array[String],
|
|
20
|
+
security_advisories: T::Array[Dependabot::SecurityAdvisory],
|
|
21
|
+
raise_on_ignored: T::Boolean,
|
|
22
|
+
cooldown_config: T.nilable(T::Hash[Symbol, T.untyped]),
|
|
23
|
+
custom_registries: T::Array[T::Hash[Symbol, String]]
|
|
24
|
+
).void
|
|
25
|
+
end
|
|
26
|
+
def initialize(
|
|
27
|
+
dependency:,
|
|
28
|
+
dependency_files:,
|
|
29
|
+
credentials:,
|
|
30
|
+
ignored_versions:,
|
|
31
|
+
security_advisories:,
|
|
32
|
+
raise_on_ignored:,
|
|
33
|
+
cooldown_config: nil,
|
|
34
|
+
custom_registries: []
|
|
35
|
+
)
|
|
36
|
+
@dependency = dependency
|
|
37
|
+
@dependency_files = dependency_files
|
|
38
|
+
@credentials = credentials
|
|
39
|
+
@ignored_versions = ignored_versions
|
|
40
|
+
@security_advisories = security_advisories
|
|
41
|
+
@raise_on_ignored = raise_on_ignored
|
|
42
|
+
@cooldown_config = cooldown_config
|
|
43
|
+
@custom_registries = custom_registries
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sig { returns(T.nilable(Gem::Version)) }
|
|
47
|
+
def latest_version
|
|
48
|
+
@latest_version ||= T.let(fetch_latest_version, T.nilable(Gem::Version))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
sig { returns(Dependabot::Dependency) }
|
|
54
|
+
attr_reader :dependency
|
|
55
|
+
|
|
56
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
57
|
+
attr_reader :dependency_files
|
|
58
|
+
|
|
59
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
60
|
+
attr_reader :credentials
|
|
61
|
+
|
|
62
|
+
sig { returns(T::Array[String]) }
|
|
63
|
+
attr_reader :ignored_versions
|
|
64
|
+
|
|
65
|
+
sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
|
|
66
|
+
attr_reader :security_advisories
|
|
67
|
+
|
|
68
|
+
sig { returns(T::Boolean) }
|
|
69
|
+
attr_reader :raise_on_ignored
|
|
70
|
+
|
|
71
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
|
72
|
+
attr_reader :cooldown_config
|
|
73
|
+
|
|
74
|
+
sig { returns(T::Array[T::Hash[Symbol, String]]) }
|
|
75
|
+
attr_reader :custom_registries
|
|
76
|
+
|
|
77
|
+
sig { returns(T.nilable(Gem::Version)) }
|
|
78
|
+
def fetch_latest_version
|
|
79
|
+
# Fetch all package releases using the PackageDetailsFetcher
|
|
80
|
+
package_fetcher = Julia::Package::PackageDetailsFetcher.new(
|
|
81
|
+
dependency: dependency,
|
|
82
|
+
credentials: credentials,
|
|
83
|
+
custom_registries: custom_registries
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
releases = package_fetcher.fetch_package_releases
|
|
87
|
+
return nil if releases.empty?
|
|
88
|
+
|
|
89
|
+
# Filter releases based on cooldown
|
|
90
|
+
if cooldown_config
|
|
91
|
+
releases = filter_releases_by_cooldown(releases)
|
|
92
|
+
return nil if releases.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Convert to versions for further filtering
|
|
96
|
+
versions = releases.map(&:version).sort
|
|
97
|
+
|
|
98
|
+
# Filter out ignored versions
|
|
99
|
+
versions = versions.reject do |version|
|
|
100
|
+
ignored_versions.any?(version.to_s)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Filter out vulnerable versions
|
|
104
|
+
filtered_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(
|
|
105
|
+
versions,
|
|
106
|
+
security_advisories
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
raise Dependabot::AllVersionsIgnored if filtered_versions.empty? && raise_on_ignored
|
|
110
|
+
|
|
111
|
+
filtered_versions.max
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sig do
|
|
115
|
+
params(
|
|
116
|
+
releases: T::Array[Dependabot::Package::PackageRelease]
|
|
117
|
+
).returns(T::Array[Dependabot::Package::PackageRelease])
|
|
118
|
+
end
|
|
119
|
+
def filter_releases_by_cooldown(releases)
|
|
120
|
+
return releases unless cooldown_config
|
|
121
|
+
return releases unless dependency_in_cooldown_scope?
|
|
122
|
+
|
|
123
|
+
releases.reject do |release|
|
|
124
|
+
cooldown_active_for_release?(release)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
|
|
129
|
+
def cooldown_active_for_release?(release)
|
|
130
|
+
cooldown_days = determine_cooldown_days(release.version)
|
|
131
|
+
return false unless cooldown_days&.positive?
|
|
132
|
+
return false unless release.released_at
|
|
133
|
+
|
|
134
|
+
# Check if enough time has passed since release
|
|
135
|
+
seconds_since_release = T.cast(Time.now - release.released_at, Float)
|
|
136
|
+
cooldown_seconds = cooldown_days * 24 * 60 * 60 # Convert days to seconds
|
|
137
|
+
seconds_since_release < cooldown_seconds
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
sig { returns(T::Boolean) }
|
|
141
|
+
def dependency_in_cooldown_scope?
|
|
142
|
+
return true unless cooldown_config
|
|
143
|
+
|
|
144
|
+
config = T.must(cooldown_config) # We know it's not nil due to guard above
|
|
145
|
+
includes = T.cast(config[:include], T.nilable(T::Array[String]))
|
|
146
|
+
excludes = T.cast(config[:exclude], T.nilable(T::Array[String]))
|
|
147
|
+
|
|
148
|
+
# Check exclusions first
|
|
149
|
+
return false if excludes&.any? { |pattern| dependency.name.match?(Regexp.new(pattern.gsub("*", ".*"))) }
|
|
150
|
+
|
|
151
|
+
# Check inclusions (if specified, dependency must match)
|
|
152
|
+
return includes.any? { |pattern| dependency.name.match?(Regexp.new(pattern.gsub("*", ".*"))) } if includes&.any?
|
|
153
|
+
|
|
154
|
+
true # Include by default if no include patterns specified
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
sig { params(version: Gem::Version).returns(T.nilable(Integer)) }
|
|
158
|
+
def determine_cooldown_days(version)
|
|
159
|
+
return nil unless cooldown_config
|
|
160
|
+
|
|
161
|
+
current_version = dependency.version ? Gem::Version.new(dependency.version) : nil
|
|
162
|
+
return nil unless current_version
|
|
163
|
+
|
|
164
|
+
version_bump_type = determine_version_bump_type(version, current_version)
|
|
165
|
+
cooldown_days_for_bump_type(version_bump_type)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
sig { params(version: Gem::Version, current_version: Gem::Version).returns(Symbol) }
|
|
169
|
+
def determine_version_bump_type(version, current_version)
|
|
170
|
+
v_segments = normalize_version_segments(version)
|
|
171
|
+
c_segments = normalize_version_segments(current_version)
|
|
172
|
+
|
|
173
|
+
compare_version_segments(v_segments, c_segments)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
sig { params(bump_type: Symbol).returns(T.nilable(Integer)) }
|
|
177
|
+
def cooldown_days_for_bump_type(bump_type)
|
|
178
|
+
return nil unless cooldown_config
|
|
179
|
+
|
|
180
|
+
config = T.must(cooldown_config) # We know it's not nil due to guard above
|
|
181
|
+
case bump_type
|
|
182
|
+
when :major
|
|
183
|
+
T.cast(config[:semver_major_days], T.nilable(Integer)) ||
|
|
184
|
+
T.cast(config[:default_days], T.nilable(Integer))
|
|
185
|
+
when :minor
|
|
186
|
+
T.cast(config[:semver_minor_days], T.nilable(Integer)) ||
|
|
187
|
+
T.cast(config[:default_days], T.nilable(Integer))
|
|
188
|
+
when :patch
|
|
189
|
+
T.cast(config[:semver_patch_days], T.nilable(Integer)) ||
|
|
190
|
+
T.cast(config[:default_days], T.nilable(Integer))
|
|
191
|
+
else
|
|
192
|
+
T.cast(config[:default_days], T.nilable(Integer))
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
sig { params(version: Gem::Version).returns([Integer, Integer, Integer]) }
|
|
197
|
+
def normalize_version_segments(version)
|
|
198
|
+
[
|
|
199
|
+
(version.segments[0] || 0).to_i,
|
|
200
|
+
(version.segments[1] || 0).to_i,
|
|
201
|
+
(version.segments[2] || 0).to_i
|
|
202
|
+
]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
sig { params(v_segments: [Integer, Integer, Integer], c_segments: [Integer, Integer, Integer]).returns(Symbol) }
|
|
206
|
+
def compare_version_segments(v_segments, c_segments)
|
|
207
|
+
v_major, v_minor, v_patch = v_segments
|
|
208
|
+
c_major, c_minor, c_patch = c_segments
|
|
209
|
+
|
|
210
|
+
if v_major > c_major
|
|
211
|
+
:major
|
|
212
|
+
elsif v_minor > c_minor
|
|
213
|
+
:minor
|
|
214
|
+
elsif v_patch > c_patch
|
|
215
|
+
:patch
|
|
216
|
+
else
|
|
217
|
+
:default
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/julia/requirement"
|
|
5
|
+
require "dependabot/julia/version"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module Julia
|
|
9
|
+
class RequirementsUpdater
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
sig do
|
|
13
|
+
params(
|
|
14
|
+
requirements: T::Array[T::Hash[Symbol, T.untyped]],
|
|
15
|
+
target_version: T.nilable(String),
|
|
16
|
+
update_strategy: T.nilable(Symbol)
|
|
17
|
+
).void
|
|
18
|
+
end
|
|
19
|
+
def initialize(requirements:, target_version:, update_strategy:)
|
|
20
|
+
@requirements = requirements
|
|
21
|
+
@target_version = target_version
|
|
22
|
+
@update_strategy = T.let(update_strategy || :bump_versions, Symbol)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
26
|
+
def updated_requirements
|
|
27
|
+
return requirements unless target_version
|
|
28
|
+
|
|
29
|
+
target_version_obj = Dependabot::Julia::Version.new(target_version)
|
|
30
|
+
|
|
31
|
+
requirements.map do |requirement|
|
|
32
|
+
update_requirement(requirement, target_version_obj)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
39
|
+
attr_reader :requirements
|
|
40
|
+
|
|
41
|
+
sig { returns(T.nilable(String)) }
|
|
42
|
+
attr_reader :target_version
|
|
43
|
+
|
|
44
|
+
sig { returns(Symbol) }
|
|
45
|
+
attr_reader :update_strategy
|
|
46
|
+
|
|
47
|
+
sig do
|
|
48
|
+
params(
|
|
49
|
+
requirement: T::Hash[Symbol, T.untyped],
|
|
50
|
+
target_version: Dependabot::Julia::Version
|
|
51
|
+
).returns(T::Hash[Symbol, T.untyped])
|
|
52
|
+
end
|
|
53
|
+
def update_requirement(requirement, target_version)
|
|
54
|
+
current_requirement = requirement[:requirement]
|
|
55
|
+
|
|
56
|
+
# If requirement is "*" or nil, use target version
|
|
57
|
+
new_requirement = if current_requirement.nil? || current_requirement == "*"
|
|
58
|
+
target_version.to_s
|
|
59
|
+
else
|
|
60
|
+
updated_version_requirement(current_requirement, target_version)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
requirement.merge(requirement: new_requirement)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sig { params(requirement_string: String, target_version: Dependabot::Julia::Version).returns(String) }
|
|
67
|
+
def updated_version_requirement(requirement_string, target_version)
|
|
68
|
+
req = Dependabot::Julia::Requirement.new(requirement_string)
|
|
69
|
+
|
|
70
|
+
# If current requirement already satisfied, keep it
|
|
71
|
+
return requirement_string if req.satisfied_by?(target_version)
|
|
72
|
+
|
|
73
|
+
# Otherwise, create a new requirement that includes the target version
|
|
74
|
+
if requirement_string.start_with?("^")
|
|
75
|
+
# Caret requirement: ^1.2 -> update to ^new_major.new_minor if needed
|
|
76
|
+
"^#{target_version.segments[0]}.#{target_version.segments[1] || 0}"
|
|
77
|
+
elsif requirement_string.start_with?("~")
|
|
78
|
+
# Tilde requirement: ~1.2.3 -> update to ~new_version
|
|
79
|
+
"~#{target_version}"
|
|
80
|
+
elsif requirement_string.include?("-")
|
|
81
|
+
# Range requirement: keep as is or expand to include target
|
|
82
|
+
requirement_string
|
|
83
|
+
else
|
|
84
|
+
# Exact version or other: use target version
|
|
85
|
+
target_version.to_s
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/update_checkers"
|
|
5
|
+
require "dependabot/update_checkers/base"
|
|
6
|
+
require "dependabot/julia/registry_client"
|
|
7
|
+
require "dependabot/julia/requirement"
|
|
8
|
+
|
|
9
|
+
module Dependabot
|
|
10
|
+
module Julia
|
|
11
|
+
# Load helper classes
|
|
12
|
+
autoload :LatestVersionFinder, "dependabot/julia/update_checker/latest_version_finder"
|
|
13
|
+
autoload :RequirementsUpdater, "dependabot/julia/update_checker/requirements_updater"
|
|
14
|
+
|
|
15
|
+
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig do
|
|
19
|
+
params(
|
|
20
|
+
dependency: Dependabot::Dependency,
|
|
21
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
22
|
+
credentials: T::Array[Dependabot::Credential],
|
|
23
|
+
repo_contents_path: T.nilable(String),
|
|
24
|
+
ignored_versions: T::Array[String],
|
|
25
|
+
raise_on_ignored: T::Boolean,
|
|
26
|
+
security_advisories: T::Array[Dependabot::SecurityAdvisory],
|
|
27
|
+
requirements_update_strategy: T.nilable(Dependabot::RequirementsUpdateStrategy),
|
|
28
|
+
dependency_group: T.nilable(Dependabot::DependencyGroup),
|
|
29
|
+
update_cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions),
|
|
30
|
+
options: T::Hash[Symbol, T.untyped]
|
|
31
|
+
).void
|
|
32
|
+
end
|
|
33
|
+
def initialize(
|
|
34
|
+
dependency:,
|
|
35
|
+
dependency_files:,
|
|
36
|
+
credentials:,
|
|
37
|
+
repo_contents_path: nil,
|
|
38
|
+
ignored_versions: [],
|
|
39
|
+
raise_on_ignored: false,
|
|
40
|
+
security_advisories: [],
|
|
41
|
+
requirements_update_strategy: nil,
|
|
42
|
+
dependency_group: nil,
|
|
43
|
+
update_cooldown: nil,
|
|
44
|
+
options: {}
|
|
45
|
+
)
|
|
46
|
+
super
|
|
47
|
+
@custom_registries = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { override.returns(T.nilable(Gem::Version)) }
|
|
51
|
+
def latest_version
|
|
52
|
+
@latest_version ||= T.let(latest_version_finder.latest_version, T.nilable(Gem::Version))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
56
|
+
def custom_registries
|
|
57
|
+
return @custom_registries if @custom_registries
|
|
58
|
+
|
|
59
|
+
registries = T.cast(options.dig(:registries, :julia), T.nilable(T::Array[T.untyped])) || []
|
|
60
|
+
# Convert string keys to symbols if needed
|
|
61
|
+
@custom_registries = registries.map do |registry|
|
|
62
|
+
if registry.is_a?(Hash)
|
|
63
|
+
registry.transform_keys(&:to_sym)
|
|
64
|
+
else
|
|
65
|
+
T.cast(registry, T::Hash[Symbol, T.untyped])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sig { override.returns(T.nilable(Gem::Version)) }
|
|
71
|
+
def latest_resolvable_version
|
|
72
|
+
# For Julia, the latest version is generally resolvable since
|
|
73
|
+
# the manifest file locks exact versions, so we use latest_version
|
|
74
|
+
@latest_resolvable_version ||= T.let(latest_version, T.nilable(Gem::Version))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
sig { override.returns(T.nilable(T.any(Dependabot::Version, String))) }
|
|
78
|
+
def latest_resolvable_version_with_no_unlock
|
|
79
|
+
# Return latest version that satisfies current requirement constraints
|
|
80
|
+
return nil unless latest_version
|
|
81
|
+
|
|
82
|
+
current_requirement = T.cast(dependency.requirements.first&.fetch(:requirement, nil), T.nilable(String))
|
|
83
|
+
|
|
84
|
+
if current_requirement.nil? || current_requirement == "*"
|
|
85
|
+
return Dependabot::Julia::Version.new(latest_version.to_s)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
req = requirement_class.new(current_requirement)
|
|
89
|
+
return unless T.cast(req.satisfied_by?(latest_version), T::Boolean)
|
|
90
|
+
|
|
91
|
+
Dependabot::Julia::Version.new(latest_version.to_s)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
95
|
+
def updated_requirements
|
|
96
|
+
Dependabot::Julia::RequirementsUpdater.new(
|
|
97
|
+
requirements: dependency.requirements,
|
|
98
|
+
target_version: latest_resolvable_version&.to_s,
|
|
99
|
+
update_strategy: requirements_update_strategy&.to_s&.to_sym
|
|
100
|
+
).updated_requirements
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
sig { returns(Dependabot::Julia::LatestVersionFinder) }
|
|
106
|
+
def latest_version_finder
|
|
107
|
+
@latest_version_finder ||= T.let(
|
|
108
|
+
Dependabot::Julia::LatestVersionFinder.new(
|
|
109
|
+
dependency: dependency,
|
|
110
|
+
dependency_files: dependency_files,
|
|
111
|
+
credentials: credentials,
|
|
112
|
+
ignored_versions: ignored_versions,
|
|
113
|
+
security_advisories: security_advisories,
|
|
114
|
+
raise_on_ignored: raise_on_ignored,
|
|
115
|
+
cooldown_config: cooldown_config,
|
|
116
|
+
custom_registries: custom_registries
|
|
117
|
+
),
|
|
118
|
+
T.nilable(Dependabot::Julia::LatestVersionFinder)
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
|
123
|
+
def cooldown_config
|
|
124
|
+
return nil unless update_cooldown
|
|
125
|
+
|
|
126
|
+
# Convert the ReleaseCooldownOptions to a hash for compatibility
|
|
127
|
+
cooldown = T.must(update_cooldown) # We know it's not nil due to guard above
|
|
128
|
+
{
|
|
129
|
+
default_days: cooldown.default_days,
|
|
130
|
+
semver_major_days: cooldown.semver_major_days,
|
|
131
|
+
semver_minor_days: cooldown.semver_minor_days,
|
|
132
|
+
semver_patch_days: cooldown.semver_patch_days,
|
|
133
|
+
include: cooldown.include,
|
|
134
|
+
exclude: cooldown.exclude
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { returns(T.class_of(Dependabot::Julia::Requirement)) }
|
|
139
|
+
def requirement_class
|
|
140
|
+
Dependabot::Julia::Requirement
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Dependabot::UpdateCheckers.register("julia", Dependabot::Julia::UpdateChecker)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/version"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Julia
|
|
8
|
+
class Version < Dependabot::Version
|
|
9
|
+
# Julia follows semantic versioning for most packages
|
|
10
|
+
# See: https://docs.julialang.org/en/v1/stdlib/Pkg/#Version-specifier-format
|
|
11
|
+
VERSION_PATTERN = T.let(/^v?(\d+(?:\.\d+)*)(?:[-+].*)?$/, Regexp)
|
|
12
|
+
|
|
13
|
+
sig { override.params(version: T.nilable(T.any(String, Integer, Gem::Version))).returns(T::Boolean) }
|
|
14
|
+
def self.correct?(version)
|
|
15
|
+
return false if version.nil?
|
|
16
|
+
|
|
17
|
+
version_string = version.to_s
|
|
18
|
+
VERSION_PATTERN.match?(version_string)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
sig { override.params(version: T.nilable(T.any(String, Integer, Gem::Version))).void }
|
|
22
|
+
def initialize(version)
|
|
23
|
+
version_string = version.to_s.strip
|
|
24
|
+
|
|
25
|
+
# Remove 'v' prefix if present (common in Julia)
|
|
26
|
+
version_string = version_string.sub(/^v/, "") if version_string.match?(/^v\d/)
|
|
27
|
+
|
|
28
|
+
@version_string = T.let(version_string, String)
|
|
29
|
+
super(version_string)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig do
|
|
33
|
+
override
|
|
34
|
+
.params(version: T.nilable(T.any(String, Integer, Gem::Version)))
|
|
35
|
+
.returns(Dependabot::Julia::Version)
|
|
36
|
+
end
|
|
37
|
+
def self.new(version)
|
|
38
|
+
T.cast(super, Dependabot::Julia::Version)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Dependabot::Utils.register_version_class("julia", Dependabot::Julia::Version)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/version"
|
|
5
|
+
require "dependabot/utils"
|
|
6
|
+
|
|
7
|
+
# Register Julia version and requirement classes
|
|
8
|
+
require "dependabot/julia/version"
|
|
9
|
+
require "dependabot/julia/requirement"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Julia
|
|
13
|
+
extend T::Sig
|
|
14
|
+
|
|
15
|
+
VERSION = "0.1.0"
|
|
16
|
+
|
|
17
|
+
sig { returns(String) }
|
|
18
|
+
def self.package_ecosystem
|
|
19
|
+
"julia"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
sig { returns(T.class_of(Dependabot::Julia::FileFetcher)) }
|
|
23
|
+
def self.file_fetcher_class
|
|
24
|
+
FileFetcher
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { returns(T.class_of(Dependabot::Julia::FileParser)) }
|
|
28
|
+
def self.file_parser_class
|
|
29
|
+
FileParser
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { returns(T.class_of(Dependabot::Julia::UpdateChecker)) }
|
|
33
|
+
def self.update_checker_class
|
|
34
|
+
UpdateChecker
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { returns(T.class_of(Dependabot::Julia::FileUpdater)) }
|
|
38
|
+
def self.file_updater_class
|
|
39
|
+
FileUpdater
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { returns(T.class_of(Dependabot::Julia::MetadataFinder)) }
|
|
43
|
+
def self.metadata_finder_class
|
|
44
|
+
MetadataFinder
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { returns(T.class_of(Dependabot::Julia::Dependency)) }
|
|
48
|
+
def self.dependency_class
|
|
49
|
+
Dependabot::Julia::Dependency
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { returns(T.class_of(Dependabot::Julia::PackageManager)) }
|
|
53
|
+
def self.package_manager_class
|
|
54
|
+
PackageManager
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
require "dependabot/julia/package_manager"
|
|
60
|
+
require "dependabot/julia/file_fetcher"
|
|
61
|
+
require "dependabot/julia/file_parser"
|
|
62
|
+
require "dependabot/julia/update_checker"
|
|
63
|
+
require "dependabot/julia/file_updater"
|
|
64
|
+
require "dependabot/julia/metadata_finder"
|
|
65
|
+
require "dependabot/julia/dependency"
|