dependabot-npm_and_yarn 0.235.0 → 0.237.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.
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
5
+ require "sorbet-runtime"
5
6
  require "dependabot/experiments"
6
7
  require "dependabot/logger"
7
8
  require "dependabot/file_fetchers"
@@ -14,6 +15,9 @@ require "dependabot/npm_and_yarn/file_parser/lockfile_parser"
14
15
  module Dependabot
15
16
  module NpmAndYarn
16
17
  class FileFetcher < Dependabot::FileFetchers::Base # rubocop:disable Metrics/ClassLength
18
+ extend T::Sig
19
+ extend T::Helpers
20
+
17
21
  require_relative "file_fetcher/path_dependency_builder"
18
22
 
19
23
  # Npm always prefixes file paths in the lockfile "version" with "file:"
@@ -56,7 +60,7 @@ module Dependabot
56
60
  def ecosystem_versions
57
61
  package_managers = {}
58
62
 
59
- package_managers["npm"] = Helpers.npm_version_numeric(package_lock.content) if package_lock
63
+ package_managers["npm"] = npm_version if package_lock
60
64
  package_managers["yarn"] = yarn_version if yarn_version
61
65
  package_managers["pnpm"] = pnpm_version if pnpm_version
62
66
  package_managers["shrinkwrap"] = 1 if shrinkwrap
@@ -67,8 +71,7 @@ module Dependabot
67
71
  }
68
72
  end
69
73
 
70
- private
71
-
74
+ sig { override.returns(T::Array[DependencyFile]) }
72
75
  def fetch_files
73
76
  fetched_files = []
74
77
  fetched_files << package_json
@@ -82,6 +85,12 @@ module Dependabot
82
85
  fetched_files.uniq
83
86
  end
84
87
 
88
+ private
89
+
90
+ def recurse_submodules_when_cloning?
91
+ true
92
+ end
93
+
85
94
  def npm_files
86
95
  fetched_npm_files = []
87
96
  fetched_npm_files << package_lock if package_lock
@@ -161,10 +170,14 @@ module Dependabot
161
170
  @inferred_npmrc = nil
162
171
  end
163
172
 
173
+ def npm_version
174
+ Helpers.npm_version_numeric(package_lock.content)
175
+ end
176
+
164
177
  def yarn_version
165
178
  return @yarn_version if defined?(@yarn_version)
166
179
 
167
- @yarn_version = package_manager.locked_version("yarn") || guess_yarn_version
180
+ @yarn_version = package_manager.requested_version("yarn") || guess_yarn_version
168
181
  end
169
182
 
170
183
  def guess_yarn_version
@@ -176,13 +189,19 @@ module Dependabot
176
189
  def pnpm_version
177
190
  return @pnpm_version if defined?(@pnpm_version)
178
191
 
179
- @pnpm_version = package_manager.locked_version("pnpm") || guess_pnpm_version
192
+ version = package_manager.requested_version("pnpm") || guess_pnpm_version
193
+
194
+ if version && Version.new(version.to_s) < Version.new("7")
195
+ raise ToolVersionNotSupported.new("PNPM", version.to_s, "7.*, 8.*")
196
+ end
197
+
198
+ @pnpm_version = version
180
199
  end
181
200
 
182
201
  def guess_pnpm_version
183
202
  return unless pnpm_lock
184
203
 
185
- Helpers.pnpm_major_version
204
+ Helpers.pnpm_version_numeric(pnpm_lock)
186
205
  end
187
206
 
188
207
  def package_manager
@@ -7,7 +7,7 @@ require "dependabot/npm_and_yarn/helpers"
7
7
 
8
8
  module Dependabot
9
9
  module NpmAndYarn
10
- class FileParser
10
+ class FileParser < Dependabot::FileParsers::Base
11
11
  class JsonLock
12
12
  def initialize(dependency_file)
13
13
  @dependency_file = dependency_file
@@ -7,7 +7,7 @@ require "dependabot/npm_and_yarn/helpers"
7
7
 
8
8
  module Dependabot
9
9
  module NpmAndYarn
10
- class FileParser
10
+ class FileParser < Dependabot::FileParsers::Base
11
11
  class LockfileParser
12
12
  require "dependabot/npm_and_yarn/file_parser/yarn_lock"
13
13
  require "dependabot/npm_and_yarn/file_parser/pnpm_lock"
@@ -5,7 +5,7 @@ require "dependabot/errors"
5
5
 
6
6
  module Dependabot
7
7
  module NpmAndYarn
8
- class FileParser
8
+ class FileParser < Dependabot::FileParsers::Base
9
9
  class PnpmLock
10
10
  def initialize(dependency_file)
11
11
  @dependency_file = dependency_file
@@ -7,7 +7,7 @@ require "dependabot/npm_and_yarn/native_helpers"
7
7
 
8
8
  module Dependabot
9
9
  module NpmAndYarn
10
- class FileParser
10
+ class FileParser < Dependabot::FileParsers::Base
11
11
  class YarnLock
12
12
  def initialize(dependency_file)
13
13
  @dependency_file = dependency_file
@@ -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/registry_parser"
14
15
  require "dependabot/git_metadata_fetcher"
15
16
  require "dependabot/git_commit_checker"
16
17
  require "dependabot/errors"
@@ -34,6 +35,15 @@ module Dependabot
34
35
  )?$
35
36
  }ix
36
37
 
38
+ def self.each_dependency(json)
39
+ DEPENDENCY_TYPES.each do |type|
40
+ deps = json[type] || {}
41
+ deps.each do |name, requirement|
42
+ yield name, requirement, type
43
+ end
44
+ end
45
+ end
46
+
37
47
  def parse
38
48
  dependency_set = DependencySet.new
39
49
  dependency_set += manifest_dependencies
@@ -41,11 +51,16 @@ module Dependabot
41
51
 
42
52
  dependencies = Helpers.dependencies_with_all_versions_metadata(dependency_set)
43
53
 
44
- # TODO: Currently, Dependabot can't handle dependencies that have both
45
- # a git source *and* a non-git source. Fix that!
46
54
  dependencies.reject do |dep|
47
- git_reqs =
48
- dep.requirements.select { |r| r.dig(:source, :type) == "git" }
55
+ reqs = dep.requirements
56
+
57
+ # Ignore dependencies defined in support files, since we don't want PRs for those
58
+ support_reqs = reqs.select { |r| support_package_files.any? { |f| f.name == r[:file] } }
59
+ next true if support_reqs.any?
60
+
61
+ # TODO: Currently, Dependabot can't handle dependencies that have both
62
+ # a git source *and* a non-git source. Fix that!
63
+ git_reqs = reqs.select { |r| r.dig(:source, :type) == "git" }
49
64
  next false if git_reqs.none?
50
65
  next true if git_reqs.map { |r| r.fetch(:source) }.uniq.count > 1
51
66
 
@@ -59,22 +74,24 @@ module Dependabot
59
74
  dependency_set = DependencySet.new
60
75
 
61
76
  package_files.each do |file|
77
+ json = JSON.parse(file.content)
78
+
62
79
  # TODO: Currently, Dependabot can't handle flat dependency files
63
80
  # (and will error at the FileUpdater stage, because the
64
81
  # UpdateChecker doesn't take account of flat resolution).
65
- next if JSON.parse(file.content)["flat"]
66
-
67
- DEPENDENCY_TYPES.each do |type|
68
- deps = JSON.parse(file.content)[type] || {}
69
- deps.each do |name, requirement|
70
- next unless requirement.is_a?(String)
71
-
72
- requirement = "*" if requirement == ""
73
- dep = build_dependency(
74
- file: file, type: type, name: name, requirement: requirement
75
- )
76
- dependency_set << dep if dep
77
- end
82
+ next if json["flat"]
83
+
84
+ self.class.each_dependency(json) do |name, requirement, type|
85
+ next unless requirement.is_a?(String)
86
+
87
+ # Skip dependencies using Yarn workspace cross-references as requirements
88
+ next if requirement.start_with?("workspace:")
89
+
90
+ requirement = "*" if requirement == ""
91
+ dep = build_dependency(
92
+ file: file, type: type, name: name, requirement: requirement
93
+ )
94
+ dependency_set << dep if dep
78
95
  end
79
96
  end
80
97
 
@@ -253,7 +270,10 @@ module Dependabot
253
270
  return unless resolved_url.start_with?("http")
254
271
  return if resolved_url.match?(/(?<!pkg\.)github/)
255
272
 
256
- registry_source_for(resolved_url, name)
273
+ RegistryParser.new(
274
+ resolved_url: resolved_url,
275
+ credentials: credentials
276
+ ).registry_source_for(name)
257
277
  end
258
278
 
259
279
  def requirement_for(requirement)
@@ -286,69 +306,23 @@ module Dependabot
286
306
  }
287
307
  end
288
308
 
289
- def registry_source_for(resolved_url, name)
290
- url =
291
- if resolved_url.include?("/~/")
292
- # Gemfury format
293
- resolved_url.split("/~/").first
294
- elsif resolved_url.include?("/#{name}/-/#{name}")
295
- # MyGet / Bintray format
296
- resolved_url.split("/#{name}/-/#{name}").first
297
- .gsub("dl.bintray.com//", "api.bintray.com/npm/").
298
- # GitLab format
299
- gsub(%r{\/projects\/\d+}, "")
300
- elsif resolved_url.include?("/#{name}/-/#{name.split('/').last}")
301
- # Sonatype Nexus / Artifactory JFrog format
302
- resolved_url.split("/#{name}/-/#{name.split('/').last}").first
303
- elsif (cred_url = url_for_relevant_cred(resolved_url)) then cred_url
304
- else
305
- resolved_url.split("/")[0..2].join("/")
306
- end
307
-
308
- { type: "registry", url: url }
309
+ def support_package_files
310
+ @support_package_files ||= sub_package_files.select(&:support_file?)
309
311
  end
310
312
 
311
- def url_for_relevant_cred(resolved_url)
312
- resolved_url_host = URI(resolved_url).host
313
-
314
- credential_matching_url =
315
- credentials
316
- .select { |cred| cred["type"] == "npm_registry" }
317
- .sort_by { |cred| cred["registry"].length }
318
- .find do |details|
319
- next true if resolved_url_host == details["registry"]
320
-
321
- uri = if details["registry"]&.include?("://")
322
- URI(details["registry"])
323
- else
324
- URI("https://#{details['registry']}")
325
- end
326
- resolved_url_host == uri.host && resolved_url.include?(details["registry"])
327
- end
328
-
329
- return unless credential_matching_url
330
-
331
- # Trim the resolved URL so that it ends at the same point as the
332
- # credential registry
333
- reg = credential_matching_url["registry"]
334
- resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg
313
+ def sub_package_files
314
+ @sub_package_files ||=
315
+ dependency_files.select { |f| f.name.end_with?("package.json") }
316
+ .reject { |f| f.name == "package.json" }
317
+ .reject { |f| f.name.include?("node_modules/") }
335
318
  end
336
319
 
337
320
  def package_files
338
321
  @package_files ||=
339
- begin
340
- sub_packages =
341
- dependency_files
342
- .select { |f| f.name.end_with?("package.json") }
343
- .reject { |f| f.name == "package.json" }
344
- .reject { |f| f.name.include?("node_modules/") }
345
- .reject(&:support_file?)
346
-
347
- [
348
- dependency_files.find { |f| f.name == "package.json" },
349
- *sub_packages
350
- ].compact
351
- end
322
+ [
323
+ dependency_files.find { |f| f.name == "package.json" },
324
+ *sub_package_files
325
+ ].compact
352
326
  end
353
327
 
354
328
  def version_class
@@ -13,7 +13,7 @@ require "dependabot/shared_helpers"
13
13
  # rubocop:disable Metrics/ClassLength
14
14
  module Dependabot
15
15
  module NpmAndYarn
16
- class FileUpdater
16
+ class FileUpdater < Dependabot::FileUpdaters::Base
17
17
  class NpmLockfileUpdater
18
18
  require_relative "npmrc_builder"
19
19
  require_relative "package_json_updater"
@@ -562,13 +562,11 @@ module Dependabot
562
562
  return content if git_dependencies_to_lock.empty?
563
563
 
564
564
  json = JSON.parse(content)
565
- NpmAndYarn::FileParser::DEPENDENCY_TYPES.each do |type|
566
- json.fetch(type, {}).each do |nm, _|
567
- updated_version = git_dependencies_to_lock.dig(nm, :version)
568
- next unless updated_version
565
+ NpmAndYarn::FileParser.each_dependency(json) do |nm, _, type|
566
+ updated_version = git_dependencies_to_lock.dig(nm, :version)
567
+ next unless updated_version
569
568
 
570
- json[type][nm] = git_dependencies_to_lock[nm][:version]
571
- end
569
+ json[type][nm] = git_dependencies_to_lock[nm][:version]
572
570
  end
573
571
 
574
572
  indent = detect_indentation(content)
@@ -605,12 +603,10 @@ module Dependabot
605
603
  def lock_deps_with_latest_reqs(content)
606
604
  json = JSON.parse(content)
607
605
 
