dependabot-npm_and_yarn 0.237.0 → 0.238.0

Sign up to get free protection for your applications and to get access to all the features.
data/helpers/package.json CHANGED
@@ -11,9 +11,9 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dependabot/yarn-lib": "^1.22.19",
14
- "@npmcli/arborist": "^7.2.0",
14
+ "@npmcli/arborist": "^7.2.1",
15
15
  "detect-indent": "^6.1.0",
16
- "nock": "^13.3.6",
16
+ "nock": "^13.3.8",
17
17
  "npm": "6.14.18",
18
18
  "@pnpm/lockfile-file": "^8.1.4",
19
19
  "@pnpm/dependency-path": "^2.1.1",
@@ -21,9 +21,9 @@
21
21
  "patch-package": "^8.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "eslint": "^8.52.0",
24
+ "eslint": "^8.54.0",
25
25
  "eslint-config-prettier": "^9.0.0",
26
26
  "jest": "^29.7.0",
27
- "prettier": "^3.0.3"
27
+ "prettier": "^3.1.0"
28
28
  }
29
29
  }
@@ -32,9 +32,17 @@ module Dependabot
32
32
  build_npmrc_content_from_lockfile
33
33
  end
34
34
 
35
- return initial_content || "" unless registry_credentials.any?
35
+ final_content = initial_content || ""
36
36
 
37
- ([initial_content] + credential_lines_for_npmrc).compact.join("\n")
37
+ return final_content unless registry_credentials.any?
38
+
39
+ credential_lines_for_npmrc.each do |credential_line|
40
+ next if final_content.include?(credential_line)
41
+
42
+ final_content = [final_content, credential_line].reject(&:empty?).join("\n")
43
+ end
44
+
45
+ final_content
38
46
  end
39
47
 
40
48
  # PROXY WORK
@@ -105,15 +113,7 @@ module Dependabot
105
113
  token = global_registry.fetch("token", nil)
106
114
  return "" unless token
107
115
 
108
- if token.include?(":")
109
- encoded_token = Base64.encode64(token).delete("\n")
110
- "_auth = #{encoded_token}\n"
111
- elsif Base64.decode64(token).ascii_only? &&
112
- Base64.decode64(token).include?(":")
113
- "_auth = #{token.delete("\n")}\n"
114
- else
115
- "_authToken = #{token}\n"
116
- end
116
+ auth_line(token, global_registry.fetch("registry")) + "\n"
117
117
  end
118
118
 
119
119
  def yarnrc_global_registry_auth_line
@@ -122,12 +122,12 @@ module Dependabot
122
122
 
123
123
  if token.include?(":")
124
124
  encoded_token = Base64.encode64(token).delete("\n")
125
- "npmAuthIdent: \"#{encoded_token}\"\n"
125
+ "npmAuthIdent: \"#{encoded_token}\""
126
126
  elsif Base64.decode64(token).ascii_only? &&
127
127
  Base64.decode64(token).include?(":")
128
- "npmAuthIdent: \"#{token.delete("\n")}\"\n"
128
+ "npmAuthIdent: \"#{token.delete("\n")}\""
129
129
  else
130
- "npmAuthToken: \"#{token}\"\n"
130
+ "npmAuthToken: \"#{token}\""
131
131
  end
132
132
  end
133
133
 
@@ -230,18 +230,7 @@ module Dependabot
230
230
  token = cred.fetch("token", nil)
231
231
  next unless token
232
232
 
