dependabot-npm_and_yarn 0.284.0 → 0.285.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c1223098b68d19c916ca4dbc2afa1aad4dd821156d85050f04502dcd41f5b03
4
- data.tar.gz: f74ab5771e2cfcdeb1ce271b0d27d9ecf5625c698996b9d7d3889b2948f4cea8
3
+ metadata.gz: bf3271285f39bf1e7f91fad3995676126cf0b580184e741e1fe10b5d295a5f4e
4
+ data.tar.gz: 91d70c4bb5a9454ffb50e38fdcb92957649a8211a1a27349b023d9a2a104ac26
5
5
  SHA512:
6
- metadata.gz: 169c3e68774b3bbe08744bf244a29a6be8ef3948279297ce176b9995703ba2906b19765170c246ad298a6fe785d601ee36a3704e45c7a52535b447b0a149b9f9
7
- data.tar.gz: 38506739cd632c0473892270620882da79a1368cc5c5e1df0de49de5483590c4cd43b602d4b50becdc613d25e3b4cda547dbadb05df6ecd2a4b2e5322a7bfe0d
6
+ metadata.gz: c3f3efeb4f22bfaad116b86ec6dcede6dfd378c2e669e9293f521ff9d285b54b7a072ce888c2eb0d44ef64605f144d9deb5f5ce9b723ee44c00366147f065474
7
+ data.tar.gz: 9426fe3a37b2b6af1c86dc79e7f596406a59329a0bc0429065387d7fe3a6cff987b69865f49c21df3bf90c8e46687dd291fc2a1a12f65fe8621487c7e8e56433
@@ -182,71 +182,93 @@ module Dependabot
182
182
 
183
183
  sig { returns(T.nilable(T.any(Integer, String))) }
184
184
  def npm_version
185
- @npm_version ||= T.let(package_manager.setup("npm"), T.nilable(T.any(Integer, String)))
185
+ @npm_version ||= T.let(package_manager_helper.setup(NpmPackageManager::NAME), T.nilable(T.any(Integer, String)))
186
186
  end
187
187
 
188
188
  sig { returns(T.nilable(T.any(Integer, String))) }
189
189
  def yarn_version
190
- @yarn_version ||= T.let(package_manager.setup("yarn"), T.nilable(T.any(Integer, String)))
190
+ @yarn_version ||= T.let(
191
+ package_manager_helper.setup(YarnPackageManager::NAME),
192
+ T.nilable(T.any(Integer, String))
193
+ )
191
194
  end
192
195
 
193
196
  sig { returns(T.nilable(T.any(Integer, String))) }
194
197
  def pnpm_version
195
- @pnpm_version ||= T.let(package_manager.setup("pnpm"), T.nilable(T.any(Integer, String)))
198
+ @pnpm_version ||= T.let(
199
+ package_manager_helper.setup(PNPMPackageManager::NAME),
200
+ T.nilable(T.any(Integer, String))
201
+ )
196
202
  end
197
203
 
198
- sig { returns(PackageManager) }
199
- def package_manager
200
- @package_manager ||= T.let(PackageManager.new(
201
- parsed_package_json,
202
- lockfiles: { npm: package_lock || shrinkwrap, yarn: yarn_lock, pnpm: pnpm_lock }
203
- ), T.nilable(PackageManager))
204
+ sig { returns(PackageManagerHelper) }
205
+ def package_manager_helper
206
+ @package_manager_helper ||= T.let(
207
+ PackageManagerHelper.new(
208
+ parsed_package_json,
209
+ lockfiles: lockfiles
210
+ ), T.nilable(PackageManagerHelper)
211
+ )
212
+ end
213
+
214
+ sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
215
+ def lockfiles
216
+ {
217
+ npm: package_lock || shrinkwrap,
218
+ yarn: yarn_lock,
219
+ pnpm: pnpm_lock
220
+ }
204
221
  end
205
222
 
206
223
  sig { returns(DependencyFile) }
207
224
  def package_json
208
- @package_json ||= T.let(fetch_file_from_host("package.json"), T.nilable(DependencyFile))
225
+ @package_json ||= T.let(fetch_file_from_host(MANIFEST_FILENAME), T.nilable(DependencyFile))
209
226
  end
210
227
 
211
228
  sig { returns(T.nilable(DependencyFile)) }
212
229
  def package_lock
213
230
  return @package_lock if defined?(@package_lock)
214
231
 
