dependabot-npm_and_yarn 0.349.0 → 0.351.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 +9 -2
- data/lib/dependabot/npm_and_yarn/dependency_grapher/lockfile_generator.rb +214 -0
- data/lib/dependabot/npm_and_yarn/dependency_grapher.rb +174 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +49 -10
- data/lib/dependabot/npm_and_yarn/helpers.rb +12 -12
- data/lib/dependabot/npm_and_yarn/registry_helper.rb +7 -1
- data/lib/dependabot/npm_and_yarn.rb +1 -0
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58fb5e0f894d8303fda1c4ee5a250a5153a209047fb47b99b827a042bdedcc43
|
|
4
|
+
data.tar.gz: 4f7dbc1168c7672fa786fbdb253def7cd208064a2722ce63e94b387ab306d117
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7a6a0aeb758960adc54fca50c16142de988e0feed5b719c6b1edd34af72469e7829d906016e6f020a5190e9b50288df1eb4d2cc0745197b11bc4da8e4dca6f5
|
|
7
|
+
data.tar.gz: ae780bf718e902b6a6c46658727ac4bdd7b94271474f11014450d4288abc05613fdb927d329b3e20176fd6fb8b322453bf7fd36132ad7f62fc815f7b17264bd3
|
|
@@ -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,214 @@
|
|
|
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
|
+
# Use --package-lock-only to generate lockfile without installing node_modules
|
|
117
|
+
# Use --ignore-scripts to prevent running any scripts
|
|
118
|
+
# Use --force to ignore platform checks
|
|
119
|
+
# Use --dry-run false because global .npmrc may have dry-run: true set
|
|
120
|
+
command = "install --package-lock-only --ignore-scripts --force --dry-run false"
|
|
121
|
+
Helpers.run_npm_command(command, fingerprint: command)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { void }
|
|
125
|
+
def run_yarn_lockfile_generation
|
|
126
|
+
if yarn_berry?
|
|
127
|
+
# Yarn Berry (2+) uses different commands
|
|
128
|
+
Helpers.run_yarn_command("install --mode update-lockfile")
|
|
129
|
+
else
|
|
130
|
+
# Yarn Classic (1.x)
|
|
131
|
+
SharedHelpers.run_shell_command(
|
|
132
|
+
"yarn install --ignore-scripts --frozen-lockfile=false",
|
|
133
|
+
fingerprint: "yarn install --ignore-scripts --frozen-lockfile=false"
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { void }
|
|
139
|
+
def run_pnpm_lockfile_generation
|
|
140
|
+
# pnpm uses --lockfile-only to generate lockfile without installing
|
|
141
|
+
command = "install --lockfile-only --ignore-scripts"
|
|
142
|
+
Helpers.run_pnpm_command(command, fingerprint: command)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
146
|
+
def read_generated_lockfile
|
|
147
|
+
lockfile_name = expected_lockfile_name
|
|
148
|
+
|
|
149
|
+
unless File.exist?(lockfile_name)
|
|
150
|
+
Dependabot.logger.warn("Lockfile #{lockfile_name} was not generated")
|
|
151
|
+
return nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
content = File.read(lockfile_name)
|
|
155
|
+
|
|
156
|
+
Dependabot::DependencyFile.new(
|
|
157
|
+
name: lockfile_name,
|
|
158
|
+
content: content,
|
|
159
|
+
directory: "/"
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sig { returns(String) }
|
|
164
|
+
def expected_lockfile_name
|
|
165
|
+
case package_manager
|
|
166
|
+
when NpmPackageManager::NAME
|
|
167
|
+
NpmPackageManager::LOCKFILE_NAME
|
|
168
|
+
when YarnPackageManager::NAME
|
|
169
|
+
YarnPackageManager::LOCKFILE_NAME
|
|
170
|
+
when PNPMPackageManager::NAME
|
|
171
|
+
PNPMPackageManager::LOCKFILE_NAME
|
|
172
|
+
else
|
|
173
|
+
"package-lock.json"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
sig { returns(T::Boolean) }
|
|
178
|
+
def yarn?
|
|
179
|
+
package_manager == YarnPackageManager::NAME
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
sig { returns(T::Boolean) }
|
|
183
|
+
def yarn_berry?
|
|
184
|
+
return false unless yarn?
|
|
185
|
+
|
|
186
|
+
# Check for .yarnrc.yml which indicates Yarn Berry
|
|
187
|
+
dependency_files.any? { |f| f.name.end_with?(".yarnrc.yml") }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sig { params(error: SharedHelpers::HelperSubprocessFailed).void }
|
|
191
|
+
def handle_generation_error(error)
|
|
192
|
+
Dependabot.logger.error(
|
|
193
|
+
"Failed to generate lockfile with #{package_manager}: #{error.message}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Log more details for debugging
|
|
197
|
+
if error.message.include?("ERESOLVE")
|
|
198
|
+
Dependabot.logger.error(
|
|
199
|
+
"Dependency resolution failed. This may be due to conflicting peer dependencies."
|
|
200
|
+
)
|
|
201
|
+
elsif error.message.include?("ENOTFOUND") || error.message.include?("ETIMEDOUT")
|
|
202
|
+
Dependabot.logger.error(
|
|
203
|
+
"Network error while generating lockfile. Registry may be unreachable."
|
|
204
|
+
)
|
|
205
|
+
elsif error.message.include?("401") || error.message.include?("403")
|
|
206
|
+
Dependabot.logger.error(
|
|
207
|
+
"Authentication error. Check that credentials are configured correctly."
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
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?(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)
|
|
@@ -284,14 +284,18 @@ module Dependabot
|
|
|
284
284
|
# TODO: Update the npm 6 updater to use these args as we currently
|
|
285
285
|
# do the same in the js updater helper, we've kept it separate for
|
|
286
286
|
# the npm 7 rollout
|
|
287
|
-
install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
|
|
288
287
|
|
|
289
|
-
|
|
288
|
+
top_level_dependencies.each do |dependency|
|
|
289
|
+
install_args = [npm_install_args(dependency)]
|
|
290
|
+
is_optional = optional_dependency?(dependency)
|
|
291
|
+
run_npm_install_lockfile_only(install_args, has_optional_dependencies: is_optional)
|
|
292
|
+
end
|
|
290
293
|
|
|
291
294
|
unless dependencies_in_current_package_json
|
|
292
295
|
File.write(T.must(package_json).name, previous_package_json)
|
|
293
296
|
|
|
294
|
-
|
|
297
|
+
# Run final install without specific dependencies
|
|
298
|
+
run_npm_install_lockfile_only([], has_optional_dependencies: false)
|
|
295
299
|
end
|
|
296
300
|
|
|
297
301
|
{ lockfile_basename => File.read(lockfile_basename) }
|
|
@@ -339,9 +343,11 @@ module Dependabot
|
|
|
339
343
|
# to work around an issue in npm 6, we don't want that here
|
|
340
344
|
# - `--ignore-scripts` disables prepare and prepack scripts which are
|
|
341
345
|
# run when installing git dependencies
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
346
|
+
# - `--save-optional` when updating optional dependencies to ensure they
|
|
347
|
+
# stay in optionalDependencies section and allow version upgrades
|
|
348
|
+
sig { params(install_args: T::Array[String], has_optional_dependencies: T::Boolean).returns(String) }
|
|
349
|
+
def run_npm_install_lockfile_only(install_args = [], has_optional_dependencies: false)
|
|
350
|
+
command_args = [
|
|
345
351
|
"install",
|
|
346
352
|
*install_args,
|
|
347
353
|
"--force",
|
|
@@ -349,9 +355,13 @@ module Dependabot
|
|
|
349
355
|
"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",
|
|
@@ -359,9 +369,31 @@ module Dependabot
|
|
|
359
369
|
"false",
|
|
360
370
|
"--ignore-scripts",
|
|
361
371
|
"--package-lock-only"
|
|
362
|
-
]
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
fingerprint_args << "--save-optional" if has_optional_dependencies
|
|
375
|
+
|
|
376
|
+
fingerprint = fingerprint_args.join(" ")
|
|
377
|
+
|
|
378
|
+
env = build_registry_env_variables
|
|
379
|
+
|
|
380
|
+
Helpers.run_npm_command(command, fingerprint: fingerprint, env: env)
|
|
381
|
+
end
|
|
363
382
|
|
|
364
|
-
|
|
383
|
+
sig { returns(T.nilable(T::Hash[String, String])) }
|
|
384
|
+
def build_registry_env_variables
|
|
385
|
+
return nil unless Dependabot::Experiments.enabled?(:enable_private_registry_for_corepack)
|
|
386
|
+
|
|
387
|
+
registry_helper = NpmAndYarn::RegistryHelper.new(
|
|
388
|
+
{
|
|
389
|
+
npmrc: dependency_files.find { |f| f.name.end_with?(".npmrc") },
|
|
390
|
+
yarnrc: dependency_files.find { |f| f.name.end_with?(".yarnrc") && !f.name.end_with?(".yarnrc.yml") },
|
|
391
|
+
yarnrc_yml: dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
|
|
392
|
+
},
|
|
393
|
+
credentials
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
registry_helper.find_corepack_env_variables
|
|
365
397
|
end
|
|
366
398
|
|
|
367
399
|
sig { params(dependency: Dependabot::Dependency).returns(String) }
|
|
@@ -408,6 +440,13 @@ module Dependabot
|
|
|
408
440
|
end
|
|
409
441
|
end
|
|
410
442
|
|
|
443
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
|
444
|
+
def optional_dependency?(dependency)
|
|
445
|
+
dependency.requirements.any? do |req|
|
|
446
|
+
req[:groups]&.include?("optionalDependencies")
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
411
450
|
# rubocop:disable Metrics/AbcSize
|
|
412
451
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
413
452
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -16,10 +16,11 @@ module Dependabot
|
|
|
16
16
|
/^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/
|
|
17
17
|
|
|
18
18
|
# NPM Version Constants
|
|
19
|
+
NPM_V11 = 11
|
|
19
20
|
NPM_V10 = 10
|
|
20
21
|
NPM_V8 = 8
|
|
21
22
|
NPM_V6 = 6
|
|
22
|
-
NPM_DEFAULT_VERSION =
|
|
23
|
+
NPM_DEFAULT_VERSION = NPM_V11
|
|
23
24
|
|
|
24
25
|
# PNPM Version Constants
|
|
25
26
|
PNPM_V10 = 10
|
|
@@ -65,7 +66,7 @@ module Dependabot
|
|
|
65
66
|
lockfile_version = lockfile_version_str.to_i
|
|
66
67
|
|
|
67
68
|
# Using npm 8 as the default for lockfile_version > 2.
|
|
68
|
-
return
|
|
69
|
+
return NPM_V11 if lockfile_version >= 3
|
|
69
70
|
return NPM_V8 if lockfile_version >= 2
|
|
70
71
|
|
|
71
72
|
NPM_V6 if lockfile_version >= 1
|
|
@@ -362,7 +363,7 @@ module Dependabot
|
|
|
362
363
|
end
|
|
363
364
|
end
|
|
364
365
|
|
|
365
|
-
#
|
|
366
|
+
# Activate the package manager for specified version by using corepack
|
|
366
367
|
sig do
|
|
367
368
|
params(
|
|
368
369
|
name: String,
|
|
@@ -375,22 +376,20 @@ module Dependabot
|
|
|
375
376
|
Dependabot.logger.info("Installing \"#{name}@#{version}\"")
|
|
376
377
|
|
|
377
378
|
begin
|
|
378
|
-
# Try to
|
|
379
|
-
output =
|
|
379
|
+
# Try to activate the specified version
|
|
380
|
+
output = package_manager_activate(name, version, env: env)
|
|
380
381
|
|
|
381
382
|
# Confirm success based on the output
|
|
382
|
-
if output.
|
|
383
|
+
if output.include?("immediate activation...")
|
|
383
384
|
Dependabot.logger.info("#{name}@#{version} successfully installed.")
|
|
384
385
|
|
|
385
386
|
Dependabot.logger.info("Activating currently installed version of #{name}: #{version}")
|
|
386
|
-
package_manager_activate(name, version)
|
|
387
|
-
|
|
388
387
|
else
|
|
389
388
|
Dependabot.logger.error("Corepack installation output unexpected: #{output}")
|
|
390
389
|
fallback_to_local_version(name)
|
|
391
390
|
end
|
|
392
391
|
rescue StandardError => e
|
|
393
|
-
Dependabot.logger.error("Error
|
|
392
|
+
Dependabot.logger.error("Error activating #{name}@#{version}: #{e.message}")
|
|
394
393
|
fallback_to_local_version(name)
|
|
395
394
|
end
|
|
396
395
|
|
|
@@ -435,13 +434,14 @@ module Dependabot
|
|
|
435
434
|
end
|
|
436
435
|
|
|
437
436
|
# Prepare the package manager for use by using corepack
|
|
438
|
-
sig { params(name: String, version: String).returns(String) }
|
|
439
|
-
def self.package_manager_activate(name, version)
|
|
437
|
+
sig { params(name: String, version: String, env: T.nilable(T::Hash[String, String])).returns(String) }
|
|
438
|
+
def self.package_manager_activate(name, version, env: {})
|
|
440
439
|
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
|
441
440
|
|
|
442
441
|
Dependabot::SharedHelpers.run_shell_command(
|
|
443
442
|
"corepack prepare #{name}@#{version} --activate",
|
|
444
|
-
fingerprint: "corepack prepare <name>@<version> --activate"
|
|
443
|
+
fingerprint: "corepack prepare <name>@<version> --activate",
|
|
444
|
+
env: env
|
|
445
445
|
).strip
|
|
446
446
|
end
|
|
447
447
|
|
|
@@ -39,7 +39,13 @@ module Dependabot
|
|
|
39
39
|
registry_info = find_registry_and_token
|
|
40
40
|
|
|
41
41
|
env_variables = {}
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
if registry_info[:registry] # Prevent the https from being stripped in the process
|
|
44
|
+
registry = registry_info[:registry]
|
|
45
|
+
registry = "https://#{T.must(registry)}" unless T.must(registry).start_with?("http://", "https://")
|
|
46
|
+
env_variables[COREPACK_NPM_REGISTRY_ENV] = registry
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
env_variables[COREPACK_NPM_TOKEN_ENV] = registry_info[:auth_token] if registry_info[:auth_token]
|
|
44
50
|
|
|
45
51
|
env_variables
|
|
@@ -10,6 +10,7 @@ require "dependabot/npm_and_yarn/file_updater"
|
|
|
10
10
|
require "dependabot/npm_and_yarn/metadata_finder"
|
|
11
11
|
require "dependabot/npm_and_yarn/requirement"
|
|
12
12
|
require "dependabot/npm_and_yarn/version"
|
|
13
|
+
require "dependabot/npm_and_yarn/dependency_grapher"
|
|
13
14
|
|
|
14
15
|
require "dependabot/pull_request_creator/labeler"
|
|
15
16
|
Dependabot::PullRequestCreator::Labeler
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.351.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.351.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.351.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -313,6 +313,8 @@ files:
|
|
|
313
313
|
- lib/dependabot/npm_and_yarn.rb
|
|
314
314
|
- lib/dependabot/npm_and_yarn/constraint_helper.rb
|
|
315
315
|
- lib/dependabot/npm_and_yarn/dependency_files_filterer.rb
|
|
316
|
+
- lib/dependabot/npm_and_yarn/dependency_grapher.rb
|
|
317
|
+
- lib/dependabot/npm_and_yarn/dependency_grapher/lockfile_generator.rb
|
|
316
318
|
- lib/dependabot/npm_and_yarn/file_fetcher.rb
|
|
317
319
|
- lib/dependabot/npm_and_yarn/file_fetcher/path_dependency_builder.rb
|
|
318
320
|
- lib/dependabot/npm_and_yarn/file_parser.rb
|
|
@@ -359,7 +361,7 @@ licenses:
|
|
|
359
361
|
- MIT
|
|
360
362
|
metadata:
|
|
361
363
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
362
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
364
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.351.0
|
|
363
365
|
rdoc_options: []
|
|
364
366
|
require_paths:
|
|
365
367
|
- lib
|