233
- # We need to ensure the registry uri ends with a trailing slash in the npmrc file
234
- # but we do not want to add one if it already exists
235
- registry_with_trailing_slash = registry.sub(%r{\/?$}, "/")
236
- if token.include?(":")
237
- encoded_token = Base64.encode64(token).delete("\n")
238
- lines << "//#{registry_with_trailing_slash}:_auth=#{encoded_token}"
239
- elsif Base64.decode64(token).ascii_only? &&
240
- Base64.decode64(token).include?(":")
241
- lines << %(//#{registry_with_trailing_slash}:_auth=#{token.delete("\n")})
242
- else
243
- lines << "//#{registry_with_trailing_slash}:_authToken=#{token}"
244
- end
233
+ lines << auth_line(token, registry)
245
234
  end
246
235
 
247
236
  return lines unless lines.any? { |str| str.include?("auth=") }
@@ -250,6 +239,26 @@ module Dependabot
250
239
  ["always-auth = true"] + lines
251
240
  end
252
241
 
242
+ def auth_line(token, registry = nil)
243
+ auth = if token.include?(":")
244
+ encoded_token = Base64.encode64(token).delete("\n")
245
+ "_auth=#{encoded_token}"
246
+ elsif Base64.decode64(token).ascii_only? &&
247
+ Base64.decode64(token).include?(":")
248
+ "_auth=#{token.delete("\n")}"
249
+ else
250
+ "_authToken=#{token}"
251
+ end
252
+
253
+ return auth unless registry
254
+
255
+ # We need to ensure the registry uri ends with a trailing slash in the npmrc file
256
+ # but we do not want to add one if it already exists
257
+ registry_with_trailing_slash = registry.sub(%r{\/?$}, "/")
258
+
259
+ "//#{registry_with_trailing_slash}:#{auth}"
260
+ end
261
+
253
262
  def npmrc_scoped_registries
254
263
  return [] unless npmrc_file
255
264
 
@@ -37,7 +37,9 @@ module Dependabot
37
37
  IRRESOLVABLE_PACKAGE = "ERR_PNPM_NO_MATCHING_VERSION"
38
38
  INVALID_REQUIREMENT = "ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER"
39
39
  UNREACHABLE_GIT = %r{ERR_PNPM_FETCH_404[ [^:print:]]+GET (?<url>https://codeload\.github\.com/[^/]+/[^/]+)/}
40
+ FORBIDDEN_PACKAGE = /ERR_PNPM_FETCH_403[ [^:print:]]+GET (?<dependency_url>.*): Forbidden - 403/
40
41
  MISSING_PACKAGE = /ERR_PNPM_FETCH_404[ [^:print:]]+GET (?<dependency_url>.*): Not Found - 404/
42
+ UNAUTHORIZED_PACKAGE = /ERR_PNPM_FETCH_401[ [^:print:]]+GET (?<dependency_url>.*): Unauthorized - 401/
41
43
 
42
44
  def run_pnpm_update(pnpm_lock:)
43
45
  SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
@@ -90,18 +92,20 @@ module Dependabot
90
92
  end
91
93
 
92
94
  if error_message.match?(UNREACHABLE_GIT)
93
- dependency_url = error_message.match(UNREACHABLE_GIT).named_captures.fetch("url")
95
+ url = error_message.match(UNREACHABLE_GIT).named_captures.fetch("url")
94
96
 
95
- raise Dependabot::GitDependenciesNotReachable, dependency_url
97
+ raise Dependabot::GitDependenciesNotReachable, url
96
98
  end
97
99
 
98
- raise unless error_message.match?(MISSING_PACKAGE)
100
+ [FORBIDDEN_PACKAGE, MISSING_PACKAGE, UNAUTHORIZED_PACKAGE].each do |regexp|
101
+ next unless error_message.match?(regexp)
99
102
 
100
- dependency_url = error_message.match(MISSING_PACKAGE)
101
- .named_captures["dependency_url"]
103
+ dependency_url = error_message.match(regexp).named_captures["dependency_url"]
102
104
 
103
- package_name = RegistryParser.new(resolved_url: dependency_url, credentials: credentials).dependency_name
104
- raise_missing_package_error(package_name, error_message, pnpm_lock)
105
+ raise_package_access_error(dependency_url, pnpm_lock)
106
+ end
107
+
108
+ raise
105
109
  end
106
110
 
107
111
  def raise_resolvability_error(error_message, pnpm_lock)
@@ -111,7 +115,8 @@ module Dependabot
111
115
  raise Dependabot::DependencyFileNotResolvable, msg
112
116
  end
113
117
 
114
- def raise_missing_package_error(package_name, _error_message, pnpm_lock)
118
+ def raise_package_access_error(dependency_url, pnpm_lock)
119
+ package_name = RegistryParser.new(resolved_url: dependency_url, credentials: credentials).dependency_name
115
120
  missing_dep = lockfile_dependencies(pnpm_lock)
116
121
  .find { |dep| dep.name == package_name }
117
122
 
@@ -186,7 +186,7 @@ module Dependabot
186
186
  end
187
187
 
188
188
  def yarn_berry_args
189
- Helpers.yarn_berry_args
189
+ @yarn_berry_args ||= Helpers.yarn_berry_args
190
190
  end
191
191
 
192
192
  def run_yarn_top_level_updater(top_level_dependency_updates:)
@@ -228,9 +228,9 @@ module Dependabot
228
228
  # Local path error: When installing a git dependency which
229
229
  # is using local file paths for sub-dependencies (e.g. unbuilt yarn
230
230
  # workspace project)
231
- sub_dep_local_path_err = 'Package "" refers to a non-existing file'
231
+ sub_dep_local_path_err = "refers to a non-existing file"
232
232
  if error_message.match?(INVALID_PACKAGE) ||
233
- error_message.start_with?(sub_dep_local_path_err)
233
+ error_message.include?(sub_dep_local_path_err)
234
234
  raise_resolvability_error(error_message, yarn_lock)
235
235
  end
236
236
 
@@ -295,7 +295,8 @@ module Dependabot
295
295
  handle_timeout(error_message, yarn_lock) if error_message.match?(TIMEOUT_FETCHING_PACKAGE)
296
296
 
297
297
  if error_message.start_with?("Couldn't find any versions") ||
298
- error_message.include?(": Not found")
298
+ error_message.include?(": Not found") ||
299
+ error_message.include?("Couldn't find match for")
299
300
 
300
301
  raise_resolvability_error(error_message, yarn_lock) unless resolvable_before_update?(yarn_lock)
301
302
 
@@ -4,6 +4,9 @@
4
4
  module Dependabot
5
5
  module NpmAndYarn
6
6
  module Helpers
7
+ YARN_PATH_NOT_FOUND =
8
+ /^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/
9
+
7
10
  def self.npm_version(lockfile_content)
8
11
  "npm#{npm_version_numeric(lockfile_content)}"
9
12
  end
@@ -51,12 +54,33 @@ module Dependabot
51
54
  end
52
55
 
53
56
  def self.yarn_major_version
54
- @yarn_major_version ||= fetch_yarn_major_version
55
- end
56
-
57
- def self.fetch_yarn_major_version
57
+ retries = 0
58
58
  output = SharedHelpers.run_shell_command("yarn --version")
59
59
  Version.new(output).major
60
+ rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
61
+ # Should never happen, can probably be removed once this settles
62
+ raise "Failed to replace ENV, not sure why" if T.must(retries).positive?
63
+
64
+ message = e.message
65
+
66
+ missing_env_var_regex = %r{Environment variable not found \((?:[^)]+)\) in #{Dir.pwd}/(?<path>\S+)}
67
+
68
+ if message.match?(missing_env_var_regex)
69
+ match = T.must(message.match(missing_env_var_regex))
70
+ path = T.must(match.named_captures["path"])
71
+
72
+ File.write(path, File.read(path).gsub(/\$\{[^}-]+\}/, ""))
73
+ retries = T.must(retries) + 1
74
+
75
+ retry
76
+ end
77
+
78
+ if YARN_PATH_NOT_FOUND.match?(message)
79
+ error = T.must(T.must(YARN_PATH_NOT_FOUND.match(message))[:error]).sub(Dir.pwd, ".")
80
+ raise MisconfiguredTooling.new("Yarn", error)
81
+ end
82
+
83
+ raise
60
84
  end
61
85
 
62
86
  def self.yarn_zero_install?
@@ -84,15 +108,21 @@ module Dependabot
84
108
  yarn_major_version >= 3 && (yarn_zero_install? || yarn_offline_cache?)
85
109
  end
86
110
 
111
+ def self.yarn_berry_disable_scripts?
112
+ yarn_major_version == 2 || !yarn_zero_install?
113
+ end
114
+
115
+ def self.yarn_4_or_higher?
116
+ yarn_major_version >= 4
117
+ end
118
+
87
119
  def self.setup_yarn_berry
88
120
  # Always disable immutable installs so yarn's CI detection doesn't prevent updates.
89
121
  SharedHelpers.run_shell_command("yarn config set enableImmutableInstalls false")
90
122
  # Do not generate a cache if offline cache disabled. Otherwise side effects may confuse further checks
91
123
  SharedHelpers.run_shell_command("yarn config set enableGlobalCache true") unless yarn_berry_skip_build?
92
124
  # We never want to execute postinstall scripts, either set this config or mode=skip-build must be set
93
- if yarn_major_version == 2 || !yarn_zero_install?
94
- SharedHelpers.run_shell_command("yarn config set enableScripts false")
95
- end
125
+ SharedHelpers.run_shell_command("yarn config set enableScripts false") if yarn_berry_disable_scripts?
96
126
  if (http_proxy = ENV.fetch("HTTP_PROXY", false))
97
127
  SharedHelpers.run_shell_command("yarn config set httpProxy #{http_proxy}")
98
128
  end
@@ -101,7 +131,7 @@ module Dependabot
101
131
  end
102
132
  return unless (ca_file_path = ENV.fetch("NODE_EXTRA_CA_CERTS", false))
103
133
 
104
- if yarn_major_version >= 4
134
+ if yarn_4_or_higher?
105
135
  SharedHelpers.run_shell_command("yarn config set httpsCaFilePath #{ca_file_path}")
106
136
  else
107
137
  SharedHelpers.run_shell_command("yarn config set caFilePath #{ca_file_path}")
@@ -1,4 +1,4 @@
1
- # typed: false
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
@@ -38,7 +38,7 @@ module Dependabot
38
38
  resolved_url
39
39
  end
40
40
 
41
- url_base.split("/")[3..-1].join("/").gsub("%2F", "/")
41
+ url_base[/@.*/].gsub("%2F", "/").split("/")[0..1].join("/")
42
42
  end
43
43
 
44
44
  private
@@ -225,17 +225,24 @@ module Dependabot
225
225
 
226
226
  @yanked[version] =
227
227
  begin
228
- status = Dependabot::RegistryClient.get(
229
- url: dependency_url + "/#{version}",
230
- headers: registry_auth_headers
231
- ).status
232
-
233
- if status == 404 && dependency_registry != "registry.npmjs.org"
234
- # Some registries don't handle escaped package names properly
228
+ if dependency_registry == "registry.npmjs.org"
229
+ status = Dependabot::RegistryClient.head(
230
+ url: registry_finder.tarball_url(version),
231
+ headers: registry_auth_headers
232
+ ).status
233
+ else
235
234
  status = Dependabot::RegistryClient.get(
236
- url: dependency_url.gsub("%2F", "/") + "/#{version}",
235
+ url: dependency_url + "/#{version}",
237
236
  headers: registry_auth_headers
238
237
  ).status
238
+
239
+ if status == 404
240
+ # Some registries don't handle escaped package names properly
241
+ status = Dependabot::RegistryClient.get(
242
+ url: dependency_url.gsub("%2F", "/") + "/#{version}",
243
+ headers: registry_auth_headers
244
+ ).status
245
+ end
239
246
  end
240
247
 
241
248
  version_not_found = status == 404
@@ -39,7 +39,14 @@ module Dependabot
39
39
  end
40
40
 
41
41
  def dependency_url
42
- "#{registry_url.gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
42
+ "#{registry_url}/#{escaped_dependency_name}"
43
+ end
44
+
45
+ def tarball_url(version)
46
+ version_without_build_metadata = version.to_s.gsub(/\+.*/, "")
47
+
48
+ # Dependency name needs to be unescaped since tarball URLs don't always work with escaped slashes
49
+ "#{registry_url}/#{dependency.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz"
43
50
  end
44
51
 
45
52
  def self.central_registry?(registry)
@@ -85,16 +92,21 @@ module Dependabot
85
92
  end
86
93
 
87
94
  def registry_url
88
- return registry if registry.start_with?("http")
89
-
90
- protocol =
91
- if registry_source_url
92
- registry_source_url.split("://").first
95
+ url =
96
+ if registry.start_with?("http")
97
+ registry
93
98
  else
94
- "https"
99
+ protocol =
100
+ if registry_source_url
101
+ registry_source_url.split("://").first
102
+ else
103
+ "https"
104
+ end
105
+
106
+ "#{protocol}://#{registry}"
95
107
  end
96
108
 
97
- "#{protocol}://#{registry}"
109
+ url.gsub(%r{/+$}, "")
98
110
  end
99
111
 
100
112
  def auth_header_for(token)
@@ -274,6 +286,10 @@ module Dependabot
274
286
  dependency.name.gsub("/", "%2F")
275
287
  end
276
288
 
289
+ def scopeless_name
290
+ dependency.name.split("/").last
291
+ end
292
+
277
293
  def registry_source_url
278
294
  sources = dependency.requirements
279
295
  .map { |r| r.fetch(:source) }.uniq.compact
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.237.0
4
+ version: 0.238.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2023-12-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.237.0
19
+ version: 0.238.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.237.0
26
+ version: 0.238.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -310,7 +310,7 @@ licenses:
310
310
  - Nonstandard
311
311
  metadata:
312
312
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
313
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.237.0
313
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.238.0
314
314
  post_install_message:
315
315
  rdoc_options: []
316
316
  require_paths: