dependabot-npm_and_yarn 0.309.0 → 0.310.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
+ require "sorbet-runtime"
6
+
5
7
  require "dependabot/npm_and_yarn/update_checker"
6
8
  require "dependabot/shared_helpers"
7
9
 
@@ -9,12 +11,23 @@ module Dependabot
9
11
  module NpmAndYarn
10
12
  class UpdateChecker
11
13
  class LibraryDetector
14
+ extend T::Sig
15
+
16
+ sig do
17
+ params(
18
+ package_json_file: Dependabot::DependencyFile,
19
+ credentials: T::Array[Dependabot::Credential],
20
+ dependency_files: T::Array[Dependabot::DependencyFile]
21
+ )
22
+ .void
23
+ end
12
24
  def initialize(package_json_file:, credentials:, dependency_files:)
13
25
  @package_json_file = package_json_file
14
26
  @credentials = credentials
15
27
  @dependency_files = dependency_files
16
28
  end
17
29
 
30
+ sig { returns(T::Boolean) }
18
31
  def library?
19
32
  return false unless package_json_may_be_for_library?
20
33
 
@@ -23,26 +36,36 @@ module Dependabot
23
36
 
24
37
  private
25
38
 
39
+ sig { returns(Dependabot::DependencyFile) }
26
40
  attr_reader :package_json_file
41
+
42
+ sig { returns(T::Array[Dependabot::Credential]) }
27
43
  attr_reader :credentials
44
+
45
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
28
46
  attr_reader :dependency_files
29
47
 
48
+ sig { returns(T::Boolean) }
30
49
  def package_json_may_be_for_library?
31
50
  return false unless project_name
32
- return false if project_name.match?(/\{\{.*\}\}/)
51
+ return false if T.must(project_name).match?(/\{\{.*\}\}/)
33
52
  return false unless parsed_package_json["version"]
34
53
  return false if parsed_package_json["private"]
35
54
 
36
55
  true
37
56
  end
38
57
 
58
+ sig { returns(T::Boolean) }
39
59
  def npm_response_matches_package_json?
40
60
  project_description = parsed_package_json["description"]
41
61
  return false unless project_description
42
62
 
43
63
  # Check if the project is listed on npm. If it is, it's a library
44
64
  url = "#{registry.chomp('/')}/#{escaped_project_name}"
45
- @project_npm_response ||= Dependabot::RegistryClient.get(url: url)
65
+ @project_npm_response ||= T.let(
66
+ Dependabot::RegistryClient.get(url: url),
67
+ T.nilable(Excon::Response)
68
+ )
46
69
  return false unless @project_npm_response.status == 200
47
70
 
48
71
  @project_npm_response.body.dup.force_encoding("UTF-8").encode
@@ -51,18 +74,25 @@ module Dependabot
51
74
  false
52
75
  end
53
76
 
77
+ sig { returns(T.nilable(String)) }
54
78
  def project_name
55
79
  parsed_package_json.fetch("name", nil)
56
80
  end
57
81
 
82
+ sig { returns(T.nilable(String)) }
58
83
  def escaped_project_name
59
84
  project_name&.gsub("/", "%2F")
60
85
  end
61
86
 
87
+ sig { returns(T::Hash[String, T.untyped]) }
62
88
  def parsed_package_json
63
- @parsed_package_json ||= JSON.parse(package_json_file.content)
89
+ @parsed_package_json ||= T.let(
90
+ JSON.parse(T.must(package_json_file.content)),
91
+ T.nilable(T::Hash[String, T.untyped])
92
+ )
64
93
  end
65
94
 
95
+ sig { returns(String) }
66
96
  def registry
67
97
  Package::RegistryFinder.new(
68
98
  dependency: nil,
@@ -70,7 +100,7 @@ module Dependabot
70
100
  npmrc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") },
71
101
  yarnrc_file: dependency_files.find { |f| f.name.end_with?(".yarnrc") },
72
102
  yarnrc_yml_file: dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
73
- ).registry_from_rc(project_name)
103
+ ).registry_from_rc(T.must(project_name))
74
104
  end
75
105
  end
