dependabot-npm_and_yarn 0.295.0 → 0.296.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 +4 -4
- data/lib/dependabot/npm_and_yarn/constraint_helper.rb +125 -72
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +5 -0
- data/lib/dependabot/npm_and_yarn/file_parser.rb +1 -6
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb +32 -1
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +17 -7
- data/lib/dependabot/npm_and_yarn/file_updater.rb +91 -34
- data/lib/dependabot/npm_and_yarn/helpers.rb +7 -0
- data/lib/dependabot/npm_and_yarn/package_manager.rb +19 -9
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40c0445c84d264374459bc6e6d333d051a116ec598ae0e208536123242a2a56b
|
4
|
+
data.tar.gz: 208a49c91628dda0ada48108de1ef09b07b8ca7b4f12df008906689e5ad173f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1a2bc9cfa98144e171f653efedd490ccf0eb02176c29496a5ee288d83a54b83f8390e3a7d67b98fa544ef3f5e4074080101d8115414fdc34cb4c3ec075a2f25
|
7
|
+
data.tar.gz: da594bed80a0ad0c2e0fef360bb026f176020c0edc1c665e4e4823f882edd72e85e07a52b5a2036c78620f0889a0e050d0342462b21f6671a8d6daaa5976cbb3
|
@@ -8,30 +8,51 @@ module Dependabot
|
|
8
8
|
module ConstraintHelper
|
9
9
|
extend T::Sig
|
10
10
|
|
11
|
-
INVALID = "invalid" # Invalid constraint
|
12
11
|
# Regex Components for Semantic Versioning
|
13
12
|
DIGIT = "\\d+" # Matches a single number (e.g., "1")
|
14
13
|
PRERELEASE = "(?:-[a-zA-Z0-9.-]+)?" # Matches optional pre-release tag (e.g., "-alpha")
|
15
14
|
BUILD_METADATA = "(?:\\+[a-zA-Z0-9.-]+)?" # Matches optional build metadata (e.g., "+001")
|
16
|
-
DOT = "\\." # Matches a literal dot "."
|
17
15
|
|
18
16
|
# Matches semantic versions:
|
19
17
|
VERSION = T.let("#{DIGIT}(?:\\.#{DIGIT}){0,2}#{PRERELEASE}#{BUILD_METADATA}".freeze, String)
|
20
18
|
|
21
|
-
VERSION_REGEX = T.let(
|
19
|
+
VERSION_REGEX = T.let(/^#{VERSION}$/, Regexp)
|
22
20
|
|
23
|
-
#
|
24
|
-
|
21
|
+
# Base regex for SemVer (major.minor.patch[-prerelease][+build])
|
22
|
+
# This pattern extracts valid semantic versioning strings based on the SemVer 2.0 specification.
|
23
|
+
SEMVER_REGEX = T.let(/
|
24
|
+
(?<version>\d+\.\d+\.\d+) # Match major.minor.patch (e.g., 1.2.3)
|
25
|
+
(?:-(?<prerelease>[a-zA-Z0-9.-]+))? # Optional prerelease (e.g., -alpha.1, -rc.1, -beta.5)
|
26
|
+
(?:\+(?<build>[a-zA-Z0-9.-]+))? # Optional build metadata (e.g., +build.20231101, +exp.sha.5114f85)
|
27
|
+
/x, Regexp)
|
28
|
+
|
29
|
+
# Full SemVer validation regex (ensures the entire string is a valid SemVer)
|
30
|
+
# This ensures the entire input strictly follows SemVer, without extra characters before/after.
|
31
|
+
SEMVER_VALIDATION_REGEX = T.let(/^#{SEMVER_REGEX}$/, Regexp)
|
32
|
+
|
33
|
+
# SemVer constraint regex (supports package.json version constraints)
|
34
|
+
# This pattern ensures proper parsing of SemVer versions with optional operators.
|
35
|
+
SEMVER_CONSTRAINT_REGEX = T.let(/
|
36
|
+
(?: (>=|<=|>|<|=|~|\^)\s*)? # Make operators optional (e.g., >=, ^, ~)
|
37
|
+
(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?) # Match full SemVer versions
|
38
|
+
| (\*|latest) # Match wildcard (*) or 'latest'
|
39
|
+
/x, Regexp)
|
40
|
+
|
41
|
+
# /(>=|<=|>|<|=|~|\^)\s*(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)|(\*|latest)/
|
42
|
+
|
43
|
+
SEMVER_OPERATOR_REGEX = /^(>=|<=|>|<|~|\^|=)$/
|
25
44
|
|
26
45
|
# Constraint Types as Constants
|
27
|
-
CARET_CONSTRAINT_REGEX = T.let(
|
28
|
-
TILDE_CONSTRAINT_REGEX = T.let(
|
29
|
-
EXACT_CONSTRAINT_REGEX = T.let(
|
30
|
-
GREATER_THAN_EQUAL_REGEX = T.let(
|
31
|
-
LESS_THAN_EQUAL_REGEX = T.let(
|
32
|
-
GREATER_THAN_REGEX = T.let(
|
33
|
-
LESS_THAN_REGEX = T.let(
|
46
|
+
CARET_CONSTRAINT_REGEX = T.let(/^\^\s*(#{VERSION})$/, Regexp)
|
47
|
+
TILDE_CONSTRAINT_REGEX = T.let(/^~\s*(#{VERSION})$/, Regexp)
|
48
|
+
EXACT_CONSTRAINT_REGEX = T.let(/^\s*(#{VERSION})$/, Regexp)
|
49
|
+
GREATER_THAN_EQUAL_REGEX = T.let(/^>=\s*(#{VERSION})$/, Regexp)
|
50
|
+
LESS_THAN_EQUAL_REGEX = T.let(/^<=\s*(#{VERSION})$/, Regexp)
|
51
|
+
GREATER_THAN_REGEX = T.let(/^>\s*(#{VERSION})$/, Regexp)
|
52
|
+
LESS_THAN_REGEX = T.let(/^<\s*(#{VERSION})$/, Regexp)
|
34
53
|
WILDCARD_REGEX = T.let(/^\*$/, Regexp)
|
54
|
+
LATEST_REGEX = T.let(/^latest$/, Regexp)
|
55
|
+
SEMVER_CONSTANTS = ["*", "latest"].freeze
|
35
56
|
|
36
57
|
# Unified Regex for Valid Constraints
|
37
58
|
VALID_CONSTRAINT_REGEX = T.let(Regexp.union(
|
@@ -42,43 +63,10 @@ module Dependabot
|
|
42
63
|
LESS_THAN_EQUAL_REGEX,
|
43
64
|
GREATER_THAN_REGEX,
|
44
65
|
LESS_THAN_REGEX,
|
45
|
-
WILDCARD_REGEX
|
66
|
+
WILDCARD_REGEX,
|
67
|
+
LATEST_REGEX
|
46
68
|
).freeze, Regexp)
|
47
69
|
|
48
|
-
# Validates if the provided semver constraint expression from a `package.json` is valid.
|
49
|
-
# A valid semver constraint expression in `package.json` can consist of multiple groups
|
50
|
-
# separated by logical OR (`||`). Within each group, space-separated constraints are treated
|
51
|
-
# as logical AND. Each individual constraint must conform to the semver rules defined in
|
52
|
-
# `VALID_CONSTRAINT_REGEX`.
|
53
|
-
#
|
54
|
-
# Example (valid `package.json` semver constraints):
|
55
|
-
# ">=1.2.3 <2.0.0 || ~3.4.5" → Valid (space-separated constraints are AND, `||` is OR)
|
56
|
-
# "^1.0.0 || >=2.0.0 <3.0.0" → Valid (caret and range constraints combined)
|
57
|
-
# "1.2.3" → Valid (exact version)
|
58
|
-
# "*" → Valid (wildcard allows any version)
|
59
|
-
#
|
60
|
-
# Example (invalid `package.json` semver constraints):
|
61
|
-
# ">=1.2.3 && <2.0.0" → Invalid (`&&` is not valid in semver)
|
62
|
-
# ">=x.y.z" → Invalid (non-numeric version parts are not valid)
|
63
|
-
# "1.2.3 ||" → Invalid (trailing OR operator)
|
64
|
-
#
|
65
|
-
# @param constraint_expression [String] The semver constraint expression from `package.json` to validate.
|
66
|
-
# @return [T::Boolean] Returns true if the constraint expression is valid semver, false otherwise.
|
67
|
-
sig { params(constraint_expression: T.nilable(String)).returns(T::Boolean) }
|
68
|
-
def self.valid_constraint_expression?(constraint_expression)
|
69
|
-
normalized_constraint = constraint_expression&.strip
|
70
|
-
|
71
|
-
# Treat nil or empty input as valid (no constraints)
|
72
|
-
return true if normalized_constraint.nil? || normalized_constraint.empty?
|
73
|
-
|
74
|
-
# Split the expression by logical OR (`||`) into groups
|
75
|
-
normalized_constraint.split("||").reject(&:empty?).all? do |or_group|
|
76
|
-
or_group.split(/\s+/).reject(&:empty?).all? do |and_constraint|
|
77
|
-
and_constraint.match?(VALID_CONSTRAINT_REGEX)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
70
|
# Extract unique constraints from the given constraint expression.
|
83
71
|
# @param constraint_expression [T.nilable(String)] The semver constraint expression.
|
84
72
|
# @return [T::Array[String]] The list of unique Ruby-compatible constraints.
|
@@ -89,17 +77,92 @@ module Dependabot
|
|
89
77
|
)
|
90
78
|
.returns(T.nilable(T::Array[String]))
|
91
79
|
end
|
92
|
-
def self.
|
93
|
-
|
94
|
-
return [] if normalized_constraint.nil? || normalized_constraint.empty?
|
95
|
-
|
96
|
-
parsed_constraints = parse_constraints(normalized_constraint, dependabot_versions)
|
80
|
+
def self.extract_ruby_constraints(constraint_expression, dependabot_versions = nil)
|
81
|
+
parsed_constraints = parse_constraints(constraint_expression, dependabot_versions)
|
97
82
|
|
98
83
|
return nil unless parsed_constraints
|
99
84
|
|
100
85
|
parsed_constraints.filter_map { |parsed| parsed[:constraint] }
|
101
86
|
end
|
102
87
|
|
88
|
+
# rubocop:disable Metrics/AbcSize
|
89
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
90
|
+
# rubocop:disable Metrics/MethodLength
|
91
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
92
|
+
sig do
|
93
|
+
params(constraint_expression: T.nilable(String))
|
94
|
+
.returns(T.nilable(T::Array[String]))
|
95
|
+
end
|
96
|
+
def self.split_constraints(constraint_expression)
|
97
|
+
normalized_constraint = constraint_expression&.strip
|
98
|
+
return [] if normalized_constraint.nil? || normalized_constraint.empty?
|
99
|
+
|
100
|
+
# Split constraints by logical OR (`||`)
|
101
|
+
constraint_groups = normalized_constraint.split("||")
|
102
|
+
|
103
|
+
# Split constraints by logical AND (`,`)
|
104
|
+
constraint_groups = constraint_groups.map do |or_constraint|
|
105
|
+
or_constraint.split(",").map(&:strip)
|
106
|
+
end.flatten
|
107
|
+
|
108
|
+
constraint_groups = constraint_groups.map do |constraint|
|
109
|
+
tokens = constraint.split(/\s+/).map(&:strip)
|
110
|
+
|
111
|
+
and_constraints = []
|
112
|
+
|
113
|
+
previous = T.let(nil, T.nilable(String))
|
114
|
+
operator = T.let(false, T.nilable(T::Boolean))
|
115
|
+
wildcard = T.let(false, T::Boolean)
|
116
|
+
|
117
|
+
tokens.each do |token|
|
118
|
+
token = token.strip
|
119
|
+
next if token.empty?
|
120
|
+
|
121
|
+
# Invalid constraint if wildcard and anything else
|
122
|
+
return nil if wildcard
|
123
|
+
|
124
|
+
# If token is one of the operators (>=, <=, >, <, ~, ^, =)
|
125
|
+
if token.match?(SEMVER_OPERATOR_REGEX)
|
126
|
+
wildcard = false
|
127
|
+
operator = true
|
128
|
+
# If token is wildcard or latest
|
129
|
+
elsif token.match?(/(\*|latest)/)
|
130
|
+
and_constraints << token
|
131
|
+
wildcard = true
|
132
|
+
operator = false
|
133
|
+
# If token is exact version (e.g., "1.2.3")
|
134
|
+
elsif token.match(VERSION_REGEX)
|
135
|
+
and_constraints << if operator
|
136
|
+
"#{previous}#{token}"
|
137
|
+
else
|
138
|
+
token
|
139
|
+
end
|
140
|
+
wildcard = false
|
141
|
+
operator = false
|
142
|
+
# If token is a valid constraint (e.g., ">=1.2.3", "<=2.0.0")
|
143
|
+
elsif token.match(VALID_CONSTRAINT_REGEX)
|
144
|
+
return nil if operator
|
145
|
+
|
146
|
+
and_constraints << token
|
147
|
+
|
148
|
+
wildcard = false
|
149
|
+
operator = false
|
150
|
+
else
|
151
|
+
# invalid constraint
|
152
|
+
return nil
|
153
|
+
end
|
154
|
+
previous = token
|
155
|
+
end
|
156
|
+
and_constraints.uniq
|
157
|
+
end.flatten
|
158
|
+
constraint_groups if constraint_groups.any?
|
159
|
+
end
|
160
|
+
|
161
|
+
# rubocop:enable Metrics/AbcSize
|
162
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
163
|
+
# rubocop:enable Metrics/MethodLength
|
164
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
165
|
+
|
103
166
|
# Find the highest version from the given constraint expression.
|
104
167
|
# @param constraint_expression [T.nilable(String)] The semver constraint expression.
|
105
168
|
# @return [T.nilable(String)] The highest version, or nil if no versions are available.
|
@@ -111,10 +174,7 @@ module Dependabot
|
|
111
174
|
.returns(T.nilable(String))
|
112
175
|
end
|
113
176
|
def self.find_highest_version_from_constraint_expression(constraint_expression, dependabot_versions = nil)
|
114
|
-
|
115
|
-
return nil if normalized_constraint.nil? || normalized_constraint.empty?
|
116
|
-
|
117
|
-
parsed_constraints = parse_constraints(normalized_constraint, dependabot_versions)
|
177
|
+
parsed_constraints = parse_constraints(constraint_expression, dependabot_versions)
|
118
178
|
|
119
179
|
return nil unless parsed_constraints
|
120
180
|
|
@@ -136,20 +196,11 @@ module Dependabot
|
|
136
196
|
.returns(T.nilable(T::Array[T::Hash[Symbol, T.nilable(String)]]))
|
137
197
|
end
|
138
198
|
def self.parse_constraints(constraint_expression, dependabot_versions = nil)
|
139
|
-
|
140
|
-
|
141
|
-
# Return an empty array for valid "no constraints" (nil or empty input)
|
142
|
-
return [] if normalized_constraint.nil? || normalized_constraint.empty?
|
199
|
+
splitted_constraints = split_constraints(constraint_expression)
|
143
200
|
|
144
|
-
|
145
|
-
return nil unless valid_constraint_expression?(normalized_constraint)
|
201
|
+
return unless splitted_constraints
|
146
202
|
|
147
|
-
|
148
|
-
constraints = normalized_constraint.split("||").flat_map do |or_group|
|
149
|
-
or_group.strip.split(/\s+/).map(&:strip)
|
150
|
-
end.then do |normalized_constraints| # rubocop:disable Style/MultilineBlockChain
|
151
|
-
to_ruby_constraints_with_versions(normalized_constraints, dependabot_versions)
|
152
|
-
end.uniq { |parsed| parsed[:constraint] } # Ensure uniqueness based on `:constraint` # rubocop:disable Style/MultilineBlockChain
|
203
|
+
constraints = to_ruby_constraints_with_versions(splitted_constraints, dependabot_versions)
|
153
204
|
constraints
|
154
205
|
end
|
155
206
|
|
@@ -162,7 +213,7 @@ module Dependabot
|
|
162
213
|
def self.to_ruby_constraints_with_versions(constraints, dependabot_versions = [])
|
163
214
|
constraints.filter_map do |constraint|
|
164
215
|
parsed = to_ruby_constraint_with_version(constraint, dependabot_versions)
|
165
|
-
parsed if parsed
|
216
|
+
parsed if parsed
|
166
217
|
end.uniq
|
167
218
|
end
|
168
219
|
|
@@ -258,8 +309,10 @@ module Dependabot
|
|
258
309
|
version < Version.new(constraint_version)
|
259
310
|
end
|
260
311
|
{ constraint: "<#{Regexp.last_match(1)}", version: found_version&.to_s }
|
261
|
-
when WILDCARD_REGEX #
|
262
|
-
{ constraint: nil, version: dependabot_versions&.max&.to_s }
|
312
|
+
when WILDCARD_REGEX # No specific constraint, resolves to the highest available version
|
313
|
+
{ constraint: nil, version: dependabot_versions&.max&.to_s }
|
314
|
+
when LATEST_REGEX
|
315
|
+
{ constraint: nil, version: dependabot_versions&.max&.to_s } # Resolves to the latest available version
|
263
316
|
end
|
264
317
|
end
|
265
318
|
|
@@ -292,7 +345,7 @@ module Dependabot
|
|
292
345
|
def self.version_components(full_version)
|
293
346
|
return [] if full_version.nil?
|
294
347
|
|
295
|
-
match = full_version.match(
|
348
|
+
match = full_version.match(SEMVER_VALIDATION_REGEX)
|
296
349
|
return [] unless match
|
297
350
|
|
298
351
|
version = match[:version]
|
@@ -342,6 +342,11 @@ module Dependabot
|
|
342
342
|
fetch_support_file(PNPMPackageManager::PNPM_WS_YML_FILENAME),
|
343
343
|
T.nilable(DependencyFile)
|
344
344
|
)
|
345
|
+
|
346
|
+
# Only fetch from parent directories if the file wasn't found initially
|
347
|
+
@pnpm_workspace_yaml ||= fetch_file_from_parent_directories(PNPMPackageManager::PNPM_WS_YML_FILENAME)
|
348
|
+
|
349
|
+
@pnpm_workspace_yaml
|
345
350
|
end
|
346
351
|
|
347
352
|
sig { returns(T.nilable(DependencyFile)) }
|
@@ -59,7 +59,7 @@ module Dependabot
|
|
59
59
|
dependency_set = DependencySet.new
|
60
60
|
dependency_set += manifest_dependencies
|
61
61
|
dependency_set += lockfile_dependencies
|
62
|
-
dependency_set += workspace_catalog_dependencies if
|
62
|
+
dependency_set += workspace_catalog_dependencies if pnpm_workspace_yml
|
63
63
|
|
64
64
|
dependencies = Helpers.dependencies_with_all_versions_metadata(dependency_set)
|
65
65
|
|
@@ -94,11 +94,6 @@ module Dependabot
|
|
94
94
|
|
95
95
|
private
|
96
96
|
|
97
|
-
sig { returns(T.nilable(T::Boolean)) }
|
98
|
-
def enable_pnpm_workspace_catalog?
|
99
|
-
pnpm_workspace_yml && Dependabot::Experiments.enabled?(:enable_pnpm_workspace_catalog)
|
100
|
-
end
|
101
|
-
|
102
97
|
sig { returns(PackageManagerHelper) }
|
103
98
|
def package_manager_helper
|
104
99
|
@package_manager_helper ||= T.let(
|
@@ -11,11 +11,15 @@ module Dependabot
|
|
11
11
|
class PackageJsonUpdater
|
12
12
|
extend T::Sig
|
13
13
|
|
14
|
+
LOCAL_PACKAGE = T.let([/portal:/, /file:/].freeze, T::Array[Regexp])
|
15
|
+
|
16
|
+
PATCH_PACKAGE = T.let([/patch:/].freeze, T::Array[Regexp])
|
17
|
+
|
14
18
|
sig do
|
15
19
|
params(
|
16
20
|
package_json: Dependabot::DependencyFile,
|
17
21
|
dependencies: T::Array[Dependabot::Dependency]
|
18
|
-
)
|
22
|
+
).void
|
19
23
|
end
|
20
24
|
def initialize(package_json:, dependencies:)
|
21
25
|
@package_json = package_json
|
@@ -115,6 +119,8 @@ module Dependabot
|
|
115
119
|
def updated_requirements(dependency)
|
116
120
|
return unless dependency.previous_requirements
|
117
121
|
|
122
|
+
preliminary_check_for_update(dependency)
|
123
|
+
|
118
124
|
updated_requirement_pairs =
|
119
125
|
dependency.requirements.zip(T.must(dependency.previous_requirements))
|
120
126
|
.reject do |new_req, old_req|
|
@@ -341,6 +347,31 @@ module Dependabot
|
|
341
347
|
|
342
348
|
0
|
343
349
|
end
|
350
|
+
|
351
|
+
sig { params(dependency: Dependabot::Dependency).void }
|
352
|
+
def preliminary_check_for_update(dependency)
|
353
|
+
T.must(dependency.previous_requirements).each do |req, _dep|
|
354
|
+
next if req.fetch(:requirement).nil?
|
355
|
+
|
356
|
+
# some deps are patched with local patches, we don't need to update them
|
357
|
+
if req.fetch(:requirement).match?(Regexp.union(PATCH_PACKAGE))
|
358
|
+
Dependabot.logger.info("Func: updated_requirements. dependency patched #{dependency.name}," \
|
359
|
+
" Requirement: '#{req.fetch(:requirement)}'")
|
360
|
+
|
361
|
+
raise DependencyFileNotResolvable,
|
362
|
+
"Dependency is patched locally, Update not required."
|
363
|
+
end
|
364
|
+
|
365
|
+
# some deps are added as local packages, we don't need to update them as they are referred to a local path
|
366
|
+
next unless req.fetch(:requirement).match?(Regexp.union(LOCAL_PACKAGE))
|
367
|
+
|
368
|
+
Dependabot.logger.info("Func: updated_requirements. local package #{dependency.name}," \
|
369
|
+
" Requirement: '#{req.fetch(:requirement)}'")
|
370
|
+
|
371
|
+
raise DependencyFileNotResolvable,
|
372
|
+
"Local package, Update not required."
|
373
|
+
end
|
374
|
+
end
|
344
375
|
end
|
345
376
|
end
|
346
377
|
end
|
@@ -24,11 +24,14 @@ module Dependabot
|
|
24
24
|
)
|
25
25
|
end
|
26
26
|
|
27
|
-
def updated_pnpm_lock_content(pnpm_lock)
|
27
|
+
def updated_pnpm_lock_content(pnpm_lock, updated_pnpm_workspace_content: nil)
|
28
28
|
@updated_pnpm_lock_content ||= {}
|
29
29
|
return @updated_pnpm_lock_content[pnpm_lock.name] if @updated_pnpm_lock_content[pnpm_lock.name]
|
30
30
|
|
31
|
-
new_content = run_pnpm_update(
|
31
|
+
new_content = run_pnpm_update(
|
32
|
+
pnpm_lock: pnpm_lock,
|
33
|
+
updated_pnpm_workspace_content: updated_pnpm_workspace_content
|
34
|
+
)
|
32
35
|
@updated_pnpm_lock_content[pnpm_lock.name] = new_content
|
33
36
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
34
37
|
handle_pnpm_lock_updater_error(e, pnpm_lock)
|
@@ -100,14 +103,17 @@ module Dependabot
|
|
100
103
|
# Peer dependencies configuration error
|
101
104
|
ERR_PNPM_PEER_DEP_ISSUES = /ERR_PNPM_PEER_DEP_ISSUES/
|
102
105
|
|
103
|
-
def run_pnpm_update(pnpm_lock:)
|
106
|
+
def run_pnpm_update(pnpm_lock:, updated_pnpm_workspace_content: nil)
|
104
107
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
105
108
|
File.write(".npmrc", npmrc_content(pnpm_lock))
|
106
109
|
|
107
110
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
+
if updated_pnpm_workspace_content
|
112
|
+
File.write("pnpm-workspace.yaml", updated_pnpm_workspace_content["pnpm-workspace.yaml"])
|
113
|
+
else
|
114
|
+
run_pnpm_update_packages
|
115
|
+
write_final_package_json_files
|
116
|
+
end
|
111
117
|
|
112
118
|
run_pnpm_install
|
113
119
|
|
@@ -140,11 +146,15 @@ module Dependabot
|
|
140
146
|
)
|
141
147
|
end
|
142
148
|
|
149
|
+
def workspace_files
|
150
|
+
@workspace_files ||= dependency_files.select { |f| f.name.end_with?("pnpm-workspace.yaml") }
|
151
|
+
end
|
152
|
+
|
143
153
|
def lockfile_dependencies(lockfile)
|
144
154
|
@lockfile_dependencies ||= {}
|
145
155
|
@lockfile_dependencies[lockfile.name] ||=
|
146
156
|
NpmAndYarn::FileParser.new(
|
147
|
-
dependency_files: [lockfile, *package_files],
|
157
|
+
dependency_files: [lockfile, *package_files, *workspace_files],
|
148
158
|
source: nil,
|
149
159
|
credentials: credentials
|
150
160
|
).parse
|
@@ -50,32 +50,21 @@ module Dependabot
|
|
50
50
|
]
|
51
51
|
end
|
52
52
|
|
53
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
54
53
|
sig { override.returns(T::Array[DependencyFile]) }
|
55
54
|
def updated_dependency_files
|
56
55
|
updated_files = T.let([], T::Array[DependencyFile])
|
57
56
|
|
58
57
|
updated_files += updated_manifest_files
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
updated_files += if pnpm_workspace.any?
|
59
|
+
update_pnpm_workspace_and_locks
|
60
|
+
else
|
61
|
+
updated_lockfiles
|
62
|
+
end
|
63
63
|
|
64
64
|
if updated_files.none?
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
all_transitive = dependencies.none?(&:top_level?)
|
69
|
-
# when there is no update in package.json
|
70
|
-
no_package_json_update = package_files.empty?
|
71
|
-
# handle the no change error for transitive dependency updates
|
72
|
-
if pnpm_locks.any? && dependencies.length.positive? && all_transitive && no_package_json_update
|
73
|
-
raise ToolFeatureNotSupported.new(
|
74
|
-
tool_name: "pnpm",
|
75
|
-
tool_type: "package_manager",
|
76
|
-
feature: "updating transitive dependencies"
|
77
|
-
)
|
78
|
-
end
|
65
|
+
if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error) && original_pnpm_locks.any?
|
66
|
+
raise_tool_not_supported_for_pnpm_if_transitive
|
67
|
+
raise_miss_configured_tooling_if_pnpm_subdirectory
|
79
68
|
end
|
80
69
|
|
81
70
|
raise NoChangeError.new(
|
@@ -94,10 +83,69 @@ module Dependabot
|
|
94
83
|
|
95
84
|
vendor_updated_files(updated_files)
|
96
85
|
end
|
97
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
98
86
|
|
99
87
|
private
|
100
88
|
|
89
|
+
sig { void }
|
90
|
+
def raise_tool_not_supported_for_pnpm_if_transitive
|
91
|
+
# ✅ Ensure there are dependencies and check if all are transitive
|
92
|
+
return if dependencies.empty? || dependencies.any?(&:top_level?)
|
93
|
+
|
94
|
+
raise ToolFeatureNotSupported.new(
|
95
|
+
tool_name: "pnpm",
|
96
|
+
tool_type: "package_manager",
|
97
|
+
feature: "updating transitive dependencies"
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
102
|
+
sig { void }
|
103
|
+
def raise_miss_configured_tooling_if_pnpm_subdirectory
|
104
|
+
workspace_files = original_pnpm_workspace
|
105
|
+
lockfiles = original_pnpm_locks
|
106
|
+
|
107
|
+
# ✅ Ensure `pnpm-workspace.yaml` is in a parent directory
|
108
|
+
return if workspace_files.empty?
|
109
|
+
return if workspace_files.any? { |f| f.directory == "/" }
|
110
|
+
return unless workspace_files.all? { |f| f.name.end_with?("../pnpm-workspace.yaml") }
|
111
|
+
|
112
|
+
# ✅ Ensure `pnpm-lock.yaml` is also in a parent directory
|
113
|
+
return if lockfiles.empty?
|
114
|
+
return if lockfiles.any? { |f| f.directory == "/" }
|
115
|
+
return unless lockfiles.all? { |f| f.name.end_with?("../pnpm-lock.yaml") }
|
116
|
+
|
117
|
+
# ❌ Raise error → Updating inside a subdirectory is misconfigured
|
118
|
+
raise MisconfiguredTooling.new(
|
119
|
+
"pnpm",
|
120
|
+
"Updating workspaces from inside a workspace subdirectory is not supported. " \
|
121
|
+
"Both `pnpm-lock.yaml` and `pnpm-workspace.yaml` exist in a parent directory. " \
|
122
|
+
"Dependabot should only update from the root workspace."
|
123
|
+
)
|
124
|
+
end
|
125
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
126
|
+
|
127
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
128
|
+
def update_pnpm_workspace_and_locks
|
129
|
+
workspace_updates = updated_pnpm_workspace_files
|
130
|
+
lock_updates = update_pnpm_locks
|
131
|
+
|
132
|
+
workspace_updates + lock_updates
|
133
|
+
end
|
134
|
+
|
135
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
136
|
+
def update_pnpm_locks
|
137
|
+
updated_files = []
|
138
|
+
pnpm_locks.each do |pnpm_lock|
|
139
|
+
next unless pnpm_lock_changed?(pnpm_lock)
|
140
|
+
|
141
|
+
updated_files << updated_file(
|
142
|
+
file: pnpm_lock,
|
143
|
+
content: updated_pnpm_lock_content(pnpm_lock)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
updated_files
|
147
|
+
end
|
148
|
+
|
101
149
|
sig { params(updated_files: T::Array[Dependabot::DependencyFile]).returns(T::Array[Dependabot::DependencyFile]) }
|
102
150
|
def vendor_updated_files(updated_files)
|
103
151
|
base_dir = T.must(updated_files.first).directory
|
@@ -222,6 +270,24 @@ module Dependabot
|
|
222
270
|
)
|
223
271
|
end
|
224
272
|
|
273
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
274
|
+
def original_pnpm_locks
|
275
|
+
@original_pnpm_locks ||= T.let(
|
276
|
+
dependency_files
|
277
|
+
.select { |f| f.name.end_with?("pnpm-lock.yaml") },
|
278
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
279
|
+
)
|
280
|
+
end
|
281
|
+
|
282
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
283
|
+
def original_pnpm_workspace
|
284
|
+
@original_pnpm_workspace ||= T.let(
|
285
|
+
dependency_files
|
286
|
+
.select { |f| f.name.end_with?("pnpm-workspace.yaml") },
|
287
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
225
291
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
226
292
|
def bun_locks
|
227
293
|
@bun_locks ||= T.let(
|
@@ -294,8 +360,6 @@ module Dependabot
|
|
294
360
|
end
|
295
361
|
end
|
296
362
|
|
297
|
-
# rubocop:disable Metrics/MethodLength
|
298
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
299
363
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
300
364
|
def updated_lockfiles
|
301
365
|
updated_files = []
|
@@ -309,14 +373,7 @@ module Dependabot
|
|
309
373
|
)
|
310
374
|
end
|
311
375
|
|
312
|
-
|
313
|
-
next unless pnpm_lock_changed?(pnpm_lock)
|
314
|
-
|
315
|
-
updated_files << updated_file(
|
316
|
-
file: pnpm_lock,
|
317
|
-
content: updated_pnpm_lock_content(pnpm_lock)
|
318
|
-
)
|
319
|
-
end
|
376
|
+
updated_files.concat(update_pnpm_locks)
|
320
377
|
|
321
378
|
bun_locks.each do |bun_lock|
|
322
379
|
next unless bun_lock_changed?(bun_lock)
|
@@ -347,9 +404,6 @@ module Dependabot
|
|
347
404
|
|
348
405
|
updated_files
|
349
406
|
end
|
350
|
-
# rubocop:enable Metrics/MethodLength
|
351
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
352
|
-
|
353
407
|
sig { params(yarn_lock: Dependabot::DependencyFile).returns(String) }
|
354
408
|
def updated_yarn_lock_content(yarn_lock)
|
355
409
|
@updated_yarn_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
@@ -361,7 +415,10 @@ module Dependabot
|
|
361
415
|
def updated_pnpm_lock_content(pnpm_lock)
|
362
416
|
@updated_pnpm_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
363
417
|
@updated_pnpm_lock_content[pnpm_lock.name] ||=
|
364
|
-
pnpm_lockfile_updater.updated_pnpm_lock_content(
|
418
|
+
pnpm_lockfile_updater.updated_pnpm_lock_content(
|
419
|
+
pnpm_lock,
|
420
|
+
updated_pnpm_workspace_content: @updated_pnpm_workspace_content
|
421
|
+
)
|
365
422
|
end
|
366
423
|
|
367
424
|
sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
|
@@ -467,6 +467,8 @@ module Dependabot
|
|
467
467
|
# Attempt to activate the local version of the package manager
|
468
468
|
sig { params(name: String).void }
|
469
469
|
def self.fallback_to_local_version(name)
|
470
|
+
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
471
|
+
|
470
472
|
Dependabot.logger.info("Falling back to activate the currently installed version of #{name}.")
|
471
473
|
|
472
474
|
# Fetch the currently installed version directly from the environment
|
@@ -553,6 +555,11 @@ module Dependabot
|
|
553
555
|
result
|
554
556
|
rescue StandardError => e
|
555
557
|
Dependabot.logger.error("Error running package manager command: #{full_command}, Error: #{e.message}")
|
558
|
+
if e.message.match?(/Response Code.*:.*404.*\(Not Found\)/) &&
|
559
|
+
e.message.include?("The remote server failed to provide the requested resource")
|
560
|
+
raise RegistryError.new(404, "The remote server failed to provide the requested resource")
|
561
|
+
end
|
562
|
+
|
556
563
|
raise
|
557
564
|
end
|
558
565
|
|
@@ -189,8 +189,10 @@ module Dependabot
|
|
189
189
|
@language_requirement ||= find_engine_constraints_as_requirement(Language::NAME)
|
190
190
|
end
|
191
191
|
|
192
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
193
|
+
# rubocop:disable Metrics/AbcSize
|
192
194
|
sig { params(name: String).returns(T.nilable(Requirement)) }
|
193
|
-
def find_engine_constraints_as_requirement(name)
|
195
|
+
def find_engine_constraints_as_requirement(name)
|
194
196
|
Dependabot.logger.info("Processing engine constraints for #{name}")
|
195
197
|
|
196
198
|
return nil unless @engines.is_a?(Hash) && @engines[name]
|
@@ -199,8 +201,7 @@ module Dependabot
|
|
199
201
|
return nil if raw_constraint.empty?
|
200
202
|
|
201
203
|
if Dependabot::Experiments.enabled?(:enable_engine_version_detection)
|
202
|
-
constraints = ConstraintHelper.
|
203
|
-
|
204
|
+
constraints = ConstraintHelper.extract_ruby_constraints(raw_constraint)
|
204
205
|
# When constraints are invalid we return constraints array nil
|
205
206
|
if constraints.nil?
|
206
207
|
Dependabot.logger.warn(
|
@@ -225,12 +226,16 @@ module Dependabot
|
|
225
226
|
|
226
227
|
end
|
227
228
|
|
228
|
-
|
229
|
-
|
229
|
+
if constraints && !constraints.empty?
|
230
|
+
Dependabot.logger.info("Parsed constraints for #{name}: #{constraints.join(', ')}")
|
231
|
+
Requirement.new(constraints)
|
232
|
+
end
|
230
233
|
rescue StandardError => e
|
231
234
|
Dependabot.logger.error("Error processing constraints for #{name}: #{e.message}")
|
232
235
|
nil
|
233
236
|
end
|
237
|
+
# rubocop:enable Metrics/AbcSize
|
238
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
234
239
|
|
235
240
|
# rubocop:disable Metrics/CyclomaticComplexity
|
236
241
|
# rubocop:disable Metrics/AbcSize
|
@@ -412,10 +417,15 @@ module Dependabot
|
|
412
417
|
|
413
418
|
Dependabot.logger.info("Installing \"#{name}@#{version}\"")
|
414
419
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
420
|
+
begin
|
421
|
+
SharedHelpers.run_shell_command(
|
422
|
+
"corepack install #{name}@#{version} --global --cache-only",
|
423
|
+
fingerprint: "corepack install <name>@<version> --global --cache-only"
|
424
|
+
)
|
425
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
426
|
+
Dependabot.logger.error("Error installing #{name}@#{version}: #{e.message}")
|
427
|
+
Helpers.fallback_to_local_version(name)
|
428
|
+
end
|
419
429
|
end
|
420
430
|
|
421
431
|
sig { params(name: T.nilable(String)).returns(String) }
|
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.
|
4
|
+
version: 0.296.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-06 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.
|
19
|
+
version: 0.296.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.
|
26
|
+
version: 0.296.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: debug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -356,7 +356,7 @@ licenses:
|
|
356
356
|
- MIT
|
357
357
|
metadata:
|
358
358
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
359
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
359
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.296.0
|
360
360
|
post_install_message:
|
361
361
|
rdoc_options: []
|
362
362
|
require_paths:
|