dependabot-npm_and_yarn 0.215.0 → 0.216.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/build +1 -1
- data/helpers/lib/yarn/subdependency-updater.js +15 -44
- data/helpers/package-lock.json +2597 -1572
- data/helpers/package.json +8 -9
- data/helpers/test/npm6/conflicting-dependency-parser.test.js +1 -2
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +3 -3
- data/helpers/test/npm6/updater.test.js +1 -2
- data/helpers/test/yarn/conflicting-dependency-parser.test.js +1 -2
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +3 -3
- data/helpers/test/yarn/updater.test.js +1 -2
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +26 -38
- data/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb +84 -0
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +21 -183
- data/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb +80 -0
- data/lib/dependabot/npm_and_yarn/file_parser.rb +23 -36
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +55 -40
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +20 -1
- data/lib/dependabot/npm_and_yarn/helpers.rb +7 -1
- data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +6 -0
- data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +1 -1
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +20 -13
- data/lib/dependabot/npm_and_yarn/update_checker.rb +5 -0
- data/lib/dependabot/npm_and_yarn/version.rb +13 -2
- metadata +37 -32
data/helpers/package.json
CHANGED
@@ -10,17 +10,16 @@
|
|
10
10
|
},
|
11
11
|
"dependencies": {
|
12
12
|
"@dependabot/yarn-lib": "^1.22.19",
|
13
|
-
"@npmcli/arborist": "^6.
|
13
|
+
"@npmcli/arborist": "^6.2.7",
|
14
14
|
"detect-indent": "^6.1.0",
|
15
|
-
"nock": "^13.
|
16
|
-
"npm": "6.14.
|
17
|
-
"semver": "^7.
|
15
|
+
"nock": "^13.3.0",
|
16
|
+
"npm": "6.14.18",
|
17
|
+
"semver": "^7.4.0"
|
18
18
|
},
|
19
19
|
"devDependencies": {
|
20
|
-
"eslint": "^8.
|
21
|
-
"eslint-config-prettier": "^8.
|
22
|
-
"jest": "^29.
|
23
|
-
"prettier": "^2.8.
|
24
|
-
"rimraf": "^3.0.2"
|
20
|
+
"eslint": "^8.38.0",
|
21
|
+
"eslint-config-prettier": "^8.8.0",
|
22
|
+
"jest": "^29.5.0",
|
23
|
+
"prettier": "^2.8.7"
|
25
24
|
}
|
26
25
|
}
|
@@ -1,7 +1,6 @@
|
|
1
1
|
const path = require("path");
|
2
2
|
const os = require("os");
|
3
3
|
const fs = require("fs");
|
4
|
-
const rimraf = require("rimraf");
|
5
4
|
const {
|
6
5
|
findConflictingDependencies,
|
7
6
|
} = require("../../lib/npm/conflicting-dependency-parser");
|
@@ -12,7 +11,7 @@ describe("findConflictingDependencies", () => {
|
|
12
11
|
beforeEach(() => {
|
13
12
|
tempDir = fs.mkdtempSync(os.tmpdir() + path.sep);
|
14
13
|
});
|
15
|
-
afterEach(() =>
|
14
|
+
afterEach(() => fs.rmdir(tempDir, { recursive: true }, () => {}));
|
16
15
|
|
17
16
|
it("finds conflicting dependencies", async () => {
|
18
17
|
helpers.copyDependencies("conflicting-dependency-parser/simple", tempDir);
|
data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json
CHANGED
@@ -454,9 +454,9 @@
|
|
454
454
|
}
|
455
455
|
},
|
456
456
|
"minimist": {
|
457
|
-
"version": "1.2.
|
458
|
-
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.
|
459
|
-
"integrity": "sha512-
|
457
|
+
"version": "1.2.8",
|
458
|
+
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
459
|
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
|
460
460
|
},
|
461
461
|
"mkdirp": {
|
462
462
|
"version": "0.5.5",
|
@@ -1,7 +1,6 @@
|
|
1
1
|
const path = require("path");
|
2
2
|
const os = require("os");
|
3
3
|
const fs = require("fs");
|
4
|
-
const rimraf = require("rimraf");
|
5
4
|
const { updateDependencyFiles } = require("../../lib/npm6/updater");
|
6
5
|
const helpers = require("./helpers");
|
7
6
|
|
@@ -10,7 +9,7 @@ describe("updater", () => {
|
|
10
9
|
beforeEach(() => {
|
11
10
|
tempDir = fs.mkdtempSync(os.tmpdir() + path.sep);
|
12
11
|
});
|
13
|
-
afterEach(() =>
|
12
|
+
afterEach(() => fs.rm(tempDir, { recursive: true }, () => {}));
|
14
13
|
|
15
14
|
it("generates an updated package-lock.json", async () => {
|
16
15
|
helpers.copyDependencies("updater/original", tempDir);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
const path = require("path");
|
2
2
|
const os = require("os");
|
3
3
|
const fs = require("fs");
|
4
|
-
const rimraf = require("rimraf");
|
5
4
|
const {
|
6
5
|
findConflictingDependencies,
|
7
6
|
} = require("../../lib/yarn/conflicting-dependency-parser");
|
@@ -12,7 +11,7 @@ fdescribe("findConflictingDependencies", () => {
|
|
12
11
|
beforeEach(() => {
|
13
12
|
tempDir = fs.mkdtempSync(os.tmpdir() + path.sep);
|
14
13
|
});
|
15
|
-
afterEach(() =>
|
14
|
+
afterEach(() => fs.rm(tempDir, { recursive: true }, () => {}));
|
16
15
|
|
17
16
|
it("finds conflicting dependencies", async () => {
|
18
17
|
helpers.copyDependencies("conflicting-dependency-parser/simple", tempDir);
|
@@ -399,9 +399,9 @@ minimatch@^3.0.4:
|
|
399
399
|
brace-expansion "^1.1.7"
|
400
400
|
|
401
401
|
minimist@^1.2.5:
|
402
|
-
version "1.2.
|
403
|
-
resolved "https://registry.
|
404
|
-
integrity sha512-
|
402
|
+
version "1.2.8"
|
403
|
+
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
404
|
+
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
405
405
|
|
406
406
|
mkdirp@^0.5.1:
|
407
407
|
version "0.5.5"
|
@@ -1,7 +1,6 @@
|
|
1
1
|
const path = require("path");
|
2
2
|
const os = require("os");
|
3
3
|
const fs = require("fs");
|
4
|
-
const rimraf = require("rimraf");
|
5
4
|
const { updateDependencyFiles } = require("../../lib/yarn/updater");
|
6
5
|
const helpers = require("./helpers");
|
7
6
|
|
@@ -10,7 +9,7 @@ describe("updater", () => {
|
|
10
9
|
beforeEach(() => {
|
11
10
|
tempDir = fs.mkdtempSync(os.tmpdir() + path.sep);
|
12
11
|
});
|
13
|
-
afterEach(() =>
|
12
|
+
afterEach(() => fs.rm(tempDir, { recursive: true }, () => {}));
|
14
13
|
|
15
14
|
function copyDependencies(sourceDir, destDir) {
|
16
15
|
const srcPackageJson = path.join(
|
@@ -10,7 +10,6 @@ require "dependabot/npm_and_yarn/file_parser/lockfile_parser"
|
|
10
10
|
|
11
11
|
module Dependabot
|
12
12
|
module NpmAndYarn
|
13
|
-
# rubocop:disable Metrics/ClassLength
|
14
13
|
class FileFetcher < Dependabot::FileFetchers::Base
|
15
14
|
require_relative "file_fetcher/path_dependency_builder"
|
16
15
|
|
@@ -50,6 +49,19 @@ module Dependabot
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
52
|
+
def package_manager_version
|
53
|
+
package_managers = {}
|
54
|
+
|
55
|
+
package_managers["npm"] = Helpers.npm_version_numeric(package_lock.content) if package_lock
|
56
|
+
package_managers["yarn"] = yarn_version if yarn_version
|
57
|
+
package_managers["shrinkwrap"] = 1 if shrinkwrap
|
58
|
+
|
59
|
+
{
|
60
|
+
ecosystem: "npm",
|
61
|
+
package_managers: package_managers
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
53
65
|
private
|
54
66
|
|
55
67
|
def fetch_files
|
@@ -65,7 +77,6 @@ module Dependabot
|
|
65
77
|
fetched_files += workspace_package_jsons
|
66
78
|
fetched_files += lerna_packages
|
67
79
|
fetched_files += path_dependencies(fetched_files)
|
68
|
-
instrument_package_manager_version
|
69
80
|
|
70
81
|
fetched_files << inferred_npmrc if inferred_npmrc
|
71
82
|
|
@@ -103,20 +114,6 @@ module Dependabot
|
|
103
114
|
@inferred_npmrc = nil
|
104
115
|
end
|
105
116
|
|
106
|
-
def instrument_package_manager_version
|
107
|
-
package_managers = {}
|
108
|
-
|
109
|
-
package_managers["npm"] = Helpers.npm_version_numeric(package_lock.content) if package_lock
|
110
|
-
package_managers["yarn"] = yarn_version if yarn_version
|
111
|
-
package_managers["shrinkwrap"] = 1 if shrinkwrap
|
112
|
-
|
113
|
-
Dependabot.instrument(
|
114
|
-
Notifications::FILE_PARSER_PACKAGE_MANAGER_VERSION_PARSED,
|
115
|
-
ecosystem: "npm",
|
116
|
-
package_managers: package_managers
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
117
|
def yarn_version
|
121
118
|
return @yarn_version if defined?(@yarn_version)
|
122
119
|
|
@@ -275,6 +272,9 @@ module Dependabot
|
|
275
272
|
current_dir = file.name.rpartition("/").first
|
276
273
|
current_dir = nil if current_dir == ""
|
277
274
|
|
275
|
+
current_depth = File.join(directory, file.name).split("/").count { |path| !path.empty? }
|
276
|
+
path_to_directory = "../" * current_depth
|
277
|
+
|
278
278
|
dep_types = NpmAndYarn::FileParser::DEPENDENCY_TYPES
|
279
279
|
parsed_manifest = JSON.parse(file.content)
|
280
280
|
dependency_objects = parsed_manifest.values_at(*dep_types).compact
|
@@ -294,7 +294,7 @@ module Dependabot
|
|
294
294
|
select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) }.
|
295
295
|
map do |name, path|
|
296
296
|
path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "")
|
297
|
-
raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/")
|
297
|
+
raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", "#{path_to_directory}..")
|
298
298
|
|
299
299
|
path = File.join(current_dir, path) unless current_dir.nil?
|
300
300
|
[name, Pathname.new(path).cleanpath.to_path]
|
@@ -353,7 +353,7 @@ module Dependabot
|
|
353
353
|
dependency_files
|
354
354
|
end
|
355
355
|
|
356
|
-
def fetch_lerna_packages_from_path(path
|
356
|
+
def fetch_lerna_packages_from_path(path)
|
357
357
|
dependency_files = []
|
358
358
|
|
359
359
|
package_json_path = File.join(path, "package.json")
|
@@ -366,19 +366,7 @@ module Dependabot
|
|
366
366
|
fetch_file_if_present(File.join(path, "npm-shrinkwrap.json"))
|
367
367
|
].compact
|
368
368
|
rescue Dependabot::DependencyFileNotFound
|
369
|
-
|
370
|
-
parsed_lerna_json["packages"].any? do |globbed_path|
|
371
|
-
next false unless globbed_path.include?("**")
|
372
|
-
|
373
|
-
File.fnmatch?(globbed_path, path)
|
374
|
-
end
|
375
|
-
|
376
|
-
if matches_double_glob && !nested
|
377
|
-
dependency_files +=
|
378
|
-
find_directories(File.join(path, "*")).flat_map do |nested_path|
|
379
|
-
fetch_lerna_packages_from_path(nested_path, true)
|
380
|
-
end
|
381
|
-
end
|
369
|
+
nil
|
382
370
|
end
|
383
371
|
|
384
372
|
dependency_files
|
@@ -396,7 +384,6 @@ module Dependabot
|
|
396
384
|
paths_array.flat_map { |path| recursive_find_directories(path) }
|
397
385
|
end
|
398
386
|
|
399
|
-
# Only expands globs one level deep, so path/**/* gets expanded to path/
|
400
387
|
def find_directories(glob)
|
401
388
|
return [glob] unless glob.include?("*") || yarn_ignored_glob(glob)
|
402
389
|
|
@@ -418,11 +405,12 @@ module Dependabot
|
|
418
405
|
def matching_paths(glob, paths)
|
419
406
|
ignored_glob = yarn_ignored_glob(glob)
|
420
407
|
glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
|
408
|
+
glob = "#{glob}/*" if glob.end_with?("**")
|
421
409
|
|
422
|
-
results = paths.select { |filename| File.fnmatch?(glob, filename) }
|
410
|
+
results = paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) }
|
423
411
|
return results unless ignored_glob
|
424
412
|
|
425
|
-
results.reject { |filename| File.fnmatch?(ignored_glob, filename) }
|
413
|
+
results.reject { |filename| File.fnmatch?(ignored_glob, filename, File::FNM_PATHNAME) }
|
426
414
|
end
|
427
415
|
|
428
416
|
def recursive_find_directories(glob, prefix = "")
|
@@ -431,11 +419,12 @@ module Dependabot
|
|
431
419
|
glob = glob.gsub(%r{^\./}, "")
|
432
420
|
glob_parts = glob.split("/")
|
433
421
|
|
434
|
-
|
435
|
-
|
422
|
+
current_glob = glob_parts.first
|
423
|
+
paths = find_directories(prefix + current_glob)
|
424
|
+
next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1)
|
436
425
|
return paths if next_parts.empty?
|
437
426
|
|
438
|
-
paths
|
427
|
+
paths += paths.flat_map do |expanded_path|
|
439
428
|
recursive_find_directories(next_parts.join("/"), "#{expanded_path}/")
|
440
429
|
end
|
441
430
|
|
@@ -497,7 +486,6 @@ module Dependabot
|
|
497
486
|
raise Dependabot::DependencyFileNotParseable, lerna_json.path
|
498
487
|
end
|
499
488
|
end
|
500
|
-
# rubocop:enable Metrics/ClassLength
|
501
489
|
end
|
502
490
|
end
|
503
491
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "dependabot/errors"
|
5
|
+
require "dependabot/npm_and_yarn/helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module NpmAndYarn
|
9
|
+
class FileParser
|
10
|
+
class JsonLock
|
11
|
+
def initialize(dependency_file)
|
12
|
+
@dependency_file = dependency_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def parsed
|
16
|
+
@parsed ||= JSON.parse(@dependency_file.content)
|
17
|
+
rescue JSON::ParserError
|
18
|
+
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
19
|
+
end
|
20
|
+
|
21
|
+
def dependencies
|
22
|
+
recursively_fetch_dependencies(parsed)
|
23
|
+
end
|
24
|
+
|
25
|
+
def details(dependency_name, _requirement, manifest_name)
|
26
|
+
if Helpers.npm_version(@dependency_file.content) == "npm8"
|
27
|
+
# NOTE: npm 8 sometimes doesn't install workspace dependencies in the
|
28
|
+
# workspace folder so we need to fallback to checking top-level
|
29
|
+
nested_details = parsed.dig("packages", node_modules_path(manifest_name, dependency_name))
|
30
|
+
details = nested_details || parsed.dig("packages", "node_modules/#{dependency_name}")
|
31
|
+
details&.slice("version", "resolved", "integrity", "dev")
|
32
|
+
else
|
33
|
+
parsed.dig("dependencies", dependency_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def recursively_fetch_dependencies(object_with_dependencies)
|
40
|
+
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
41
|
+
|
42
|
+
dependencies = object_with_dependencies["dependencies"]
|
43
|
+
dependencies ||= object_with_dependencies.fetch("packages", {})
|
44
|
+
|
45
|
+
dependencies.each do |name, details|
|
46
|
+
next if name.empty? # v3 lockfiles include an empty key holding info of the current package
|
47
|
+
|
48
|
+
version = Version.semver_for(details["version"])
|
49
|
+
next unless version
|
50
|
+
|
51
|
+
dependency_args = {
|
52
|
+
name: name.split("node_modules/").last,
|
53
|
+
version: version,
|
54
|
+
package_manager: "npm_and_yarn",
|
55
|
+
requirements: []
|
56
|
+
}
|
57
|
+
|
58
|
+
if details["bundled"]
|
59
|
+
dependency_args[:subdependency_metadata] =
|
60
|
+
[{ npm_bundled: details["bundled"] }]
|
61
|
+
end
|
62
|
+
|
63
|
+
if details["dev"]
|
64
|
+
dependency_args[:subdependency_metadata] =
|
65
|
+
[{ production: !details["dev"] }]
|
66
|
+
end
|
67
|
+
|
68
|
+
dependency_set << Dependency.new(**dependency_args)
|
69
|
+
dependency_set += recursively_fetch_dependencies(details)
|
70
|
+
end
|
71
|
+
|
72
|
+
dependency_set
|
73
|
+
end
|
74
|
+
|
75
|
+
def node_modules_path(manifest_name, dependency_name)
|
76
|
+
return "node_modules/#{dependency_name}" if manifest_name == "package.json"
|
77
|
+
|
78
|
+
workspace_path = manifest_name.gsub("/package.json", "")
|
79
|
+
File.join(workspace_path, "node_modules", dependency_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -8,15 +8,23 @@ module Dependabot
|
|
8
8
|
module NpmAndYarn
|
9
9
|
class FileParser
|
10
10
|
class LockfileParser
|
11
|
+
require "dependabot/npm_and_yarn/file_parser/yarn_lock"
|
12
|
+
require "dependabot/npm_and_yarn/file_parser/json_lock"
|
13
|
+
|
11
14
|
def initialize(dependency_files:)
|
12
15
|
@dependency_files = dependency_files
|
13
16
|
end
|
14
17
|
|
15
18
|
def parse_set
|
16
19
|
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
|
21
|
+
# NOTE: The DependencySet will de-dupe our dependencies, so they
|
22
|
+
# end up unique by name. That's not a perfect representation of
|
23
|
+
# the nested nature of JS resolution, but it makes everything work
|
24
|
+
# comparably to other flat-resolution strategies
|
25
|
+
(yarn_locks + package_locks + shrinkwraps).each do |file|
|
26
|
+
dependency_set += lockfile_for(file).dependencies
|
27
|
+
end
|
20
28
|
|
21
29
|
dependency_set
|
22
30
|
end
|
@@ -27,12 +35,7 @@ module Dependabot
|
|
27
35
|
|
28
36
|
def lockfile_details(dependency_name:, requirement:, manifest_name:)
|
29
37
|
potential_lockfiles_for_manifest(manifest_name).each do |lockfile|
|
30
|
-
details =
|
31
|
-
if [*package_locks, *shrinkwraps].include?(lockfile)
|
32
|
-
npm_lockfile_details(lockfile, dependency_name, manifest_name)
|
33
|
-
else
|
34
|
-
yarn_lockfile_details(lockfile, dependency_name, requirement, manifest_name)
|
35
|
-
end
|
38
|
+
details = lockfile_for(lockfile).details(dependency_name, requirement, manifest_name)
|
36
39
|
|
37
40
|
return details if details
|
38
41
|
end
|
@@ -56,182 +59,17 @@ module Dependabot
|
|
56
59
|
filter_map { |nm| dependency_files.find { |f| f.name == nm } }
|
57
60
|
end
|
58
61
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
if Helpers.npm_version(lockfile.content) == "npm8"
|
63
|
-
# NOTE: npm 8 sometimes doesn't install workspace dependencies in the
|
64
|
-
# workspace folder so we need to fallback to checking top-level
|
65
|
-
nested_details = parsed_lockfile.dig("packages", node_modules_path(manifest_name, dependency_name))
|
66
|
-
details = nested_details || parsed_lockfile.dig("packages", "node_modules/#{dependency_name}")
|
67
|
-
details&.slice("version", "resolved", "integrity", "dev")
|
68
|
-
else
|
69
|
-
parsed_lockfile.dig("dependencies", dependency_name)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def yarn_lockfile_details(lockfile, dependency_name, requirement, _manifest_name)
|
74
|
-
parsed_yarn_lock = parse_yarn_lock(lockfile)
|
75
|
-
details_candidates =
|
76
|
-
parsed_yarn_lock.
|
77
|
-
select { |k, _| k.split(/(?<=\w)\@/)[0] == dependency_name }
|
78
|
-
|
79
|
-
# If there's only one entry for this dependency, use it, even if
|
80
|
-
# the requirement in the lockfile doesn't match
|
81
|
-
if details_candidates.one?
|
82
|
-
details_candidates.first.last
|
83
|
-
else
|
84
|
-
details_candidates.find do |k, _|
|
85
|
-
k.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
|
86
|
-
end&.last
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def node_modules_path(manifest_name, dependency_name)
|
91
|
-
return "node_modules/#{dependency_name}" if manifest_name == "package.json"
|
92
|
-
|
93
|
-
workspace_path = manifest_name.gsub("/package.json", "")
|
94
|
-
File.join(workspace_path, "node_modules", dependency_name)
|
95
|
-
end
|
96
|
-
|
97
|
-
def yarn_lock_dependencies
|
98
|
-
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
99
|
-
|
100
|
-
yarn_locks.each do |yarn_lock|
|
101
|
-
parse_yarn_lock(yarn_lock).each do |req, details|
|
102
|
-
next unless semver_version_for(details["version"])
|
103
|
-
next if alias_package?(req)
|
104
|
-
next if workspace_package?(req)
|
105
|
-
next if req == "__metadata"
|
106
|
-
|
107
|
-
# NOTE: The DependencySet will de-dupe our dependencies, so they
|
108
|
-
# end up unique by name. That's not a perfect representation of
|
109
|
-
# the nested nature of JS resolution, but it makes everything work
|
110
|
-
# comparably to other flat-resolution strategies
|
111
|
-
dependency_set << Dependency.new(
|
112
|
-
name: req.split(/(?<=\w)\@/).first,
|
113
|
-
version: semver_version_for(details["version"]),
|
114
|
-
package_manager: "npm_and_yarn",
|
115
|
-
requirements: []
|
116
|
-
)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
dependency_set
|
121
|
-
end
|
122
|
-
|
123
|
-
def package_lock_dependencies
|
124
|
-
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
125
|
-
|
126
|
-
# NOTE: The DependencySet will de-dupe our dependencies, so they
|
127
|
-
# end up unique by name. That's not a perfect representation of
|
128
|
-
# the nested nature of JS resolution, but it makes everything work
|
129
|
-
# comparably to other flat-resolution strategies
|
130
|
-
package_locks.each do |package_lock|
|
131
|
-
parsed_lockfile = parse_package_lock(package_lock)
|
132
|
-
deps = recursively_fetch_npm_lock_dependencies(parsed_lockfile)
|
133
|
-
dependency_set += deps
|
134
|
-
end
|
135
|
-
|
136
|
-
dependency_set
|
137
|
-
end
|
138
|
-
|
139
|
-
def shrinkwrap_dependencies
|
140
|
-
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
141
|
-
|
142
|
-
# NOTE: The DependencySet will de-dupe our dependencies, so they
|
143
|
-
# end up unique by name. That's not a perfect representation of
|
144
|
-
# the nested nature of JS resolution, but it makes everything work
|
145
|
-
# comparably to other flat-resolution strategies
|
146
|
-
shrinkwraps.each do |shrinkwrap|
|
147
|
-
parsed_lockfile = parse_shrinkwrap(shrinkwrap)
|
148
|
-
deps = recursively_fetch_npm_lock_dependencies(parsed_lockfile)
|
149
|
-
dependency_set += deps
|
150
|
-
end
|
151
|
-
|
152
|
-
dependency_set
|
153
|
-
end
|
154
|
-
|
155
|
-
def recursively_fetch_npm_lock_dependencies(object_with_dependencies)
|
156
|
-
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
157
|
-
|
158
|
-
object_with_dependencies.
|
159
|
-
fetch("dependencies", {}).each do |name, details|
|
160
|
-
next unless semver_version_for(details["version"])
|
161
|
-
|
162
|
-
dependency_args = {
|
163
|
-
name: name,
|
164
|
-
version: semver_version_for(details["version"]),
|
165
|
-
package_manager: "npm_and_yarn",
|
166
|
-
requirements: []
|
167
|
-
}
|
168
|
-
|
169
|
-
if details["bundled"]
|
170
|
-
dependency_args[:subdependency_metadata] =
|
171
|
-
[{ npm_bundled: details["bundled"] }]
|
172
|
-
end
|
173
|
-
|
174
|
-
if details["dev"]
|
175
|
-
dependency_args[:subdependency_metadata] =
|
176
|
-
[{ production: !details["dev"] }]
|
177
|
-
end
|
178
|
-
|
179
|
-
dependency_set << Dependency.new(**dependency_args)
|
180
|
-
dependency_set += recursively_fetch_npm_lock_dependencies(details)
|
181
|
-
end
|
182
|
-
|
183
|
-
dependency_set
|
184
|
-
end
|
185
|
-
|
186
|
-
def semver_version_for(version_string)
|
187
|
-
# The next two lines are to guard against improperly formatted
|
188
|
-
# versions in a lockfile, such as an empty string or additional
|
189
|
-
# characters. NPM/yarn fixes these when running an update, so we can
|
190
|
-
# safely ignore these versions.
|
191
|
-
return if version_string == ""
|
192
|
-
return unless version_class.correct?(version_string)
|
193
|
-
|
194
|
-
version_string
|
62
|
+
def parsed_lockfile(file)
|
63
|
+
lockfile_for(file).parsed
|
195
64
|
end
|
196
65
|
|
197
|
-
def
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
def parse_package_lock(package_lock)
|
206
|
-
@parse_package_lock ||= {}
|
207
|
-
@parse_package_lock[package_lock.name] ||=
|
208
|
-
JSON.parse(package_lock.content)
|
209
|
-
rescue JSON::ParserError
|
210
|
-
raise Dependabot::DependencyFileNotParseable, package_lock.path
|
211
|
-
end
|
212
|
-
|
213
|
-
def parse_shrinkwrap(shrinkwrap)
|
214
|
-
@parse_shrinkwrap ||= {}
|
215
|
-
@parse_shrinkwrap[shrinkwrap.name] ||=
|
216
|
-
JSON.parse(shrinkwrap.content)
|
217
|
-
rescue JSON::ParserError
|
218
|
-
raise Dependabot::DependencyFileNotParseable, shrinkwrap.path
|
219
|
-
end
|
220
|
-
|
221
|
-
def parse_yarn_lock(yarn_lock)
|
222
|
-
@parsed_yarn_lock ||= {}
|
223
|
-
@parsed_yarn_lock[yarn_lock.name] ||=
|
224
|
-
SharedHelpers.in_a_temporary_directory do
|
225
|
-
File.write("yarn.lock", yarn_lock.content)
|
226
|
-
|
227
|
-
SharedHelpers.run_helper_subprocess(
|
228
|
-
command: NativeHelpers.helper_path,
|
229
|
-
function: "yarn:parseLockfile",
|
230
|
-
args: [Dir.pwd]
|
231
|
-
)
|
232
|
-
rescue SharedHelpers::HelperSubprocessFailed
|
233
|
-
raise Dependabot::DependencyFileNotParseable, yarn_lock.path
|
234
|
-
end
|
66
|
+
def lockfile_for(file)
|
67
|
+
@lockfiles ||= {}
|
68
|
+
@lockfiles[file.name] ||= if [*package_locks, *shrinkwraps].include?(file)
|
69
|
+
JsonLock.new(file)
|
70
|
+
else
|
71
|
+
YarnLock.new(file)
|
72
|
+
end
|
235
73
|
end
|
236
74
|
|
237
75
|
def package_locks
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/shared_helpers"
|
4
|
+
require "dependabot/errors"
|
5
|
+
require "dependabot/npm_and_yarn/native_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module NpmAndYarn
|
9
|
+
class FileParser
|
10
|
+
class YarnLock
|
11
|
+
def initialize(dependency_file)
|
12
|
+
@dependency_file = dependency_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def parsed
|
16
|
+
@parsed ||= SharedHelpers.in_a_temporary_directory do
|
17
|
+
File.write("yarn.lock", @dependency_file.content)
|
18
|
+
|
19
|
+
SharedHelpers.run_helper_subprocess(
|
20
|
+
command: NativeHelpers.helper_path,
|
21
|
+
function: "yarn:parseLockfile",
|
22
|
+
args: [Dir.pwd]
|
23
|
+
)
|
24
|
+
rescue SharedHelpers::HelperSubprocessFailed
|
25
|
+
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def dependencies
|
30
|
+
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
31
|
+
|
32
|
+
parsed.each do |reqs, details|
|
33
|
+
reqs.split(", ").each do |req|
|
34
|
+
version = Version.semver_for(details["version"])
|
35
|
+
next unless version
|
36
|
+
next if alias_package?(req)
|
37
|
+
next if workspace_package?(req)
|
38
|
+
next if req == "__metadata"
|
39
|
+
|
40
|
+
dependency_set << Dependency.new(
|
41
|
+
name: req.split(/(?<=\w)\@/).first,
|
42
|
+
version: version,
|
43
|
+
package_manager: "npm_and_yarn",
|
44
|
+
requirements: []
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
dependency_set
|
50
|
+
end
|
51
|
+
|
52
|
+
def details(dependency_name, requirement, _manifest_name)
|
53
|
+
details_candidates =
|
54
|
+
parsed.
|
55
|
+
select { |k, _| k.split(/(?<=\w)\@/)[0] == dependency_name }
|
56
|
+
|
57
|
+
# If there's only one entry for this dependency, use it, even if
|
58
|
+
# the requirement in the lockfile doesn't match
|
59
|
+
if details_candidates.one?
|
60
|
+
details_candidates.first.last
|
61
|
+
else
|
62
|
+
details_candidates.find do |k, _|
|
63
|
+
k.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
|
64
|
+
end&.last
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def alias_package?(requirement)
|
71
|
+
requirement.match?(/@npm:(.+@(?!npm))/)
|
72
|
+
end
|
73
|
+
|
74
|
+
def workspace_package?(requirement)
|
75
|
+
requirement.include?("@workspace:")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|