608
- NpmAndYarn::FileParser::DEPENDENCY_TYPES.each do |type|
609
- json.fetch(type, {}).each do |nm, requirement|
610
- next unless requirement == "latest"
606
+ NpmAndYarn::FileParser.each_dependency(json) do |nm, requirement, type|
607
+ next unless requirement == "latest"
611
608
 
612
- json[type][nm] = "*"
613
- end
609
+ json[type][nm] = "*"
614
610
  end
615
611
 
616
612
  indent = detect_indentation(content)
@@ -721,17 +717,15 @@ module Dependabot
721
717
 
722
718
  dependency_names_to_restore = (dependencies.map(&:name) + git_dependencies_to_lock.keys).uniq
723
719
 
724
- NpmAndYarn::FileParser::DEPENDENCY_TYPES.each do |type|
725
- parsed_package_json.fetch(type, {}).each do |dependency_name, original_requirement|
726
- next unless dependency_names_to_restore.include?(dependency_name)
720
+ NpmAndYarn::FileParser.each_dependency(parsed_package_json) do |dependency_name, original_requirement, type|
721
+ next unless dependency_names_to_restore.include?(dependency_name)
727
722
 
728
- locked_requirement = parsed_updated_lockfile_content.dig("packages", "", type, dependency_name)
729
- next unless locked_requirement
723
+ locked_requirement = parsed_updated_lockfile_content.dig("packages", "", type, dependency_name)
724
+ next unless locked_requirement
730
725
 
731
- locked_req = %("#{dependency_name}": "#{locked_requirement}")
732
- original_req = %("#{dependency_name}": "#{original_requirement}")
733
- updated_lockfile_content = updated_lockfile_content.gsub(locked_req, original_req)
734
- end
726
+ locked_req = %("#{dependency_name}": "#{locked_requirement}")
727
+ original_req = %("#{dependency_name}": "#{original_requirement}")
728
+ updated_lockfile_content = updated_lockfile_content.gsub(locked_req, original_req)
735
729
  end
736
730
 
737
731
  updated_lockfile_content
@@ -5,7 +5,7 @@ require "dependabot/npm_and_yarn/file_updater"
5
5
 
6
6
  module Dependabot
7
7
  module NpmAndYarn
8
- class FileUpdater
8
+ class FileUpdater < Dependabot::FileUpdaters::Base
9
9
  # Build a .npmrc file from the lockfile content, credentials, and any
10
10
  # committed .npmrc
11
11
  # We should refactor this to use UpdateChecker::RegistryFinder
@@ -140,7 +140,10 @@ module Dependabot
140
140
  @dependency_urls = dependencies.map do |dependency|
141
141
  UpdateChecker::RegistryFinder.new(
142
142
  dependency: dependency,
143
- credentials: credentials
143
+ credentials: credentials,
144
+ npmrc_file: npmrc_file,
145
+ yarnrc_file: yarnrc_file,
146
+ yarnrc_yml_file: yarnrc_yml_file
144
147
  ).dependency_url
145
148
  end
146
149
  return @dependency_urls
@@ -309,6 +312,11 @@ module Dependabot
309
312
  .find { |f| f.name.end_with?(".yarnrc") }
310
313
  end
311
314
 
315
+ def yarnrc_yml_file
316
+ @yarnrc_yml_file ||= dependency_files
317
+ .find { |f| f.name.end_with?(".yarnrc.yml") }
318
+ end
319
+
312
320
  def yarn_lock
313
321
  @yarn_lock ||= dependency_files.find { |f| f.name == "yarn.lock" }
314
322
  end
@@ -6,7 +6,7 @@ require "dependabot/npm_and_yarn/file_parser"
6
6
 
7
7
  module Dependabot
8
8
  module NpmAndYarn
9
- class FileUpdater
9
+ class FileUpdater < Dependabot::FileUpdaters::Base
10
10
  class PackageJsonPreparer
11
11
  def initialize(package_json_content:)
12
12
  @package_json_content = package_json_content
@@ -72,14 +72,12 @@ module Dependabot
72
72
 
73
73
  @git_ssh_requirements_to_swap = []
74
74
 
75
- NpmAndYarn::FileParser::DEPENDENCY_TYPES.each do |t|
76
- JSON.parse(package_json_content).fetch(t, {}).each do |_, req|
77
- next unless req.is_a?(String)
78
- next unless req.start_with?("git+ssh:")
75
+ NpmAndYarn::FileParser.each_dependency(JSON.parse(package_json_content)) do |_, req, _t|
76
+ next unless req.is_a?(String)
77
+ next unless req.start_with?("git+ssh:")
79
78
 
80
- req = req.split("#").first
81
- @git_ssh_requirements_to_swap << req
82
- end
79
+ req = req.split("#").first
80
+ @git_ssh_requirements_to_swap << req
83
81
  end
84
82
 
85
83
  @git_ssh_requirements_to_swap
@@ -5,7 +5,7 @@ require "dependabot/npm_and_yarn/file_updater"
5
5
 
6
6
  module Dependabot
7
7
  module NpmAndYarn
8
- class FileUpdater
8
+ class FileUpdater < Dependabot::FileUpdaters::Base
9
9
  class PackageJsonUpdater
10
10
  def initialize(package_json:, dependencies:)
11
11
  @package_json = package_json
@@ -3,11 +3,12 @@
3
3
 
4
4
  require "dependabot/npm_and_yarn/helpers"
5
5
  require "dependabot/npm_and_yarn/update_checker/registry_finder"
6
+ require "dependabot/npm_and_yarn/registry_parser"
6
7
  require "dependabot/shared_helpers"
7
8
 
8
9
  module Dependabot
9
10
  module NpmAndYarn
10
- class FileUpdater
11
+ class FileUpdater < Dependabot::FileUpdaters::Base
11
12
  class PnpmLockfileUpdater
12
13
  require_relative "npmrc_builder"
13
14
  require_relative "package_json_updater"
@@ -35,7 +36,8 @@ module Dependabot
35
36
 
36
37
  IRRESOLVABLE_PACKAGE = "ERR_PNPM_NO_MATCHING_VERSION"
37
38
  INVALID_REQUIREMENT = "ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER"
38
- MISSING_PACKAGE = /(?<package_req>.*?) is not in the npm registry, or you have no permission to fetch it/
39
+ UNREACHABLE_GIT = %r{ERR_PNPM_FETCH_404[ [^:print:]]+GET (?<url>https://codeload\.github\.com/[^/]+/[^/]+)/}
40
+ MISSING_PACKAGE = /ERR_PNPM_FETCH_404[ [^:print:]]+GET (?<dependency_url>.*): Not Found - 404/
39
41
 
40
42
  def run_pnpm_update(pnpm_lock:)
41
43
  SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
@@ -87,11 +89,18 @@ module Dependabot
87
89
  raise_resolvability_error(error_message, pnpm_lock)
88
90
  end
89
91
 
92
+ if error_message.match?(UNREACHABLE_GIT)
93
+ dependency_url = error_message.match(UNREACHABLE_GIT).named_captures.fetch("url")
94
+
95
+ raise Dependabot::GitDependenciesNotReachable, dependency_url
96
+ end
97
+
90
98
  raise unless error_message.match?(MISSING_PACKAGE)
91
99
 
92
- package_name = error_message.match(MISSING_PACKAGE)
93
- .named_captures["package_req"]
94
- .split(/(?<=\w)\@/).first
100
+ dependency_url = error_message.match(MISSING_PACKAGE)
101
+ .named_captures["dependency_url"]
102
+
103
+ package_name = RegistryParser.new(resolved_url: dependency_url, credentials: credentials).dependency_name
95
104
  raise_missing_package_error(package_name, error_message, pnpm_lock)
96
105
  end
97
106
 
@@ -14,7 +14,7 @@ require "dependabot/errors"
14
14
  # rubocop:disable Metrics/ClassLength
15
15
  module Dependabot
16
16
  module NpmAndYarn
17
- class FileUpdater
17
+ class FileUpdater < Dependabot::FileUpdaters::Base
18
18
  class YarnLockfileUpdater
19
19
  require_relative "npmrc_builder"
20
20
  require_relative "package_json_updater"
@@ -25,6 +25,16 @@ module Dependabot
25
25
  end
26
26
  end
27
27
 
28
+ # Mapping from lockfile versions to PNPM versions is at
29
+ # https://github.com/pnpm/spec/tree/274ff02de23376ad59773a9f25ecfedd03a41f64/lockfile, but simplify it for now.
30
+ def self.pnpm_version_numeric(pnpm_lock)
31
+ if pnpm_lockfile_version(pnpm_lock).to_f >= 5.4
32
+ 8
33
+ else
34
+ 6
35
+ end
36
+ end
37
+
28
38
  def self.fetch_yarnrc_yml_value(key, default_value)
29
39
  if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
30
40
  yarnrc.fetch(key, default_value)
@@ -44,20 +54,11 @@ module Dependabot
44
54
  @yarn_major_version ||= fetch_yarn_major_version
45
55
  end
46
56
 
47
- def self.pnpm_major_version
48
- @pnpm_major_version ||= fetch_pnpm_major_version
49
- end
50
-
51
57
  def self.fetch_yarn_major_version
52
58
  output = SharedHelpers.run_shell_command("yarn --version")
53
59
  Version.new(output).major
54
60
  end
55
61
 
56
- def self.fetch_pnpm_major_version
57
- output = SharedHelpers.run_shell_command("pnpm --version")
58
- Version.new(output).major
59
- end
60
-
61
62
  def self.yarn_zero_install?
62
63
  File.exist?(".pnp.cjs")
63
64
  end
@@ -122,6 +123,10 @@ module Dependabot
122
123
  SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
123
124
  end
124
125
 
126
+ def self.pnpm_lockfile_version(pnpm_lock)
127
+ pnpm_lock.content.match(/^lockfileVersion: ['"]?(?<version>[\d.]+)/)[:version]
128
+ end
129
+
125
130
  def self.dependencies_with_all_versions_metadata(dependency_set)
126
131
  dependency_set.dependencies.map do |dependency|
127
132
  dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name)
@@ -8,11 +8,11 @@ module Dependabot
8
8
  @package_json = package_json
9
9
  end
10
10
 
11
- def locked_version(name)
12
- locked = @package_json.fetch("packageManager", nil)
13
- return unless locked
11
+ def requested_version(name)
12
+ version = @package_json.fetch("packageManager", nil)
13
+ return unless version
14
14
 
15
- version_match = locked.match(/#{name}@(?<version>\d+.\d+.\d+)/)
15
+ version_match = version.match(/#{name}@(?<version>\d+.\d+.\d+)/)
16
16
  version_match&.named_captures&.fetch("version", nil)
17
17
  end
18
18
  end
@@ -0,0 +1,75 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module NpmAndYarn
6
+ class RegistryParser
7
+ def initialize(resolved_url:, credentials:)
8
+ @resolved_url = resolved_url
9
+ @credentials = credentials
10
+ end
11
+
12
+ def registry_source_for(name)
13
+ url =
14
+ if resolved_url.include?("/~/")
15
+ # Gemfury format
16
+ resolved_url.split("/~/").first
17
+ elsif resolved_url.include?("/#{name}/-/#{name}")
18
+ # MyGet / Bintray format
19
+ resolved_url.split("/#{name}/-/#{name}").first
20
+ .gsub("dl.bintray.com//", "api.bintray.com/npm/").
21
+ # GitLab format
22
+ gsub(%r{\/projects\/\d+}, "")
23
+ elsif resolved_url.include?("/#{name}/-/#{name.split('/').last}")
24
+ # Sonatype Nexus / Artifactory JFrog format
25
+ resolved_url.split("/#{name}/-/#{name.split('/').last}").first
26
+ elsif (cred_url = url_for_relevant_cred) then cred_url
27
+ else
28
+ resolved_url.split("/")[0..2].join("/")
29
+ end
30
+
31
+ { type: "registry", url: url }
32
+ end
33
+
34
+ def dependency_name
35
+ url_base = if resolved_url.include?("/-/")
36
+ resolved_url.split("/-/").first
37
+ else
38
+ resolved_url
39
+ end
40
+
41
+ url_base.split("/")[3..-1].join("/").gsub("%2F", "/")
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :resolved_url, :credentials
47
+
48
+ def url_for_relevant_cred
49
+ resolved_url_host = URI(resolved_url).host
50
+
51
+ credential_matching_url =
52
+ credentials
53
+ .select { |cred| cred["type"] == "npm_registry" }
54
+ .sort_by { |cred| cred["registry"].length }
55
+ .find do |details|
56
+ next true if resolved_url_host == details["registry"]
57
+
58
+ uri = if details["registry"]&.include?("://")
59
+ URI(details["registry"])
60
+ else
61
+ URI("https://#{details['registry']}")
62
+ end
63
+ resolved_url_host == uri.host && resolved_url.include?(details["registry"])
64
+ end
65
+
66
+ return unless credential_matching_url
67
+
68
+ # Trim the resolved URL so that it ends at the same point as the
69
+ # credential registry
70
+ reg = credential_matching_url["registry"]
71
+ resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg
72
+ end
73
+ end
74
+ end
75
+ end