dependabot-npm_and_yarn 0.350.0 → 0.352.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/helpers/build +1 -1
- data/helpers/lib/npm6/peer-dependency-checker.js +0 -1
- data/helpers/lib/npm6/subdependency-updater.js +0 -1
- data/helpers/lib/npm6/updater.js +0 -1
- data/helpers/package-lock.json +1027 -1010
- data/helpers/package.json +3 -3
- data/lib/dependabot/npm_and_yarn/constraint_helper.rb +9 -2
- data/lib/dependabot/npm_and_yarn/dependency_grapher/lockfile_generator.rb +217 -0
- data/lib/dependabot/npm_and_yarn/dependency_grapher.rb +174 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +34 -15
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +4 -0
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +4 -0
- data/lib/dependabot/npm_and_yarn/helpers.rb +59 -1
- data/lib/dependabot/npm_and_yarn/native_helpers.rb +0 -6
- data/lib/dependabot/npm_and_yarn/registry_helper.rb +12 -4
- data/lib/dependabot/npm_and_yarn.rb +1 -0
- metadata +6 -4
data/helpers/package.json
CHANGED
|
@@ -261,9 +261,16 @@ module Dependabot
|
|
|
261
261
|
|
|
262
262
|
full_version = Regexp.last_match(1)
|
|
263
263
|
|
|
264
|
-
# Normalize version:
|
|
264
|
+
# Normalize version: ensure it has major.minor.patch format
|
|
265
265
|
version_parts = T.must(full_version).split(".")
|
|
266
|
-
full_version =
|
|
266
|
+
full_version = case version_parts.length
|
|
267
|
+
when 1
|
|
268
|
+
"#{full_version}.0.0" # major only -> major.0.0
|
|
269
|
+
when 2
|
|
270
|
+
"#{full_version}.0" # major.minor -> major.minor.0
|
|
271
|
+
else
|
|
272
|
+
full_version # already major.minor.patch
|
|
273
|
+
end
|
|
267
274
|
|
|
268
275
|
_, major, minor = version_components(full_version)
|
|
269
276
|
return nil if major.nil?
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "dependabot/dependency_file"
|
|
7
|
+
require "dependabot/shared_helpers"
|
|
8
|
+
require "dependabot/npm_and_yarn/helpers"
|
|
9
|
+
require "dependabot/npm_and_yarn/package_manager"
|
|
10
|
+
require "dependabot/npm_and_yarn/file_updater/npmrc_builder"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module NpmAndYarn
|
|
14
|
+
class DependencyGrapher < Dependabot::DependencyGraphers::Base
|
|
15
|
+
class LockfileGenerator
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig do
|
|
19
|
+
params(
|
|
20
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
21
|
+
package_manager: String,
|
|
22
|
+
credentials: T::Array[Dependabot::Credential]
|
|
23
|
+
).void
|
|
24
|
+
end
|
|
25
|
+
def initialize(dependency_files:, package_manager:, credentials:)
|
|
26
|
+
@dependency_files = dependency_files
|
|
27
|
+
@package_manager = package_manager
|
|
28
|
+
@credentials = credentials
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
32
|
+
def generate
|
|
33
|
+
SharedHelpers.in_a_temporary_directory do
|
|
34
|
+
write_temporary_files
|
|
35
|
+
run_lockfile_generation
|
|
36
|
+
read_generated_lockfile
|
|
37
|
+
end
|
|
38
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
39
|
+
handle_generation_error(e)
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
46
|
+
attr_reader :dependency_files
|
|
47
|
+
|
|
48
|
+
sig { returns(String) }
|
|
49
|
+
attr_reader :package_manager
|
|
50
|
+
|
|
51
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
52
|
+
attr_reader :credentials
|
|
53
|
+
|
|
54
|
+
sig { void }
|
|
55
|
+
def write_temporary_files
|
|
56
|
+
write_package_files
|
|
57
|
+
write_npmrc
|
|
58
|
+
write_yarnrc if yarn?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
sig { void }
|
|
62
|
+
def write_package_files
|
|
63
|
+
dependency_files.each do |file|
|
|
64
|
+
next unless file.name.end_with?(
|
|
65
|
+
"package.json", ".npmrc", ".yarnrc", ".yarnrc.yml", "pnpm-workspace.yaml"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
path = file.name
|
|
69
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
70
|
+
File.write(path, file.content)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { void }
|
|
75
|
+
def write_npmrc
|
|
76
|
+
# Skip if .npmrc already exists in dependency files (already written above)
|
|
77
|
+
return if dependency_files.any? { |f| f.name.end_with?(".npmrc") }
|
|
78
|
+
|
|
79
|
+
# Use NpmrcBuilder to generate npmrc content from credentials
|
|
80
|
+
npmrc_content = FileUpdater::NpmrcBuilder.new(
|
|
81
|
+
credentials: credentials,
|
|
82
|
+
dependency_files: dependency_files
|
|
83
|
+
).npmrc_content
|
|
84
|
+
|
|
85
|
+
return if npmrc_content.empty?
|
|
86
|
+
|
|
87
|
+
File.write(".npmrc", npmrc_content)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
sig { void }
|
|
91
|
+
def write_yarnrc
|
|
92
|
+
return unless yarn_berry?
|
|
93
|
+
|
|
94
|
+
# For Yarn Berry, set up the environment properly
|
|
95
|
+
Helpers.setup_yarn_berry
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
sig { void }
|
|
99
|
+
def run_lockfile_generation
|
|
100
|
+
Dependabot.logger.info("Generating lockfile using #{package_manager}")
|
|
101
|
+
|
|
102
|
+
case package_manager
|
|
103
|
+
when NpmPackageManager::NAME
|
|
104
|
+
run_npm_lockfile_generation
|
|
105
|
+
when YarnPackageManager::NAME
|
|
106
|
+
run_yarn_lockfile_generation
|
|
107
|
+
when PNPMPackageManager::NAME
|
|
108
|
+
run_pnpm_lockfile_generation
|
|
109
|
+
else
|
|
110
|
+
raise "Unknown package manager: #{package_manager}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sig { void }
|
|
115
|
+
def run_npm_lockfile_generation
|
|
116
|
+
# Set dependency files and credentials for automatic env variable injection
|
|
117
|
+
Helpers.dependency_files = dependency_files
|
|
118
|
+
Helpers.credentials = credentials
|
|
119
|
+
|
|
120
|
+
# Use --package-lock-only to generate lockfile without installing node_modules
|
|
121
|
+
# Use --ignore-scripts to prevent running any scripts
|
|
122
|
+
# Use --force to ignore platform checks
|
|
123
|
+
command = "install --package-lock-only --ignore-scripts --force"
|
|
124
|
+
Helpers.run_npm_command(command, fingerprint: command)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
sig { void }
|
|
128
|
+
def run_yarn_lockfile_generation
|
|
129
|
+
if yarn_berry?
|
|
130
|
+
# Yarn Berry (2+) uses different commands
|
|
131
|
+
Helpers.run_yarn_command("install --mode update-lockfile")
|
|
132
|
+
else
|
|
133
|
+
# Yarn Classic (1.x)
|
|
134
|
+
SharedHelpers.run_shell_command(
|
|
135
|
+
"yarn install --ignore-scripts --frozen-lockfile=false",
|
|
136
|
+
fingerprint: "yarn install --ignore-scripts --frozen-lockfile=false"
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
sig { void }
|
|
142
|
+
def run_pnpm_lockfile_generation
|
|
143
|
+
# pnpm uses --lockfile-only to generate lockfile without installing
|
|
144
|
+
command = "install --lockfile-only --ignore-scripts"
|
|
145
|
+
Helpers.run_pnpm_command(command, fingerprint: command)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
149
|
+
def read_generated_lockfile
|
|
150
|
+
lockfile_name = expected_lockfile_name
|
|
151
|
+
|
|
152
|
+
unless File.exist?(lockfile_name)
|
|
153
|
+
Dependabot.logger.warn("Lockfile #{lockfile_name} was not generated")
|
|
154
|
+
return nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
content = File.read(lockfile_name)
|
|
158
|
+
|
|
159
|
+
Dependabot::DependencyFile.new(
|
|
160
|
+
name: lockfile_name,
|
|
161
|
+
content: content,
|
|
162
|
+
directory: "/"
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
sig { returns(String) }
|
|
167
|
+
def expected_lockfile_name
|
|
168
|
+
case package_manager
|
|
169
|
+
when NpmPackageManager::NAME
|
|
170
|
+
NpmPackageManager::LOCKFILE_NAME
|
|
171
|
+
when YarnPackageManager::NAME
|
|
172
|
+
YarnPackageManager::LOCKFILE_NAME
|
|
173
|
+
when PNPMPackageManager::NAME
|
|
174
|
+
PNPMPackageManager::LOCKFILE_NAME
|
|
175
|
+
else
|
|
176
|
+
"package-lock.json"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
sig { returns(T::Boolean) }
|
|
181
|
+
def yarn?
|
|
182
|
+
package_manager == YarnPackageManager::NAME
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
sig { returns(T::Boolean) }
|
|
186
|
+
def yarn_berry?
|
|
187
|
+
return false unless yarn?
|
|
188
|
+
|
|
189
|
+
# Check for .yarnrc.yml which indicates Yarn Berry
|
|
190
|
+
dependency_files.any? { |f| f.name.end_with?(".yarnrc.yml") }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
sig { params(error: SharedHelpers::HelperSubprocessFailed).void }
|
|
194
|
+
def handle_generation_error(error)
|
|
195
|
+
Dependabot.logger.error(
|
|
196
|
+
"Failed to generate lockfile with #{package_manager}: #{error.message}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Log more details for debugging
|
|
200
|
+
if error.message.include?("ERESOLVE")
|
|
201
|
+
Dependabot.logger.error(
|
|
202
|
+
"Dependency resolution failed. This may be due to conflicting peer dependencies."
|
|
203
|
+
)
|
|
204
|
+
elsif error.message.include?("ENOTFOUND") || error.message.include?("ETIMEDOUT")
|
|
205
|
+
Dependabot.logger.error(
|
|
206
|
+
"Network error while generating lockfile. Registry may be unreachable."
|
|
207
|
+
)
|
|
208
|
+
elsif error.message.include?("401") || error.message.include?("403")
|
|
209
|
+
Dependabot.logger.error(
|
|
210
|
+
"Authentication error. Check that credentials are configured correctly."
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "dependabot/dependency_graphers"
|
|
7
|
+
require "dependabot/dependency_graphers/base"
|
|
8
|
+
require "dependabot/npm_and_yarn/file_parser"
|
|
9
|
+
require "dependabot/npm_and_yarn/package_manager"
|
|
10
|
+
require "dependabot/npm_and_yarn/helpers"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module NpmAndYarn
|
|
14
|
+
class DependencyGrapher < Dependabot::DependencyGraphers::Base
|
|
15
|
+
extend T::Sig
|
|
16
|
+
|
|
17
|
+
require_relative "dependency_grapher/lockfile_generator"
|
|
18
|
+
|
|
19
|
+
sig { override.returns(Dependabot::DependencyFile) }
|
|
20
|
+
def relevant_dependency_file
|
|
21
|
+
# Prefer lockfile if present, otherwise use package.json
|
|
22
|
+
lockfile || package_json
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig { override.void }
|
|
26
|
+
def prepare!
|
|
27
|
+
if lockfile.nil?
|
|
28
|
+
Dependabot.logger.info("No lockfile found, generating ephemeral lockfile for dependency graphing")
|
|
29
|
+
generate_ephemeral_lockfile!
|
|
30
|
+
emit_missing_lockfile_warning!
|
|
31
|
+
end
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
sig { returns(Dependabot::DependencyFile) }
|
|
38
|
+
def package_json
|
|
39
|
+
return T.must(@package_json) if defined?(@package_json)
|
|
40
|
+
|
|
41
|
+
T.must(
|
|
42
|
+
@package_json = T.let(
|
|
43
|
+
T.must(dependency_files.find { |f| f.name.end_with?(NpmAndYarn::MANIFEST_FILENAME) }),
|
|
44
|
+
T.nilable(Dependabot::DependencyFile)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
50
|
+
def lockfile
|
|
51
|
+
return @lockfile if defined?(@lockfile)
|
|
52
|
+
|
|
53
|
+
@lockfile = T.let(
|
|
54
|
+
dependency_files.find do |f|
|
|
55
|
+
f.name.end_with?(
|
|
56
|
+
NpmPackageManager::LOCKFILE_NAME,
|
|
57
|
+
YarnPackageManager::LOCKFILE_NAME,
|
|
58
|
+
PNPMPackageManager::LOCKFILE_NAME
|
|
59
|
+
)
|
|
60
|
+
end,
|
|
61
|
+
T.nilable(Dependabot::DependencyFile)
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
|
66
|
+
def parsed_package_json
|
|
67
|
+
@parsed_package_json ||= T.let(
|
|
68
|
+
JSON.parse(T.must(package_json.content)),
|
|
69
|
+
T.nilable(T::Hash[String, T.untyped])
|
|
70
|
+
)
|
|
71
|
+
rescue JSON::ParserError
|
|
72
|
+
{}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
|
|
76
|
+
def lockfiles_hash
|
|
77
|
+
{
|
|
78
|
+
npm: dependency_files.find { |f| f.name.end_with?(NpmPackageManager::LOCKFILE_NAME) },
|
|
79
|
+
yarn: dependency_files.find { |f| f.name.end_with?(YarnPackageManager::LOCKFILE_NAME) },
|
|
80
|
+
pnpm: dependency_files.find { |f| f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME) }
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sig { returns(String) }
|
|
85
|
+
def detected_package_manager
|
|
86
|
+
@detected_package_manager ||= T.let(
|
|
87
|
+
PackageManagerDetector.new(
|
|
88
|
+
lockfiles_hash,
|
|
89
|
+
parsed_package_json
|
|
90
|
+
).detect_package_manager,
|
|
91
|
+
T.nilable(String)
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
sig { void }
|
|
96
|
+
def generate_ephemeral_lockfile!
|
|
97
|
+
generator = LockfileGenerator.new(
|
|
98
|
+
dependency_files: dependency_files,
|
|
99
|
+
package_manager: detected_package_manager,
|
|
100
|
+
credentials: file_parser.credentials
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
ephemeral_lockfile = generator.generate
|
|
104
|
+
return unless ephemeral_lockfile
|
|
105
|
+
|
|
106
|
+
# Inject the ephemeral lockfile into the dependency files
|
|
107
|
+
# so the file parser can use it
|
|
108
|
+
inject_ephemeral_lockfile(ephemeral_lockfile)
|
|
109
|
+
|
|
110
|
+
Dependabot.logger.info(
|
|
111
|
+
"Successfully generated ephemeral #{ephemeral_lockfile.name} for dependency graphing"
|
|
112
|
+
)
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
Dependabot.logger.warn(
|
|
115
|
+
"Failed to generate ephemeral lockfile: #{e.message}. " \
|
|
116
|
+
"Dependency versions may not be resolved."
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
sig { params(ephemeral_lockfile: Dependabot::DependencyFile).void }
|
|
121
|
+
def inject_ephemeral_lockfile(ephemeral_lockfile)
|
|
122
|
+
dependency_files << ephemeral_lockfile
|
|
123
|
+
|
|
124
|
+
# Clear our cached lockfile reference so it picks up the new one
|
|
125
|
+
@lockfile = T.let(nil, T.nilable(Dependabot::DependencyFile))
|
|
126
|
+
|
|
127
|
+
# Clear the FileParser's memoized lockfile references so it will
|
|
128
|
+
# find the newly injected lockfile when parse is called
|
|
129
|
+
file_parser.instance_variable_set(:@package_lock, nil)
|
|
130
|
+
file_parser.instance_variable_set(:@yarn_lock, nil)
|
|
131
|
+
file_parser.instance_variable_set(:@pnpm_lock, nil)
|
|
132
|
+
# Also clear the lockfile_parser which parses lockfile content
|
|
133
|
+
file_parser.instance_variable_set(:@lockfile_parser, nil)
|
|
134
|
+
# Also clear the package_manager_helper which caches lockfile info
|
|
135
|
+
file_parser.instance_variable_set(:@package_manager_helper, nil)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { void }
|
|
139
|
+
def emit_missing_lockfile_warning!
|
|
140
|
+
Dependabot.logger.warn(
|
|
141
|
+
"No lockfile was found in this repository. " \
|
|
142
|
+
"Dependabot generated a temporary lockfile to determine exact dependency versions.\n\n" \
|
|
143
|
+
"To ensure consistent builds and security scanning, we recommend:\n" \
|
|
144
|
+
" - Committing your package-lock.json (npm), yarn.lock (yarn), or pnpm-lock.yaml (pnpm)\n" \
|
|
145
|
+
" - Setting up a scheduled Dependabot graph job to periodically scan for changes\n\n" \
|
|
146
|
+
"Without a committed lockfile, resolved dependency versions may change between scans " \
|
|
147
|
+
"due to new package releases."
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Fetches subdependencies for a given dependency.
|
|
152
|
+
# For npm/yarn/pnpm, we can extract this from the lockfile parser if available.
|
|
153
|
+
sig { override.params(dependency: Dependabot::Dependency).returns(T::Array[String]) }
|
|
154
|
+
def fetch_subdependencies(dependency)
|
|
155
|
+
# Check if the parser has attached depends_on metadata
|
|
156
|
+
dependency.metadata.fetch(:depends_on, [])
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
sig { override.params(_dependency: Dependabot::Dependency).returns(String) }
|
|
160
|
+
def purl_pkg_for(_dependency)
|
|
161
|
+
"npm"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# npm packages use the package name as-is for the purl
|
|
165
|
+
sig { params(dependency: Dependabot::Dependency).returns(String) }
|
|
166
|
+
def purl_name_for(dependency)
|
|
167
|
+
# Handle scoped packages: @scope/name -> %40scope/name (URL encoded @)
|
|
168
|
+
dependency.name.sub(/^@/, "%40")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
Dependabot::DependencyGraphers.register("npm_and_yarn", Dependabot::NpmAndYarn::DependencyGrapher)
|
|
@@ -139,6 +139,10 @@ module Dependabot
|
|
|
139
139
|
return lockfile.content if npmrc_disables_lockfile?
|
|
140
140
|
return lockfile.content unless updatable_dependencies.any?
|
|
141
141
|
|
|
142
|
+
# Set dependency files and credentials for automatic env variable injection
|
|
143
|
+
Helpers.dependency_files = dependency_files
|
|
144
|
+
Helpers.credentials = credentials
|
|
145
|
+
|
|
142
146
|
@updated_lockfile_content ||= T.let(
|
|
143
147
|
SharedHelpers.in_a_temporary_directory do
|
|
144
148
|
write_temporary_dependency_files
|
|
@@ -284,14 +288,18 @@ module Dependabot
|
|
|
284
288
|
# TODO: Update the npm 6 updater to use these args as we currently
|
|
285
289
|
# do the same in the js updater helper, we've kept it separate for
|
|
286
290
|
# the npm 7 rollout
|
|
287
|
-
install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
|
|
288
291
|
|
|
289
|
-
|
|
292
|
+
top_level_dependencies.each do |dependency|
|
|
293
|
+
install_args = [npm_install_args(dependency)]
|
|
294
|
+
is_optional = optional_dependency?(dependency)
|
|
295
|
+
run_npm_install_lockfile_only(install_args, has_optional_dependencies: is_optional)
|
|
296
|
+
end
|
|
290
297
|
|
|
291
298
|
unless dependencies_in_current_package_json
|
|
292
299
|
File.write(T.must(package_json).name, previous_package_json)
|
|
293
300
|
|
|
294
|
-
|
|
301
|
+
# Run final install without specific dependencies
|
|
302
|
+
run_npm_install_lockfile_only([], has_optional_dependencies: false)
|
|
295
303
|
end
|
|
296
304
|
|
|
297
305
|
{ lockfile_basename => File.read(lockfile_basename) }
|
|
@@ -335,31 +343,35 @@ module Dependabot
|
|
|
335
343
|
#
|
|
336
344
|
# Other npm flags:
|
|
337
345
|
# - `--force` ignores checks for platform (os, cpu) and engines
|
|
338
|
-
# - `--dry-run=false` the updater sets a global .npmrc with `dry-run: true`
|
|
339
|
-
# to work around an issue in npm 6, we don't want that here
|
|
340
346
|
# - `--ignore-scripts` disables prepare and prepack scripts which are
|
|
341
347
|
# run when installing git dependencies
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
348
|
+
# - `--save-optional` when updating optional dependencies to ensure they
|
|
349
|
+
# stay in optionalDependencies section and allow version upgrades
|
|
350
|
+
sig { params(install_args: T::Array[String], has_optional_dependencies: T::Boolean).returns(String) }
|
|
351
|
+
def run_npm_install_lockfile_only(install_args = [], has_optional_dependencies: false)
|
|
352
|
+
command_args = [
|
|
345
353
|
"install",
|
|
346
354
|
*install_args,
|
|
347
355
|
"--force",
|
|
348
|
-
"--dry-run",
|
|
349
|
-
"false",
|
|
350
356
|
"--ignore-scripts",
|
|
351
357
|
"--package-lock-only"
|
|
352
|
-
]
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
command_args << "--save-optional" if has_optional_dependencies
|
|
353
361
|
|
|
354
|
-
|
|
362
|
+
command = command_args.join(" ")
|
|
363
|
+
|
|
364
|
+
fingerprint_args = [
|
|
355
365
|
"install",
|
|
356
366
|
install_args.empty? ? "" : "<install_args>",
|
|
357
367
|
"--force",
|
|
358
|
-
"--dry-run",
|
|
359
|
-
"false",
|
|
360
368
|
"--ignore-scripts",
|
|
361
369
|
"--package-lock-only"
|
|
362
|
-
]
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
fingerprint_args << "--save-optional" if has_optional_dependencies
|
|
373
|
+
|
|
374
|
+
fingerprint = fingerprint_args.join(" ")
|
|
363
375
|
|
|
364
376
|
Helpers.run_npm_command(command, fingerprint: fingerprint)
|
|
365
377
|
end
|
|
@@ -408,6 +420,13 @@ module Dependabot
|
|
|
408
420
|
end
|
|
409
421
|
end
|
|
410
422
|
|
|
423
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
|
424
|
+
def optional_dependency?(dependency)
|
|
425
|
+
dependency.requirements.any? do |req|
|
|
426
|
+
req[:groups]&.include?("optionalDependencies")
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
411
430
|
# rubocop:disable Metrics/AbcSize
|
|
412
431
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
413
432
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -143,6 +143,10 @@ module Dependabot
|
|
|
143
143
|
.returns(String)
|
|
144
144
|
end
|
|
145
145
|
def run_pnpm_update(pnpm_lock:, updated_pnpm_workspace_content: nil)
|
|
146
|
+
# Set dependency files and credentials for automatic env variable injection
|
|
147
|
+
Helpers.dependency_files = dependency_files
|
|
148
|
+
Helpers.credentials = credentials
|
|
149
|
+
|
|
146
150
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
|
147
151
|
File.write(".npmrc", npmrc_content(pnpm_lock))
|
|
148
152
|
|
|
@@ -89,6 +89,10 @@ module Dependabot
|
|
|
89
89
|
|
|
90
90
|
sig { params(yarn_lock: Dependabot::DependencyFile).returns(String) }
|
|
91
91
|
def updated_yarn_lock(yarn_lock)
|
|
92
|
+
# Set dependency files and credentials for automatic env variable injection
|
|
93
|
+
Helpers.dependency_files = dependency_files
|
|
94
|
+
Helpers.credentials = credentials
|
|
95
|
+
|
|
92
96
|
base_dir = T.must(dependency_files.first).directory
|
|
93
97
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
|
94
98
|
write_temporary_dependency_files(yarn_lock)
|
|
@@ -5,6 +5,8 @@ require "dependabot/dependency"
|
|
|
5
5
|
require "dependabot/file_parsers"
|
|
6
6
|
require "dependabot/file_parsers/base"
|
|
7
7
|
require "dependabot/shared_helpers"
|
|
8
|
+
require "dependabot/npm_and_yarn/registry_helper"
|
|
9
|
+
require "dependabot/experiments"
|
|
8
10
|
require "sorbet-runtime"
|
|
9
11
|
|
|
10
12
|
module Dependabot
|
|
@@ -12,6 +14,32 @@ module Dependabot
|
|
|
12
14
|
module Helpers # rubocop:disable Metrics/ModuleLength
|
|
13
15
|
extend T::Sig
|
|
14
16
|
|
|
17
|
+
# Thread-local storage for dependency files and credentials
|
|
18
|
+
# This allows automatic env variable injection without passing parameters everywhere
|
|
19
|
+
class << self
|
|
20
|
+
extend T::Sig
|
|
21
|
+
|
|
22
|
+
sig { params(files: T::Array[Dependabot::DependencyFile]).void }
|
|
23
|
+
def dependency_files=(files)
|
|
24
|
+
Thread.current[:npm_and_yarn_dependency_files] = files
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { returns(T.nilable(T::Array[Dependabot::DependencyFile])) }
|
|
28
|
+
def dependency_files
|
|
29
|
+
T.cast(Thread.current[:npm_and_yarn_dependency_files], T.nilable(T::Array[Dependabot::DependencyFile]))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { params(creds: T::Array[Dependabot::Credential]).void }
|
|
33
|
+
def credentials=(creds)
|
|
34
|
+
Thread.current[:npm_and_yarn_credentials] = creds
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { returns(T.nilable(T::Array[Dependabot::Credential])) }
|
|
38
|
+
def credentials
|
|
39
|
+
T.cast(Thread.current[:npm_and_yarn_credentials], T.nilable(T::Array[Dependabot::Credential]))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
15
43
|
YARN_PATH_NOT_FOUND =
|
|
16
44
|
/^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/
|
|
17
45
|
|
|
@@ -268,13 +296,14 @@ module Dependabot
|
|
|
268
296
|
).returns(String)
|
|
269
297
|
end
|
|
270
298
|
def self.run_npm_command(command, fingerprint: command, env: nil)
|
|
299
|
+
merged_env = merge_corepack_env(env)
|
|
271
300
|
if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
|
|
272
301
|
package_manager_run_command(
|
|
273
302
|
NpmPackageManager::NAME,
|
|
274
303
|
command,
|
|
275
304
|
fingerprint: fingerprint,
|
|
276
305
|
output_observer: ->(output) { command_observer(output) },
|
|
277
|
-
env:
|
|
306
|
+
env: merged_env
|
|
278
307
|
)
|
|
279
308
|
else
|
|
280
309
|
Dependabot::SharedHelpers.run_shell_command(
|
|
@@ -510,6 +539,35 @@ module Dependabot
|
|
|
510
539
|
raise
|
|
511
540
|
end
|
|
512
541
|
|
|
542
|
+
sig { params(env: T.nilable(T::Hash[String, String])).returns(T.nilable(T::Hash[String, String])) }
|
|
543
|
+
def self.merge_corepack_env(env)
|
|
544
|
+
corepack_env = build_corepack_env_variables
|
|
545
|
+
return env if corepack_env.nil? || corepack_env.empty?
|
|
546
|
+
return corepack_env if env.nil?
|
|
547
|
+
|
|
548
|
+
corepack_env.merge(env)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
sig { returns(T.nilable(T::Hash[String, String])) }
|
|
552
|
+
def self.build_corepack_env_variables
|
|
553
|
+
return nil unless Dependabot::Experiments.enabled?(:enable_private_registry_for_corepack)
|
|
554
|
+
return nil if dependency_files.nil? || credentials.nil?
|
|
555
|
+
|
|
556
|
+
files = T.must(dependency_files)
|
|
557
|
+
creds = T.must(credentials)
|
|
558
|
+
|
|
559
|
+
registry_helper = RegistryHelper.new(
|
|
560
|
+
{
|
|
561
|
+
npmrc: files.find { |f| f.name.end_with?(".npmrc") },
|
|
562
|
+
yarnrc: files.find { |f| f.name.end_with?(".yarnrc") && !f.name.end_with?(".yarnrc.yml") },
|
|
563
|
+
yarnrc_yml: files.find { |f| f.name.end_with?(".yarnrc.yml") }
|
|
564
|
+
},
|
|
565
|
+
creds
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
registry_helper.find_corepack_env_variables
|
|
569
|
+
end
|
|
570
|
+
|
|
513
571
|
private_class_method :run_single_yarn_command
|
|
514
572
|
|
|
515
573
|
sig { params(pnpm_lock: DependencyFile).returns(T.nilable(String)) }
|
|
@@ -25,16 +25,12 @@ module Dependabot
|
|
|
25
25
|
def self.run_npm8_subdependency_update_command(dependency_names)
|
|
26
26
|
# NOTE: npm options
|
|
27
27
|
# - `--force` ignores checks for platform (os, cpu) and engines
|
|
28
|
-
# - `--dry-run=false` the updater sets a global .npmrc with dry-run: true to
|
|
29
|
-
# work around an issue in npm 6, we don't want that here
|
|
30
28
|
# - `--ignore-scripts` disables prepare and prepack scripts which are run
|
|
31
29
|
# when installing git dependencies
|
|
32
30
|
command = [
|
|
33
31
|
"update",
|
|
34
32
|
*dependency_names,
|
|
35
33
|
"--force",
|
|
36
|
-
"--dry-run",
|
|
37
|
-
"false",
|
|
38
34
|
"--ignore-scripts",
|
|
39
35
|
"--package-lock-only"
|
|
40
36
|
].join(" ")
|
|
@@ -43,8 +39,6 @@ module Dependabot
|
|
|
43
39
|
"update",
|
|
44
40
|
"<dependency_names>",
|
|
45
41
|
"--force",
|
|
46
|
-
"--dry-run",
|
|
47
|
-
"false",
|
|
48
42
|
"--ignore-scripts",
|
|
49
43
|
"--package-lock-only"
|
|
50
44
|
].join(" ")
|