76
106
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  ################################################################################
@@ -6,6 +6,8 @@
6
6
  # https://docs.npmjs.com/misc/semver #
7
7
  ################################################################################
8
8
 
9
+ require "sorbet-runtime"
10
+
9
11
  require "dependabot/npm_and_yarn/requirement"
10
12
  require "dependabot/npm_and_yarn/update_checker"
11
13
  require "dependabot/npm_and_yarn/version"
@@ -15,6 +17,8 @@ module Dependabot
15
17
  module NpmAndYarn
16
18
  class UpdateChecker
17
19
  class RequirementsUpdater
20
+ extend T::Sig
21
+
18
22
  VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
19
23
  SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/
20
24
  ALLOWED_UPDATE_STRATEGIES = T.let(
@@ -27,8 +31,16 @@ module Dependabot
27
31
  T::Array[Dependabot::RequirementsUpdateStrategy]
28
32
  )
29
33
 
30
- def initialize(requirements:, updated_source:, update_strategy:,
31
- latest_resolvable_version:)
34
+ sig do
35
+ params(
36
+ requirements: T::Array[T::Hash[Symbol, T.untyped]],
37
+ updated_source: T.nilable(T::Hash[Symbol, T.untyped]),
38
+ update_strategy: Dependabot::RequirementsUpdateStrategy,
39
+ latest_resolvable_version: T.nilable(T.any(String, Gem::Version))
40
+ )
41
+ .void
42
+ end
43
+ def initialize(requirements:, updated_source:, update_strategy:, latest_resolvable_version:)
32
44
  @requirements = requirements
33
45
  @updated_source = updated_source
34
46
  @update_strategy = update_strategy
@@ -37,10 +49,13 @@ module Dependabot
37
49
 
38
50
  return unless latest_resolvable_version
39
51
 
40
- @latest_resolvable_version =
41
- version_class.new(latest_resolvable_version)
52
+ @latest_resolvable_version = T.let(
53
+ version_class.new(latest_resolvable_version),
54
+ NpmAndYarn::Version
55
+ )
42
56
  end
43
57
 
58
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
44
59
  def updated_requirements
45
60
  return requirements if update_strategy.lockfile_only?
46
61
 
@@ -62,17 +77,26 @@ module Dependabot
62
77
 
63
78
  private
64
79
 
80
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
65
81
  attr_reader :requirements
82
+
83
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
66
84
  attr_reader :updated_source
85
+
86
+ sig { returns(Dependabot::RequirementsUpdateStrategy) }
67
87
  attr_reader :update_strategy
88
+
89
+ sig { returns(T.nilable(NpmAndYarn::Version)) }
68
90
  attr_reader :latest_resolvable_version
69
91
 
92
+ sig { void }
70
93
  def check_update_strategy
71
94
  return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
72
95
 
73
96
  raise "Unknown update strategy: #{update_strategy}"
74
97
  end
75
98
 
99
+ sig { returns(T::Boolean) }
76
100
  def updating_from_git_to_npm?
77
101
  return false unless updated_source.nil?
78
102
 
@@ -80,6 +104,7 @@ module Dependabot
80
104
  original_source&.fetch(:type) == "git"
81
105
  end
82
106
 
107
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
83
108
  def initial_req_after_source_change(req)
84
109
  return req unless updating_from_git_to_npm?
85
110
  return req unless req[:requirement].nil?
@@ -87,12 +112,13 @@ module Dependabot
87
112
  req.merge(requirement: "^#{latest_resolvable_version}")
88
113
  end
89
114
 
115
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
90
116
  def update_version_requirement(req)
91
117
  current_requirement = req[:requirement]
92
118
 
93
119
  if current_requirement.match?(/(<|-\s)/i)
94
120
  ruby_req = ruby_requirements(current_requirement).first
95
- return req if ruby_req.satisfied_by?(latest_resolvable_version)
121
+ return req if ruby_req&.satisfied_by?(latest_resolvable_version)
96
122
 
97
123
  updated_req = update_range_requirement(current_requirement)
98
124
  return req.merge(requirement: updated_req)
@@ -102,6 +128,7 @@ module Dependabot
102
128
  req.merge(requirement: update_version_string(reqs.first))
103
129
  end
104
130
 
131
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
105
132
  def update_version_requirement_if_needed(req)
106
133
  current_requirement = req[:requirement]
107
134
  version = latest_resolvable_version
@@ -113,6 +140,7 @@ module Dependabot
113
140
  update_version_requirement(req)
114
141
  end
115
142
 
143
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
116
144
  def widen_requirement(req)
117
145
  current_requirement = req[:requirement]
118
146
  version = latest_resolvable_version
@@ -135,22 +163,25 @@ module Dependabot
135
163
  req.merge(requirement: updated_requirement)
136
164
  end
137
165
 
166
+ sig { params(requirement_string: String).returns(T::Array[NpmAndYarn::Requirement]) }
138
167
  def ruby_requirements(requirement_string)
139
168
  NpmAndYarn::Requirement
140
169
  .requirements_array(requirement_string)
141
170
  end
142
171
 
172
+ sig { params(req_string: String).returns(String) }
143
173
  def update_range_requirement(req_string)
144
174
  range_requirements =
145
175
  req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) }
146
176
 
147
177
  if range_requirements.count == 1
148
- range_requirement = range_requirements.first
178
+ range_requirement = T.must(range_requirements.first)
149
179
  versions = range_requirement.scan(VERSION_REGEX)
150
- upper_bound = versions.map { |v| version_class.new(v) }.max
180
+ version_objects = versions.map { |v| version_class.new(v.to_s) }
181
+ upper_bound = T.must(version_objects.max)
151
182
  new_upper_bound = update_greatest_version(
152
- upper_bound,
153
- latest_resolvable_version
183
+ upper_bound.to_s,
184
+ T.must(latest_resolvable_version)
154
185
  )
155
186
 
156
187
  req_string.sub(
@@ -158,45 +189,51 @@ module Dependabot
158
189
  new_upper_bound.to_s
159
190
  )
160
191
  else
161
- req_string + " || ^#{latest_resolvable_version}"
192
+ req_string + " || ^#{T.must(latest_resolvable_version)}"
162
193
  end
163
194
  end
164
195
 
196
+ sig { params(req_string: String).returns(String) }
165
197
  def update_version_string(req_string)
166
198
  req_string
167
199
  .sub(VERSION_REGEX) do |old_version|
168
200
  if old_version.match?(/\d-/) ||
169
- latest_resolvable_version.to_s.match?(/\d-/)
170
- latest_resolvable_version.to_s
201
+ T.must(latest_resolvable_version).to_s.match?(/\d-/)
202
+ T.must(latest_resolvable_version).to_s
171
203
  else
172
204
  old_parts = old_version.split(".")
173
- new_parts = latest_resolvable_version.to_s.split(".")
174
- .first(old_parts.count)
205
+ new_parts = T.must(latest_resolvable_version).to_s.split(".")
206
+ .first(old_parts.count)
175
207
  new_parts.map.with_index do |part, i|
176
- old_parts[i].match?(/^x\b/) ? "x" : part
208
+ old_parts[i]&.match?(/^x\b/) ? "x" : part
177
209
  end.join(".")
178
210
  end
179
211
  end
180
212
  end
181
213
 
214
+ sig { params(old_version: String, version_to_be_permitted: NpmAndYarn::Version).returns(String) }
182
215
  def update_greatest_version(old_version, version_to_be_permitted)
183
216
  version = version_class.new(old_version)
184
217
  version = version.release if version.prerelease?
185
218
 
186
219
  index_to_update =
187
- version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
220
+ version.segments.map.with_index { |seg, i| T.cast(seg, Integer).zero? ? 0 : i }.max || 0
188
221
 
189
222
  version.segments.map.with_index do |_, index|
190
- if index < index_to_update
191
- version_to_be_permitted.segments[index]
192
- elsif index == index_to_update
193
- version_to_be_permitted.segments[index] + 1
194
- else
195
- 0
196
- end
223
+ segment_value =
224
+ if index < index_to_update
225
+ T.cast(version_to_be_permitted.segments[index], Integer)
226
+ elsif index == index_to_update
227
+ # Cast to Integer before adding 1 to ensure correct type
228
+ T.cast(version_to_be_permitted.segments[index], Integer) + 1
229
+ else
230
+ 0
231
+ end
232
+ segment_value.to_s
197
233
  end.join(".")
198
234
  end
199
235
 
236
+ sig { returns(T.class_of(NpmAndYarn::Version)) }
200
237
  def version_class
201
238
  NpmAndYarn::Version
202
239
  end
@@ -1,7 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "stringio"
5
+ require "sorbet-runtime"
6
+
5
7
  require "dependabot/dependency"
6
8
  require "dependabot/errors"
7
9
  require "dependabot/logger"
@@ -16,6 +18,15 @@ module Dependabot
16
18
  module NpmAndYarn
17
19
  class UpdateChecker < Dependabot::UpdateCheckers::Base
18
20
  class VulnerabilityAuditor
21
+ extend T::Sig
22
+
23
+ sig do
24
+ params(
25
+ dependency_files: T::Array[Dependabot::DependencyFile],
26
+ credentials: T::Array[Dependabot::Credential]
27
+ )
28
+ .void
29
+ end
19
30
  def initialize(dependency_files:, credentials:)
20
31
  @dependency_files = dependency_files
21
32
  @credentials = credentials
@@ -43,6 +54,13 @@ module Dependabot
43
54
  # * :top_level_ancestors [Array<String>] the names of all top-level dependencies with a transitive
44
55
  # dependency on the dependency
45
56
  # * :explanation [String] an explanation for why the project failed the vulnerability auditor run
57
+ sig do
58
+ params(
59
+ dependency: Dependabot::Dependency,
60
+ security_advisories: T::Array[Dependabot::SecurityAdvisory]
61
+ )
62
+ .returns(T::Hash[String, T.untyped])
63
+ end
46
64
  def audit(dependency:, security_advisories:)
47
65
  Dependabot.logger.info("VulnerabilityAuditor: starting audit")
48
66
 
@@ -76,10 +94,13 @@ module Dependabot
76
94
  }
77
95
  end
78
96
 
79
- audit_result = SharedHelpers.run_helper_subprocess(
80
- command: NativeHelpers.helper_path,
81
- function: "npm:vulnerabilityAuditor",
82
- args: [Dir.pwd, vuln_versions]
97
+ audit_result = T.cast(
98
+ SharedHelpers.run_helper_subprocess(
99
+ command: NativeHelpers.helper_path,
100
+ function: "npm:vulnerabilityAuditor",
101
+ args: [Dir.pwd, vuln_versions]
102
+ ),
103
+ T::Hash[String, T.untyped]
83
104
  )
84
105
 
85
106
  validation_result = validate_audit_result(audit_result, security_advisories)
@@ -94,24 +115,36 @@ module Dependabot
94
115
  end
95
116
  rescue SharedHelpers::HelperSubprocessFailed => e
96
117
  log_helper_subprocess_failure(dependency, e)
97
- fix_unavailable
118
+ T.must(fix_unavailable)
98
119
  end
99
120
  # rubocop:enable Metrics/MethodLength
100
121
 
101
122
  private
102
123
 
124
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
103
125
  attr_reader :dependency_files
126
+
127
+ sig { returns(T::Array[Dependabot::Credential]) }
104
128
  attr_reader :credentials
105
129
 
130
+ sig { params(validation_result: Symbol, dependency: Dependabot::Dependency).returns(String) }
106
131
  def explain_fix_unavailable(validation_result, dependency)
107
132
  case validation_result
108
133
  when :fix_unavailable, :dependency_still_vulnerable, :downgrades_dependencies
109
134
  "No patched version available for #{dependency.name}"
110
135
  when :fix_incomplete
111
136
  "The lockfile might be out of sync?"
137
+ else
138
+ raise "Unexpected validation result: #{validation_result}"
112
139
  end
113
140
  end
114
141
 
142
+ sig do
143
+ params(
144
+ audit_result: T::Hash[String, T.untyped],
145
+ security_advisories: T::Array[Dependabot::SecurityAdvisory]
146
+ ).returns(Symbol)
147
+ end
115
148
  def validate_audit_result(audit_result, security_advisories)
116
149
  return :fix_unavailable unless audit_result["fix_available"]
117
150
  return :dependency_still_vulnerable if dependency_still_vulnerable?(audit_result, security_advisories)
@@ -121,6 +154,13 @@ module Dependabot
121
154
  :viable
122
155
  end
123
156
 
157
+ sig do
158
+ params(
159
+ audit_result: T::Hash[String, T.untyped],
160
+ security_advisories: T::Array[Dependabot::SecurityAdvisory]
161
+ )
162
+ .returns(T::Boolean)
163
+ end
124
164
  def dependency_still_vulnerable?(audit_result, security_advisories)
125
165
  # vulnerable dependency is removed if the target version is nil
126
166
  return false unless audit_result["target_version"]
@@ -129,6 +169,7 @@ module Dependabot
129
169
  security_advisories.any? { |a| a.vulnerable?(version) }
130
170
  end
131
171
 
172
+ sig { params(audit_result: T::Hash[String, T.untyped]).returns(T::Boolean) }
132
173
  def downgrades_dependencies?(audit_result)
133
174
  return true if downgrades_version?(audit_result["current_version"], audit_result["target_version"])
134
175
 
@@ -137,6 +178,13 @@ module Dependabot
137
178
  end
138
179
  end
139
180
 
181
+ sig do
182
+ params(
183
+ current_version: T.nilable(T.any(String, Integer, Gem::Version)),
184
+ target_version: T.nilable(T.any(String, Integer, Gem::Version))
185
+ )
186
+ .returns(T::Boolean)
187
+ end
140
188
  def downgrades_version?(current_version, target_version)
141
189
  return false unless target_version
142
190
 
@@ -145,14 +193,19 @@ module Dependabot
145
193
  current > target
146
194
  end
147
195
 
196
+ sig { params(audit_result: T::Hash[String, T.untyped]).returns(T::Boolean) }
148
197
  def fix_incomplete?(audit_result)
149
198
  audit_result["fix_updates"].any? { |update| !update.key?("target_version") } ||
150
199
  audit_result["fix_updates"].empty?
151
200
  end
152
201
 
202
+ sig do
203
+ params(dependency: Dependabot::Dependency,
204
+ error: Dependabot::SharedHelpers::HelperSubprocessFailed).void
205
+ end
153
206
  def log_helper_subprocess_failure(dependency, error)
154
207
  # See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context
155
- context = error.error_context || {}
208
+ context = error.error_context
156
209
 
157
210
  builder = ::StringIO.new
158
211
  builder << "VulnerabilityAuditor: "
@@ -167,7 +167,7 @@ module Dependabot
167
167
  requirements: dependency.requirements,
168
168
  updated_source: updated_source,
169
169
  latest_resolvable_version: resolvable_version,
170
- update_strategy: requirements_update_strategy
170
+ update_strategy: T.must(requirements_update_strategy)
171
171
  ).updated_requirements
172
172
  end
173
173
 
@@ -326,7 +326,7 @@ module Dependabot
326
326
  requirements: original_dep.requirements,
327
327
  updated_source: original_dep == dependency ? updated_source : original_source(original_dep),
328
328
  latest_resolvable_version: version,
329
- update_strategy: requirements_update_strategy
329
+ update_strategy: T.must(requirements_update_strategy)
330
330
  ).updated_requirements,
331
331
  previous_version: previous_version,
332
332
  previous_requirements: original_dep.requirements,
@@ -516,7 +516,7 @@ module Dependabot
516
516
 
517
517
  @library =
518
518
  LibraryDetector.new(
519
- package_json_file: package_json,
519
+ package_json_file: T.must(package_json),
520
520
  credentials: credentials,
521
521
  dependency_files: dependency_files
522
522
  ).library?
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-npm_and_yarn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.309.0
4
+ version: 0.310.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-17 00:00:00.000000000 Z
10
+ date: 2025-04-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dependabot-common
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.309.0
18
+ version: 0.310.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.309.0
25
+ version: 0.310.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -356,7 +356,7 @@ licenses:
356
356
  - MIT
357
357
  metadata:
358
358
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
359
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.309.0
359
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.310.0
360
360
  rdoc_options: []
361
361
  require_paths:
362
362
  - lib