dependabot-pre_commit 0.361.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/pre_commit/additional_dependency_checkers/base.rb +89 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/go.rb +160 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/node.rb +175 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/python.rb +213 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/ruby.rb +169 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/rust.rb +179 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers.rb +46 -0
- data/lib/dependabot/pre_commit/file_fetcher.rb +70 -0
- data/lib/dependabot/pre_commit/file_parser.rb +210 -0
- data/lib/dependabot/pre_commit/file_updater.rb +188 -0
- data/lib/dependabot/pre_commit/helpers.rb +82 -0
- data/lib/dependabot/pre_commit/metadata_finder.rb +28 -0
- data/lib/dependabot/pre_commit/package/package_details_fetcher.rb +199 -0
- data/lib/dependabot/pre_commit/package_manager.rb +29 -0
- data/lib/dependabot/pre_commit/requirement.rb +23 -0
- data/lib/dependabot/pre_commit/update_checker/latest_version_finder.rb +240 -0
- data/lib/dependabot/pre_commit/update_checker.rb +284 -0
- data/lib/dependabot/pre_commit/version.rb +15 -0
- data/lib/dependabot/pre_commit.rb +20 -0
- metadata +356 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 756b5487cc3c7be59843fde399c176c66d3716acca89a364b9a9b2309001a0eb
|
|
4
|
+
data.tar.gz: 11184797fefef083921b31eef2b74ca04a27e70ddf0e3f48cc7819554a8c2c56
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ee6ac4a1364fa3f9c7d897c80dd5d68edf73552b522b112175bcda3e1e76b0e35cabb4904c1fd22027709de68544b38e0b92473217cbad72cf5f12fdc7fb1c91
|
|
7
|
+
data.tar.gz: adcecfc80611486b0446a56cc6a95d011b1213e4266aff09b9bd0623fff26c1e87f513726449c8f4e7a34a3f1919cf1ba98a2e9f604696dc0f4cb5335b176e8b
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module PreCommit
|
|
8
|
+
module AdditionalDependencyCheckers
|
|
9
|
+
# Abstract base class for language-specific additional_dependency update checkers.
|
|
10
|
+
# Each language implementation should inherit from this class and implement
|
|
11
|
+
# the abstract methods.
|
|
12
|
+
#
|
|
13
|
+
# The checker is responsible for:
|
|
14
|
+
# 1. Finding the latest available version from the language's registry (PyPI, npm, etc.)
|
|
15
|
+
# 2. Generating updated requirements that preserve the original version constraint operators
|
|
16
|
+
#
|
|
17
|
+
# Example implementation for a new language:
|
|
18
|
+
#
|
|
19
|
+
# class MyLanguage < Base
|
|
20
|
+
# def latest_version
|
|
21
|
+
# # Delegate to ecosystem's UpdateChecker
|
|
22
|
+
# ecosystem_checker = Dependabot::UpdateCheckers
|
|
23
|
+
# .for_package_manager("my_pm")
|
|
24
|
+
# .new(dependency: build_ecosystem_dependency, ...)
|
|
25
|
+
# ecosystem_checker.latest_version&.to_s
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def updated_requirements(latest_version)
|
|
29
|
+
# # Build updated requirements preserving operators
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# AdditionalDependencyCheckers.register("my_language", MyLanguage)
|
|
34
|
+
#
|
|
35
|
+
class Base
|
|
36
|
+
extend T::Sig
|
|
37
|
+
extend T::Helpers
|
|
38
|
+
|
|
39
|
+
abstract!
|
|
40
|
+
|
|
41
|
+
sig do
|
|
42
|
+
params(
|
|
43
|
+
source: T::Hash[Symbol, T.untyped],
|
|
44
|
+
credentials: T::Array[Dependabot::Credential],
|
|
45
|
+
requirements: T::Array[T::Hash[Symbol, T.untyped]],
|
|
46
|
+
current_version: T.nilable(String)
|
|
47
|
+
).void
|
|
48
|
+
end
|
|
49
|
+
def initialize(source:, credentials:, requirements:, current_version:)
|
|
50
|
+
@source = source
|
|
51
|
+
@credentials = credentials
|
|
52
|
+
@requirements = requirements
|
|
53
|
+
@current_version = current_version
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Find the latest available version for this dependency
|
|
57
|
+
# Should delegate to the appropriate ecosystem UpdateChecker
|
|
58
|
+
# Returns nil if no update is available or if there's an error
|
|
59
|
+
sig { abstract.returns(T.nilable(String)) }
|
|
60
|
+
def latest_version; end
|
|
61
|
+
|
|
62
|
+
# Generate updated requirements for the new version
|
|
63
|
+
# Should preserve the original version constraint operator (>=, ~=, etc.)
|
|
64
|
+
# and update the source hash with the new original_string
|
|
65
|
+
sig { abstract.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
66
|
+
def updated_requirements(latest_version); end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
71
|
+
attr_reader :source
|
|
72
|
+
|
|
73
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
74
|
+
attr_reader :credentials
|
|
75
|
+
|
|
76
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
77
|
+
attr_reader :requirements
|
|
78
|
+
|
|
79
|
+
sig { returns(T.nilable(String)) }
|
|
80
|
+
attr_reader :current_version
|
|
81
|
+
|
|
82
|
+
sig { returns(T.nilable(String)) }
|
|
83
|
+
def package_name
|
|
84
|
+
source[:package_name]&.to_s
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/dependency"
|
|
6
|
+
require "dependabot/update_checkers"
|
|
7
|
+
require "dependabot/go_modules/version"
|
|
8
|
+
require "dependabot/pre_commit/additional_dependency_checkers"
|
|
9
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module PreCommit
|
|
13
|
+
module AdditionalDependencyCheckers
|
|
14
|
+
class Go < Base
|
|
15
|
+
extend T::Sig
|
|
16
|
+
|
|
17
|
+
sig { override.returns(T.nilable(String)) }
|
|
18
|
+
def latest_version
|
|
19
|
+
return nil unless package_name
|
|
20
|
+
|
|
21
|
+
@latest_version ||= T.let(
|
|
22
|
+
fetch_latest_version_via_go_checker,
|
|
23
|
+
T.nilable(String)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
28
|
+
def updated_requirements(latest_version)
|
|
29
|
+
requirements.map do |original_req|
|
|
30
|
+
original_source = original_req[:source]
|
|
31
|
+
next original_req unless original_source.is_a?(Hash)
|
|
32
|
+
next original_req unless original_source[:type] == "additional_dependency"
|
|
33
|
+
|
|
34
|
+
new_requirement = "v#{latest_version}"
|
|
35
|
+
|
|
36
|
+
new_original_string = build_original_string(
|
|
37
|
+
original_name: original_source[:original_name] || original_source[:package_name],
|
|
38
|
+
requirement: new_requirement
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
new_source = original_source.merge(original_string: new_original_string)
|
|
42
|
+
|
|
43
|
+
original_req.merge(
|
|
44
|
+
requirement: new_requirement,
|
|
45
|
+
source: new_source
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
sig { returns(T.nilable(String)) }
|
|
53
|
+
def fetch_latest_version_via_go_checker
|
|
54
|
+
go_checker = go_update_checker
|
|
55
|
+
return nil unless go_checker
|
|
56
|
+
|
|
57
|
+
latest = go_checker.latest_version
|
|
58
|
+
Dependabot.logger.info("Go UpdateChecker found latest version: #{latest || 'none'}")
|
|
59
|
+
|
|
60
|
+
latest&.to_s
|
|
61
|
+
rescue Dependabot::PrivateSourceTimedOut,
|
|
62
|
+
Dependabot::PrivateSourceAuthenticationFailure,
|
|
63
|
+
Dependabot::DependencyFileNotResolvable,
|
|
64
|
+
Dependabot::DependencyNotFound,
|
|
65
|
+
Excon::Error::Timeout,
|
|
66
|
+
Excon::Error::Socket => e
|
|
67
|
+
Dependabot.logger.warn("Error checking Go module: #{e.message}")
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
72
|
+
def go_update_checker
|
|
73
|
+
@go_update_checker ||= T.let(
|
|
74
|
+
build_go_update_checker,
|
|
75
|
+
T.nilable(Dependabot::UpdateCheckers::Base)
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
80
|
+
def build_go_update_checker
|
|
81
|
+
go_dependency = build_go_dependency
|
|
82
|
+
return nil unless go_dependency
|
|
83
|
+
|
|
84
|
+
Dependabot.logger.info("Delegating to Go UpdateChecker for module: #{go_dependency.name}")
|
|
85
|
+
|
|
86
|
+
Dependabot::UpdateCheckers.for_package_manager("go_modules").new(
|
|
87
|
+
dependency: go_dependency,
|
|
88
|
+
dependency_files: build_go_dependency_files,
|
|
89
|
+
credentials: credentials,
|
|
90
|
+
ignored_versions: [],
|
|
91
|
+
security_advisories: [],
|
|
92
|
+
raise_on_ignored: false
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
sig { returns(T.nilable(Dependabot::Dependency)) }
|
|
97
|
+
def build_go_dependency
|
|
98
|
+
return nil unless package_name
|
|
99
|
+
|
|
100
|
+
version = current_version || extract_version_from_requirement
|
|
101
|
+
|
|
102
|
+
Dependabot::Dependency.new(
|
|
103
|
+
name: T.must(package_name),
|
|
104
|
+
version: version,
|
|
105
|
+
requirements: [{
|
|
106
|
+
requirement: version ? "v#{version}" : nil,
|
|
107
|
+
groups: [],
|
|
108
|
+
file: "go.mod",
|
|
109
|
+
source: { type: "default", source: T.must(package_name) }
|
|
110
|
+
}],
|
|
111
|
+
package_manager: "go_modules"
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
sig { returns(T.nilable(String)) }
|
|
116
|
+
def extract_version_from_requirement
|
|
117
|
+
req_string = requirements.first&.dig(:requirement)
|
|
118
|
+
return nil unless req_string
|
|
119
|
+
|
|
120
|
+
# Go versions are like "v1.2.3" — strip the leading "v"
|
|
121
|
+
version = req_string.to_s.delete_prefix("v")
|
|
122
|
+
return nil unless Dependabot::GoModules::Version.correct?(version)
|
|
123
|
+
|
|
124
|
+
version
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
128
|
+
def build_go_dependency_files
|
|
129
|
+
version = current_version || extract_version_from_requirement
|
|
130
|
+
version_string = version ? "v#{version}" : ""
|
|
131
|
+
content = "module dependabot/pre-commit-dummy\n\ngo 1.21\n\nrequire #{package_name} #{version_string}\n"
|
|
132
|
+
|
|
133
|
+
[
|
|
134
|
+
Dependabot::DependencyFile.new(
|
|
135
|
+
name: "go.mod",
|
|
136
|
+
content: content
|
|
137
|
+
)
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
sig do
|
|
142
|
+
params(
|
|
143
|
+
original_name: T.nilable(String),
|
|
144
|
+
requirement: T.nilable(String)
|
|
145
|
+
).returns(String)
|
|
146
|
+
end
|
|
147
|
+
def build_original_string(original_name:, requirement:)
|
|
148
|
+
base = original_name.to_s
|
|
149
|
+
base = "#{base}@#{requirement}" if requirement
|
|
150
|
+
base
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers.register(
|
|
158
|
+
"golang",
|
|
159
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers::Go
|
|
160
|
+
)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "excon"
|
|
5
|
+
require "json"
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
require "dependabot/dependency"
|
|
8
|
+
require "dependabot/update_checkers"
|
|
9
|
+
require "dependabot/pre_commit/additional_dependency_checkers"
|
|
10
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module PreCommit
|
|
14
|
+
module AdditionalDependencyCheckers
|
|
15
|
+
class Node < Base
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig { override.returns(T.nilable(String)) }
|
|
19
|
+
def latest_version
|
|
20
|
+
return nil unless package_name
|
|
21
|
+
|
|
22
|
+
@latest_version ||= T.let(
|
|
23
|
+
fetch_latest_version_via_npm_checker,
|
|
24
|
+
T.nilable(String)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
29
|
+
def updated_requirements(latest_version)
|
|
30
|
+
requirements.map do |original_req|
|
|
31
|
+
original_source = original_req[:source]
|
|
32
|
+
next original_req unless original_source.is_a?(Hash)
|
|
33
|
+
next original_req unless original_source[:type] == "additional_dependency"
|
|
34
|
+
|
|
35
|
+
original_requirement = original_req[:requirement]
|
|
36
|
+
new_requirement = build_updated_requirement(original_requirement, latest_version)
|
|
37
|
+
|
|
38
|
+
new_original_string = build_original_string(
|
|
39
|
+
package_name: original_source[:original_name] || original_source[:package_name],
|
|
40
|
+
requirement: new_requirement
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
new_source = original_source.merge(original_string: new_original_string)
|
|
44
|
+
|
|
45
|
+
original_req.merge(
|
|
46
|
+
requirement: new_requirement,
|
|
47
|
+
source: new_source
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
sig { returns(T.nilable(String)) }
|
|
55
|
+
def fetch_latest_version_via_npm_checker
|
|
56
|
+
npm_checker = npm_update_checker
|
|
57
|
+
return nil unless npm_checker
|
|
58
|
+
|
|
59
|
+
latest = npm_checker.latest_version
|
|
60
|
+
Dependabot.logger.info("Node UpdateChecker found latest version: #{latest || 'none'}")
|
|
61
|
+
|
|
62
|
+
latest&.to_s
|
|
63
|
+
rescue Dependabot::DependabotError, Excon::Error, JSON::ParserError => e
|
|
64
|
+
Dependabot.logger.debug("Error checking Node package #{package_name}: #{e.message}")
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
69
|
+
def npm_update_checker
|
|
70
|
+
@npm_update_checker ||= T.let(
|
|
71
|
+
build_npm_update_checker,
|
|
72
|
+
T.nilable(Dependabot::UpdateCheckers::Base)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
77
|
+
def build_npm_update_checker
|
|
78
|
+
npm_dependency = build_npm_dependency
|
|
79
|
+
return nil unless npm_dependency
|
|
80
|
+
|
|
81
|
+
Dependabot.logger.info("Delegating to npm_and_yarn UpdateChecker for package: #{npm_dependency.name}")
|
|
82
|
+
|
|
83
|
+
Dependabot::UpdateCheckers.for_package_manager("npm_and_yarn").new(
|
|
84
|
+
dependency: npm_dependency,
|
|
85
|
+
dependency_files: build_npm_dependency_files,
|
|
86
|
+
credentials: credentials,
|
|
87
|
+
ignored_versions: [],
|
|
88
|
+
security_advisories: [],
|
|
89
|
+
raise_on_ignored: false
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
sig { returns(T.nilable(Dependabot::Dependency)) }
|
|
94
|
+
def build_npm_dependency
|
|
95
|
+
return nil unless package_name
|
|
96
|
+
|
|
97
|
+
version = current_version || extract_version_from_requirement
|
|
98
|
+
|
|
99
|
+
Dependabot::Dependency.new(
|
|
100
|
+
name: T.must(package_name),
|
|
101
|
+
version: version,
|
|
102
|
+
requirements: [{
|
|
103
|
+
requirement: version || nil,
|
|
104
|
+
groups: ["dependencies"],
|
|
105
|
+
file: "package.json",
|
|
106
|
+
source: nil
|
|
107
|
+
}],
|
|
108
|
+
package_manager: "npm_and_yarn"
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig { returns(T.nilable(String)) }
|
|
113
|
+
def extract_version_from_requirement
|
|
114
|
+
req_string = requirements.first&.dig(:requirement)
|
|
115
|
+
return nil unless req_string
|
|
116
|
+
|
|
117
|
+
version_part = req_string.sub(/\A[~^]|[><=]+\s*/, "")
|
|
118
|
+
return version_part if version_part.match?(/\A\d+(?:\.\d+)*(?:-[\w.]+)?(?:\+[\w.]+)?\z/)
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
124
|
+
def build_npm_dependency_files
|
|
125
|
+
version = current_version || extract_version_from_requirement
|
|
126
|
+
content = JSON.generate(
|
|
127
|
+
{
|
|
128
|
+
"name" => "dependabot-pre-commit-check",
|
|
129
|
+
"version" => "0.0.1",
|
|
130
|
+
"dependencies" => {
|
|
131
|
+
T.must(package_name) => version || "*"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
[
|
|
137
|
+
Dependabot::DependencyFile.new(
|
|
138
|
+
name: "package.json",
|
|
139
|
+
content: content
|
|
140
|
+
)
|
|
141
|
+
]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
sig do
|
|
145
|
+
params(
|
|
146
|
+
package_name: T.nilable(String),
|
|
147
|
+
requirement: T.nilable(String)
|
|
148
|
+
).returns(String)
|
|
149
|
+
end
|
|
150
|
+
def build_original_string(package_name:, requirement:)
|
|
151
|
+
base = package_name.to_s
|
|
152
|
+
base = "#{base}@#{requirement}" if requirement
|
|
153
|
+
base
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) }
|
|
157
|
+
def build_updated_requirement(original_requirement, new_version)
|
|
158
|
+
return new_version unless original_requirement
|
|
159
|
+
|
|
160
|
+
operator_match = original_requirement.match(/\A(?<op>[~^]|[><=]+)\s*/)
|
|
161
|
+
if operator_match
|
|
162
|
+
"#{operator_match[:op]}#{new_version}"
|
|
163
|
+
else
|
|
164
|
+
new_version
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers.register(
|
|
173
|
+
"node",
|
|
174
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers::Node
|
|
175
|
+
)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/dependency"
|
|
6
|
+
require "dependabot/update_checkers"
|
|
7
|
+
require "dependabot/requirements_update_strategy"
|
|
8
|
+
require "dependabot/python/update_checker/requirements_updater"
|
|
9
|
+
require "dependabot/pre_commit/additional_dependency_checkers"
|
|
10
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module PreCommit
|
|
14
|
+
module AdditionalDependencyCheckers
|
|
15
|
+
class Python < Base
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig { override.returns(T.nilable(String)) }
|
|
19
|
+
def latest_version
|
|
20
|
+
return nil unless package_name
|
|
21
|
+
|
|
22
|
+
@latest_version ||= T.let(
|
|
23
|
+
fetch_latest_version_via_pip_checker,
|
|
24
|
+
T.nilable(String)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
29
|
+
def updated_requirements(latest_version)
|
|
30
|
+
requirements.map do |original_req|
|
|
31
|
+
original_source = original_req[:source]
|
|
32
|
+
next original_req unless original_source.is_a?(Hash)
|
|
33
|
+
next original_req unless original_source[:type] == "additional_dependency"
|
|
34
|
+
|
|
35
|
+
original_requirement = original_req[:requirement]
|
|
36
|
+
new_requirement = build_updated_requirement(original_requirement, latest_version)
|
|
37
|
+
|
|
38
|
+
new_original_string = build_original_string(
|
|
39
|
+
original_name: original_source[:original_name] || original_source[:package_name],
|
|
40
|
+
extras: original_source[:extras],
|
|
41
|
+
requirement: new_requirement
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
new_source = original_source.merge(original_string: new_original_string)
|
|
45
|
+
|
|
46
|
+
original_req.merge(
|
|
47
|
+
requirement: new_requirement,
|
|
48
|
+
source: new_source
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
sig { returns(T.nilable(String)) }
|
|
56
|
+
def fetch_latest_version_via_pip_checker
|
|
57
|
+
pip_checker = pip_update_checker
|
|
58
|
+
return nil unless pip_checker
|
|
59
|
+
|
|
60
|
+
latest = pip_checker.latest_version
|
|
61
|
+
Dependabot.logger.info("Python UpdateChecker found latest version: #{latest || 'none'}")
|
|
62
|
+
|
|
63
|
+
latest&.to_s
|
|
64
|
+
rescue Dependabot::PrivateSourceTimedOut,
|
|
65
|
+
Dependabot::PrivateSourceAuthenticationFailure,
|
|
66
|
+
Dependabot::DependencyFileNotResolvable,
|
|
67
|
+
Dependabot::DependencyNotFound,
|
|
68
|
+
Excon::Error::Timeout,
|
|
69
|
+
Excon::Error::Socket => e
|
|
70
|
+
Dependabot.logger.warn("Error checking Python package: #{e.message}")
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
75
|
+
def pip_update_checker
|
|
76
|
+
@pip_update_checker ||= T.let(
|
|
77
|
+
build_pip_update_checker,
|
|
78
|
+
T.nilable(Dependabot::UpdateCheckers::Base)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
83
|
+
def build_pip_update_checker
|
|
84
|
+
pip_dependency = build_pip_dependency
|
|
85
|
+
return nil unless pip_dependency
|
|
86
|
+
|
|
87
|
+
Dependabot.logger.info("Delegating to Python UpdateChecker for package: #{pip_dependency.name}")
|
|
88
|
+
|
|
89
|
+
Dependabot::UpdateCheckers.for_package_manager("pip").new(
|
|
90
|
+
dependency: pip_dependency,
|
|
91
|
+
dependency_files: build_pip_dependency_files,
|
|
92
|
+
credentials: credentials,
|
|
93
|
+
ignored_versions: [],
|
|
94
|
+
security_advisories: [],
|
|
95
|
+
raise_on_ignored: false
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
sig { returns(T.nilable(Dependabot::Dependency)) }
|
|
100
|
+
def build_pip_dependency
|
|
101
|
+
return nil unless package_name
|
|
102
|
+
|
|
103
|
+
# Extract version from requirement string if current_version is nil
|
|
104
|
+
# This prevents Python's UpdateChecker from treating it as a sub-dependency
|
|
105
|
+
version = current_version || extract_version_from_requirement
|
|
106
|
+
|
|
107
|
+
# Use an exact requirement (==version) to force Python's UpdateChecker
|
|
108
|
+
# to use the :requirements resolver instead of subdependency_resolver.
|
|
109
|
+
exact_requirement = version ? "==#{version}" : nil
|
|
110
|
+
|
|
111
|
+
# Build a dependency that Python's UpdateChecker understands
|
|
112
|
+
Dependabot::Dependency.new(
|
|
113
|
+
name: T.must(package_name),
|
|
114
|
+
version: version,
|
|
115
|
+
requirements: [{
|
|
116
|
+
requirement: exact_requirement,
|
|
117
|
+
groups: [],
|
|
118
|
+
file: "requirements.txt",
|
|
119
|
+
source: nil
|
|
120
|
+
}],
|
|
121
|
+
package_manager: "pip"
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
sig { returns(T.nilable(String)) }
|
|
126
|
+
def extract_version_from_requirement
|
|
127
|
+
req_string = requirements.first&.dig(:requirement)
|
|
128
|
+
return nil unless req_string
|
|
129
|
+
|
|
130
|
+
match = req_string.match(/[\d]+(?:\.[\d]+)*(?:\.?\w+)*/)
|
|
131
|
+
match&.[](0)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
135
|
+
def build_pip_dependency_files
|
|
136
|
+
version = current_version || extract_version_from_requirement
|
|
137
|
+
exact_requirement = version ? "==#{version}" : ""
|
|
138
|
+
content = "#{package_name}#{exact_requirement}\n"
|
|
139
|
+
|
|
140
|
+
[
|
|
141
|
+
Dependabot::DependencyFile.new(
|
|
142
|
+
name: "requirements.txt",
|
|
143
|
+
content: content
|
|
144
|
+
)
|
|
145
|
+
]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
sig do
|
|
149
|
+
params(
|
|
150
|
+
original_name: T.nilable(String),
|
|
151
|
+
extras: T.nilable(String),
|
|
152
|
+
requirement: T.nilable(String)
|
|
153
|
+
).returns(String)
|
|
154
|
+
end
|
|
155
|
+
def build_original_string(original_name:, extras:, requirement:)
|
|
156
|
+
base = original_name.to_s
|
|
157
|
+
base = "#{base}[#{extras}]" if extras
|
|
158
|
+
base = "#{base}#{requirement}" if requirement
|
|
159
|
+
base
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
sig { params(original_requirement: T.nilable(String), new_version: String).returns(T.nilable(String)) }
|
|
163
|
+
def build_updated_requirement(original_requirement, new_version)
|
|
164
|
+
return ">=#{new_version}" unless original_requirement
|
|
165
|
+
|
|
166
|
+
updater = Dependabot::Python::UpdateChecker::RequirementsUpdater.new(
|
|
167
|
+
requirements: [{
|
|
168
|
+
requirement: original_requirement,
|
|
169
|
+
file: "requirements.txt",
|
|
170
|
+
groups: [],
|
|
171
|
+
source: nil
|
|
172
|
+
}],
|
|
173
|
+
update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions,
|
|
174
|
+
has_lockfile: false,
|
|
175
|
+
latest_resolvable_version: new_version
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
updated_reqs = updater.updated_requirements
|
|
179
|
+
updated_req = updated_reqs.first&.fetch(:requirement, nil)
|
|
180
|
+
|
|
181
|
+
return ">=#{new_version}" if updated_req == :unfixable
|
|
182
|
+
|
|
183
|
+
if updated_req == original_requirement
|
|
184
|
+
force_bump_lower_bounds(original_requirement, new_version)
|
|
185
|
+
else
|
|
186
|
+
updated_req || ">=#{new_version}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sig { params(requirement: String, new_version: String).returns(String) }
|
|
191
|
+
def force_bump_lower_bounds(requirement, new_version)
|
|
192
|
+
constraints = requirement.split(",").map(&:strip)
|
|
193
|
+
|
|
194
|
+
updated = constraints.map do |constraint|
|
|
195
|
+
if constraint.match?(/\A(>=|>|~=)/)
|
|
196
|
+
operator = T.must(constraint.match(/\A(>=|>|~=)/))[1]
|
|
197
|
+
"#{operator}#{new_version}"
|
|
198
|
+
else
|
|
199
|
+
constraint
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
updated.join(",")
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers.register(
|
|
211
|
+
"python",
|
|
212
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers::Python
|
|
213
|
+
)
|