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,169 @@
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 Ruby < 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_bundler_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_bundler_checker
56
+ bundler_checker = bundler_update_checker
57
+ return nil unless bundler_checker
58
+
59
+ latest = bundler_checker.latest_version
60
+ Dependabot.logger.info("Ruby 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 Ruby gem #{package_name}: #{e.message}")
65
+ nil
66
+ end
67
+
68
+ sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
69
+ def bundler_update_checker
70
+ @bundler_update_checker ||= T.let(
71
+ build_bundler_update_checker,
72
+ T.nilable(Dependabot::UpdateCheckers::Base)
73
+ )
74
+ end
75
+
76
+ sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
77
+ def build_bundler_update_checker
78
+ bundler_dependency = build_bundler_dependency
79
+ return nil unless bundler_dependency
80
+
81
+ Dependabot.logger.info("Delegating to bundler UpdateChecker for gem: #{bundler_dependency.name}")
82
+
83
+ Dependabot::UpdateCheckers.for_package_manager("bundler").new(
84
+ dependency: bundler_dependency,
85
+ dependency_files: build_bundler_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_bundler_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 ? "= #{version}" : nil,
104
+ groups: [],
105
+ file: "Gemfile",
106
+ source: nil
107
+ }],
108
+ package_manager: "bundler"
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*/, "").strip
118
+ return version_part if version_part.match?(/\A\d+(?:\.\d+)*(?:\.\w+)?\z/)
119
+
120
+ nil
121
+ end
122
+
123
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
124
+ def build_bundler_dependency_files
125
+ version = current_version || extract_version_from_requirement
126
+ version_constraint = version ? ", \"#{version}\"" : ""
127
+ gemfile_content = "source 'https://rubygems.org'\ngem '#{package_name}'#{version_constraint}\n"
128
+
129
+ [
130
+ Dependabot::DependencyFile.new(
131
+ name: "Gemfile",
132
+ content: gemfile_content
133
+ )
134
+ ]
135
+ end
136
+
137
+ sig do
138
+ params(
139
+ package_name: T.nilable(String),
140
+ requirement: T.nilable(String)
141
+ ).returns(String)
142
+ end
143
+ def build_original_string(package_name:, requirement:)
144
+ base = package_name.to_s
145
+ base = "#{base}:#{requirement}" if requirement
146
+ base
147
+ end
148
+
149
+ sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) }
150
+ def build_updated_requirement(original_requirement, new_version)
151
+ return new_version unless original_requirement
152
+
153
+ # Handle Ruby gem version operators: ~>, >=, >, <=, <, =, !=
154
+ operator_match = original_requirement.match(/\A(?<op>[~><=!]+)\s*/)
155
+ if operator_match
156
+ "#{operator_match[:op]} #{new_version}"
157
+ else
158
+ new_version
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ Dependabot::PreCommit::AdditionalDependencyCheckers.register(
167
+ "ruby",
168
+ Dependabot::PreCommit::AdditionalDependencyCheckers::Ruby
169
+ )
@@ -0,0 +1,179 @@
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 Rust < 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_cargo_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
+ cli: original_source[:extras] == "cli"
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_cargo_checker
57
+ cargo_checker = cargo_update_checker
58
+ return nil unless cargo_checker
59
+
60
+ latest = cargo_checker.latest_version
61
+ Dependabot.logger.info("Cargo UpdateChecker found latest version: #{latest || 'none'}")
62
+
63
+ latest&.to_s
64
+ rescue Dependabot::DependabotError, Excon::Error, JSON::ParserError => e
65
+ Dependabot.logger.debug("Error checking Rust package #{package_name}: #{e.message}")
66
+ nil
67
+ end
68
+
69
+ sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
70
+ def cargo_update_checker
71
+ @cargo_update_checker ||= T.let(
72
+ build_cargo_update_checker,
73
+ T.nilable(Dependabot::UpdateCheckers::Base)
74
+ )
75
+ end
76
+
77
+ sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
78
+ def build_cargo_update_checker
79
+ cargo_dependency = build_cargo_dependency
80
+ return nil unless cargo_dependency
81
+
82
+ Dependabot.logger.info("Delegating to cargo UpdateChecker for package: #{cargo_dependency.name}")
83
+
84
+ Dependabot::UpdateCheckers.for_package_manager("cargo").new(
85
+ dependency: cargo_dependency,
86
+ dependency_files: build_cargo_dependency_files,
87
+ credentials: credentials,
88
+ ignored_versions: [],
89
+ security_advisories: [],
90
+ raise_on_ignored: false
91
+ )
92
+ end
93
+
94
+ sig { returns(T.nilable(Dependabot::Dependency)) }
95
+ def build_cargo_dependency
96
+ return nil unless package_name
97
+
98
+ version = current_version || extract_version_from_requirement
99
+
100
+ Dependabot::Dependency.new(
101
+ name: T.must(package_name),
102
+ version: version,
103
+ requirements: [{
104
+ requirement: version ? "=#{version}" : nil,
105
+ groups: ["dependencies"],
106
+ file: "Cargo.toml",
107
+ source: nil
108
+ }],
109
+ package_manager: "cargo"
110
+ )
111
+ end
112
+
113
+ sig { returns(T.nilable(String)) }
114
+ def extract_version_from_requirement
115
+ req_string = requirements.first&.dig(:requirement)
116
+ return nil unless req_string
117
+
118
+ version_part = req_string.sub(/\A(?:[~^]|[><=]+)\s*/, "")
119
+ return version_part if version_part.match?(/\A\d+(?:\.\d+)*(?:-[\w.]+)?(?:\+[\w.]+)?\z/)
120
+
121
+ nil
122
+ end
123
+
124
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
125
+ def build_cargo_dependency_files
126
+ version = current_version || extract_version_from_requirement
127
+ requirement = version ? "=#{version}" : "*"
128
+
129
+ toml_content = <<~TOML
130
+ [package]
131
+ name = "dependabot-pre-commit-check"
132
+ version = "0.0.1"
133
+ edition = "2021"
134
+
135
+ [dependencies]
136
+ #{T.must(package_name)} = "#{requirement}"
137
+ TOML
138
+
139
+ [
140
+ Dependabot::DependencyFile.new(
141
+ name: "Cargo.toml",
142
+ content: toml_content
143
+ )
144
+ ]
145
+ end
146
+
147
+ sig do
148
+ params(
149
+ package_name: T.nilable(String),
150
+ requirement: T.nilable(String),
151
+ cli: T::Boolean
152
+ ).returns(String)
153
+ end
154
+ def build_original_string(package_name:, requirement:, cli:)
155
+ base = cli ? "cli:#{package_name}" : package_name.to_s
156
+ base = "#{base}:#{requirement}" if requirement
157
+ base
158
+ end
159
+
160
+ sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) }
161
+ def build_updated_requirement(original_requirement, new_version)
162
+ return new_version unless original_requirement
163
+
164
+ operator_match = original_requirement.match(/\A(?<op>[~^]|[><=]+)\s*/)
165
+ if operator_match
166
+ "#{operator_match[:op]}#{new_version}"
167
+ else
168
+ new_version
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ Dependabot::PreCommit::AdditionalDependencyCheckers.register(
177
+ "rust",
178
+ Dependabot::PreCommit::AdditionalDependencyCheckers::Rust
179
+ )
@@ -0,0 +1,46 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/pre_commit/additional_dependency_checkers/base"
6
+
7
+ module Dependabot
8
+ module PreCommit
9
+ # Registry for additional_dependency update checkers by language.
10
+ # Similar pattern to Dependabot::UpdateCheckers but for pre-commit hook languages.
11
+ #
12
+ # Usage:
13
+ # checker_class = AdditionalDependencyCheckers.for_language("python")
14
+ # checker = checker_class.new(source: source, credentials: credentials, ...)
15
+ # latest_version = checker.latest_version
16
+ #
17
+ module AdditionalDependencyCheckers
18
+ extend T::Sig
19
+
20
+ @checkers = T.let({}, T::Hash[String, T.class_of(Base)])
21
+
22
+ sig { params(language: String).returns(T.class_of(Base)) }
23
+ def self.for_language(language)
24
+ checker = @checkers[language.downcase]
25
+ return checker if checker
26
+
27
+ raise "Unsupported language for additional_dependencies: #{language}"
28
+ end
29
+
30
+ sig { params(language: String, checker: T.class_of(Base)).void }
31
+ def self.register(language, checker)
32
+ @checkers[language.downcase] = checker
33
+ end
34
+
35
+ sig { params(language: String).returns(T::Boolean) }
36
+ def self.supported?(language)
37
+ @checkers.key?(language.downcase)
38
+ end
39
+
40
+ sig { returns(T::Array[String]) }
41
+ def self.supported_languages
42
+ @checkers.keys
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/file_fetchers"
5
+ require "dependabot/file_fetchers/base"
6
+ require "dependabot/file_filtering"
7
+
8
+ module Dependabot
9
+ module PreCommit
10
+ class FileFetcher < Dependabot::FileFetchers::Base
11
+ extend T::Sig
12
+
13
+ CONFIG_FILE_PATTERN = /\.pre-commit(-config)?\.ya?ml$/i
14
+
15
+ sig { override.returns(String) }
16
+ def self.required_files_message
17
+ "Repo must contain a .pre-commit-config.yaml, .pre-commit-config.yml, " \
18
+ ".pre-commit.yaml, or .pre-commit.yml file."
19
+ end
20
+
21
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
22
+ def self.required_files_in?(filenames)
23
+ filenames.any? { |f| f.match?(CONFIG_FILE_PATTERN) }
24
+ end
25
+
26
+ sig { override.returns(T::Array[DependencyFile]) }
27
+ def fetch_files
28
+ unless allow_beta_ecosystems?
29
+ raise Dependabot::DependencyFileNotFound.new(
30
+ nil,
31
+ "PreCommit support is currently in beta. Set ALLOW_BETA_ECOSYSTEMS=true to enable it."
32
+ )
33
+ end
34
+
35
+ fetched_files = []
36
+ fetched_files << pre_commit_config
37
+
38
+ fetched_files.reject do |file|
39
+ Dependabot::FileFiltering.should_exclude_path?(file.name, "file from final collection", @exclude_paths)
40
+ end
41
+ end
42
+
43
+ sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
44
+ def ecosystem_versions
45
+ nil
46
+ end
47
+
48
+ private
49
+
50
+ sig { returns(Dependabot::DependencyFile) }
51
+ def pre_commit_config
52
+ @pre_commit_config ||= T.let(
53
+ fetch_file_from_host(config_file_name),
54
+ T.nilable(Dependabot::DependencyFile)
55
+ )
56
+ end
57
+
58
+ sig { returns(String) }
59
+ def config_file_name
60
+ @config_file_name ||= T.let(
61
+ repo_contents.find { |f| f.name.match?(CONFIG_FILE_PATTERN) }&.name ||
62
+ ".pre-commit-config.yaml",
63
+ T.nilable(String)
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ Dependabot::FileFetchers.register("pre_commit", Dependabot::PreCommit::FileFetcher)
@@ -0,0 +1,210 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "yaml"
5
+ require "sorbet-runtime"
6
+ require "dependabot/dependency"
7
+ require "dependabot/file_parsers"
8
+ require "dependabot/file_parsers/base"
9
+ require "dependabot/errors"
10
+ require "dependabot/pre_commit/package_manager"
11
+ require "dependabot/pre_commit/version"
12
+ require "dependabot/pre_commit/requirement"
13
+ require "dependabot/cargo/requirement"
14
+ require "dependabot/npm_and_yarn/requirement"
15
+ require "dependabot/python/requirement_parser"
16
+ require "dependabot/bundler/requirement"
17
+ require "dependabot/go_modules/requirement_parser"
18
+
19
+ module Dependabot
20
+ module PreCommit
21
+ class FileParser < Dependabot::FileParsers::Base
22
+ extend T::Sig
23
+
24
+ require "dependabot/file_parsers/base/dependency_set"
25
+
26
+ CONFIG_FILE_PATTERN = /\.pre-commit(-config)?\.ya?ml$/i
27
+ ECOSYSTEM = "pre_commit"
28
+
29
+ LANGUAGE_PARSERS = T.let(
30
+ {
31
+ "python" => ->(dep_string) { Dependabot::Python::RequirementParser.parse(dep_string) },
32
+ "node" => ->(dep_string) { Dependabot::NpmAndYarn::Requirement.parse_dep_string(dep_string) },
33
+ "rust" => ->(dep_string) { Dependabot::Cargo::Requirement.parse_dep_string(dep_string) },
34
+ "golang" => ->(dep_string) { Dependabot::GoModules::RequirementParser.parse(dep_string) },
35
+ "ruby" => ->(dep_string) { Dependabot::Bundler::Requirement.parse_dep_string(dep_string) }
36
+ }.freeze,
37
+ T::Hash[String, T.proc.params(dep_string: String).returns(T.nilable(T::Hash[Symbol, T.untyped]))]
38
+ )
39
+
40
+ sig { override.returns(Ecosystem) }
41
+ def ecosystem
42
+ @ecosystem ||= T.let(
43
+ Ecosystem.new(
44
+ name: ECOSYSTEM,
45
+ package_manager: package_manager
46
+ ),
47
+ T.nilable(Ecosystem)
48
+ )
49
+ end
50
+
51
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
52
+ def parse
53
+ dependency_set = DependencySet.new
54
+
55
+ pre_commit_config_files.each do |file|
56
+ dependency_set += parse_config_file(file)
57
+ end
58
+
59
+ dependency_set.dependencies
60
+ end
61
+
62
+ private
63
+
64
+ sig { returns(Ecosystem::VersionManager) }
65
+ def package_manager
66
+ @package_manager ||= T.let(PackageManager.new, T.nilable(Dependabot::PreCommit::PackageManager))
67
+ end
68
+
69
+ sig { params(file: Dependabot::DependencyFile).returns(DependencySet) }
70
+ def parse_config_file(file)
71
+ dependency_set = DependencySet.new
72
+
73
+ yaml = YAML.safe_load(T.must(file.content), aliases: true)
74
+ return dependency_set unless yaml.is_a?(Hash)
75
+
76
+ repos = yaml.fetch("repos", [])
77
+ repos.each do |repo|
78
+ next unless repo.is_a?(Hash)
79
+
80
+ dependency = parse_repo(repo, file)
81
+ dependency_set << dependency if dependency
82
+
83
+ additional_deps = parse_additional_dependencies(repo, file)
84
+ additional_deps.each { |dep| dependency_set << dep }
85
+ end
86
+
87
+ dependency_set
88
+ rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias => e
89
+ raise Dependabot::DependencyFileNotParseable.new(file.path, e.message)
90
+ end
91
+
92
+ sig do
93
+ params(
94
+ repo: T::Hash[String, T.untyped],
95
+ file: Dependabot::DependencyFile
96
+ ).returns(T.nilable(Dependency))
97
+ end
98
+ def parse_repo(repo, file)
99
+ repo_url = repo["repo"]
100
+ rev = repo["rev"]
101
+
102
+ return nil if repo_url.nil? || rev.nil?
103
+ return nil if %w(local meta).include?(repo_url)
104
+
105
+ Dependency.new(
106
+ name: repo_url,
107
+ version: rev,
108
+ requirements: [{
109
+ requirement: nil,
110
+ groups: [],
111
+ file: file.name,
112
+ source: {
113
+ type: "git",
114
+ url: repo_url,
115
+ ref: rev,
116
+ branch: nil
117
+ }
118
+ }],
119
+ package_manager: ECOSYSTEM
120
+ )
121
+ end
122
+
123
+ sig do
124
+ params(
125
+ repo: T::Hash[String, T.untyped],
126
+ file: Dependabot::DependencyFile
127
+ ).returns(T::Array[Dependabot::Dependency])
128
+ end
129
+ def parse_additional_dependencies(repo, file)
130
+ dependencies = []
131
+ repo_url = repo["repo"]
132
+
133
+ return dependencies if repo_url.nil? || %w(local meta).include?(repo_url)
134
+
135
+ hooks = repo.fetch("hooks", [])
136
+ hooks.each do |hook|
137
+ next unless hook.is_a?(Hash)
138
+
139
+ hook_deps = parse_hook_additional_dependencies(hook, repo_url, file)
140
+ dependencies.concat(hook_deps)
141
+ end
142
+
143
+ dependencies
144
+ end
145
+
146
+ sig do
147
+ params(
148
+ hook: T::Hash[String, T.untyped],
149
+ repo_url: String,
150
+ file: Dependabot::DependencyFile
151
+ ).returns(T::Array[Dependabot::Dependency])
152
+ end
153
+ def parse_hook_additional_dependencies(hook, repo_url, file)
154
+ dependencies = []
155
+
156
+ return dependencies unless hook["id"]
157
+
158
+ additional_deps = hook.fetch("additional_dependencies", [])
159
+ return dependencies if additional_deps.empty?
160
+
161
+ parser = LANGUAGE_PARSERS[hook["language"]]
162
+ return dependencies unless parser
163
+
164
+ additional_deps.each do |dep_string|
165
+ next unless dep_string.is_a?(String)
166
+
167
+ parsed = parser.call(dep_string)
168
+ next unless parsed
169
+
170
+ dependencies << Dependabot::Dependency.new(
171
+ name: parsed[:normalised_name],
172
+ version: parsed[:version],
173
+ requirements: [{
174
+ requirement: parsed[:requirement],
175
+ groups: ["additional_dependencies"],
176
+ file: file.name,
177
+ source: {
178
+ type: "additional_dependency",
179
+ language: hook["language"],
180
+ package_name: parsed[:normalised_name],
181
+ original_name: parsed[:name],
182
+ hook_id: hook["id"],
183
+ hook_repo: repo_url,
184
+ extras: parsed[:extras],
185
+ original_string: dep_string
186
+ }
187
+ }],
188
+ package_manager: ECOSYSTEM
189
+ )
190
+ end
191
+
192
+ dependencies
193
+ end
194
+
195
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
196
+ def pre_commit_config_files
197
+ dependency_files.select { |f| f.name.match?(CONFIG_FILE_PATTERN) }
198
+ end
199
+
200
+ sig { override.void }
201
+ def check_required_files
202
+ return if pre_commit_config_files.any?
203
+
204
+ raise "No pre-commit configuration file found!"
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ Dependabot::FileParsers.register("pre_commit", Dependabot::PreCommit::FileParser)