dependabot-npm_and_yarn 0.373.0 → 0.374.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0c7fd9ad9edbc848ecc61bacd020254d35d569f4b1cea212522b993af8644e4
4
- data.tar.gz: d2c20af4468b35af219b1f515f9be0f03d5d1a33d4a8c3bc999b9eeb07e08cb7
3
+ metadata.gz: 6d73a3111c08214ea172fa372dc8a3170c06e320ff7584da2b3bbf874de01b53
4
+ data.tar.gz: 64dde7b56d164827bd2a9ad46665b52bd97ffa93cfa35032ef03f0b827f344dc
5
5
  SHA512:
6
- metadata.gz: f0088c2492c6d9c1c4719dd3ce080cf56ef29e0a71d5b0e68f6a1994323d6ce267576a1a9e1f2ea3f89481fa9519df63968a455f9b5a68b72d76d9664ab1befd
7
- data.tar.gz: 7dcaf7008726c174dd201f7316f56454a9f251a3418d9c15494c584f90156042e9fad1634be2b9510502022e5563c05b9e26cb1ade420989832e82bc8e628544
6
+ metadata.gz: dd062f7ceca3feef8ac45235c5063c45a990347cd31c6702d4fbe377a932398ee556cf94124ca0db27bcdaea9f01ec55b1cdf16d6fb498c158c66fb8735c84ce
7
+ data.tar.gz: d84403b98bf265e074e087a386441929bfd66fdf01a5339503e8c93977107577f2b84b00330f6a049b7c594da104bc3367be36f4c44ff542704e415dfed3a125
@@ -222,7 +222,7 @@ export async function findVulnerableDependencies(
222
222
  return left.localeCompare(right);
223
223
  });
224
224
  const remainingVulnerableTargets = vulnLocations
225
- .map((loc) => lookupChildLocation(fixTree, loc)?.version)
225
+ .map((loc) => resolveNodeFromLocation(fixTree, loc)?.version)
226
226
  .filter((version): version is string => typeof version === "string");
227
227
 
228
228
  // If any originally vulnerable location still exists after applying the
@@ -233,7 +233,7 @@ export async function findVulnerableDependencies(
233
233
  remainingVulnerableTargets[0] ?? fixTree.children.get(name)?.version;
234
234
 
235
235
  for (const update of response.fix_updates) {
236
- update.target_version = lookupChildLocation(
236
+ update.target_version = resolveNodeFromLocation(
237
237
  fixTree,
238
238
  update.dependency_location!
239
239
  )?.version;
@@ -358,12 +358,17 @@ function groupBy<T>(
358
358
  }
359
359
 
360
360
  /**
361
- * Look up the child node at the given location in the tree rooted at `root`.
362
- * `root` is an ArboristNode, and `location` is a string containing the normalized
363
- * path to the child node, relative to the root. This string can be taken from
364
- * ArboristNode.location and will be of the form "node_modules/foo/node_modules/bar".
361
+ * Resolve the node at the given location in the tree rooted at `root`, returning
362
+ * null if the location is invalid or doesn't exist in the tree. The location is a
363
+ * string containing the normalized path to the node, relative to the root, and can
364
+ * be taken from ArboristNode.location. It will be of the form "node_modules/foo/node_modules/bar".
365
+ *
366
+ * If the exact package location does not exist in the tree, the function will use
367
+ * Arborist's resolve method to attempt to resolve the target package from the furthest
368
+ * existing ancestor location. This allows us to handle cases where the fixed package
369
+ * is hoisted to a higher level in the tree than the vulnerable package was originally located.
365
370
  */
366
- function lookupChildLocation(
371
+ function resolveNodeFromLocation(
367
372
  root: Arborist.Node,
368
373
  location: string
369
374
  ): Arborist.Node | Arborist.Link | null {
@@ -374,14 +379,20 @@ function lookupChildLocation(
374
379
  const parts = location
375
380
  .substring("node_modules/".length)
376
381
  .split("/node_modules/");
377
- let current: Arborist.Node | Arborist.Link | undefined = root;
378
- for (const part of parts) {
379
- if (!current) {
380
- return null;
382
+ const target = parts[parts.length - 1];
383
+ let current: Arborist.Node | Arborist.Link = root;
384
+ for (const pkgName of parts) {
385
+ const next: Arborist.Node | Arborist.Link | undefined =
386
+ current.children.get(pkgName);
387
+ if (!next) {
388
+ // This is the furthest we can resolve down the tree based on the given location.
389
+ // Ask the tree to resolve the target node.
390
+ return current.resolve(target) as Arborist.Node | null;
381
391
  }
382
- current = current.children.get(part);
392
+ current = next;
383
393
  }
384
- return current ?? null;
394
+ // We've resolved the entire location successfully.
395
+ return current;
385
396
  }
386
397
 
387
398
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "test-vulnerability-auditor-fix-hoists-package",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "test-vulnerability-auditor-fix-hoists-package",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "@dependabot-fixtures/npm-parent-dependency-with-more-versions": "1.0.0",
12
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions": "1.0.0"
13
+ }
14
+ },
15
+ "node_modules/@dependabot-fixtures/npm-parent-dependency-with-more-versions": {
16
+ "version": "1.0.0",
17
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-parent-dependency-with-more-versions/-/npm-parent-dependency-with-more-versions-1.0.0.tgz",
18
+ "integrity": "sha512-Ys1u0synVJwqj1+bgo6g0uWMMDg3v55IG8O6qEM2WKP0Y9lmxSoN2egArdfBZcKuut+1EBcWmtM89g6P40EFJw==",
19
+ "license": "ISC",
20
+ "dependencies": {
21
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions": "^1.0.0"
22
+ }
23
+ },
24
+ "node_modules/@dependabot-fixtures/npm-parent-dependency-with-more-versions/node_modules/@dependabot-fixtures/npm-transitive-dependency-with-more-versions": {
25
+ "version": "1.0.1",
26
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-transitive-dependency-with-more-versions/-/npm-transitive-dependency-with-more-versions-1.0.1.tgz",
27
+ "integrity": "sha512-L25+LUfJNPO3T+/RdhG62Hv2gIwiZLWR05qqqV1mzqD9goLy31/oc5rcF8/0pjOF45Zv/JqZm2whi4qhXa9plw==",
28
+ "license": "ISC"
29
+ },
30
+ "node_modules/@dependabot-fixtures/npm-transitive-dependency-with-more-versions": {
31
+ "version": "1.0.0",
32
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-transitive-dependency-with-more-versions/-/npm-transitive-dependency-with-more-versions-1.0.0.tgz",
33
+ "integrity": "sha512-IHtKNrRBm6bDrL2Jf1w+ZMg/4MmAb6MMHmP8CVebKnfn6Za7h39L7hG/ozA0vKI1ZZpGSfkRshvCd9EFFAc8IA==",
34
+ "license": "ISC"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "test-vulnerability-auditor-fix-hoists-package",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "dependencies": {
10
+ "@dependabot-fixtures/npm-parent-dependency-with-more-versions": "1.0.0",
11
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions": "1.0.0"
12
+ }
13
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "test-vulnerability-auditor-fix-removes-package",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "test-vulnerability-auditor-fix-removes-package",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "@dependabot-fixtures/npm-remove-dependency": "^10.0.0"
12
+ }
13
+ },
14
+ "node_modules/@dependabot-fixtures/npm-remove-dependency": {
15
+ "version": "10.0.0",
16
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-remove-dependency/-/npm-remove-dependency-10.0.0.tgz",
17
+ "integrity": "sha512-QMgb6isjtNCnql6Nn+/h2v759qIW4f1ZDER8IbUJaIuyppDq3BqABbiwTFNenynu39yQZ1YAUFbWuNGeECNLyw==",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@dependabot-fixtures/npm-transitive-dependency": "1.0.0"
21
+ }
22
+ },
23
+ "node_modules/@dependabot-fixtures/npm-transitive-dependency": {
24
+ "version": "1.0.0",
25
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-transitive-dependency/-/npm-transitive-dependency-1.0.0.tgz",
26
+ "integrity": "sha512-nFbzQH0TRgdzSA2/FH6MPnxZDpD+5Bgz00aD5Edgbc1wY/k8VC9s7lnk22dBTgJLwoY7MgbrnAf9rAvN08hHVg==",
27
+ "license": "ISC"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "test-vulnerability-auditor-fix-removes-package",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "dependencies": {
10
+ "@dependabot-fixtures/npm-remove-dependency": "^10.0.0"
11
+ }
12
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "locked-transitive-dependency-outdated",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "locked-transitive-dependency-outdated",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@dependabot-fixtures/npm-parent-dependency": "2.0.0"
13
+ }
14
+ },
15
+ "node_modules/@dependabot-fixtures/npm-intermediate-dependency": {
16
+ "version": "0.0.1",
17
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-intermediate-dependency/-/npm-intermediate-dependency-0.0.1.tgz",
18
+ "integrity": "sha512-/N77Dzpfg8BIfFgpJrMk86ueUYTVhmpc4RobuHpIpKSc3GZr4Ltu4au92brnUGk66UkzgrMmtgqRXO8OrOspKQ==",
19
+ "dependencies": {
20
+ "@dependabot-fixtures/npm-transitive-dependency": "1.0.0"
21
+ }
22
+ },
23
+ "node_modules/@dependabot-fixtures/npm-parent-dependency": {
24
+ "version": "2.0.0",
25
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-parent-dependency/-/npm-parent-dependency-2.0.0.tgz",
26
+ "integrity": "sha512-5LtLEL1yzO2TdkNX3R9cvr+nKmhw5h4xM0wkFTJeK14wxlI9d8gEYA+I2hUi+IP96ucBSztAnOgZVwoJHEZb6A==",
27
+ "dependencies": {
28
+ "@dependabot-fixtures/npm-intermediate-dependency": "0.0.1"
29
+ }
30
+ },
31
+ "node_modules/@dependabot-fixtures/npm-transitive-dependency": {
32
+ "version": "1.0.0",
33
+ "resolved": "https://registry.npmjs.org/@dependabot-fixtures/npm-transitive-dependency/-/npm-transitive-dependency-1.0.0.tgz",
34
+ "integrity": "sha512-nFbzQH0TRgdzSA2/FH6MPnxZDpD+5Bgz00aD5Edgbc1wY/k8VC9s7lnk22dBTgJLwoY7MgbrnAf9rAvN08hHVg=="
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "locked-transitive-dependency-outdated",
3
+ "version": "1.0.0",
4
+ "description": "test fixture where the lockfile contains a vulnerability but it's not in sync with this file",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {}
13
+ }
@@ -52,28 +52,34 @@ describe("findVulnerableDependencies", () => {
52
52
  },
53
53
  ];
54
54
  const actual = await findVulnerableDependencies(tempDir, advisories);
55
- const expected = {
56
- dependency_name: "@msgpack/msgpack",
57
- fix_updates: [
55
+
56
+ expect(actual.dependency_name).toBe("@msgpack/msgpack");
57
+ expect(actual.current_version).toBe("2.7.2");
58
+ expect(actual.fix_available).toBe(true);
59
+ expect(actual.target_version).toBe("2.8.0");
60
+
61
+ expect(actual.top_level_ancestors).toHaveLength(2);
62
+ expect(actual.top_level_ancestors).toEqual(
63
+ expect.arrayContaining(["@msgpack/msgpack", "wireql-client"])
64
+ );
65
+
66
+ expect(actual.fix_updates).toHaveLength(2);
67
+ expect(actual.fix_updates).toEqual(
68
+ expect.arrayContaining([
58
69
  {
59
70
  dependency_name: "@msgpack/msgpack",
60
71
  current_version: "2.7.2",
61
- top_level_ancestors: [],
62
72
  target_version: "2.8.0",
73
+ top_level_ancestors: [],
63
74
  },
64
75
  {
65
76
  dependency_name: "@msgpack/msgpack",
66
77
  current_version: "3.0.0",
67
- top_level_ancestors: ["wireql-client"],
68
78
  target_version: "3.1.3",
79
+ top_level_ancestors: ["wireql-client"],
69
80
  },
70
- ],
71
- top_level_ancestors: ["@msgpack/msgpack", "wireql-client"],
72
- current_version: "2.7.2",
73
- fix_available: true,
74
- target_version: "2.8.0",
75
- };
76
- expect(actual).toEqual(expected);
81
+ ])
82
+ );
77
83
  });
78
84
 
79
85
  it("finds vulnerable dependencies through workspace link nodes", async () => {
@@ -95,15 +101,130 @@ describe("findVulnerableDependencies", () => {
95
101
  // chains and the dependency would appear to have no top-level ancestors.
96
102
  // Note: the ancestor name comes from the Link target's directory ("app"),
97
103
  // not its package.json name ("@test/app").
104
+ expect(actual.dependency_name).toBe(
105
+ "@dependabot-fixtures/npm-parent-dependency"
106
+ );
107
+ expect(actual.current_version).toBe("2.0.0");
98
108
  expect(actual.fix_available).toBe(true);
109
+ expect(actual.target_version).toBe("2.0.2");
110
+
99
111
  expect(actual.top_level_ancestors).toEqual(["app"]);
112
+
100
113
  expect(actual.fix_updates).toEqual([
101
114
  {
102
115
  dependency_name: "@dependabot-fixtures/npm-parent-dependency",
103
116
  current_version: "2.0.0",
104
- top_level_ancestors: ["app"],
105
117
  target_version: "2.0.2",
118
+ top_level_ancestors: ["app"],
106
119
  },
107
120
  ]);
108
121
  });
122
+
123
+ it("returns empty fix updates when package-lock.json is outdated", async () => {
124
+ helpers.copyDependencies(
125
+ "vulnerability-auditor/outdated-package-lock",
126
+ tempDir
127
+ );
128
+
129
+ const advisories = [
130
+ {
131
+ dependency_name: "@dependabot-fixtures/npm-transitive-dependency",
132
+ affected_versions: [">= 0, < 1.0.1"],
133
+ },
134
+ ];
135
+ const actual = await findVulnerableDependencies(tempDir, advisories);
136
+ const expected = {
137
+ current_version: "1.0.0",
138
+ dependency_name: "@dependabot-fixtures/npm-transitive-dependency",
139
+ fix_available: true,
140
+ fix_updates: [],
141
+ target_version: undefined,
142
+ top_level_ancestors: [],
143
+ };
144
+ expect(actual).toEqual(expected);
145
+ });
146
+
147
+ it("has undefined target_version when a vulnerable package is removed", async () => {
148
+ helpers.copyDependencies(
149
+ "vulnerability-auditor/fix-removes-package",
150
+ tempDir
151
+ );
152
+
153
+ const advisories = [
154
+ {
155
+ dependency_name: "@dependabot-fixtures/npm-transitive-dependency",
156
+ affected_versions: [">= 0, < 1.0.1"],
157
+ },
158
+ ];
159
+ const actual = await findVulnerableDependencies(tempDir, advisories);
160
+ const expected = {
161
+ current_version: "1.0.0",
162
+ dependency_name: "@dependabot-fixtures/npm-transitive-dependency",
163
+ fix_available: true,
164
+ fix_updates: [
165
+ {
166
+ current_version: "10.0.0",
167
+ dependency_name: "@dependabot-fixtures/npm-remove-dependency",
168
+ target_version: "10.0.1",
169
+ top_level_ancestors: [],
170
+ },
171
+ ],
172
+ target_version: undefined,
173
+ top_level_ancestors: ["@dependabot-fixtures/npm-remove-dependency"],
174
+ };
175
+ expect(actual).toEqual(expected);
176
+ });
177
+
178
+ it("has defined target_versions in fix_updates when a vulnerable package is hoisted", async () => {
179
+ helpers.copyDependencies(
180
+ "vulnerability-auditor/fix-hoists-package",
181
+ tempDir
182
+ );
183
+
184
+ const advisories = [
185
+ {
186
+ dependency_name:
187
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions",
188
+ affected_versions: [">= 0, < 1.0.2"],
189
+ },
190
+ ];
191
+ const actual = await findVulnerableDependencies(tempDir, advisories);
192
+
193
+ expect(actual.dependency_name).toBe(
194
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions"
195
+ );
196
+ expect(actual.current_version).toBe("1.0.0");
197
+ expect(actual.fix_available).toBe(true);
198
+ expect(actual.target_version).toBe("1.0.2");
199
+
200
+ expect(actual.top_level_ancestors).toHaveLength(2);
201
+ expect(actual.top_level_ancestors).toEqual(
202
+ expect.arrayContaining([
203
+ "@dependabot-fixtures/npm-parent-dependency-with-more-versions",
204
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions",
205
+ ])
206
+ );
207
+
208
+ expect(actual.fix_updates).toHaveLength(2);
209
+ expect(actual.fix_updates).toEqual(
210
+ expect.arrayContaining([
211
+ {
212
+ dependency_name:
213
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions",
214
+ current_version: "1.0.1",
215
+ target_version: "1.0.2",
216
+ top_level_ancestors: [
217
+ "@dependabot-fixtures/npm-parent-dependency-with-more-versions",
218
+ ],
219
+ },
220
+ {
221
+ dependency_name:
222
+ "@dependabot-fixtures/npm-transitive-dependency-with-more-versions",
223
+ current_version: "1.0.0",
224
+ target_version: "1.0.2",
225
+ top_level_ancestors: [],
226
+ },
227
+ ])
228
+ );
229
+ });
109
230
  });
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "json"
5
+ require "yaml"
4
6
  require "sorbet-runtime"
5
7
 
6
8
  require "dependabot/dependency_graphers"
@@ -152,12 +154,138 @@ module Dependabot
152
154
  )
153
155
  end
154
156
 
155
- # Fetches subdependencies for a given dependency.
156
- # For npm/yarn/pnpm, we can extract this from the lockfile parser if available.
157
157
  sig { override.params(dependency: Dependabot::Dependency).returns(T::Array[String]) }
158
158
  def fetch_subdependencies(dependency)
159
- # Check if the parser has attached depends_on metadata
160
- dependency.metadata.fetch(:depends_on, [])
159
+ package_relationships.fetch(dependency.name, [])
160
+ end
161
+
162
+ sig { returns(T::Hash[String, T::Array[String]]) }
163
+ def package_relationships
164
+ @package_relationships ||= T.let(
165
+ fetch_package_relationships,
166
+ T.nilable(T::Hash[String, T::Array[String]])
167
+ )
168
+ end
169
+
170
+ sig { returns(T::Hash[String, T::Array[String]]) }
171
+ def fetch_package_relationships
172
+ case detected_package_manager
173
+ when NpmPackageManager::NAME
174
+ fetch_npm_lock_relationships
175
+ when YarnPackageManager::NAME
176
+ fetch_yarn_lock_relationships
177
+ when PNPMPackageManager::NAME
178
+ fetch_pnpm_lock_relationships
179
+ else
180
+ {}
181
+ end
182
+ end
183
+
184
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
185
+ def npm_lockfile
186
+ return @npm_lockfile if defined?(@npm_lockfile)
187
+
188
+ @npm_lockfile = T.let(
189
+ dependency_files.find { |f| f.name.end_with?(NpmPackageManager::LOCKFILE_NAME) },
190
+ T.nilable(Dependabot::DependencyFile)
191
+ )
192
+ end
193
+
194
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
195
+ def yarn_lockfile
196
+ return @yarn_lockfile if defined?(@yarn_lockfile)
197
+
198
+ @yarn_lockfile = T.let(
199
+ dependency_files.find { |f| f.name.end_with?(YarnPackageManager::LOCKFILE_NAME) },
200
+ T.nilable(Dependabot::DependencyFile)
201
+ )
202
+ end
203
+
204
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
205
+ def pnpm_lockfile
206
+ return @pnpm_lockfile if defined?(@pnpm_lockfile)
207
+
208
+ @pnpm_lockfile = T.let(
209
+ dependency_files.find { |f| f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME) },
210
+ T.nilable(Dependabot::DependencyFile)
211
+ )
212
+ end
213
+
214
+ sig { returns(T::Hash[String, T::Array[String]]) }
215
+ def fetch_npm_lock_relationships
216
+ parsed = JSON.parse(T.must(T.must(npm_lockfile).content))
217
+ packages = parsed.fetch("packages", {})
218
+
219
+ # v3/v2 lockfiles use a flat "packages" section
220
+ if packages.is_a?(Hash) && !packages.empty?
221
+ return packages.each_with_object({}) do |(path, details), rels|
222
+ next if path.empty? # skip root package entry
223
+ next unless details.is_a?(Hash)
224
+
225
+ children = details.fetch("dependencies", {}).keys
226
+ next if children.empty?
227
+
228
+ rels[path.split("node_modules/").last] = children
229
+ end
230
+ end
231
+
232
+ # if packages isn't present, attempt a v1 fallback
233
+ fetch_npm_v1_lock_relationships(parsed)
234
+ end
235
+
236
+ sig { params(parsed: T::Hash[String, T.untyped]).returns(T::Hash[String, T::Array[String]]) }
237
+ def fetch_npm_v1_lock_relationships(parsed)
238
+ dependencies = parsed.fetch("dependencies", {})
239
+ return {} unless dependencies.is_a?(Hash)
240
+
241
+ dependencies.each_with_object({}) do |(name, details), rels|
242
+ next unless details.is_a?(Hash)
243
+
244
+ nested = details.fetch("dependencies", nil)
245
+ next unless nested.is_a?(Hash)
246
+
247
+ children = nested.keys
248
+ rels[name] = children unless children.empty?
249
+ rels.merge!(fetch_npm_v1_lock_relationships(details))
250
+ end
251
+ end
252
+
253
+ sig { returns(T::Hash[String, T::Array[String]]) }
254
+ def fetch_yarn_lock_relationships
255
+ parsed = FileParser::YarnLock.new(T.must(yarn_lockfile)).parsed
256
+
257
+ parsed.each_with_object({}) do |(req, details), rels|
258
+ next unless details.is_a?(Hash)
259
+
260
+ parent_name = T.must(req.split(/(?<=\w)\@/).first)
261
+ children = details.fetch("dependencies", {})&.keys || []
262
+
263
+ next if children.empty?
264
+
265
+ rels[parent_name] ||= []
266
+ rels[parent_name].concat(children).uniq!
267
+ end
268
+ end
269
+
270
+ sig { returns(T::Hash[String, T::Array[String]]) }
271
+ def fetch_pnpm_lock_relationships
272
+ parsed = YAML.safe_load(T.must(T.must(pnpm_lockfile).content)) || {}
273
+
274
+ # v9+ uses "snapshots" for resolved dependency details; v6 uses "packages"
275
+ entries = parsed.fetch("snapshots", nil) || parsed.fetch("packages", {})
276
+
277
+ entries.each_with_object({}) do |(key, details), rels|
278
+ next unless details.is_a?(Hash)
279
+
280
+ # Keys are "/name@version" (v6) or "name@version" (v9)
281
+ parent_name = key.sub(%r{^/}, "").split(/(?<=\w)\@/).first
282
+ children = details.fetch("dependencies", {})&.keys || []
283
+
284
+ next if children.empty?
285
+
286
+ rels[parent_name] ||= []
287
+ rels[parent_name].concat(children).uniq!
288
+ end
161
289
  end
162
290
 
163
291
  sig { override.params(_dependency: Dependabot::Dependency).returns(String) }
@@ -71,10 +71,7 @@ module Dependabot
71
71
  name: package_name,
72
72
  version: version,
73
73
  package_manager: "npm_and_yarn",
74
- requirements: [],
75
- metadata: {
76
- depends_on: details&.fetch("dependencies", {})&.keys || []
77
- }
74
+ requirements: []
78
75
  }
79
76
 
80
77
  if details["bundled"]
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.373.0
4
+ version: 0.374.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.373.0
18
+ version: 0.374.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.373.0
25
+ version: 0.374.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -271,6 +271,12 @@ files:
271
271
  - helpers/package.json
272
272
  - helpers/patches/npm++pacote+9.5.12.patch
273
273
  - helpers/run.ts
274
+ - helpers/test/npm/fixtures/vulnerability-auditor/fix-hoists-package/package-lock.json
275
+ - helpers/test/npm/fixtures/vulnerability-auditor/fix-hoists-package/package.json
276
+ - helpers/test/npm/fixtures/vulnerability-auditor/fix-removes-package/package-lock.json
277
+ - helpers/test/npm/fixtures/vulnerability-auditor/fix-removes-package/package.json
278
+ - helpers/test/npm/fixtures/vulnerability-auditor/outdated-package-lock/package-lock.json
279
+ - helpers/test/npm/fixtures/vulnerability-auditor/outdated-package-lock/package.json
274
280
  - helpers/test/npm/fixtures/vulnerability-auditor/simple/package-lock.json
275
281
  - helpers/test/npm/fixtures/vulnerability-auditor/simple/package.json
276
282
  - helpers/test/npm/fixtures/vulnerability-auditor/update-needed-across-two-versions/package-lock.json
@@ -364,7 +370,7 @@ licenses:
364
370
  - MIT
365
371
  metadata:
366
372
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
367
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.373.0
373
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.374.0
368
374
  rdoc_options: []
369
375
  require_paths:
370
376
  - lib