dependabot-npm_and_yarn 0.132.0 → 0.133.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/build +2 -2
- data/helpers/package-lock.json +28609 -0
- data/helpers/package.json +2 -2
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +40 -16
- data/lib/dependabot/npm_and_yarn/file_updater.rb +9 -19
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +263 -220
- metadata +21 -7
- data/helpers/yarn.lock +0 -7958
data/helpers/package.json
CHANGED
@@ -10,14 +10,14 @@
|
|
10
10
|
},
|
11
11
|
"dependencies": {
|
12
12
|
"@dependabot/yarn-lib": "^1.21.1",
|
13
|
-
"@npmcli/arborist": "^2.2.
|
13
|
+
"@npmcli/arborist": "^2.2.2",
|
14
14
|
"detect-indent": "^6.0.0",
|
15
15
|
"npm6": "npm:npm@6.14.11",
|
16
16
|
"npm7": "npm:npm@7.4.0",
|
17
17
|
"semver": "^7.3.4"
|
18
18
|
},
|
19
19
|
"devDependencies": {
|
20
|
-
"eslint": "^7.
|
20
|
+
"eslint": "^7.20.0",
|
21
21
|
"eslint-config-prettier": "^7.2.0",
|
22
22
|
"jest": "^26.6.3",
|
23
23
|
"prettier": "^2.2.1",
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "dependabot/dependency_file"
|
4
4
|
require "dependabot/npm_and_yarn/file_parser"
|
5
|
+
require "dependabot/npm_and_yarn/helpers"
|
5
6
|
|
6
7
|
module Dependabot
|
7
8
|
module NpmAndYarn
|
@@ -23,23 +24,9 @@ module Dependabot
|
|
23
24
|
potential_lockfiles_for_manifest(manifest_name).each do |lockfile|
|
24
25
|
details =
|
25
26
|
if [*package_locks, *shrinkwraps].include?(lockfile)
|
26
|
-
|
27
|
-
parsed_lockfile.dig("dependencies", dependency_name)
|
27
|
+
npm_lockfile_details(lockfile, dependency_name, manifest_name)
|
28
28
|
else
|
29
|
-
|
30
|
-
details_candidates =
|
31
|
-
parsed_yarn_lock.
|
32
|
-
select { |k, _| k.split(/(?<=\w)\@/)[0] == dependency_name }
|
33
|
-
|
34
|
-
# If there's only one entry for this dependency, use it, even if
|
35
|
-
# the requirement in the lockfile doesn't match
|
36
|
-
if details_candidates.one?
|
37
|
-
details_candidates.first.last
|
38
|
-
else
|
39
|
-
details_candidates.find do |k, _|
|
40
|
-
k.split(/(?<=\w)\@/)[1..-1].join("@") == requirement
|
41
|
-
end&.last
|
42
|
-
end
|
29
|
+
yarn_lockfile_details(lockfile, dependency_name, requirement, manifest_name)
|
43
30
|
end
|
44
31
|
|
45
32
|
return details if details
|
@@ -65,6 +52,43 @@ module Dependabot
|
|
65
52
|
compact
|
66
53
|
end
|
67
54
|
|
55
|
+
def npm_lockfile_details(lockfile, dependency_name, manifest_name)
|
56
|
+
parsed_lockfile = parse_package_lock(lockfile)
|
57
|
+
|
58
|
+
if Helpers.npm_version(lockfile.content) == "npm7"
|
59
|
+
parsed_lockfile.dig(
|
60
|
+
"packages",
|
61
|
+
node_modules_path(manifest_name, dependency_name)
|
62
|
+
)&.slice("version", "resolved", "integrity", "dev")
|
63
|
+
else
|
64
|
+
parsed_lockfile.dig("dependencies", dependency_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def yarn_lockfile_details(lockfile, dependency_name, requirement, _manifest_name)
|
69
|
+
parsed_yarn_lock = parse_yarn_lock(lockfile)
|
70
|
+
details_candidates =
|
71
|
+
parsed_yarn_lock.
|
72
|
+
select { |k, _| k.split(/(?<=\w)\@/)[0] == dependency_name }
|
73
|
+
|
74
|
+
# If there's only one entry for this dependency, use it, even if
|
75
|
+
# the requirement in the lockfile doesn't match
|
76
|
+
if details_candidates.one?
|
77
|
+
details_candidates.first.last
|
78
|
+
else
|
79
|
+
details_candidates.find do |k, _|
|
80
|
+
k.split(/(?<=\w)\@/)[1..-1].join("@") == requirement
|
81
|
+
end&.last
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def node_modules_path(manifest_name, dependency_name)
|
86
|
+
return "node_modules/#{dependency_name}" if manifest_name == "package.json"
|
87
|
+
|
88
|
+
workspace_path = manifest_name.gsub("/package.json", "")
|
89
|
+
File.join(workspace_path, "node_modules", dependency_name)
|
90
|
+
end
|
91
|
+
|
68
92
|
def yarn_lock_dependencies
|
69
93
|
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
70
94
|
|
@@ -117,11 +117,11 @@ module Dependabot
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def package_lock_changed?(package_lock)
|
120
|
-
package_lock.content !=
|
120
|
+
package_lock.content != updated_lockfile_content(package_lock)
|
121
121
|
end
|
122
122
|
|
123
123
|
def shrinkwrap_changed?(shrinkwrap)
|
124
|
-
shrinkwrap.content !=
|
124
|
+
shrinkwrap.content != updated_lockfile_content(shrinkwrap)
|
125
125
|
end
|
126
126
|
|
127
127
|
def updated_manifest_files
|
@@ -150,7 +150,7 @@ module Dependabot
|
|
150
150
|
|
151
151
|
updated_files << updated_file(
|
152
152
|
file: package_lock,
|
153
|
-
content:
|
153
|
+
content: updated_lockfile_content(package_lock)
|
154
154
|
)
|
155
155
|
end
|
156
156
|
|
@@ -159,7 +159,7 @@ module Dependabot
|
|
159
159
|
|
160
160
|
updated_files << updated_file(
|
161
161
|
file: shrinkwrap,
|
162
|
-
content:
|
162
|
+
content: updated_lockfile_content(shrinkwrap)
|
163
163
|
)
|
164
164
|
end
|
165
165
|
|
@@ -181,25 +181,15 @@ module Dependabot
|
|
181
181
|
)
|
182
182
|
end
|
183
183
|
|
184
|
-
def
|
185
|
-
@
|
186
|
-
@
|
187
|
-
npm_lockfile_updater.updated_lockfile_content(package_lock)
|
188
|
-
end
|
189
|
-
|
190
|
-
def updated_shrinkwrap_content(shrinkwrap)
|
191
|
-
@updated_shrinkwrap_content ||= {}
|
192
|
-
@updated_shrinkwrap_content[shrinkwrap.name] ||=
|
193
|
-
npm_lockfile_updater.updated_lockfile_content(shrinkwrap)
|
194
|
-
end
|
195
|
-
|
196
|
-
def npm_lockfile_updater
|
197
|
-
@npm_lockfile_updater ||=
|
184
|
+
def updated_lockfile_content(file)
|
185
|
+
@updated_lockfile_content ||= {}
|
186
|
+
@updated_lockfile_content[file.name] ||=
|
198
187
|
NpmLockfileUpdater.new(
|
188
|
+
lockfile: file,
|
199
189
|
dependencies: dependencies,
|
200
190
|
dependency_files: dependency_files,
|
201
191
|
credentials: credentials
|
202
|
-
)
|
192
|
+
).updated_lockfile.content
|
203
193
|
end
|
204
194
|
|
205
195
|
def updated_package_json_content(file)
|
@@ -17,35 +17,22 @@ module Dependabot
|
|
17
17
|
require_relative "npmrc_builder"
|
18
18
|
require_relative "package_json_updater"
|
19
19
|
|
20
|
-
def initialize(dependencies:, dependency_files:, credentials:)
|
20
|
+
def initialize(lockfile:, dependencies:, dependency_files:, credentials:)
|
21
|
+
@lockfile = lockfile
|
21
22
|
@dependencies = dependencies
|
22
23
|
@dependency_files = dependency_files
|
23
24
|
@credentials = credentials
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@updated_lockfile_content ||= {}
|
31
|
-
@updated_lockfile_content[lockfile.name] ||=
|
32
|
-
SharedHelpers.in_a_temporary_directory do
|
33
|
-
path = Pathname.new(lockfile.name).dirname.to_s
|
34
|
-
lockfile_name = Pathname.new(lockfile.name).basename.to_s
|
35
|
-
write_temporary_dependency_files(lockfile.name)
|
36
|
-
updated_files = Dir.chdir(path) do
|
37
|
-
run_current_npm_update(lockfile_name: lockfile_name, lockfile_content: lockfile.content)
|
38
|
-
end
|
39
|
-
updated_content = updated_files.fetch(lockfile_name)
|
40
|
-
post_process_npm_lockfile(lockfile.content, updated_content, lockfile.name)
|
41
|
-
end
|
42
|
-
rescue SharedHelpers::HelperSubprocessFailed => e
|
43
|
-
handle_npm_updater_error(e, lockfile)
|
27
|
+
def updated_lockfile
|
28
|
+
updated_file = lockfile.dup
|
29
|
+
updated_file.content = updated_lockfile_content
|
30
|
+
updated_file
|
44
31
|
end
|
45
32
|
|
46
33
|
private
|
47
34
|
|
48
|
-
attr_reader :dependencies, :dependency_files, :credentials
|
35
|
+
attr_reader :lockfile, :dependencies, :dependency_files, :credentials
|
49
36
|
|
50
37
|
UNREACHABLE_GIT = /fatal: repository '(?<url>.*)' not found/.freeze
|
51
38
|
FORBIDDEN_GIT = /fatal: Authentication failed for '(?<url>.*)'/.freeze
|
@@ -63,6 +50,21 @@ module Dependabot
|
|
63
50
|
NPM7_MISSING_GIT_REF = /already exists and is not an empty directory/.freeze
|
64
51
|
NPM6_MISSING_GIT_REF = /did not match any file\(s\) known to git/.freeze
|
65
52
|
|
53
|
+
def updated_lockfile_content
|
54
|
+
return lockfile.content if npmrc_disables_lockfile?
|
55
|
+
return lockfile.content unless updatable_dependencies.any?
|
56
|
+
|
57
|
+
@updated_lockfile_content ||=
|
58
|
+
SharedHelpers.in_a_temporary_directory do
|
59
|
+
write_temporary_dependency_files
|
60
|
+
updated_files = Dir.chdir(lockfile_directory) { run_current_npm_update }
|
61
|
+
updated_lockfile_content = updated_files.fetch(lockfile_basename)
|
62
|
+
post_process_npm_lockfile(updated_lockfile_content)
|
63
|
+
end
|
64
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
65
|
+
handle_npm_updater_error(e)
|
66
|
+
end
|
67
|
+
|
66
68
|
def top_level_dependencies
|
67
69
|
dependencies.select(&:top_level?)
|
68
70
|
end
|
@@ -71,16 +73,14 @@ module Dependabot
|
|
71
73
|
dependencies.reject(&:top_level?)
|
72
74
|
end
|
73
75
|
|
74
|
-
def updatable_dependencies
|
76
|
+
def updatable_dependencies
|
75
77
|
dependencies.reject do |dependency|
|
76
|
-
dependency_up_to_date?(
|
77
|
-
top_level_dependency_update_not_required?(dependency, lockfile)
|
78
|
+
dependency_up_to_date?(dependency) || top_level_dependency_update_not_required?(dependency)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
81
|
-
def lockfile_dependencies
|
82
|
-
@lockfile_dependencies ||=
|
83
|
-
@lockfile_dependencies[lockfile.name] ||=
|
82
|
+
def lockfile_dependencies
|
83
|
+
@lockfile_dependencies ||=
|
84
84
|
NpmAndYarn::FileParser.new(
|
85
85
|
dependency_files: [lockfile, *package_files],
|
86
86
|
source: nil,
|
@@ -88,9 +88,8 @@ module Dependabot
|
|
88
88
|
).parse
|
89
89
|
end
|
90
90
|
|
91
|
-
def dependency_up_to_date?(
|
92
|
-
existing_dep = lockfile_dependencies
|
93
|
-
find { |dep| dep.name == dependency.name }
|
91
|
+
def dependency_up_to_date?(dependency)
|
92
|
+
existing_dep = lockfile_dependencies.find { |dep| dep.name == dependency.name }
|
94
93
|
|
95
94
|
# If the dependency is missing but top level it should be treated as
|
96
95
|
# not up to date
|
@@ -106,93 +105,81 @@ module Dependabot
|
|
106
105
|
# proj). npm 7 introduces workspace support so we explitly want to
|
107
106
|
# update the root lockfile and check if the dependency is in the
|
108
107
|
# lockfile
|
109
|
-
def top_level_dependency_update_not_required?(dependency
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
req_dir = Pathname.new(req[:file]).dirname.to_s
|
114
|
-
req_dir == lockfile_dir
|
115
|
-
end
|
116
|
-
|
117
|
-
dependency_in_lockfile = lockfile_dependencies(lockfile).any? do |dep|
|
118
|
-
dep.name == dependency.name
|
119
|
-
end
|
120
|
-
|
121
|
-
dependency.top_level? && requirements_for_path.empty? && !dependency_in_lockfile
|
108
|
+
def top_level_dependency_update_not_required?(dependency)
|
109
|
+
dependency.top_level? &&
|
110
|
+
!dependency_in_package_json?(dependency) &&
|
111
|
+
!dependency_in_lockfile?(dependency)
|
122
112
|
end
|
123
113
|
|
124
|
-
def run_current_npm_update
|
125
|
-
|
126
|
-
{ name: d.name, version: d.version, requirements: d.requirements }
|
127
|
-
end
|
128
|
-
|
129
|
-
run_npm_updater(
|
130
|
-
lockfile_name: lockfile_name,
|
131
|
-
top_level_dependency_updates: top_level_dependency_updates,
|
132
|
-
lockfile_content: lockfile_content
|
133
|
-
)
|
114
|
+
def run_current_npm_update
|
115
|
+
run_npm_updater(top_level_dependencies: top_level_dependencies)
|
134
116
|
end
|
135
117
|
|
136
|
-
def run_previous_npm_update
|
118
|
+
def run_previous_npm_update
|
137
119
|
previous_top_level_dependencies = top_level_dependencies.map do |d|
|
138
|
-
|
120
|
+
Dependabot::Dependency.new(
|
139
121
|
name: d.name,
|
122
|
+
package_manager: d.package_manager,
|
140
123
|
version: d.previous_version,
|
141
|
-
|
142
|
-
|
124
|
+
previous_version: d.previous_version,
|
125
|
+
requirements: d.previous_requirements,
|
126
|
+
previous_requirements: d.previous_requirements
|
127
|
+
)
|
143
128
|
end
|
144
129
|
|
145
|
-
run_npm_updater(
|
146
|
-
lockfile_name: lockfile_name,
|
147
|
-
top_level_dependency_updates: previous_top_level_dependencies,
|
148
|
-
lockfile_content: lockfile_content
|
149
|
-
)
|
130
|
+
run_npm_updater(top_level_dependencies: previous_top_level_dependencies)
|
150
131
|
end
|
151
132
|
|
152
|
-
def run_npm_updater(
|
133
|
+
def run_npm_updater(top_level_dependencies:)
|
153
134
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
154
|
-
if
|
155
|
-
run_npm_top_level_updater(
|
156
|
-
lockfile_name: lockfile_name,
|
157
|
-
top_level_dependency_updates: top_level_dependency_updates,
|
158
|
-
lockfile_content: lockfile_content
|
159
|
-
)
|
135
|
+
if top_level_dependencies.any?
|
136
|
+
run_npm_top_level_updater(top_level_dependencies: top_level_dependencies)
|
160
137
|
else
|
161
|
-
run_npm_subdependency_updater
|
138
|
+
run_npm_subdependency_updater
|
162
139
|
end
|
163
140
|
end
|
164
141
|
end
|
165
142
|
|
166
|
-
def run_npm_top_level_updater(
|
167
|
-
if npm7?
|
168
|
-
run_npm_7_top_level_updater(
|
169
|
-
lockfile_name: lockfile_name,
|
170
|
-
top_level_dependency_updates: top_level_dependency_updates
|
171
|
-
)
|
143
|
+
def run_npm_top_level_updater(top_level_dependencies:)
|
144
|
+
if npm7?
|
145
|
+
run_npm_7_top_level_updater(top_level_dependencies: top_level_dependencies)
|
172
146
|
else
|
173
147
|
SharedHelpers.run_helper_subprocess(
|
174
148
|
command: NativeHelpers.helper_path,
|
175
149
|
function: "npm6:update",
|
176
150
|
args: [
|
177
151
|
Dir.pwd,
|
178
|
-
|
179
|
-
|
152
|
+
lockfile_basename,
|
153
|
+
top_level_dependencies.map(&:to_h)
|
180
154
|
]
|
181
155
|
)
|
182
156
|
end
|
183
157
|
end
|
184
158
|
|
185
|
-
def run_npm_7_top_level_updater(
|
186
|
-
|
187
|
-
|
159
|
+
def run_npm_7_top_level_updater(top_level_dependencies:)
|
160
|
+
dependencies_in_current_package_json = top_level_dependencies.any? do |dependency|
|
161
|
+
dependency_in_package_json?(dependency)
|
162
|
+
end
|
163
|
+
|
164
|
+
# NOTE: When updating a dependency in a nested workspace project we
|
165
|
+
# need to run `npm install` without any arguments to update the root
|
166
|
+
# level lockfile after having updated the nested packages package.json
|
167
|
+
# requirement, otherwise npm will add the dependency as a new
|
168
|
+
# top-level dependency to the root lockfile.
|
169
|
+
install_args = ""
|
170
|
+
if dependencies_in_current_package_json
|
171
|
+
# TODO: Update the npm 6 updater to use these args as we currently
|
172
|
+
# do the same in the js updater helper, we've kept it seperate for
|
173
|
+
# the npm 7 rollout
|
174
|
+
install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
|
175
|
+
end
|
176
|
+
|
177
|
+
# NOTE: npm options
|
188
178
|
# - `--force` ignores checks for platform (os, cpu) and engines
|
189
|
-
# - `--
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
top_level_dependency_updates: top_level_dependency_updates,
|
194
|
-
flattenend_manifest_dependencies: flattenend_manifest_dependencies
|
195
|
-
)
|
179
|
+
# - `--dry-run=false` the updater sets a global .npmrc with dry-run:
|
180
|
+
# true to work around an issue in npm 6, we don't want that here
|
181
|
+
# - `--ignore-scripts` disables prepare and prepack scripts which are
|
182
|
+
# run when installing git dependencies
|
196
183
|
command = [
|
197
184
|
"npm",
|
198
185
|
"install",
|
@@ -204,26 +191,27 @@ module Dependabot
|
|
204
191
|
"--package-lock-only"
|
205
192
|
].join(" ")
|
206
193
|
SharedHelpers.run_shell_command(command)
|
207
|
-
{
|
194
|
+
{ lockfile_basename => File.read(lockfile_basename) }
|
208
195
|
end
|
209
196
|
|
210
|
-
def run_npm_subdependency_updater
|
211
|
-
if npm7?
|
212
|
-
run_npm_7_subdependency_updater
|
197
|
+
def run_npm_subdependency_updater
|
198
|
+
if npm7?
|
199
|
+
run_npm_7_subdependency_updater
|
213
200
|
else
|
214
201
|
SharedHelpers.run_helper_subprocess(
|
215
202
|
command: NativeHelpers.helper_path,
|
216
203
|
function: "npm6:updateSubdependency",
|
217
|
-
args: [Dir.pwd,
|
204
|
+
args: [Dir.pwd, lockfile_basename, sub_dependencies.map(&:to_h)]
|
218
205
|
)
|
219
206
|
end
|
220
207
|
end
|
221
208
|
|
222
|
-
def run_npm_7_subdependency_updater
|
209
|
+
def run_npm_7_subdependency_updater
|
223
210
|
dependency_names = sub_dependencies.map(&:name)
|
211
|
+
# NOTE: npm options
|
212
|
+
# - `--force` ignores checks for platform (os, cpu) and engines
|
224
213
|
# - `--dry-run=false` the updater sets a global .npmrc with dry-run: true to
|
225
214
|
# work around an issue in npm 6, we don't want that here
|
226
|
-
# - `--force` ignores checks for platform (os, cpu) and engines
|
227
215
|
# - `--ignore-scripts` disables prepare and prepack scripts which are run
|
228
216
|
# when installing git dependencies
|
229
217
|
command = [
|
@@ -237,59 +225,64 @@ module Dependabot
|
|
237
225
|
"--package-lock-only"
|
238
226
|
].join(" ")
|
239
227
|
SharedHelpers.run_shell_command(command)
|
240
|
-
{
|
228
|
+
{ lockfile_basename => File.read(lockfile_basename) }
|
241
229
|
end
|
242
230
|
|
243
|
-
|
244
|
-
|
245
|
-
# 7 rollout
|
246
|
-
def npm_top_level_updater_args(top_level_dependency_updates:, flattenend_manifest_dependencies:)
|
247
|
-
top_level_dependency_updates.map do |dependency|
|
248
|
-
# NOTE: For git dependencies we loose some information about the
|
249
|
-
# requirement that's only available in the package.json, e.g. when
|
250
|
-
# specifying a semver tag:
|
251
|
-
# `dependabot/depeendabot-core#semver:^0.1` - this is required to
|
252
|
-
# pass the correct install argument to `npm install`
|
253
|
-
existing_version_requirement = flattenend_manifest_dependencies[dependency.fetch(:name)]
|
254
|
-
npm_install_args(
|
255
|
-
dependency.fetch(:name),
|
256
|
-
dependency.fetch(:version),
|
257
|
-
dependency.fetch(:requirements),
|
258
|
-
existing_version_requirement
|
259
|
-
)
|
260
|
-
end
|
231
|
+
def updated_version_requirement_for_dependency(dependency)
|
232
|
+
flattenend_manifest_dependencies[dependency.name]
|
261
233
|
end
|
262
234
|
|
263
|
-
|
264
|
-
|
265
|
-
|
235
|
+
# TODO: Add the raw updated requirement to the Dependency instance
|
236
|
+
# instead of fishing it out of the updated package json, we need to do
|
237
|
+
# this because we don't store the same requirement in
|
238
|
+
# Dependency#requirements for git dependencies - see PackageJsonUpdater
|
239
|
+
def flattenend_manifest_dependencies
|
240
|
+
return @flattenend_manifest_dependencies if defined?(@flattenend_manifest_dependencies)
|
266
241
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
242
|
+
@flattenend_manifest_dependencies =
|
243
|
+
NpmAndYarn::FileParser::DEPENDENCY_TYPES.inject({}) do |deps, type|
|
244
|
+
deps.merge(parsed_package_json[type] || {})
|
245
|
+
end
|
271
246
|
end
|
272
247
|
|
273
|
-
def npm_install_args(
|
274
|
-
git_requirement = requirements.find { |req| req[:source] && req[:source][:type] == "git" }
|
248
|
+
def npm_install_args(dependency)
|
249
|
+
git_requirement = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" }
|
275
250
|
|
276
251
|
if git_requirement
|
277
|
-
|
252
|
+
# NOTE: For git dependencies we loose some information about the
|
253
|
+
# requirement that's only available in the package.json, e.g. when
|
254
|
+
# specifying a semver tag:
|
255
|
+
# `dependabot/depeendabot-core#semver:^0.1` - this is required to
|
256
|
+
# pass the correct install argument to `npm install`
|
257
|
+
updated_version_requirement = updated_version_requirement_for_dependency(dependency)
|
258
|
+
updated_version_requirement ||= git_requirement[:source][:url]
|
278
259
|
|
279
260
|
# NOTE: Git is configured to auth over https while updating
|
280
|
-
|
261
|
+
updated_version_requirement = updated_version_requirement.gsub(
|
281
262
|
%r{git\+ssh://git@(.*?)[:/]}, 'https://\1/'
|
282
263
|
)
|
283
264
|
|
284
265
|
# NOTE: Keep any semver range that has already been updated by the
|
285
266
|
# PackageJsonUpdater when installing the new version
|
286
|
-
if
|
287
|
-
"#{
|
267
|
+
if updated_version_requirement.include?(dependency.version)
|
268
|
+
"#{dependency.name}@#{updated_version_requirement}"
|
288
269
|
else
|
289
|
-
"#{
|
270
|
+
"#{dependency.name}@#{updated_version_requirement.sub(/#.*/, '')}##{dependency.version}"
|
290
271
|
end
|
291
272
|
else
|
292
|
-
"#{
|
273
|
+
"#{dependency.name}@#{dependency.version}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def dependency_in_package_json?(dependency)
|
278
|
+
dependency.requirements.any? do |req|
|
279
|
+
req[:file] == package_json.name
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def dependency_in_lockfile?(dependency)
|
284
|
+
lockfile_dependencies.any? do |dep|
|
285
|
+
dep.name == dependency.name
|
293
286
|
end
|
294
287
|
end
|
295
288
|
|
@@ -297,14 +290,14 @@ module Dependabot
|
|
297
290
|
# rubocop:disable Metrics/CyclomaticComplexity
|
298
291
|
# rubocop:disable Metrics/PerceivedComplexity
|
299
292
|
# rubocop:disable Metrics/MethodLength
|
300
|
-
def handle_npm_updater_error(error
|
293
|
+
def handle_npm_updater_error(error)
|
301
294
|
error_message = error.message
|
302
295
|
if error_message.match?(MISSING_PACKAGE)
|
303
296
|
package_name = error_message.match(MISSING_PACKAGE).
|
304
297
|
named_captures["package_req"]
|
305
298
|
sanitized_name = sanitize_package_name(package_name)
|
306
299
|
sanitized_error = error_message.gsub(package_name, sanitized_name)
|
307
|
-
handle_missing_package(sanitized_name, sanitized_error
|
300
|
+
handle_missing_package(sanitized_name, sanitized_error)
|
308
301
|
end
|
309
302
|
|
310
303
|
# Invalid package: When the package.json doesn't include a name or
|
@@ -316,7 +309,7 @@ module Dependabot
|
|
316
309
|
if error_message.match?(INVALID_PACKAGE) ||
|
317
310
|
error_message.include?("Invalid package name") ||
|
318
311
|
error_message.include?(sub_dep_local_path_error)
|
319
|
-
raise_resolvability_error(error_message
|
312
|
+
raise_resolvability_error(error_message)
|
320
313
|
end
|
321
314
|
|
322
315
|
# TODO: Move this logic to the version resolver and check if a new
|
@@ -340,7 +333,7 @@ module Dependabot
|
|
340
333
|
# queries
|
341
334
|
if error_message.include?("No matching vers") &&
|
342
335
|
dependencies_in_error_message?(error_message) &&
|
343
|
-
resolvable_before_update?
|
336
|
+
resolvable_before_update?
|
344
337
|
|
345
338
|
# Raise a bespoke error so we can capture and ignore it if
|
346
339
|
# we're trying to create a new PR (which will be created
|
@@ -353,7 +346,7 @@ module Dependabot
|
|
353
346
|
named_captures["package_req"]
|
354
347
|
sanitized_name = sanitize_package_name(package_name)
|
355
348
|
sanitized_error = error_message.gsub(package_name, sanitized_name)
|
356
|
-
handle_missing_package(sanitized_name, sanitized_error
|
349
|
+
handle_missing_package(sanitized_name, sanitized_error)
|
357
350
|
end
|
358
351
|
|
359
352
|
# Some private registries return a 403 when the user is readonly
|
@@ -362,7 +355,7 @@ module Dependabot
|
|
362
355
|
named_captures["package_req"]
|
363
356
|
sanitized_name = sanitize_package_name(package_name)
|
364
357
|
sanitized_error = error_message.gsub(package_name, sanitized_name)
|
365
|
-
handle_missing_package(sanitized_name, sanitized_error
|
358
|
+
handle_missing_package(sanitized_name, sanitized_error)
|
366
359
|
end
|
367
360
|
|
368
361
|
if (git_error = error_message.match(UNREACHABLE_GIT) || error_message.match(FORBIDDEN_GIT))
|
@@ -379,9 +372,8 @@ module Dependabot
|
|
379
372
|
# people to re-generate their lockfiles (Future feature idea: add a
|
380
373
|
# way to click-to-fix the lockfile from the issue)
|
381
374
|
if error_message.include?("Cannot read property 'match' of ") &&
|
382
|
-
!resolvable_before_update?
|
383
|
-
raise_missing_lockfile_version_resolvability_error(error_message
|
384
|
-
lockfile)
|
375
|
+
!resolvable_before_update?
|
376
|
+
raise_missing_lockfile_version_resolvability_error(error_message)
|
385
377
|
end
|
386
378
|
|
387
379
|
if (error_message.include?("No matching vers") ||
|
@@ -390,8 +382,8 @@ module Dependabot
|
|
390
382
|
error_message.include?("Invalid tag name") ||
|
391
383
|
error_message.match?(NPM6_MISSING_GIT_REF) ||
|
392
384
|
error_message.match?(NPM7_MISSING_GIT_REF)) &&
|
393
|
-
!resolvable_before_update?
|
394
|
-
raise_resolvability_error(error_message
|
385
|
+
!resolvable_before_update?
|
386
|
+
raise_resolvability_error(error_message)
|
395
387
|
end
|
396
388
|
|
397
389
|
# NOTE: This check was introduced in npm7/arborist
|
@@ -407,17 +399,15 @@ module Dependabot
|
|
407
399
|
# rubocop:enable Metrics/PerceivedComplexity
|
408
400
|
# rubocop:enable Metrics/MethodLength
|
409
401
|
|
410
|
-
def raise_resolvability_error(error_message
|
402
|
+
def raise_resolvability_error(error_message)
|
411
403
|
dependency_names = dependencies.map(&:name).join(", ")
|
412
404
|
msg = "Error whilst updating #{dependency_names} in "\
|
413
405
|
"#{lockfile.path}:\n#{error_message}"
|
414
406
|
raise Dependabot::DependencyFileNotResolvable, msg
|
415
407
|
end
|
416
408
|
|
417
|
-
def raise_missing_lockfile_version_resolvability_error(error_message
|
418
|
-
|
419
|
-
lockfile_dir = Pathname.new(lockfile.name).dirname
|
420
|
-
modules_path = lockfile_dir.join("node_modules")
|
409
|
+
def raise_missing_lockfile_version_resolvability_error(error_message)
|
410
|
+
modules_path = File.join(lockfile_directory, "node_modules")
|
421
411
|
# NOTE: don't include the dependency names to prevent opening
|
422
412
|
# multiple issues for each dependency that fails because we unique
|
423
413
|
# issues on the error message (issue detail) on the backend
|
@@ -432,11 +422,10 @@ module Dependabot
|
|
432
422
|
raise Dependabot::DependencyFileNotResolvable, msg
|
433
423
|
end
|
434
424
|
|
435
|
-
def handle_missing_package(package_name, error_message
|
436
|
-
missing_dep = lockfile_dependencies
|
437
|
-
find { |dep| dep.name == package_name }
|
425
|
+
def handle_missing_package(package_name, error_message)
|
426
|
+
missing_dep = lockfile_dependencies.find { |dep| dep.name == package_name }
|
438
427
|
|
439
|
-
raise_resolvability_error(error_message
|
428
|
+
raise_resolvability_error(error_message) unless missing_dep
|
440
429
|
|
441
430
|
reg = NpmAndYarn::UpdateChecker::RegistryFinder.new(
|
442
431
|
dependency: missing_dep,
|
@@ -458,23 +447,14 @@ module Dependabot
|
|
458
447
|
end
|
459
448
|
end
|
460
449
|
|
461
|
-
def resolvable_before_update?
|
462
|
-
@resolvable_before_update
|
463
|
-
return @resolvable_before_update[lockfile.name] if @resolvable_before_update.key?(lockfile.name)
|
450
|
+
def resolvable_before_update?
|
451
|
+
return @resolvable_before_update if defined?(@resolvable_before_update)
|
464
452
|
|
465
|
-
@resolvable_before_update
|
453
|
+
@resolvable_before_update =
|
466
454
|
begin
|
467
455
|
SharedHelpers.in_a_temporary_directory do
|
468
|
-
write_temporary_dependency_files(
|
469
|
-
|
470
|
-
update_package_json: false
|
471
|
-
)
|
472
|
-
|
473
|
-
lockfile_name = Pathname.new(lockfile.name).basename.to_s
|
474
|
-
path = Pathname.new(lockfile.name).dirname.to_s
|
475
|
-
Dir.chdir(path) do
|
476
|
-
run_previous_npm_update(lockfile_name: lockfile_name, lockfile_content: lockfile.content)
|
477
|
-
end
|
456
|
+
write_temporary_dependency_files(update_package_json: false)
|
457
|
+
Dir.chdir(lockfile_directory) { run_previous_npm_update }
|
478
458
|
end
|
479
459
|
|
480
460
|
true
|
@@ -492,12 +472,10 @@ module Dependabot
|
|
492
472
|
end
|
493
473
|
end
|
494
474
|
|
495
|
-
def write_temporary_dependency_files(
|
496
|
-
|
497
|
-
write_lockfiles(lockfile_name)
|
475
|
+
def write_temporary_dependency_files(update_package_json: true)
|
476
|
+
write_lockfiles
|
498
477
|
|
499
|
-
|
500
|
-
File.write(File.join(dir, ".npmrc"), npmrc_content)
|
478
|
+
File.write(File.join(lockfile_directory, ".npmrc"), npmrc_content)
|
501
479
|
|
502
480
|
package_files.each do |file|
|
503
481
|
path = file.name
|
@@ -524,9 +502,9 @@ module Dependabot
|
|
524
502
|
end
|
525
503
|
end
|
526
504
|
|
527
|
-
def write_lockfiles
|
505
|
+
def write_lockfiles
|
528
506
|
excluded_lock =
|
529
|
-
case
|
507
|
+
case lockfile.name
|
530
508
|
when "package-lock.json" then "npm-shrinkwrap.json"
|
531
509
|
when "npm-shrinkwrap.json" then "package-lock.json"
|
532
510
|
end
|
@@ -627,22 +605,86 @@ module Dependabot
|
|
627
605
|
@git_ssh_requirements_to_swap
|
628
606
|
end
|
629
607
|
|
630
|
-
def post_process_npm_lockfile(
|
631
|
-
updated_content = replace_project_metadata(updated_content, original_content)
|
632
|
-
|
608
|
+
def post_process_npm_lockfile(updated_lockfile_content)
|
633
609
|
# Switch SSH requirements back for git dependencies
|
634
|
-
|
610
|
+
updated_lockfile_content = replace_swapped_git_ssh_requirements(updated_lockfile_content)
|
635
611
|
|
636
612
|
# Switch from details back for git dependencies (they will have
|
637
613
|
# changed because we locked them)
|
638
|
-
|
614
|
+
updated_lockfile_content = replace_locked_git_dependencies(updated_lockfile_content)
|
615
|
+
|
616
|
+
parsed_updated_lockfile_content = JSON.parse(updated_lockfile_content)
|
617
|
+
|
618
|
+
# Restore lockfile name attribute from the original lockfile
|
619
|
+
updated_lockfile_content = replace_project_name(updated_lockfile_content, parsed_updated_lockfile_content)
|
620
|
+
|
621
|
+
# Restore npm 7 "packages" "name" entry from package.json if previously set
|
622
|
+
updated_lockfile_content = restore_packages_name(updated_lockfile_content, parsed_updated_lockfile_content)
|
639
623
|
|
640
624
|
# Switch back npm 7 lockfile "pacakages" requirements from the package.json
|
641
|
-
|
625
|
+
updated_lockfile_content = restore_locked_package_dependencies(
|
626
|
+
updated_lockfile_content, parsed_updated_lockfile_content
|
627
|
+
)
|
642
628
|
|
643
629
|
# Switch back the protocol of tarball resolutions if they've changed
|
644
630
|
# (fixes an npm bug, which appears to be applied inconsistently)
|
645
|
-
replace_tarball_urls(
|
631
|
+
replace_tarball_urls(updated_lockfile_content)
|
632
|
+
end
|
633
|
+
|
634
|
+
def replace_project_name(updated_lockfile_content, parsed_updated_lockfile_content)
|
635
|
+
current_name = parsed_updated_lockfile_content["name"]
|
636
|
+
original_name = parsed_lockfile["name"]
|
637
|
+
if original_name
|
638
|
+
updated_lockfile_content = replace_lockfile_name_attribute(
|
639
|
+
current_name, original_name, updated_lockfile_content
|
640
|
+
)
|
641
|
+
end
|
642
|
+
updated_lockfile_content
|
643
|
+
end
|
644
|
+
|
645
|
+
def restore_packages_name(updated_lockfile_content, parsed_updated_lockfile_content)
|
646
|
+
return updated_lockfile_content unless npm7?
|
647
|
+
|
648
|
+
current_name = parsed_updated_lockfile_content.dig("packages", "", "name")
|
649
|
+
original_name = parsed_lockfile.dig("packages", "", "name")
|
650
|
+
|
651
|
+
# TODO: Submit a patch to npm fixing this issue making `npm install`
|
652
|
+
# consistent with `npm install --package-lock-only`
|
653
|
+
#
|
654
|
+
# NOTE: This is a workaround for npm adding a `name` attribute to the
|
655
|
+
# packages section in the lockfile because we install using
|
656
|
+
# `--package-lock-only`
|
657
|
+
if !original_name
|
658
|
+
updated_lockfile_content = remove_lockfile_packages_name_attribute(
|
659
|
+
current_name, updated_lockfile_content
|
660
|
+
)
|
661
|
+
elsif original_name && original_name != current_name
|
662
|
+
updated_lockfile_content = replace_lockfile_packages_name_attribute(
|
663
|
+
current_name, original_name, updated_lockfile_content
|
664
|
+
)
|
665
|
+
end
|
666
|
+
|
667
|
+
updated_lockfile_content
|
668
|
+
end
|
669
|
+
|
670
|
+
def replace_lockfile_name_attribute(current_name, original_name, updated_lockfile_content)
|
671
|
+
updated_lockfile_content.sub(
|
672
|
+
/"name":\s"#{current_name}"/,
|
673
|
+
"\"name\": \"#{original_name}\""
|
674
|
+
)
|
675
|
+
end
|
676
|
+
|
677
|
+
def replace_lockfile_packages_name_attribute(current_name, original_name, updated_lockfile_content)
|
678
|
+
packages_key_line = '"": {'
|
679
|
+
updated_lockfile_content.sub(
|
680
|
+
/(#{packages_key_line}[\n\s]+"name":\s)"#{current_name}"/,
|
681
|
+
'\1"' + original_name + '"'
|
682
|
+
)
|
683
|
+
end
|
684
|
+
|
685
|
+
def remove_lockfile_packages_name_attribute(current_name, updated_lockfile_content)
|
686
|
+
packages_key_line = '"": {'
|
687
|
+
updated_lockfile_content.gsub(/(#{packages_key_line})[\n\s]+"name":\s"#{current_name}",/, '\1')
|
646
688
|
end
|
647
689
|
|
648
690
|
# NOTE: This is a workaround to "sync" what's in package.json
|
@@ -654,43 +696,38 @@ module Dependabot
|
|
654
696
|
# `package.json` requirement for eslint at `^1.0.0`, in which case we
|
655
697
|
# need to copy this from the manifest to the lockfile after the update
|
656
698
|
# has finished.
|
657
|
-
def restore_locked_package_dependencies(
|
658
|
-
return
|
659
|
-
|
660
|
-
original_package = updated_package_json_content_for_lockfile_name(lockfile_name)
|
661
|
-
return lockfile_content unless original_package
|
699
|
+
def restore_locked_package_dependencies(updated_lockfile_content, parsed_updated_lockfile_content)
|
700
|
+
return updated_lockfile_content unless npm7?
|
662
701
|
|
663
|
-
parsed_package = JSON.parse(original_package)
|
664
|
-
parsed_lockfile = JSON.parse(lockfile_content)
|
665
702
|
dependency_names_to_restore = (dependencies.map(&:name) + git_dependencies_to_lock.keys).uniq
|
666
703
|
|
667
704
|
NpmAndYarn::FileParser::DEPENDENCY_TYPES.each do |type|
|
668
|
-
|
705
|
+
parsed_package_json.fetch(type, {}).each do |dependency_name, original_requirement|
|
669
706
|
next unless dependency_names_to_restore.include?(dependency_name)
|
670
707
|
|
671
|
-
locked_requirement =
|
708
|
+
locked_requirement = parsed_updated_lockfile_content.dig("packages", "", type, dependency_name)
|
672
709
|
next unless locked_requirement
|
673
710
|
|
674
711
|
locked_req = %("#{dependency_name}": "#{locked_requirement}")
|
675
712
|
original_req = %("#{dependency_name}": "#{original_requirement}")
|
676
|
-
|
713
|
+
updated_lockfile_content = updated_lockfile_content.gsub(locked_req, original_req)
|
677
714
|
end
|
678
715
|
end
|
679
716
|
|
680
|
-
|
717
|
+
updated_lockfile_content
|
681
718
|
end
|
682
719
|
|
683
|
-
def replace_swapped_git_ssh_requirements(
|
720
|
+
def replace_swapped_git_ssh_requirements(updated_lockfile_content)
|
684
721
|
git_ssh_requirements_to_swap.each do |req|
|
685
722
|
new_r = req.gsub(%r{git\+ssh://git@(.*?)[:/]}, 'git+https://\1/')
|
686
723
|
old_r = req.gsub(%r{git@(.*?)[:/]}, 'git@\1/')
|
687
|
-
|
724
|
+
updated_lockfile_content = updated_lockfile_content.gsub(new_r, old_r)
|
688
725
|
end
|
689
726
|
|
690
|
-
|
727
|
+
updated_lockfile_content
|
691
728
|
end
|
692
729
|
|
693
|
-
def replace_locked_git_dependencies(
|
730
|
+
def replace_locked_git_dependencies(updated_lockfile_content)
|
694
731
|
# Switch from details back for git dependencies (they will have
|
695
732
|
# changed because we locked them)
|
696
733
|
git_dependencies_to_lock.each do |dependency_name, details|
|
@@ -701,44 +738,33 @@ module Dependabot
|
|
701
738
|
# updates the lockfile "from" field to the new git commit when we
|
702
739
|
# run npm install
|
703
740
|
original_from = %("from": "#{details[:from]}")
|
704
|
-
if npm7?
|
741
|
+
if npm7?
|
705
742
|
# NOTE: The `from` syntax has changed in npm 7 to inclued the dependency name
|
706
743
|
npm7_locked_from = %("from": "#{dependency_name}@#{details[:version]}")
|
707
|
-
|
744
|
+
updated_lockfile_content = updated_lockfile_content.gsub(npm7_locked_from, original_from)
|
708
745
|
else
|
709
746
|
npm6_locked_from = %("from": "#{details[:version]}")
|
710
|
-
|
747
|
+
updated_lockfile_content = updated_lockfile_content.gsub(npm6_locked_from, original_from)
|
711
748
|
end
|
712
749
|
end
|
713
750
|
|
714
|
-
|
751
|
+
updated_lockfile_content
|
715
752
|
end
|
716
753
|
|
717
|
-
def replace_tarball_urls(
|
754
|
+
def replace_tarball_urls(updated_lockfile_content)
|
718
755
|
tarball_urls.each do |url|
|
719
756
|
trimmed_url = url.gsub(/(\d+\.)*tgz$/, "")
|
720
757
|
incorrect_url = if url.start_with?("https")
|
721
758
|
trimmed_url.gsub(/^https:/, "http:")
|
722
759
|
else trimmed_url.gsub(/^http:/, "https:")
|
723
760
|
end
|
724
|
-
|
761
|
+
updated_lockfile_content = updated_lockfile_content.gsub(
|
725
762
|
/#{Regexp.quote(incorrect_url)}(?=(\d+\.)*tgz")/,
|
726
763
|
trimmed_url
|
727
764
|
)
|
728
765
|
end
|
729
766
|
|
730
|
-
|
731
|
-
end
|
732
|
-
|
733
|
-
def replace_project_metadata(new_content, old_content)
|
734
|
-
old_name = old_content.match(/(?<="name": ").*(?=",)/)&.to_s
|
735
|
-
|
736
|
-
if old_name
|
737
|
-
new_content = new_content.
|
738
|
-
sub(/(?<="name": ").*(?=",)/, old_name)
|
739
|
-
end
|
740
|
-
|
741
|
-
new_content
|
767
|
+
updated_lockfile_content
|
742
768
|
end
|
743
769
|
|
744
770
|
def tarball_urls
|
@@ -765,15 +791,6 @@ module Dependabot
|
|
765
791
|
).npmrc_content
|
766
792
|
end
|
767
793
|
|
768
|
-
def updated_package_json_content_for_lockfile_name(lockfile_name)
|
769
|
-
lockfile_basename = Pathname.new(lockfile_name).basename.to_s
|
770
|
-
package_name = lockfile_name.sub(lockfile_basename, "package.json")
|
771
|
-
package_json = package_files.find { |f| f.name == package_name }
|
772
|
-
return unless package_json
|
773
|
-
|
774
|
-
updated_package_json_content(package_json)
|
775
|
-
end
|
776
|
-
|
777
794
|
def updated_package_json_content(file)
|
778
795
|
@updated_package_json_content ||= {}
|
779
796
|
@updated_package_json_content[file.name] ||=
|
@@ -787,8 +804,10 @@ module Dependabot
|
|
787
804
|
npmrc_content.match?(/^package-lock\s*=\s*false/)
|
788
805
|
end
|
789
806
|
|
790
|
-
def npm7?
|
791
|
-
|
807
|
+
def npm7?
|
808
|
+
return @npm7 if defined?(@npm7)
|
809
|
+
|
810
|
+
@npm7 = Dependabot::NpmAndYarn::Helpers.npm_version(lockfile.content) == "npm7"
|
792
811
|
end
|
793
812
|
|
794
813
|
def sanitized_package_json_content(content)
|
@@ -802,6 +821,30 @@ module Dependabot
|
|
802
821
|
package_name.gsub("%2f", "/").gsub("%2F", "/")
|
803
822
|
end
|
804
823
|
|
824
|
+
def lockfile_directory
|
825
|
+
Pathname.new(lockfile.name).dirname.to_s
|
826
|
+
end
|
827
|
+
|
828
|
+
def lockfile_basename
|
829
|
+
Pathname.new(lockfile.name).basename.to_s
|
830
|
+
end
|
831
|
+
|
832
|
+
def parsed_lockfile
|
833
|
+
@parsed_lockfile ||= JSON.parse(lockfile.content)
|
834
|
+
end
|
835
|
+
|
836
|
+
def parsed_package_json
|
837
|
+
return {} unless package_json
|
838
|
+
return @parsed_package_json if defined?(@parsed_package_json)
|
839
|
+
|
840
|
+
@parsed_package_json = JSON.parse(updated_package_json_content(package_json))
|
841
|
+
end
|
842
|
+
|
843
|
+
def package_json
|
844
|
+
package_name = lockfile.name.sub(lockfile_basename, "package.json")
|
845
|
+
package_files.find { |f| f.name == package_name }
|
846
|
+
end
|
847
|
+
|
805
848
|
def package_locks
|
806
849
|
@package_locks ||=
|
807
850
|
dependency_files.
|