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.
@@ -0,0 +1,240 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/errors"
6
+ require "dependabot/pre_commit/file_parser"
7
+ require "dependabot/pre_commit/package/package_details_fetcher"
8
+ require "dependabot/pre_commit/requirement"
9
+ require "dependabot/pre_commit/update_checker"
10
+ require "dependabot/pre_commit/helpers"
11
+ require "dependabot/package/package_latest_version_finder"
12
+ require "dependabot/shared_helpers"
13
+ require "dependabot/update_checkers/version_filters"
14
+
15
+ module Dependabot
16
+ module PreCommit
17
+ class UpdateChecker
18
+ class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
19
+ extend T::Sig
20
+
21
+ sig do
22
+ params(
23
+ dependency: Dependabot::Dependency,
24
+ dependency_files: T::Array[Dependabot::DependencyFile],
25
+ credentials: T::Array[Dependabot::Credential],
26
+ ignored_versions: T::Array[String],
27
+ raise_on_ignored: T::Boolean,
28
+ options: T::Hash[Symbol, T.untyped],
29
+ cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
30
+ ).void
31
+ end
32
+ def initialize(
33
+ dependency:,
34
+ dependency_files:,
35
+ credentials:,
36
+ ignored_versions:,
37
+ raise_on_ignored:,
38
+ options: {},
39
+ cooldown_options: nil
40
+ )
41
+ @dependency = dependency
42
+ @dependency_files = dependency_files
43
+ @credentials = credentials
44
+ @ignored_versions = ignored_versions
45
+ @raise_on_ignored = raise_on_ignored
46
+ @options = options
47
+ @cooldown_options = cooldown_options
48
+
49
+ @git_helper = T.let(git_helper, Dependabot::PreCommit::Helpers::Githelper)
50
+ super(
51
+ dependency: dependency,
52
+ dependency_files: dependency_files,
53
+ credentials: credentials,
54
+ ignored_versions: ignored_versions,
55
+ security_advisories: [],
56
+ cooldown_options: cooldown_options,
57
+ raise_on_ignored: raise_on_ignored,
58
+ options: options
59
+ )
60
+ end
61
+
62
+ sig { returns(Dependabot::Dependency) }
63
+ attr_reader :dependency
64
+
65
+ sig { returns(T::Array[Dependabot::Credential]) }
66
+ attr_reader :credentials
67
+
68
+ sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
69
+ attr_reader :cooldown_options
70
+
71
+ sig { returns(T::Array[String]) }
72
+ attr_reader :ignored_versions
73
+
74
+ sig { returns(T::Boolean) }
75
+ attr_reader :raise_on_ignored
76
+
77
+ sig { override.returns(T.nilable(Dependabot::Package::PackageDetails)) }
78
+ def package_details; end
79
+
80
+ sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
81
+ def latest_release
82
+ release = available_release
83
+ return nil unless release
84
+
85
+ Dependabot.logger.info("Available release version/ref is #{release}")
86
+
87
+ release = cooldown_filter(release)
88
+ if release.nil?
89
+ Dependabot.logger.info("Returning current version/ref (no viable filtered release) #{current_version}")
90
+ return current_version
91
+ end
92
+
93
+ release
94
+ end
95
+
96
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
97
+ def latest_version_tag
98
+ available_latest_version_tag
99
+ end
100
+
101
+ private
102
+
103
+ sig { returns(T.nilable(Dependabot::PreCommit::Package::PackageDetailsFetcher)) }
104
+ def package_details_fetcher
105
+ @package_details_fetcher ||= T.let(
106
+ Dependabot::PreCommit::Package::PackageDetailsFetcher
107
+ .new(
108
+ dependency: dependency,
109
+ credentials: credentials,
110
+ ignored_versions: ignored_versions,
111
+ raise_on_ignored: raise_on_ignored
112
+ ),
113
+ T.nilable(Dependabot::PreCommit::Package::PackageDetailsFetcher)
114
+ )
115
+ end
116
+
117
+ sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
118
+ def available_release
119
+ @available_release = T.let(
120
+ T.must(package_details_fetcher).release_list_for_git_dependency,
121
+ T.nilable(T.any(Dependabot::Version, String))
122
+ )
123
+ end
124
+
125
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
126
+ def available_latest_version_tag
127
+ @latest_version_tag = T.let(
128
+ T.must(package_details_fetcher).latest_version_tag,
129
+ T.nilable(T::Hash[Symbol, T.untyped])
130
+ )
131
+ end
132
+
133
+ sig { override.returns(T::Boolean) }
134
+ def cooldown_enabled?
135
+ true
136
+ end
137
+
138
+ sig do
139
+ params(release: T.nilable(T.any(Dependabot::Version, String)))
140
+ .returns(T.nilable(T.any(Dependabot::Version, String)))
141
+ end
142
+ def cooldown_filter(release)
143
+ return release unless cooldown_enabled?
144
+ return release unless cooldown_options
145
+
146
+ Dependabot.logger.info("Initializing cooldown filter")
147
+ release_date = commit_metadata_details
148
+
149
+ unless release_date
150
+ Dependabot.logger.info("No release date found, skipping cooldown filtering")
151
+ return release
152
+ end
153
+
154
+ if release_in_cooldown_period?(Time.parse(release_date))
155
+ Dependabot.logger.info("Filtered out (cooldown) #{dependency.name}, #{release}")
156
+ return nil
157
+ end
158
+
159
+ release
160
+ end
161
+
162
+ sig { returns(T.nilable(String)) }
163
+ def commit_metadata_details
164
+ @commit_metadata_details ||= T.let(
165
+ begin
166
+ url = @git_helper.git_commit_checker.dependency_source_details&.fetch(:url)
167
+ source = T.must(Source.from_url(url))
168
+
169
+ SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
170
+ repo_contents_path = File.join(temp_dir, File.basename(source.repo))
171
+
172
+ SharedHelpers.run_shell_command("git clone --bare --no-recurse-submodules #{url} #{repo_contents_path}")
173
+ Dir.chdir(repo_contents_path) do
174
+ date = SharedHelpers.run_shell_command(
175
+ "git show --no-patch --format=\"%cd\" " \
176
+ "--date=iso #{commit_ref}"
177
+ )
178
+ Dependabot.logger.info("Found release date : #{Time.parse(date)}")
179
+ return date
180
+ end
181
+ end
182
+ rescue StandardError => e
183
+ Dependabot.logger.error("Error (pre_commit) while checking release date for #{dependency.name}")
184
+ Dependabot.logger.error(e.message)
185
+
186
+ nil
187
+ end,
188
+ T.nilable(String)
189
+ )
190
+ end
191
+
192
+ sig { params(release_date: Time).returns(T::Boolean) }
193
+ def release_in_cooldown_period?(release_date)
194
+ cooldown = @cooldown_options
195
+
196
+ return false unless T.must(cooldown).included?(dependency.name)
197
+
198
+ days = T.must(cooldown).default_days
199
+ passed_seconds = Time.now.to_i - release_date.to_i
200
+
201
+ Dependabot.logger.info(
202
+ "Days since release : #{passed_seconds / (3600 * 24)} " \
203
+ "(cooldown days #{T.must(cooldown_options).default_days})"
204
+ )
205
+
206
+ passed_seconds < days * DAY_IN_SECONDS
207
+ end
208
+
209
+ sig { returns(String) }
210
+ def commit_ref
211
+ T.cast(latest_version_tag&.fetch(:commit_sha), String)
212
+ end
213
+
214
+ sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
215
+ def current_version
216
+ return dependency.source_details(allowed_types: ["git"])&.fetch(:ref) if release_type_sha?
217
+
218
+ T.let(dependency.numeric_version, T.nilable(Dependabot::Version))
219
+ end
220
+
221
+ sig { returns(T::Boolean) }
222
+ def release_type_sha?
223
+ available_release.is_a?(String)
224
+ end
225
+
226
+ sig { returns(Dependabot::PreCommit::Helpers::Githelper) }
227
+ def git_helper
228
+ Helpers::Githelper.new(
229
+ dependency: dependency,
230
+ credentials: credentials,
231
+ ignored_versions: ignored_versions,
232
+ raise_on_ignored: raise_on_ignored,
233
+ consider_version_branches_pinned: false,
234
+ dependency_source_details: nil
235
+ )
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,284 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/errors"
7
+ require "dependabot/pre_commit/requirement"
8
+ require "dependabot/pre_commit/version"
9
+ require "dependabot/pre_commit/additional_dependency_checkers"
10
+ require "dependabot/pre_commit/additional_dependency_checkers/node"
11
+ require "dependabot/pre_commit/additional_dependency_checkers/python"
12
+ require "dependabot/pre_commit/additional_dependency_checkers/ruby"
13
+ require "dependabot/pre_commit/additional_dependency_checkers/go"
14
+ require "dependabot/pre_commit/additional_dependency_checkers/rust"
15
+ require "dependabot/update_checkers"
16
+ require "dependabot/update_checkers/base"
17
+ require "dependabot/update_checkers/version_filters"
18
+
19
+ module Dependabot
20
+ module PreCommit
21
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
22
+ extend T::Sig
23
+
24
+ require_relative "update_checker/latest_version_finder"
25
+
26
+ sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
27
+ def latest_version
28
+ return additional_dependency_latest_version if additional_dependency?
29
+
30
+ @latest_version ||= T.let(
31
+ T.must(latest_version_finder).latest_release,
32
+ T.nilable(T.any(String, Gem::Version))
33
+ )
34
+ end
35
+
36
+ sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
37
+ def latest_resolvable_version
38
+ latest_version
39
+ end
40
+
41
+ sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
42
+ def latest_resolvable_version_with_no_unlock
43
+ dependency.version
44
+ end
45
+
46
+ sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
47
+ def updated_requirements
48
+ return additional_dependency_updated_requirements if additional_dependency?
49
+
50
+ dependency.requirements.map do |req|
51
+ source = T.cast(req[:source], T.nilable(T::Hash[Symbol, T.untyped]))
52
+ updated = updated_ref(source)
53
+ next req unless updated
54
+
55
+ current = T.cast(source&.[](:ref), T.nilable(String))
56
+
57
+ # Maintain a short git hash only if it matches the latest
58
+ if T.cast(req[:type], T.nilable(String)) == "git" &&
59
+ git_commit_checker.ref_looks_like_commit_sha?(updated) &&
60
+ current && git_commit_checker.ref_looks_like_commit_sha?(current) &&
61
+ updated.start_with?(current)
62
+ next req
63
+ end
64
+
65
+ new_source = T.must(source).merge(ref: updated)
66
+ req.merge(source: new_source)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ sig { returns(T.nilable(Dependabot::PreCommit::UpdateChecker::LatestVersionFinder)) }
73
+ def latest_version_finder
74
+ @latest_version_finder ||=
75
+ T.let(
76
+ LatestVersionFinder.new(
77
+ dependency: dependency,
78
+ credentials: credentials,
79
+ dependency_files: dependency_files,
80
+ ignored_versions: ignored_versions,
81
+ raise_on_ignored: raise_on_ignored,
82
+ cooldown_options: update_cooldown
83
+ ),
84
+ T.nilable(Dependabot::PreCommit::UpdateChecker::LatestVersionFinder)
85
+ )
86
+ end
87
+
88
+ sig { override.returns(T.nilable(Dependabot::Version)) }
89
+ def current_version
90
+ return super if dependency.numeric_version
91
+
92
+ # For git dependencies, try to parse the version from the ref
93
+ source_details = dependency.source_details(allowed_types: ["git"])
94
+ return nil unless source_details
95
+
96
+ ref = T.cast(source_details.fetch(:ref, nil), T.nilable(String))
97
+ return nil unless ref
98
+
99
+ version_string = ref.sub(/^v/, "")
100
+ return nil unless version_class.correct?(version_string)
101
+
102
+ version_class.new(version_string)
103
+ end
104
+
105
+ sig { override.returns(T::Boolean) }
106
+ def latest_version_resolvable_with_full_unlock?
107
+ false
108
+ end
109
+
110
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
111
+ def updated_dependencies_after_full_unlock
112
+ raise NotImplementedError
113
+ end
114
+
115
+ sig { returns(T.nilable(String)) }
116
+ def latest_commit_for_pinned_ref
117
+ @latest_commit_for_pinned_ref ||= T.let(
118
+ begin
119
+ head_commit_for_ref_sha = git_commit_checker.head_commit_for_pinned_ref
120
+ if head_commit_for_ref_sha
121
+ head_commit_for_ref_sha
122
+ else
123
+ url = T.cast(git_commit_checker.dependency_source_details&.fetch(:url), T.nilable(String))
124
+ source = T.must(Source.from_url(T.must(url)))
125
+
126
+ SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
127
+ repo_contents_path = File.join(temp_dir, File.basename(source.repo))
128
+
129
+ SharedHelpers.run_shell_command("git clone --no-recurse-submodules #{url} #{repo_contents_path}")
130
+
131
+ Dir.chdir(repo_contents_path) do
132
+ ref = T.cast(git_commit_checker.dependency_source_details&.fetch(:ref), T.nilable(String))
133
+ ref_branch = find_container_branch(T.must(ref))
134
+ git_commit_checker.head_commit_for_local_branch(ref_branch) if ref_branch
135
+ end
136
+ end
137
+ end
138
+ end,
139
+ T.nilable(String)
140
+ )
141
+ end
142
+
143
+ sig { params(source: T.nilable(T::Hash[Symbol, T.untyped])).returns(T.nilable(String)) }
144
+ def updated_ref(source)
145
+ return unless git_commit_checker.git_dependency?
146
+
147
+ source_git_commit_checker = git_helper.git_commit_checker_for(source)
148
+
149
+ # Return the git tag if updating a pinned version
150
+ if source_git_commit_checker.pinned_ref_looks_like_version? &&
151
+ (new_tag = T.must(latest_version_finder).latest_version_tag)
152
+ return T.cast(new_tag.fetch(:tag), String)
153
+ end
154
+
155
+ # Return the pinned git commit if one is available
156
+ if source_git_commit_checker.pinned_ref_looks_like_commit_sha? &&
157
+ (new_commit_sha = latest_commit_sha)
158
+ return new_commit_sha
159
+ end
160
+
161
+ nil
162
+ end
163
+
164
+ sig { returns(T.nilable(String)) }
165
+ def latest_commit_sha
166
+ new_tag = T.must(latest_version_finder).latest_version_tag
167
+
168
+ if new_tag
169
+ return T.cast(new_tag.fetch(:commit_sha), String) if git_commit_checker.local_tag_for_pinned_sha
170
+
171
+ return latest_commit_for_pinned_ref
172
+
173
+ end
174
+
175
+ # If there's no tag but we have a latest_version (commit SHA), use it
176
+ latest_ver = latest_version
177
+ return latest_ver if latest_ver.is_a?(String)
178
+
179
+ nil
180
+ end
181
+
182
+ sig { returns(Dependabot::GitCommitChecker) }
183
+ def git_commit_checker
184
+ @git_commit_checker ||= T.let(git_helper.git_commit_checker, T.nilable(Dependabot::GitCommitChecker))
185
+ end
186
+
187
+ sig { returns(Dependabot::PreCommit::Helpers::Githelper) }
188
+ def git_helper
189
+ Helpers::Githelper.new(
190
+ dependency: dependency,
191
+ credentials: credentials,
192
+ ignored_versions: ignored_versions,
193
+ raise_on_ignored: raise_on_ignored,
194
+ consider_version_branches_pinned: false,
195
+ dependency_source_details: nil
196
+ )
197
+ end
198
+
199
+ sig { params(sha: String).returns(T.nilable(String)) }
200
+ def find_container_branch(sha)
201
+ branches_including_ref = SharedHelpers.run_shell_command(
202
+ "git branch --remotes --contains #{sha}",
203
+ fingerprint: "git branch --remotes --contains <sha>"
204
+ ).split("\n").map { |branch| branch.strip.gsub("origin/", "") }
205
+ return if branches_including_ref.empty?
206
+
207
+ current_branch = branches_including_ref.find { |branch| branch.start_with?("HEAD -> ") }
208
+
209
+ if current_branch
210
+ current_branch.delete_prefix("HEAD -> ")
211
+ elsif branches_including_ref.size > 1
212
+ raise "Multiple ambiguous branches (#{branches_including_ref.join(', ')}) include #{sha}!"
213
+ else
214
+ branches_including_ref.first
215
+ end
216
+ end
217
+
218
+ # Additional dependency support methods
219
+
220
+ sig { returns(T::Boolean) }
221
+ def additional_dependency?
222
+ requirement = dependency.requirements.first
223
+ return false unless requirement
224
+
225
+ source = T.cast(requirement[:source], T.nilable(T::Hash[Symbol, T.untyped]))
226
+ return false unless source
227
+
228
+ T.cast(source[:type], T.nilable(String)) == "additional_dependency"
229
+ end
230
+
231
+ sig { returns(T.nilable(T.any(String, Gem::Version))) }
232
+ def additional_dependency_latest_version
233
+ source = T.cast(dependency.requirements.first&.dig(:source), T::Hash[Symbol, T.untyped])
234
+ language = T.cast(source[:language], T.nilable(String))
235
+ return nil unless language && AdditionalDependencyCheckers.supported?(language)
236
+
237
+ checker = additional_dependency_checker(language, source)
238
+ return nil unless checker
239
+
240
+ latest = checker.latest_version
241
+ Dependabot.logger.info("Latest version for #{dependency.name}: #{latest || 'none'}")
242
+
243
+ latest
244
+ end
245
+
246
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
247
+ def additional_dependency_updated_requirements
248
+ source = T.cast(dependency.requirements.first&.dig(:source), T::Hash[Symbol, T.untyped])
249
+ language = T.cast(source[:language], T.nilable(String))
250
+ return dependency.requirements unless language && AdditionalDependencyCheckers.supported?(language)
251
+
252
+ checker = additional_dependency_checker(language, source)
253
+ return dependency.requirements unless checker
254
+
255
+ latest = checker.latest_version
256
+ return dependency.requirements unless latest
257
+
258
+ checker.updated_requirements(latest)
259
+ end
260
+
261
+ sig do
262
+ params(
263
+ language: String,
264
+ source: T::Hash[Symbol, T.untyped]
265
+ ).returns(T.nilable(Dependabot::PreCommit::AdditionalDependencyCheckers::Base))
266
+ end
267
+ def additional_dependency_checker(language, source)
268
+ checker_class = AdditionalDependencyCheckers.for_language(language)
269
+ checker_class.new(
270
+ source: source,
271
+ credentials: credentials,
272
+ requirements: dependency.requirements,
273
+ current_version: dependency.version
274
+ )
275
+ rescue StandardError => e
276
+ Dependabot.logger.error("Error creating checker for #{language}: #{e.message}")
277
+ nil
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ Dependabot::UpdateCheckers
284
+ .register("pre_commit", Dependabot::PreCommit::UpdateChecker)
@@ -0,0 +1,15 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/version"
5
+ require "dependabot/utils"
6
+
7
+ module Dependabot
8
+ module PreCommit
9
+ class Version < Dependabot::Version
10
+ end
11
+ end
12
+ end
13
+
14
+ Dependabot::Utils
15
+ .register_version_class("pre_commit", Dependabot::PreCommit::Version)
@@ -0,0 +1,20 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # These all need to be required so the various classes can be registered in a
5
+ # lookup table of package manager names to concrete classes.
6
+ require "dependabot/pre_commit/file_fetcher"
7
+ require "dependabot/pre_commit/file_parser"
8
+ require "dependabot/pre_commit/update_checker"
9
+ require "dependabot/pre_commit/file_updater"
10
+ require "dependabot/pre_commit/metadata_finder"
11
+ require "dependabot/pre_commit/version"
12
+ require "dependabot/pre_commit/requirement"
13
+ require "dependabot/pre_commit/helpers"
14
+
15
+ require "dependabot/pull_request_creator/labeler"
16
+ Dependabot::PullRequestCreator::Labeler
17
+ .register_label_details("pre_commit", name: "pre_commit", colour: "000000")
18
+
19
+ require "dependabot/dependency"
20
+ Dependabot::Dependency.register_production_check("pre_commit", ->(_) { true })