215
- @package_lock ||= T.let(fetch_file_if_present("package-lock.json"), T.nilable(DependencyFile))
232
+ @package_lock ||= T.let(fetch_file_if_present(NpmPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
216
233
  end
217
234
 
218
235
  sig { returns(T.nilable(DependencyFile)) }
219
236
  def yarn_lock
220
237
  return @yarn_lock if defined?(@yarn_lock)
221
238
 
222
- @yarn_lock ||= T.let(fetch_file_if_present("yarn.lock"), T.nilable(DependencyFile))
239
+ @yarn_lock ||= T.let(fetch_file_if_present(YarnPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
223
240
  end
224
241
 
225
242
  sig { returns(T.nilable(DependencyFile)) }
226
243
  def pnpm_lock
227
244
  return @pnpm_lock if defined?(@pnpm_lock)
228
245
 
229
- @pnpm_lock ||= T.let(fetch_file_if_present("pnpm-lock.yaml"), T.nilable(DependencyFile))
246
+ @pnpm_lock ||= T.let(fetch_file_if_present(PNPMPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
230
247
  end
231
248
 
232
249
  sig { returns(T.nilable(DependencyFile)) }
233
250
  def shrinkwrap
234
251
  return @shrinkwrap if defined?(@shrinkwrap)
235
252
 
236
- @shrinkwrap ||= T.let(fetch_file_if_present("npm-shrinkwrap.json"), T.nilable(DependencyFile))
253
+ @shrinkwrap ||= T.let(
254
+ fetch_file_if_present(
255
+ NpmPackageManager::SHRINKWRAP_LOCKFILE_NAME
256
+ ),
257
+ T.nilable(DependencyFile)
258
+ )
237
259
  end
238
260
 
239
261
  sig { returns(T.nilable(DependencyFile)) }
240
262
  def npmrc
241
263
  return @npmrc if defined?(@npmrc)
242
264
 
243
- @npmrc ||= T.let(fetch_support_file(".npmrc"), T.nilable(DependencyFile))
265
+ @npmrc ||= T.let(fetch_support_file(NpmPackageManager::RC_FILENAME), T.nilable(DependencyFile))
244
266
 
245
267
  return @npmrc if @npmrc || directory == "/"
246
268
 
247
269
  # Loop through parent directories looking for an npmrc
248
270
  (1..directory.split("/").count).each do |i|
249
- @npmrc = fetch_file_from_host(("../" * i) + ".npmrc")
271
+ @npmrc = fetch_file_from_host(("../" * i) + NpmPackageManager::RC_FILENAME)
250
272
  .tap { |f| f.support_file = true }
251
273
  break if @npmrc
252
274
  rescue Dependabot::DependencyFileNotFound
@@ -261,13 +283,13 @@ module Dependabot
261
283
  def yarnrc
262
284
  return @yarnrc if defined?(@yarnrc)
263
285
 
264
- @yarnrc ||= T.let(fetch_support_file(".yarnrc"), T.nilable(DependencyFile))
286
+ @yarnrc ||= T.let(fetch_support_file(YarnPackageManager::RC_FILENAME), T.nilable(DependencyFile))
265
287
 
266
288
  return @yarnrc if @yarnrc || directory == "/"
267
289
 
268
290
  # Loop through parent directories looking for an yarnrc
269
291
  (1..directory.split("/").count).each do |i|
270
- @yarnrc = fetch_file_from_host(("../" * i) + ".yarnrc")
292
+ @yarnrc = fetch_file_from_host(("../" * i) + YarnPackageManager::RC_FILENAME)
271
293
  .tap { |f| f.support_file = true }
272
294
  break if @yarnrc
273
295
  rescue Dependabot::DependencyFileNotFound
@@ -280,21 +302,24 @@ module Dependabot
280
302
 
281
303
  sig { returns(T.nilable(DependencyFile)) }
282
304
  def yarnrc_yml
283
- @yarnrc_yml ||= T.let(fetch_support_file(".yarnrc.yml"), T.nilable(DependencyFile))
305
+ @yarnrc_yml ||= T.let(fetch_support_file(YarnPackageManager::RC_YML_FILENAME), T.nilable(DependencyFile))
284
306
  end
285
307
 
286
308
  sig { returns(T.nilable(DependencyFile)) }
287
309
  def pnpm_workspace_yaml
288
310
  return @pnpm_workspace_yaml if defined?(@pnpm_workspace_yaml)
289
311
 
290
- @pnpm_workspace_yaml = T.let(fetch_support_file("pnpm-workspace.yaml"), T.nilable(DependencyFile))
312
+ @pnpm_workspace_yaml = T.let(
313
+ fetch_support_file(PNPMPackageManager::PNPM_WS_YML_FILENAME),
314
+ T.nilable(DependencyFile)
315
+ )
291
316
  end
292
317
 
293
318
  sig { returns(T.nilable(DependencyFile)) }
294
319
  def lerna_json
295
320
  return @lerna_json if defined?(@lerna_json)
296
321
 
297
- @lerna_json = T.let(fetch_support_file("lerna.json"), T.nilable(DependencyFile))
322
+ @lerna_json = T.let(fetch_support_file(LERNA_JSON_FILENAME), T.nilable(DependencyFile))
298
323
  end
299
324
 
300
325
  sig { returns(T::Array[DependencyFile]) }
@@ -329,7 +354,7 @@ module Dependabot
329
354
  filename = path
330
355
  # NPM/Yarn support loading path dependencies from tarballs:
331
356
  # https://docs.npmjs.com/cli/pack.html
332
- filename = File.join(filename, "package.json") unless filename.end_with?(".tgz", ".tar", ".tar.gz")
357
+ filename = File.join(filename, MANIFEST_FILENAME) unless filename.end_with?(".tgz", ".tar", ".tar.gz")
333
358
  cleaned_name = Pathname.new(filename).cleanpath.to_path
334
359
  next if fetched_files.map(&:name).include?(cleaned_name)
335
360
 
@@ -380,7 +405,7 @@ module Dependabot
380
405
  # rubocop:disable Metrics/AbcSize
381
406
  sig { params(file: DependencyFile).returns(T::Array[[String, String]]) }
382
407
  def path_dependency_details_from_manifest(file)
383
- return [] unless file.name.end_with?("package.json")
408
+ return [] unless file.name.end_with?(MANIFEST_FILENAME)
384
409
 
385
410
  current_dir = file.name.rpartition("/").first
386
411
  current_dir = nil if current_dir == ""
@@ -471,9 +496,9 @@ module Dependabot
471
496
  return [] unless package_json
472
497
 
473
498
  [package_json] + [
474
- fetch_file_if_present(File.join(path, "package-lock.json")),
475
- fetch_file_if_present(File.join(path, "yarn.lock")),
476
- fetch_file_if_present(File.join(path, "npm-shrinkwrap.json"))
499
+ fetch_file_if_present(File.join(path, NpmPackageManager::LOCKFILE_NAME)),
500
+ fetch_file_if_present(File.join(path, YarnPackageManager::LOCKFILE_NAME)),
501
+ fetch_file_if_present(File.join(path, NpmPackageManager::SHRINKWRAP_LOCKFILE_NAME))
477
502
  ]
478
503
  end
479
504
 
@@ -542,7 +567,7 @@ module Dependabot
542
567
 
543
568
  sig { params(workspace: String).returns(T.nilable(DependencyFile)) }
544
569
  def fetch_package_json_if_present(workspace)
545
- file = File.join(workspace, "package.json")
570
+ file = File.join(workspace, MANIFEST_FILENAME)
546
571
 
547
572
  begin
548
573
  fetch_file_from_host(file)
@@ -635,4 +660,4 @@ module Dependabot
635
660
  end
636
661
 
637
662
  Dependabot::FileFetchers
638
- .register("npm_and_yarn", Dependabot::NpmAndYarn::FileFetcher)
663
+ .register(Dependabot::NpmAndYarn::ECOSYSTEM, Dependabot::NpmAndYarn::FileFetcher)
@@ -19,7 +19,7 @@ require "sorbet-runtime"
19
19
 
20
20
  module Dependabot
21
21
  module NpmAndYarn
22
- class FileParser < Dependabot::FileParsers::Base
22
+ class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
23
23
  extend T::Sig
24
24
 
25
25
  require "dependabot/file_parsers/base/dependency_set"
@@ -78,8 +78,82 @@ module Dependabot
78
78
  end
79
79
  end
80
80
 
81
+ sig { returns(Ecosystem) }
82
+ def ecosystem
83
+ @ecosystem ||= T.let(
84
+ Ecosystem.new(
85
+ name: ECOSYSTEM,
86
+ package_manager: package_manager_helper.package_manager
87
+ ),
88
+ T.nilable(Ecosystem)
89
+ )
90
+ end
91
+
81
92
  private
82
93
 
94
+ sig { returns(PackageManagerHelper) }
95
+ def package_manager_helper
96
+ @package_manager_helper ||= T.let(
97
+ PackageManagerHelper.new(
98
+ parsed_package_json,
99
+ lockfiles: lockfiles
100
+ ), T.nilable(PackageManagerHelper)
101
+ )
102
+ end
103
+
104
+ sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
105
+ def lockfiles
106
+ {
107
+ npm: package_lock || shrinkwrap,
108
+ yarn: yarn_lock,
109
+ pnpm: pnpm_lock
110
+ }
111
+ end
112
+
113
+ sig { returns(T.untyped) }
114
+ def parsed_package_json
115
+ JSON.parse(T.must(package_json.content))
116
+ rescue JSON::ParserError
117
+ raise Dependabot::DependencyFileNotParseable, package_json.path
118
+ end
119
+
120
+ sig { returns(Dependabot::DependencyFile) }
121
+ def package_json
122
+ # Declare the instance variable with T.let and the correct type
123
+ @package_json ||= T.let(
124
+ T.must(dependency_files.find { |f| f.name == MANIFEST_FILENAME }),
125
+ T.nilable(Dependabot::DependencyFile)
126
+ )
127
+ end
128
+
129
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
130
+ def shrinkwrap
131
+ @shrinkwrap ||= T.let(dependency_files.find do |f|
132
+ f.name == NpmPackageManager::SHRINKWRAP_LOCKFILE_NAME
133
+ end, T.nilable(Dependabot::DependencyFile))
134
+ end
135
+
136
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
137
+ def package_lock
138
+ @package_lock ||= T.let(dependency_files.find do |f|
139
+ f.name == NpmPackageManager::LOCKFILE_NAME
140
+ end, T.nilable(Dependabot::DependencyFile))
141
+ end
142
+
143
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
144
+ def yarn_lock
145
+ @yarn_lock ||= T.let(dependency_files.find do |f|
146
+ f.name == YarnPackageManager::LOCKFILE_NAME
147
+ end, T.nilable(Dependabot::DependencyFile))
148
+ end
149
+
150
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
151
+ def pnpm_lock
152
+ @pnpm_lock ||= T.let(dependency_files.find do |f|
153
+ f.name == PNPMPackageManager::LOCKFILE_NAME
154
+ end, T.nilable(Dependabot::DependencyFile))
155
+ end
156
+
83
157
  sig { returns(Dependabot::FileParsers::Base::DependencySet) }
84
158
  def manifest_dependencies
85
159
  dependency_set = DependencySet.new
@@ -154,7 +228,7 @@ module Dependabot
154
228
  Dependency.new(
155
229
  name: name,
156
230
  version: converted_version,
157
- package_manager: "npm_and_yarn",
231
+ package_manager: ECOSYSTEM,
158
232
  requirements: [{
159
233
  requirement: requirement_for(requirement),
160
234
  file: file.name,
@@ -166,7 +240,10 @@ module Dependabot
166
240
 
167
241
  sig { override.void }
168
242
  def check_required_files
169
- raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json")
243
+ return if get_original_file(MANIFEST_FILENAME)
244
+
245
+ raise DependencyFileNotFound.new(nil,
246
+ "#{MANIFEST_FILENAME} not found.")
170
247
  end
171
248
 
172
249
  sig { params(requirement: String).returns(T::Boolean) }
@@ -186,7 +263,7 @@ module Dependabot
186
263
 
187
264
  sig { params(requirement: String).returns(T::Boolean) }
188
265
  def alias_package?(requirement)
189
- requirement.start_with?("npm:")
266
+ requirement.start_with?("#{NpmPackageManager::NAME}:")
190
267
  end
191
268
 
192
269
  sig { params(requirement: String).returns(T::Boolean) }
@@ -208,7 +285,7 @@ module Dependabot
208
285
 
209
286
  sig { params(name: String).returns(T::Boolean) }
210
287
  def aliased_package_name?(name)
211
- name.include?("@npm:")
288
+ name.include?("@#{NpmPackageManager::NAME}:")
212
289
  end
213
290
 
214
291
  sig { returns(T::Array[String]) }
@@ -370,8 +447,8 @@ module Dependabot
370
447
  def sub_package_files
371
448
  return T.must(@sub_package_files) if defined?(@sub_package_files)
372
449
 
373
- files = dependency_files.select { |f| f.name.end_with?("package.json") }
374
- .reject { |f| f.name == "package.json" }
450
+ files = dependency_files.select { |f| f.name.end_with?(MANIFEST_FILENAME) }
451
+ .reject { |f| f.name == MANIFEST_FILENAME }
375
452
  .reject { |f| f.name.include?("node_modules/") }
376
453
  @sub_package_files ||= T.let(files, T.nilable(T::Array[Dependabot::DependencyFile]))
377
454
  end
@@ -380,7 +457,7 @@ module Dependabot
380
457
  def package_files
381
458
  @package_files ||= T.let(
382
459
  [
383
- dependency_files.find { |f| f.name == "package.json" },
460
+ dependency_files.find { |f| f.name == MANIFEST_FILENAME },
384
461
  *sub_package_files
385
462
  ].compact, T.nilable(T::Array[DependencyFile])
386
463
  )
@@ -37,7 +37,7 @@ module Dependabot
37
37
  # Determines the npm version depends to the feature flag
38
38
  # If the feature flag is enabled, we are going to use the minimum version npm 8
39
39
  # Otherwise, we are going to use old versionining npm 6
40
- sig { params(lockfile: DependencyFile).returns(Integer) }
40
+ sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
41
41
  def self.npm_version_numeric(lockfile)
42
42
  fallback_version_npm8 = Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6)
43
43
 
@@ -46,10 +46,15 @@ module Dependabot
46
46
  npm_version_numeric_npm6_or_higher(lockfile)
47
47
  end
48
48
 
49
- sig { params(lockfile: DependencyFile).returns(Integer) }
49
+ sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
50
50
  def self.npm_version_numeric_npm6_or_higher(lockfile)
51
- lockfile_content = T.must(lockfile.content)
52
- return NPM_V8 if JSON.parse(lockfile_content)["lockfileVersion"].to_i >= 2
51
+ lockfile_content = lockfile&.content
52
+
53
+ if lockfile_content.nil? ||
54
+ lockfile_content.strip.empty? ||
55
+ JSON.parse(lockfile_content)["lockfileVersion"].to_i >= 2
56
+ return NPM_V8
57
+ end
53
58
 
54
59
  NPM_V6
55
60
  rescue JSON::ParserError
@@ -60,9 +65,9 @@ module Dependabot
60
65
  # - NPM 7 uses lockfileVersion 2
61
66
  # - NPM 8 uses lockfileVersion 2
62
67
  # - NPM 9 uses lockfileVersion 3
63
- sig { params(lockfile: DependencyFile).returns(Integer) }
68
+ sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
64
69
  def self.npm_version_numeric_npm8_or_higher(lockfile)
65
- lockfile_content = lockfile.content
70
+ lockfile_content = lockfile&.content
66
71
 
67
72
  # Return default NPM version if there's no lockfile or it's empty
68
73
  return NPM_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?
@@ -85,8 +90,12 @@ module Dependabot
85
90
  NPM_DEFAULT_VERSION # Fallback to default npm version if parsing fails
86
91
  end
87
92
 
88
- sig { params(yarn_lock: DependencyFile).returns(Integer) }
93
+ sig { params(yarn_lock: T.nilable(DependencyFile)).returns(Integer) }
89
94
  def self.yarn_version_numeric(yarn_lock)
95
+ lockfile_content = yarn_lock&.content
96
+
97
+ return YARN_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?
98
+
90
99
  if yarn_berry?(yarn_lock)
91
100
  YARN_DEFAULT_VERSION
92
101
  else
@@ -97,8 +106,12 @@ module Dependabot
97
106
  # Mapping from lockfile versions to PNPM versions is at
98
107
  # https://github.com/pnpm/spec/tree/274ff02de23376ad59773a9f25ecfedd03a41f64/lockfile, but simplify it for now.
99
108
 
100
- sig { params(pnpm_lock: DependencyFile).returns(Integer) }
109
+ sig { params(pnpm_lock: T.nilable(DependencyFile)).returns(Integer) }
101
110
  def self.pnpm_version_numeric(pnpm_lock)
111
+ lockfile_content = pnpm_lock&.content
112
+
113
+ return PNPM_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?
114
+
102
115
  pnpm_lockfile_version = pnpm_lockfile_version(pnpm_lock).to_f
103
116
  return PNPM_V9 if pnpm_lockfile_version >= 9.0
104
117
  return PNPM_V8 if pnpm_lockfile_version >= 6.0
@@ -117,7 +130,7 @@ module Dependabot
117
130
 
118
131
  sig { params(package_lock: T.nilable(DependencyFile)).returns(T::Boolean) }
119
132
  def self.npm8?(package_lock)
120
- return true unless package_lock
133
+ return true unless package_lock&.content
121
134
 
122
135
  npm_version_numeric(package_lock) == NPM_V8
123
136
  end
@@ -2,18 +2,225 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/shared_helpers"
5
+ require "dependabot/ecosystem"
5
6
  require "dependabot/npm_and_yarn/version_selector"
6
7
 
7
8
  module Dependabot
8
9
  module NpmAndYarn
9
- class PackageManager
10
+ ECOSYSTEM = "npm_and_yarn"
11
+ MANIFEST_FILENAME = "package.json"
12
+ LERNA_JSON_FILENAME = "lerna.json"
13
+
14
+ MANIFEST_PACKAGE_MANAGER_KEY = "packageManager"
15
+ MANIFEST_ENGINES_KEY = "engines"
16
+
17
+ class NpmPackageManager < Ecosystem::VersionManager
18
+ extend T::Sig
19
+ NAME = "npm"
20
+ RC_FILENAME = ".npmrc"
21
+ LOCKFILE_NAME = "package-lock.json"
22
+ SHRINKWRAP_LOCKFILE_NAME = "npm-shrinkwrap.json"
23
+
24
+ NPM_V6 = "6"
25
+ NPM_V7 = "7"
26
+ NPM_V8 = "8"
27
+ NPM_V9 = "9"
28
+
29
+ # Keep versions in ascending order
30
+ SUPPORTED_VERSIONS = T.let([
31
+ Version.new(NPM_V6),
32
+ Version.new(NPM_V7),
33
+ Version.new(NPM_V8),
34
+ Version.new(NPM_V9)
35
+ ].freeze, T::Array[Dependabot::Version])
36
+
37
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
38
+
39
+ sig { params(raw_version: String).void }
40
+ def initialize(raw_version)
41
+ super(
42
+ NAME,
43
+ Version.new(raw_version),
44
+ DEPRECATED_VERSIONS,
45
+ SUPPORTED_VERSIONS
46
+ )
47
+ end
48
+
49
+ sig { override.returns(T::Boolean) }
50
+ def deprecated?
51
+ false
52
+ end
53
+
54
+ sig { override.returns(T::Boolean) }
55
+ def unsupported?
56
+ false
57
+ end
58
+ end
59
+
60
+ class YarnPackageManager < Ecosystem::VersionManager
61
+ extend T::Sig
62
+ NAME = "yarn"
63
+ RC_FILENAME = ".yarnrc"
64
+ RC_YML_FILENAME = ".yarnrc.yml"
65
+ LOCKFILE_NAME = "yarn.lock"
66
+
67
+ YARN_V1 = "1"
68
+ YARN_V2 = "2"
69
+ YARN_V3 = "3"
70
+
71
+ SUPPORTED_VERSIONS = T.let([
72
+ Version.new(YARN_V1),
73
+ Version.new(YARN_V2),
74
+ Version.new(YARN_V3)
75
+ ].freeze, T::Array[Dependabot::Version])
76
+
77
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
78
+
79
+ sig { params(raw_version: String).void }
80
+ def initialize(raw_version)
81
+ super(
82
+ NAME,
83
+ Version.new(raw_version),
84
+ DEPRECATED_VERSIONS,
85
+ SUPPORTED_VERSIONS
86
+ )
87
+ end
88
+
89
+ sig { override.returns(T::Boolean) }
90
+ def deprecated?
91
+ false
92
+ end
93
+
94
+ sig { override.returns(T::Boolean) }
95
+ def unsupported?
96
+ false
97
+ end
98
+ end
99
+
100
+ class PNPMPackageManager < Ecosystem::VersionManager
101
+ extend T::Sig
102
+ NAME = "pnpm"
103
+ LOCKFILE_NAME = "pnpm-lock.yaml"
104
+ PNPM_WS_YML_FILENAME = "pnpm-workspace.yaml"
105
+
106
+ PNPM_V7 = "7"
107
+ PNPM_V8 = "8"
108
+ PNPM_V9 = "9"
109
+
110
+ SUPPORTED_VERSIONS = T.let([
111
+ Version.new(PNPM_V7),
112
+ Version.new(PNPM_V8),
113
+ Version.new(PNPM_V9)
114
+ ].freeze, T::Array[Dependabot::Version])
115
+
116
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
117
+
118
+ sig { params(raw_version: String).void }
119
+ def initialize(raw_version)
120
+ super(
121
+ NAME,
122
+ Version.new(raw_version),
123
+ DEPRECATED_VERSIONS,
124
+ SUPPORTED_VERSIONS
125
+ )
126
+ end
127
+
128
+ sig { override.returns(T::Boolean) }
129
+ def deprecated?
130
+ false
131
+ end
132
+
133
+ sig { override.returns(T::Boolean) }
134
+ def unsupported?
135
+ false
136
+ end
137
+ end
138
+
139
+ DEFAULT_PACKAGE_MANAGER = NpmPackageManager::NAME
140
+
141
+ PACKAGE_MANAGER_CLASSES = {
142
+ NpmPackageManager::NAME => NpmPackageManager,
143
+ YarnPackageManager::NAME => YarnPackageManager,
144
+ PNPMPackageManager::NAME => PNPMPackageManager
145
+ }.freeze
146
+
147
+ class PackageManagerDetector
148
+ extend T::Sig
149
+ extend T::Helpers
150
+
151
+ sig do
152
+ params(
153
+ lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)],
154
+ package_json: T::Hash[String, T.untyped]
155
+ ).void
156
+ end
157
+ def initialize(lockfiles, package_json)
158
+ @lockfiles = lockfiles
159
+ @package_json = package_json
160
+ @manifest_package_manager = package_json["packageManager"]
161
+ @engines = package_json.fetch(MANIFEST_ENGINES_KEY, nil)
162
+ end
163
+
164
+ # Returns npm, yarn, or pnpm based on the lockfiles, package.json, and engines
165
+ # Defaults to npm if no package manager is detected
166
+ sig { returns(String) }
167
+ def detect_package_manager
168
+ name_from_lockfiles || name_from_package_manager_attr || name_from_engines || DEFAULT_PACKAGE_MANAGER
169
+ end
170
+
171
+ private
172
+
173
+ sig { returns(T.nilable(String)) }
174
+ def name_from_lockfiles
175
+ PACKAGE_MANAGER_CLASSES.each_key do |manager_name| # iterates keys in order as defined in the hash
176
+ return manager_name.to_s if @lockfiles[manager_name.to_sym]
177
+ end
178
+ nil
179
+ end
180
+
181
+ sig { returns(T.nilable(String)) }
182
+ def name_from_package_manager_attr
183
+ return unless @manifest_package_manager
184
+
185
+ PACKAGE_MANAGER_CLASSES.each_key do |manager_name| # iterates keys in order as defined in the hash
186
+ return manager_name.to_s if @manifest_package_manager.start_with?("#{manager_name}@")
187
+ end
188
+ end
189
+
190
+ sig { returns(T.nilable(String)) }
191
+ def name_from_engines
192
+ return unless @engines.is_a?(Hash)
193
+
194
+ PACKAGE_MANAGER_CLASSES.each_key do |manager_name|
195
+ return manager_name if @engines[manager_name]
196
+ end
197
+ nil
198
+ end
199
+ end
200
+
201
+ class PackageManagerHelper
10
202
  extend T::Sig
11
203
  extend T::Helpers
204
+
205
+ sig do
206
+ params(
207
+ package_json: T::Hash[String, T.untyped],
208
+ lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]
209
+ ).void
210
+ end
12
211
  def initialize(package_json, lockfiles:)
13
212
  @package_json = package_json
14
213
  @lockfiles = lockfiles
15
- @package_manager = package_json.fetch("packageManager", nil)
16
- @engines = package_json.fetch("engines", nil)
214
+ @manifest_package_manager = package_json[MANIFEST_PACKAGE_MANAGER_KEY]
215
+ @engines = package_json.fetch(MANIFEST_ENGINES_KEY, nil)
216
+ @package_manager_detector = PackageManagerDetector.new(@lockfiles, @package_json)
217
+ end
218
+
219
+ sig { returns(Ecosystem::VersionManager) }
220
+ def package_manager
221
+ package_manager_by_name(
222
+ @package_manager_detector.detect_package_manager
223
+ )
17
224
  end
18
225
 
19
226
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -23,24 +230,29 @@ module Dependabot
23
230
  # i.e. if { engines : "pnpm" : "6" } and { packageManager: "pnpm@6.0.2" },
24
231
  # we go for the specificity mentioned in packageManager (6.0.2)
25
232
 
26
- unless @package_manager&.start_with?("#{name}@") || (@package_manager&.==name.to_s) || @package_manager.nil?
233
+ unless @manifest_package_manager&.start_with?("#{name}@") ||
234
+ (@manifest_package_manager&.==name.to_s) ||
235
+ @manifest_package_manager.nil?
27
236
  return
28
237
  end
29
238
 
30
- if @engines && @package_manager.nil?
239
+ if @engines && @manifest_package_manager.nil?
31
240
  # if "packageManager" doesn't exists in manifest file,
32
241
  # we check if we can extract "engines" information
33
242
  version = check_engine_version(name)
34
243
 
35
- elsif @package_manager&.==name.to_s
244
+ elsif @manifest_package_manager&.==name.to_s
36
245
  # if "packageManager" is found but no version is specified (i.e. pnpm@1.2.3),
37
246
  # we check if we can get "engines" info to override default version
38
247
  version = check_engine_version(name) if @engines
39
248
 
40
- elsif @package_manager&.start_with?("#{name}@")
249
+ elsif @manifest_package_manager&.start_with?("#{name}@")
41
250
  # if "packageManager" info has version specification i.e. yarn@3.3.1
42
251
  # we go with the version in "packageManager"
43
- Dependabot.logger.info("Found \"packageManager\" : \"#{@package_manager}\". Skipped checking \"engines\".")
252
+ Dependabot.logger.info(
253
+ "Found \"#{MANIFEST_PACKAGE_MANAGER_KEY}\" : \"#{@manifest_package_manager}\". " \
254
+ "Skipped checking \"#{MANIFEST_ENGINES_KEY}\"."
255
+ )
44
256
  end
45
257
 
46
258
  version ||= requested_version(name)
@@ -55,7 +267,7 @@ module Dependabot
55
267
  if version
56
268
  raise_if_unsupported!(name, version.to_s)
57
269
 
58
- install(name, version) if name == "pnpm"
270
+ install(name, version) if name == PNPMPackageManager::NAME
59
271
  end
60
272
  end
61
273
 
@@ -66,11 +278,22 @@ module Dependabot
66
278
 
67
279
  private
68
280
 
281
+ sig { params(name: String).returns(Ecosystem::VersionManager) }
282
+ def package_manager_by_name(name)
283
+ package_manager_class = PACKAGE_MANAGER_CLASSES[name]
284
+
285
+ package_manager_class ||= PACKAGE_MANAGER_CLASSES[DEFAULT_PACKAGE_MANAGER]
286
+
287
+ version = Helpers.send(:"#{name}_version_numeric", @lockfiles[name.to_sym])
288
+
289
+ package_manager_class.new(version.to_s)
290
+ end
291
+
69
292
  def raise_if_unsupported!(name, version)
70
- return unless name == "pnpm"
293
+ return unless name == PNPMPackageManager::NAME
71
294
  return unless Version.new(version) < Version.new("7")
72
295
 
73
- raise ToolVersionNotSupported.new("PNPM", version, "7.*, 8.*")
296
+ raise ToolVersionNotSupported.new(PNPMPackageManager::NAME.upcase, version, "7.*, 8.*")
74
297
  end
75
298
 
76
299
  def install(name, version)
@@ -83,9 +306,9 @@ module Dependabot
83
306
  end
84
307
 
85
308
  def requested_version(name)
86
- return unless @package_manager
309
+ return unless @manifest_package_manager
87
310
 
88
- match = @package_manager.match(/^#{name}@(?<version>\d+.\d+.\d+)/)
311
+ match = @manifest_package_manager.match(/^#{name}@(?<version>\d+.\d+.\d+)/)
89
312
  return unless match
90
313
 
91
314
  Dependabot.logger.info("Requested version #{match['version']}")
@@ -111,7 +334,7 @@ module Dependabot
111
334
  return if engine_versions.empty?
112
335
 
113
336
  version = engine_versions[name]
114
- Dependabot.logger.info("Returned (engines) info \"#{name}\" : \"#{version}\"")
337
+ Dependabot.logger.info("Returned (#{MANIFEST_ENGINES_KEY}) info \"#{name}\" : \"#{version}\"")
115
338
  version
116
339
  end
117
340
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-npm_and_yarn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.284.0
4
+ version: 0.285.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-05 00:00:00.000000000 Z
11
+ date: 2024-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.284.0
19
+ version: 0.285.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.284.0
26
+ version: 0.285.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -346,7 +346,7 @@ licenses:
346
346
  - MIT
347
347
  metadata:
348
348
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
349
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.284.0
349
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.285.0
350
350
  post_install_message:
351
351
  rdoc_options: []
352
352
  require_paths: