dependabot-npm_and_yarn 0.287.0 → 0.289.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a81729237750f9d53bf9197345ac3f563f267d9683ac202ebca543413bd912ef
4
- data.tar.gz: 5caefb54429a28a52aeff8b50ba8f80554f67baa70eaf6e5bef7040d52d7ebaa
3
+ metadata.gz: 3b61a2e379cb066af66a91f9cbfd25d89755129946f7093b2e1d5be7f3642133
4
+ data.tar.gz: 10995493f890b53c62af1c14f7d29a165ee32096b8b84a529f0822984d6f1480
5
5
  SHA512:
6
- metadata.gz: 2d4fca7ae33a0540e6940de6946420c08e06be84daf21b37c9efe7234af43050de4efe74191254ace8b12e7e977c74d66557067e9a9bdc07eaf5964bc1996060
7
- data.tar.gz: ab4bd3efd5fe75244c87ec30fb31e1585ce26ac586d42a4b9b8ea84fe86a4015d54dbdc515afe228fb319520fdedc1b1aaa1ab89f71f38bd5cf76683c4c780a9
6
+ metadata.gz: 3fb1619f2f8ba90e8bbe7945c7b1e179abd09004b940c1d2514d9f4290a7679955ad0fcc0db26ebcb4b810c5903e4d66ddb3f3d0b3ca0a265c9636f34f272519
7
+ data.tar.gz: ae15d52f683156e2df0ddf213c254e0b9cd6e9324da8eda5367dae8d1c59629497739be437e3c76c6597ce9ccbbbd06509196ba9efad51240e3e98a9c99d91f4
@@ -107,6 +107,7 @@ module Dependabot
107
107
  fetched_yarn_files << yarn_lock if yarn_lock
108
108
  fetched_yarn_files << yarnrc if yarnrc
109
109
  fetched_yarn_files << yarnrc_yml if yarnrc_yml
110
+ create_yarn_cache
110
111
  fetched_yarn_files
111
112
  end
112
113
 
@@ -244,6 +245,20 @@ module Dependabot
244
245
  return @pnpm_lock if defined?(@pnpm_lock)
245
246
 
246
247
  @pnpm_lock ||= T.let(fetch_file_if_present(PNPMPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
248
+
249
+ return @pnpm_lock if @pnpm_lock || directory == "/"
250
+
251
+ # Loop through parent directories looking for a pnpm-lock
252
+ (1..directory.split("/").count).each do |i|
253
+ @pnpm_lock = fetch_file_from_host(("../" * i) + PNPMPackageManager::LOCKFILE_NAME)
254
+ .tap { |f| f.support_file = true }
255
+ break if @pnpm_lock
256
+ rescue Dependabot::DependencyFileNotFound
257
+ # Ignore errors (pnpm_lock.yaml may not be present)
258
+ nil
259
+ end
260
+
261
+ @pnpm_lock
247
262
  end
248
263
 
249
264
  sig { returns(T.nilable(DependencyFile)) }
@@ -655,6 +670,19 @@ module Dependabot
655
670
  rescue JSON::ParserError
656
671
  raise Dependabot::DependencyFileNotParseable, T.must(lerna_json).path
657
672
  end
673
+
674
+ sig { void }
675
+ def create_yarn_cache
676
+ if repo_contents_path.nil?
677
+ Dependabot.logger.info("Repository contents path is nil")
678
+ elsif Dir.exist?(T.must(repo_contents_path))
679
+ Dir.chdir(T.must(repo_contents_path)) do
680
+ FileUtils.mkdir_p(".yarn/cache")
681
+ end
682
+ else
683
+ Dependabot.logger.info("Repository contents path does not exist")
684
+ end
685
+ end
658
686
  end
659
687
  end
660
688
  end
@@ -11,6 +11,7 @@ require "dependabot/npm_and_yarn/helpers"
11
11
  require "dependabot/npm_and_yarn/native_helpers"
12
12
  require "dependabot/npm_and_yarn/version"
13
13
  require "dependabot/npm_and_yarn/requirement"
14
+ require "dependabot/npm_and_yarn/package_manager"
14
15
  require "dependabot/npm_and_yarn/registry_parser"
15
16
  require "dependabot/git_metadata_fetcher"
16
17
  require "dependabot/git_commit_checker"
@@ -83,7 +84,8 @@ module Dependabot
83
84
  @ecosystem ||= T.let(
84
85
  Ecosystem.new(
85
86
  name: ECOSYSTEM,
86
- package_manager: package_manager_helper.package_manager
87
+ package_manager: package_manager_helper.package_manager,
88
+ language: package_manager_helper.language
87
89
  ),
88
90
  T.nilable(Ecosystem)
89
91
  )
@@ -477,4 +479,4 @@ module Dependabot
477
479
  end
478
480
 
479
481
  Dependabot::FileParsers
480
- .register("npm_and_yarn", Dependabot::NpmAndYarn::FileParser)
482
+ .register(Dependabot::NpmAndYarn::ECOSYSTEM, Dependabot::NpmAndYarn::FileParser)
@@ -1,9 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/dependency"
5
5
  require "dependabot/file_parsers"
6
6
  require "dependabot/file_parsers/base"
7
+ require "dependabot/shared_helpers"
7
8
  require "sorbet-runtime"
8
9
 
9
10
  module Dependabot
@@ -15,6 +16,7 @@ module Dependabot
15
16
  /^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/
16
17
 
17
18
  # NPM Version Constants
19
+ NPM_V10 = 10
18
20
  NPM_V8 = 8
19
21
  NPM_V6 = 6
20
22
  NPM_DEFAULT_VERSION = NPM_V8
@@ -39,6 +41,10 @@ module Dependabot
39
41
  # Otherwise, we are going to use old versionining npm 6
40
42
  sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
41
43
  def self.npm_version_numeric(lockfile)
44
+ if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
45
+ return npm_version_numeric_latest(lockfile)
46
+ end
47
+
42
48
  fallback_version_npm8 = Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6)
43
49
 
44
50
  return npm_version_numeric_npm8_or_higher(lockfile) if fallback_version_npm8
@@ -90,6 +96,36 @@ module Dependabot
90
96
  NPM_DEFAULT_VERSION # Fallback to default npm version if parsing fails
91
97
  end
92
98
 
99
+ # rubocop:disable Metrics/PerceivedComplexity
100
+ sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
101
+ def self.npm_version_numeric_latest(lockfile)
102
+ lockfile_content = lockfile&.content
103
+
104
+ # Return npm 10 as the default if the lockfile is missing or empty
105
+ return NPM_V10 if lockfile_content.nil? || lockfile_content.strip.empty?
106
+
107
+ # Parse the lockfile content to extract the `lockfileVersion`
108
+ parsed_lockfile = JSON.parse(lockfile_content)
109
+ lockfile_version = parsed_lockfile["lockfileVersion"]&.to_i
110
+
111
+ # Determine the appropriate npm version based on `lockfileVersion`
112
+ if lockfile_version.nil?
113
+ NPM_V10 # Use npm 10 if `lockfileVersion` is missing or nil
114
+ elsif lockfile_version >= 3
115
+ NPM_V10 # Use npm 10 for lockfileVersion 3 or higher
116
+ elsif lockfile_version >= 2
117
+ NPM_V8 # Use npm 8 for lockfileVersion 2
118
+ elsif lockfile_version >= 1
119
+ # Use npm 8 if the fallback version flag is enabled, otherwise use npm 6
120
+ Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6) ? NPM_V8 : NPM_V6
121
+ else
122
+ NPM_V10 # Default to npm 10 for unexpected or unsupported versions
123
+ end
124
+ rescue JSON::ParserError
125
+ NPM_V8 # Fallback to npm 8 if the lockfile content cannot be parsed
126
+ end
127
+ # rubocop:enable Metrics/PerceivedComplexity
128
+
93
129
  sig { params(yarn_lock: T.nilable(DependencyFile)).returns(Integer) }
94
130
  def self.yarn_version_numeric(yarn_lock)
95
131
  lockfile_content = yarn_lock&.content
@@ -110,9 +146,14 @@ module Dependabot
110
146
  def self.pnpm_version_numeric(pnpm_lock)
111
147
  lockfile_content = pnpm_lock&.content
112
148
 
113
- return PNPM_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?
149
+ return PNPM_DEFAULT_VERSION if !lockfile_content || lockfile_content.strip.empty?
150
+
151
+ pnpm_lockfile_version_str = pnpm_lockfile_version(pnpm_lock)
152
+
153
+ return PNPM_FALLBACK_VERSION unless pnpm_lockfile_version_str
154
+
155
+ pnpm_lockfile_version = pnpm_lockfile_version_str.to_f
114
156
 
115
- pnpm_lockfile_version = pnpm_lockfile_version(pnpm_lock).to_f
116
157
  return PNPM_V9 if pnpm_lockfile_version >= 9.0
117
158
  return PNPM_V8 if pnpm_lockfile_version >= 6.0
118
159
  return PNPM_V7 if pnpm_lockfile_version >= 5.4
@@ -120,6 +161,7 @@ module Dependabot
120
161
  PNPM_FALLBACK_VERSION
121
162
  end
122
163
 
164
+ sig { params(key: String, default_value: String).returns(T.untyped) }
123
165
  def self.fetch_yarnrc_yml_value(key, default_value)
124
166
  if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
125
167
  yarnrc.fetch(key, default_value)
@@ -132,6 +174,10 @@ module Dependabot
132
174
  def self.npm8?(package_lock)
133
175
  return true unless package_lock&.content
134
176
 
177
+ if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
178
+ return npm_version_numeric_latest(package_lock) >= NPM_V8
179
+ end
180
+
135
181
  npm_version_numeric(package_lock) == NPM_V8
136
182
  end
137
183
 
@@ -252,9 +298,12 @@ module Dependabot
252
298
  # set to false. Yarn commands should _not_ be ran outside of this helper
253
299
  # to ensure that postinstall scripts are never executed, as they could
254
300
  # contain malicious code.
301
+ sig { params(commands: T::Array[String]).void }
255
302
  def self.run_yarn_commands(*commands)
256
303
  setup_yarn_berry
257
- commands.each { |cmd, fingerprint| run_single_yarn_command(cmd, fingerprint: fingerprint) }
304
+ commands.each do |cmd, fingerprint|
305
+ run_single_yarn_command(cmd, fingerprint: fingerprint) if cmd
306
+ end
258
307
  end
259
308
 
260
309
  # Run single npm command returning stdout/stderr.
@@ -267,10 +316,44 @@ module Dependabot
267
316
  if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
268
317
  package_manager_run_command(NpmPackageManager::NAME, command, fingerprint: fingerprint)
269
318
  else
270
- SharedHelpers.run_shell_command("corepack npm #{command}", fingerprint: "corepack npm #{fingerprint}")
319
+ Dependabot::SharedHelpers.run_shell_command(
320
+ "corepack npm #{command}",
321
+ fingerprint: "corepack npm #{fingerprint}"
322
+ )
271
323
  end
272
324
  end
273
325
 
326
+ sig { returns(T.nilable(String)) }
327
+ def self.node_version
328
+ version = run_node_command("-v", fingerprint: "-v").strip
329
+
330
+ # Validate the output format (e.g., "v20.18.1" or "20.18.1")
331
+ if version.match?(/^v?\d+(\.\d+){2}$/)
332
+ version.strip.delete_prefix("v") # Remove the "v" prefix if present
333
+ end
334
+ rescue StandardError => e
335
+ puts "Error retrieving Node.js version: #{e.message}"
336
+ nil
337
+ end
338
+
339
+ sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
340
+ def self.run_node_command(command, fingerprint: nil)
341
+ full_command = "node #{command}"
342
+
343
+ Dependabot.logger.info("Running node command: #{full_command}")
344
+
345
+ result = Dependabot::SharedHelpers.run_shell_command(
346
+ full_command,
347
+ fingerprint: "node #{fingerprint || command}"
348
+ )
349
+
350
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
351
+ result
352
+ rescue StandardError => e
353
+ Dependabot.logger.error("Error running node command: #{full_command}, Error: #{e.message}")
354
+ raise
355
+ end
356
+
274
357
  # Setup yarn and run a single yarn command returning stdout/stderr
275
358
  sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
276
359
  def self.run_yarn_command(command, fingerprint: nil)
@@ -284,7 +367,10 @@ module Dependabot
284
367
  if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
285
368
  package_manager_run_command(PNPMPackageManager::NAME, command, fingerprint: fingerprint)
286
369
  else
287
- SharedHelpers.run_shell_command("pnpm #{command}", fingerprint: "pnpm #{fingerprint || command}")
370
+ Dependabot::SharedHelpers.run_shell_command(
371
+ "pnpm #{command}",
372
+ fingerprint: "pnpm #{fingerprint || command}"
373
+ )
288
374
  end
289
375
  end
290
376
 
@@ -294,13 +380,16 @@ module Dependabot
294
380
  if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
295
381
  package_manager_run_command(YarnPackageManager::NAME, command, fingerprint: fingerprint)
296
382
  else
297
- SharedHelpers.run_shell_command("yarn #{command}", fingerprint: "yarn #{fingerprint || command}")
383
+ Dependabot::SharedHelpers.run_shell_command(
384
+ "yarn #{command}",
385
+ fingerprint: "yarn #{fingerprint || command}"
386
+ )
298
387
  end
299
388
  end
300
389
 
301
390
  # Install the package manager for specified version by using corepack
302
391
  # and prepare it for use by using corepack
303
- sig { params(name: String, version: String).void }
392
+ sig { params(name: String, version: String).returns(String) }
304
393
  def self.install(name, version)
305
394
  Dependabot.logger.info("Installing \"#{name}@#{version}\"")
306
395
 
@@ -309,30 +398,40 @@ module Dependabot
309
398
  installed_version = package_manager_version(name)
310
399
 
311
400
  Dependabot.logger.info("Installed version of #{name}: #{installed_version}")
401
+
402
+ installed_version
312
403
  end
313
404
 
314
405
  # Install the package manager for specified version by using corepack
315
406
  sig { params(name: String, version: String).void }
316
407
  def self.package_manager_install(name, version)
317
- SharedHelpers.run_shell_command(
408
+ Dependabot::SharedHelpers.run_shell_command(
318
409
  "corepack install #{name}@#{version} --global --cache-only",
319
410
  fingerprint: "corepack install <name>@<version> --global --cache-only"
320
- )
411
+ ).strip
321
412
  end
322
413
 
323
414
  # Prepare the package manager for use by using corepack
324
415
  sig { params(name: String, version: String).void }
325
416
  def self.package_manager_activate(name, version)
326
- SharedHelpers.run_shell_command(
417
+ Dependabot::SharedHelpers.run_shell_command(
327
418
  "corepack prepare #{name}@#{version} --activate",
328
419
  fingerprint: "corepack prepare --activate"
329
- )
420
+ ).strip
330
421
  end
331
422
 
332
423
  # Get the version of the package manager by using corepack
333
424
  sig { params(name: String).returns(String) }
334
425
  def self.package_manager_version(name)
335
- package_manager_run_command(name, "-v")
426
+ Dependabot.logger.info("Fetching version for package manager: #{name}")
427
+
428
+ version = package_manager_run_command(name, "-v").strip
429
+
430
+ Dependabot.logger.info("Version for #{name}: #{version}")
431
+ version
432
+ rescue StandardError => e
433
+ Dependabot.logger.error("Error fetching version for package manager #{name}: #{e.message}")
434
+ raise
336
435
  end
337
436
 
338
437
  # Run single command on package manager returning stdout/stderr
@@ -344,15 +443,30 @@ module Dependabot
344
443
  ).returns(String)
345
444
  end
346
445
  def self.package_manager_run_command(name, command, fingerprint: nil)
347
- SharedHelpers.run_shell_command(
348
- "corepack #{name} #{command}",
446
+ full_command = "corepack #{name} #{command}"
447
+
448
+ Dependabot.logger.info("Running package manager command: #{full_command}")
449
+
450
+ result = Dependabot::SharedHelpers.run_shell_command(
451
+ full_command,
349
452
  fingerprint: "corepack #{name} #{fingerprint || command}"
350
- )
453
+ ).strip
454
+
455
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
456
+ result
457
+ rescue StandardError => e
458
+ Dependabot.logger.error("Error running package manager command: #{full_command}, Error: #{e.message}")
459
+ raise
351
460
  end
461
+
352
462
  private_class_method :run_single_yarn_command
353
463
 
464
+ sig { params(pnpm_lock: DependencyFile).returns(T.nilable(String)) }
354
465
  def self.pnpm_lockfile_version(pnpm_lock)
355
- pnpm_lock.content.match(/^lockfileVersion: ['"]?(?<version>[\d.]+)/)[:version]
466
+ match = T.must(pnpm_lock.content).match(/^lockfileVersion: ['"]?(?<version>[\d.]+)/)
467
+ return match[:version] if match
468
+
469
+ nil
356
470
  end
357
471
 
358
472
  sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) }
@@ -1,8 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/shared_helpers"
5
5
  require "dependabot/ecosystem"
6
+ require "dependabot/npm_and_yarn/requirement"
6
7
  require "dependabot/npm_and_yarn/version_selector"
7
8
 
8
9
  module Dependabot
@@ -10,6 +11,37 @@ module Dependabot
10
11
  ECOSYSTEM = "npm_and_yarn"
11
12
  MANIFEST_FILENAME = "package.json"
12
13
  LERNA_JSON_FILENAME = "lerna.json"
14
+ PACKAGE_MANAGER_VERSION_REGEX = /
15
+ ^ # Start of string
16
+ (?<major>\d+) # Major version (required, numeric)
17
+ \. # Separator between major and minor versions
18
+ (?<minor>\d+) # Minor version (required, numeric)
19
+ \. # Separator between minor and patch versions
20
+ (?<patch>\d+) # Patch version (required, numeric)
21
+ ( # Start pre-release section
22
+ -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional, alphanumeric or dot-separated)
23
+ )?
24
+ ( # Start build metadata section
25
+ \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional, alphanumeric or dot-separated)
26
+ )?
27
+ $ # End of string
28
+ /x # Extended mode for readability
29
+
30
+ VALID_REQUIREMENT_CONSTRAINT = /
31
+ ^ # Start of string
32
+ (?<operator>=|>|>=|<|<=|~>|\\^) # Allowed operators
33
+ \s* # Optional whitespace
34
+ (?<major>\d+) # Major version (required)
35
+ (\.(?<minor>\d+))? # Minor version (optional)
36
+ (\.(?<patch>\d+))? # Patch version (optional)
37
+ ( # Start pre-release section
38
+ -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional)
39
+ )?
40
+ ( # Start build metadata section
41
+ \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional)
42
+ )?
43
+ $ # End of string
44
+ /x # Extended mode for readability
13
45
 
14
46
  MANIFEST_PACKAGE_MANAGER_KEY = "packageManager"
15
47
  MANIFEST_ENGINES_KEY = "engines"
@@ -25,24 +57,32 @@ module Dependabot
25
57
  NPM_V7 = "7"
26
58
  NPM_V8 = "8"
27
59
  NPM_V9 = "9"
60
+ NPM_V10 = "10"
28
61
 
29
62
  # Keep versions in ascending order
30
63
  SUPPORTED_VERSIONS = T.let([
31
64
  Version.new(NPM_V6),
32
65
  Version.new(NPM_V7),
33
66
  Version.new(NPM_V8),
34
- Version.new(NPM_V9)
67
+ Version.new(NPM_V9),
68
+ Version.new(NPM_V10)
35
69
  ].freeze, T::Array[Dependabot::Version])
36
70
 
37
71
  DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
38
72
 
39
- sig { params(raw_version: String).void }
40
- def initialize(raw_version)
73
+ sig do
74
+ params(
75
+ raw_version: String,
76
+ requirement: T.nilable(Dependabot::NpmAndYarn::Requirement)
77
+ ).void
78
+ end
79
+ def initialize(raw_version, requirement: nil)
41
80
  super(
42
81
  NAME,
43
82
  Version.new(raw_version),
44
83
  DEPRECATED_VERSIONS,
45
- SUPPORTED_VERSIONS
84
+ SUPPORTED_VERSIONS,
85
+ requirement
46
86
  )
47
87
  end
48
88
 
@@ -76,13 +116,19 @@ module Dependabot
76
116
 
77
117
  DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
78
118
 
79
- sig { params(raw_version: String).void }
80
- def initialize(raw_version)
119
+ sig do
120
+ params(
121
+ raw_version: String,
122
+ requirement: T.nilable(Requirement)
123
+ ).void
124
+ end
125
+ def initialize(raw_version, requirement: nil)
81
126
  super(
82
127
  NAME,
83
128
  Version.new(raw_version),
84
129
  DEPRECATED_VERSIONS,
85
- SUPPORTED_VERSIONS
130
+ SUPPORTED_VERSIONS,
131
+ requirement
86
132
  )
87
133
  end
88
134
 
@@ -115,13 +161,19 @@ module Dependabot
115
161
 
116
162
  DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
117
163
 
118
- sig { params(raw_version: String).void }
119
- def initialize(raw_version)
164
+ sig do
165
+ params(
166
+ raw_version: String,
167
+ requirement: T.nilable(Requirement)
168
+ ).void
169
+ end
170
+ def initialize(raw_version, requirement: nil)
120
171
  super(
121
172
  NAME,
122
173
  Version.new(raw_version),
123
174
  DEPRECATED_VERSIONS,
124
- SUPPORTED_VERSIONS
175
+ SUPPORTED_VERSIONS,
176
+ requirement
125
177
  )
126
178
  end
127
179
 
@@ -138,11 +190,20 @@ module Dependabot
138
190
 
139
191
  DEFAULT_PACKAGE_MANAGER = NpmPackageManager::NAME
140
192
 
141
- PACKAGE_MANAGER_CLASSES = {
193
+ # Define a type alias for the expected class interface
194
+ NpmAndYarnPackageManagerClassType = T.type_alias do
195
+ T.any(
196
+ T.class_of(Dependabot::NpmAndYarn::NpmPackageManager),
197
+ T.class_of(Dependabot::NpmAndYarn::YarnPackageManager),
198
+ T.class_of(Dependabot::NpmAndYarn::PNPMPackageManager)
199
+ )
200
+ end
201
+
202
+ PACKAGE_MANAGER_CLASSES = T.let({
142
203
  NpmPackageManager::NAME => NpmPackageManager,
143
204
  YarnPackageManager::NAME => YarnPackageManager,
144
205
  PNPMPackageManager::NAME => PNPMPackageManager
145
- }.freeze
206
+ }.freeze, T::Hash[String, NpmAndYarnPackageManagerClassType])
146
207
 
147
208
  class PackageManagerDetector
148
209
  extend T::Sig
@@ -151,21 +212,34 @@ module Dependabot
151
212
  sig do
152
213
  params(
153
214
  lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)],
154
- package_json: T::Hash[String, T.untyped]
215
+ package_json: T.nilable(T::Hash[String, T.untyped])
155
216
  ).void
156
217
  end
157
218
  def initialize(lockfiles, package_json)
158
219
  @lockfiles = lockfiles
159
220
  @package_json = package_json
160
- @manifest_package_manager = package_json["packageManager"]
161
- @engines = package_json.fetch(MANIFEST_ENGINES_KEY, nil)
221
+ @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String))
222
+ @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, {}), T::Hash[String, T.untyped])
162
223
  end
163
224
 
164
225
  # Returns npm, yarn, or pnpm based on the lockfiles, package.json, and engines
165
226
  # Defaults to npm if no package manager is detected
166
227
  sig { returns(String) }
167
228
  def detect_package_manager
168
- name_from_lockfiles || name_from_package_manager_attr || name_from_engines || DEFAULT_PACKAGE_MANAGER
229
+ package_manager = name_from_lockfiles ||
230
+ name_from_package_manager_attr ||
231
+ name_from_engines
232
+
233
+ if package_manager
234
+ Dependabot.logger.info("Detected package manager: #{package_manager}")
235
+ else
236
+ package_manager = DEFAULT_PACKAGE_MANAGER
237
+ Dependabot.logger.info("Default package manager used: #{package_manager}")
238
+ end
239
+ package_manager
240
+ rescue StandardError => e
241
+ Dependabot.logger.error("Error detecting package manager: #{e.message}")
242
+ DEFAULT_PACKAGE_MANAGER
169
243
  end
170
244
 
171
245
  private
@@ -195,22 +269,62 @@ module Dependabot
195
269
  end
196
270
  end
197
271
 
272
+ class Language < Ecosystem::VersionManager
273
+ extend T::Sig
274
+ NAME = "node"
275
+
276
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
277
+
278
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
279
+
280
+ sig do
281
+ params(
282
+ raw_version: T.nilable(String),
283
+ requirement: T.nilable(Requirement)
284
+ ).void
285
+ end
286
+ def initialize(raw_version, requirement: nil)
287
+ super(
288
+ NAME,
289
+ Version.new(raw_version),
290
+ DEPRECATED_VERSIONS,
291
+ SUPPORTED_VERSIONS,
292
+ requirement
293
+ )
294
+ end
295
+
296
+ sig { override.returns(T::Boolean) }
297
+ def deprecated?
298
+ false
299
+ end
300
+
301
+ sig { override.returns(T::Boolean) }
302
+ def unsupported?
303
+ false
304
+ end
305
+ end
306
+
198
307
  class PackageManagerHelper
199
308
  extend T::Sig
200
309
  extend T::Helpers
201
310
 
202
311
  sig do
203
312
  params(
204
- package_json: T::Hash[String, T.untyped],
313
+ package_json: T.nilable(T::Hash[String, T.untyped]),
205
314
  lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]
206
315
  ).void
207
316
  end
208
317
  def initialize(package_json, lockfiles:)
209
318
  @package_json = package_json
210
319
  @lockfiles = lockfiles
211
- @manifest_package_manager = package_json[MANIFEST_PACKAGE_MANAGER_KEY]
212
- @engines = package_json.fetch(MANIFEST_ENGINES_KEY, nil)
213
- @package_manager_detector = PackageManagerDetector.new(@lockfiles, @package_json)
320
+ @package_manager_detector = T.let(PackageManagerDetector.new(lockfiles, package_json), PackageManagerDetector)
321
+ @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String))
322
+ @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, nil), T.nilable(T::Hash[String, T.untyped]))
323
+
324
+ @installed_versions = T.let({}, T::Hash[String, String])
325
+
326
+ @language = T.let(nil, T.nilable(Ecosystem::VersionManager))
327
+ @language_requirement = T.let(nil, T.nilable(Requirement))
214
328
  end
215
329
 
216
330
  sig { returns(Ecosystem::VersionManager) }
@@ -220,8 +334,54 @@ module Dependabot
220
334
  )
221
335
  end
222
336
 
337
+ sig { returns(Ecosystem::VersionManager) }
338
+ def language
339
+ @language ||= Language.new(
340
+ Helpers.node_version,
341
+ requirement: language_requirement
342
+ )
343
+ end
344
+
345
+ sig { returns(T.nilable(Requirement)) }
346
+ def language_requirement
347
+ @language_requirement ||= find_engine_constraints_as_requirement(Language::NAME)
348
+ end
349
+
350
+ sig { params(name: String).returns(T.nilable(Requirement)) }
351
+ def find_engine_constraints_as_requirement(name)
352
+ Dependabot.logger.info("Processing engine constraints for #{name}")
353
+
354
+ return nil unless @engines.is_a?(Hash) && @engines[name]
355
+
356
+ raw_constraint = @engines[name].to_s.strip
357
+ return nil if raw_constraint.empty?
358
+
359
+ raw_constraints = raw_constraint.split
360
+ constraints = raw_constraints.map do |constraint|
361
+ case constraint
362
+ when /^\d+$/
363
+ ">=#{constraint}.0.0 <#{constraint.to_i + 1}.0.0"
364
+ when /^\d+\.\d+$/
365
+ ">=#{constraint} <#{constraint.split('.').first.to_i + 1}.0.0"
366
+ when /^\d+\.\d+\.\d+$/
367
+ "=#{constraint}"
368
+ else
369
+ Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
370
+ constraint
371
+ end
372
+ end
373
+
374
+ Dependabot.logger.info("Parsed constraints for #{name}: #{constraints.join(', ')}")
375
+ Requirement.new(constraints)
376
+ rescue StandardError => e
377
+ Dependabot.logger.error("Error processing constraints for #{name}: #{e.message}")
378
+ nil
379
+ end
380
+
223
381
  # rubocop:disable Metrics/CyclomaticComplexity
224
382
  # rubocop:disable Metrics/PerceivedComplexity
383
+ # rubocop:disable Metrics/AbcSize
384
+ sig { params(name: String).returns(T.nilable(T.any(Integer, String))) }
225
385
  def setup(name)
226
386
  # we prioritize version mentioned in "packageManager" instead of "engines"
227
387
  # i.e. if { engines : "pnpm" : "6" } and { packageManager: "pnpm@6.0.2" },
@@ -257,13 +417,13 @@ module Dependabot
257
417
 
258
418
  if version
259
419
  raise_if_unsupported!(name, version.to_s)
260
- install(name, version)
420
+ install(name, version.to_s)
261
421
  end
262
422
  else
263
423
  version ||= requested_version(name)
264
424
 
265
425
  if version
266
- raise_if_unsupported!(name, version)
426
+ raise_if_unsupported!(name, version.to_s)
267
427
 
268
428
  install(name, version)
269
429
  else
@@ -272,30 +432,74 @@ module Dependabot
272
432
  if version
273
433
  raise_if_unsupported!(name, version.to_s)
274
434
 
275
- install(name, version) if name == PNPMPackageManager::NAME
435
+ install(name, version.to_s) if name == PNPMPackageManager::NAME
276
436
  end
277
437
  end
278
438
  end
279
439
  version
280
440
  end
281
- # rubocop:enable Metrics/CyclomaticComplexity
282
- # rubocop:enable Metrics/PerceivedComplexity
283
-
284
- private
285
441
 
286
442
  sig { params(name: T.nilable(String)).returns(Ecosystem::VersionManager) }
287
443
  def package_manager_by_name(name)
288
- name = DEFAULT_PACKAGE_MANAGER if name.nil? || PACKAGE_MANAGER_CLASSES[name].nil?
444
+ Dependabot.logger.info("Resolving package manager for: #{name || 'default'}")
289
445
 
290
- package_manager_class = PACKAGE_MANAGER_CLASSES[name]
446
+ name = ensure_valid_package_manager(name)
447
+ package_manager_class = T.must(PACKAGE_MANAGER_CLASSES[name])
291
448
 
292
- package_manager_class ||= PACKAGE_MANAGER_CLASSES[DEFAULT_PACKAGE_MANAGER]
449
+ installed_version = installed_version(name)
450
+ Dependabot.logger.info("Installed version for #{name}: #{installed_version}")
293
451
 
294
- version = Helpers.send(:"#{name}_version_numeric", @lockfiles[name.to_sym])
452
+ package_manager_requirement = find_engine_constraints_as_requirement(name)
453
+ if package_manager_requirement
454
+ Dependabot.logger.info("Version requirement for #{name}: #{package_manager_requirement}")
455
+ else
456
+ Dependabot.logger.info("No version requirement found for #{name}")
457
+ end
295
458
 
296
- package_manager_class.new(version.to_s)
459
+ package_manager_instance = package_manager_class.new(
460
+ installed_version,
461
+ requirement: package_manager_requirement
462
+ )
463
+
464
+ Dependabot.logger.info("Package manager resolved for #{name}: #{package_manager_instance}")
465
+ package_manager_instance
466
+ rescue StandardError => e
467
+ Dependabot.logger.error("Error resolving package manager for #{name || 'default'}: #{e.message}")
468
+ raise
297
469
  end
298
470
 
471
+ # rubocop:enable Metrics/CyclomaticComplexity
472
+ # rubocop:enable Metrics/PerceivedComplexity
473
+ # rubocop:enable Metrics/AbcSize
474
+ # Retrieve the installed version of the package manager by executing
475
+ # the "corepack <name> -v" command and using the output.
476
+ # If the output does not match the expected version format (PACKAGE_MANAGER_VERSION_REGEX),
477
+ # fall back to the version inferred from the dependency files.
478
+ sig { params(name: String).returns(String) }
479
+ def installed_version(name)
480
+ # Return the memoized version if it has already been computed
481
+ return T.must(@installed_versions[name]) if @installed_versions.key?(name)
482
+
483
+ # Attempt to get the installed version through the package manager version command
484
+ @installed_versions[name] = Helpers.package_manager_version(name)
485
+
486
+ # If we can't get the installed version, we need to install the package manager and get the version
487
+ unless @installed_versions[name]&.match?(PACKAGE_MANAGER_VERSION_REGEX)
488
+ setup(name)
489
+ @installed_versions[name] = Helpers.package_manager_version(name)
490
+ end
491
+
492
+ # If we can't get the installed version or the version is invalid, we need to get inferred version
493
+ unless @installed_versions[name]&.match?(PACKAGE_MANAGER_VERSION_REGEX)
494
+ @installed_versions[name] = Helpers.public_send(:"#{name}_version_numeric", @lockfiles[name.to_sym]).to_s
495
+ end
496
+
497
+ T.must(@installed_versions[name])
498
+ end
499
+
500
+ private
501
+
502
+ sig { params(name: String, version: String).void }
299
503
  def raise_if_unsupported!(name, version)
300
504
  return unless name == PNPMPackageManager::NAME
301
505
  return unless Version.new(version) < Version.new("7")
@@ -303,6 +507,7 @@ module Dependabot
303
507
  raise ToolVersionNotSupported.new(PNPMPackageManager::NAME.upcase, version, "7.*, 8.*")
304
508
  end
305
509
 
510
+ sig { params(name: String, version: T.nilable(String)).void }
306
511
  def install(name, version)
307
512
  if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
308
513
  return Helpers.install(name, version.to_s)
@@ -316,6 +521,13 @@ module Dependabot
316
521
  )
317
522
  end
318
523
 
524
+ sig { params(name: T.nilable(String)).returns(String) }
525
+ def ensure_valid_package_manager(name)
526
+ name = DEFAULT_PACKAGE_MANAGER if name.nil? || PACKAGE_MANAGER_CLASSES[name].nil?
527
+ name
528
+ end
529
+
530
+ sig { params(name: String).returns(T.nilable(String)) }
319
531
  def requested_version(name)
320
532
  return unless @manifest_package_manager
321
533
 
@@ -326,6 +538,7 @@ module Dependabot
326
538
  match["version"]
327
539
  end
328
540
 
541
+ sig { params(name: String).returns(T.nilable(T.any(Integer, String))) }
329
542
  def guessed_version(name)
330
543
  lockfile = @lockfiles[name.to_sym]
331
544
  return unless lockfile
@@ -339,6 +552,8 @@ module Dependabot
339
552
 
340
553
  sig { params(name: T.untyped).returns(T.nilable(String)) }
341
554
  def check_engine_version(name)
555
+ return if @package_json.nil?
556
+
342
557
  version_selector = VersionSelector.new
343
558
  engine_versions = version_selector.setup(@package_json, name)
344
559
 
@@ -70,8 +70,8 @@ module Dependabot
70
70
  run_yarn_updater(path, lockfile_name)
71
71
  elsif lockfile.name.end_with?("pnpm-lock.yaml")
72
72
  run_pnpm_updater(path, lockfile_name)
73
- elsif Helpers.npm8?(lockfile)
74
- run_npm8_updater(path, lockfile_name)
73
+ elsif !Helpers.npm8?(lockfile)
74
+ run_npm6_updater(path, lockfile_name)
75
75
  else
76
76
  run_npm_updater(path, lockfile_name)
77
77
  end
@@ -143,7 +143,7 @@ module Dependabot
143
143
  end
144
144
  end
145
145
 
146
- def run_npm8_updater(path, lockfile_name)
146
+ def run_npm_updater(path, lockfile_name)
147
147
  SharedHelpers.with_git_configured(credentials: credentials) do
148
148
  Dir.chdir(path) do
149
149
  NativeHelpers.run_npm8_subdependency_update_command([dependency.name])
@@ -153,7 +153,7 @@ module Dependabot
153
153
  end
154
154
  end
155
155
 
156
- def run_npm_updater(path, lockfile_name)
156
+ def run_npm6_updater(path, lockfile_name)
157
157
  SharedHelpers.with_git_configured(credentials: credentials) do
158
158
  Dir.chdir(path) do
159
159
  SharedHelpers.run_helper_subprocess(
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.287.0
4
+ version: 0.289.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-19 00:00:00.000000000 Z
11
+ date: 2024-12-05 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.287.0
19
+ version: 0.289.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.287.0
26
+ version: 0.289.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.287.0
349
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.289.0
350
350
  post_install_message:
351
351
  rdoc_options: []
352
352
  require_paths: