dependabot-bun 0.296.2 → 0.296.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/.eslintrc +11 -0
  3. data/helpers/README.md +29 -0
  4. data/helpers/build +26 -0
  5. data/helpers/jest.config.js +5 -0
  6. data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
  7. data/helpers/lib/npm/index.js +9 -0
  8. data/helpers/lib/npm/vulnerability-auditor.js +291 -0
  9. data/helpers/lib/npm6/helpers.js +25 -0
  10. data/helpers/lib/npm6/index.js +9 -0
  11. data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
  12. data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
  13. data/helpers/lib/npm6/subdependency-updater.js +78 -0
  14. data/helpers/lib/npm6/updater.js +199 -0
  15. data/helpers/lib/pnpm/index.js +5 -0
  16. data/helpers/lib/pnpm/lockfile-parser.js +82 -0
  17. data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
  18. data/helpers/lib/yarn/fix-duplicates.js +80 -0
  19. data/helpers/lib/yarn/helpers.js +54 -0
  20. data/helpers/lib/yarn/index.js +14 -0
  21. data/helpers/lib/yarn/lockfile-parser.js +21 -0
  22. data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
  23. data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
  24. data/helpers/lib/yarn/subdependency-updater.js +83 -0
  25. data/helpers/lib/yarn/updater.js +209 -0
  26. data/helpers/package-lock.json +28519 -0
  27. data/helpers/package.json +29 -0
  28. data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
  29. data/helpers/run.js +30 -0
  30. data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
  31. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
  32. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  33. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
  34. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  35. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
  36. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  37. data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
  38. data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
  39. data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
  40. data/helpers/test/npm6/helpers.js +21 -0
  41. data/helpers/test/npm6/updater.test.js +30 -0
  42. data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
  43. data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
  44. data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
  45. data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
  46. data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
  47. data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
  48. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  49. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
  50. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
  51. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
  52. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  53. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
  54. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  55. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
  56. data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
  57. data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
  58. data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
  59. data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
  60. data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
  61. data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
  62. data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
  63. data/helpers/test/yarn/helpers.js +18 -0
  64. data/helpers/test/yarn/updater.test.js +117 -0
  65. data/lib/dependabot/bun/bun_package_manager.rb +47 -0
  66. data/lib/dependabot/bun/constraint_helper.rb +359 -0
  67. data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
  68. data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
  69. data/lib/dependabot/bun/file_fetcher.rb +402 -0
  70. data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
  71. data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
  72. data/lib/dependabot/bun/file_parser.rb +477 -0
  73. data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
  74. data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
  75. data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
  76. data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
  77. data/lib/dependabot/bun/file_updater.rb +203 -0
  78. data/lib/dependabot/bun/helpers.rb +93 -0
  79. data/lib/dependabot/bun/language.rb +45 -0
  80. data/lib/dependabot/bun/metadata_finder.rb +214 -0
  81. data/lib/dependabot/bun/native_helpers.rb +19 -0
  82. data/lib/dependabot/bun/package_manager.rb +280 -0
  83. data/lib/dependabot/bun/package_name.rb +118 -0
  84. data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
  85. data/lib/dependabot/bun/registry_helper.rb +188 -0
  86. data/lib/dependabot/bun/registry_parser.rb +93 -0
  87. data/lib/dependabot/bun/requirement.rb +146 -0
  88. data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
  89. data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
  90. data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
  91. data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
  92. data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
  93. data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
  94. data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
  95. data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
  96. data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
  97. data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
  98. data/lib/dependabot/bun/update_checker.rb +455 -0
  99. data/lib/dependabot/bun/version.rb +138 -0
  100. data/lib/dependabot/bun/version_selector.rb +61 -0
  101. data/lib/dependabot/bun.rb +337 -35
  102. metadata +108 -65
  103. data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
  104. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
  105. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
  106. data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
  107. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
  108. data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
  109. data/lib/dependabot/javascript/bun/helpers.rb +0 -72
  110. data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
  111. data/lib/dependabot/javascript/bun/requirement.rb +0 -11
  112. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
  113. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
  114. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
  115. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
  116. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
  117. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
  118. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
  119. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
  120. data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
  121. data/lib/dependabot/javascript/bun/version.rb +0 -11
  122. data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
  123. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
  124. data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
  125. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
  126. data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
  127. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
  128. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
  129. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
  130. data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
  131. data/lib/dependabot/javascript/shared/language.rb +0 -45
  132. data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
  133. data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
  134. data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
  135. data/lib/dependabot/javascript/shared/package_name.rb +0 -118
  136. data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
  137. data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
  138. data/lib/dependabot/javascript/shared/requirement.rb +0 -144
  139. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
  140. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
  141. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
  142. data/lib/dependabot/javascript/shared/version.rb +0 -133
  143. data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
  144. data/lib/dependabot/javascript.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0eb8192cb230095bd5c5c41ca47242ba70625b32e7e77eda267893b9984dc4ff
4
- data.tar.gz: 4036b1f4381b20a6d797f5a7afa0d50d8dc1850acb10293835675132dceedbeb
3
+ metadata.gz: 02ce7a49ca717ce8cf12b669fc7caf8d2a685bc00cfe9f8d45771b1839b01abb
4
+ data.tar.gz: f56292f958d22f40a726b649000e2ccbc7d6f87bc869579f62e4d77c7ca458f0
5
5
  SHA512:
6
- metadata.gz: c2559a11a91f31650bf5dc84a5d418c7280d55bab80b659986df71c9893cb9b494ccb8ec29f5bb1f9a3c154ee0b122919bc29021fc7ad1fb134e4d3c2230428d
7
- data.tar.gz: 5433aab5f7fae6978db3f4d85ad18d0e91e37a725d9c2158b0b547247103b626e4fd41993f2be76d832dd2e7b89aeb975dd92bfc29b233e8db9be19a6d5a82c2
6
+ metadata.gz: ccff16321a7243441dc13cf525a69eb0bffc9413537fdd7d7131f185d366d0a8dfd1ec28980d2805740e8af4bb671615bf7ec7ba1ef0a415b96d8dcd2f9065c9
7
+ data.tar.gz: 86b243daf58eb0912265c8f04f9677173b5dd60f183bb6ea62186d31cb0862be133abdb855a55eab6153f90c2b5e5f865c77030b9847ea78af7df73a2287bf65
data/helpers/.eslintrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": [
3
+ "prettier"
4
+ ],
5
+ "env": {
6
+ "node": true
7
+ },
8
+ "parserOptions": {
9
+ "ecmaVersion": "latest"
10
+ }
11
+ }
data/helpers/README.md ADDED
@@ -0,0 +1,29 @@
1
+ Native JavaScript helpers
2
+ -------------------------
3
+
4
+ This directory contains helper functions for npm and yarn, natively written in
5
+ Javascript so that we can utilize the package managers internal APIs and other
6
+ native tooling for these ecosystems.
7
+
8
+ These helpers are called from the Ruby code via `run.js`, they are passed
9
+ arguments via stdin and return JSON data to stdout.
10
+
11
+ ## Testing
12
+
13
+ When working on these helpers, it's convenient to write some high level tests in
14
+ JavaScript to make it easier to debug the code.
15
+
16
+ You can now run the tests from this directory by running:
17
+
18
+ ```
19
+ yarn test path/to/test.js
20
+ ```
21
+
22
+ ### Debugging
23
+
24
+ In order to run an interactive debugger:
25
+
26
+ - `node --inspect-brk node_modules/.bin/jest --runInBand path/to/test/test.js`
27
+ - In Chrome, navigate to `chrome://inspect`
28
+ - Click `Open dedicated DevTools for Node`
29
+ - You'll now be able to interactively debug using the Chrome dev tools.
data/helpers/build ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ if [ -z "$DEPENDABOT_NATIVE_HELPERS_PATH" ]; then
6
+ echo "Unable to build, DEPENDABOT_NATIVE_HELPERS_PATH is not set"
7
+ exit 1
8
+ fi
9
+
10
+ install_dir="$DEPENDABOT_NATIVE_HELPERS_PATH/bun"
11
+ mkdir -p "$install_dir"
12
+
13
+ helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
14
+ cp -r \
15
+ "$helpers_dir/lib" \
16
+ "$helpers_dir/test" \
17
+ "$helpers_dir/run.js" \
18
+ "$helpers_dir/.eslintrc" \
19
+ "$helpers_dir/jest.config.js" \
20
+ "$helpers_dir/package.json" \
21
+ "$helpers_dir/package-lock.json" \
22
+ "$helpers_dir/patches" \
23
+ "$install_dir"
24
+
25
+ cd "$install_dir"
26
+ npm ci --no-audit --fetch-timeout=600000 --fetch-retries=5 --no-dry-run --no-ignore-scripts
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ verbose: true,
3
+ rootDir: "test",
4
+ testEnvironment: "node",
5
+ };
@@ -0,0 +1,78 @@
1
+ /* Conflicting dependency parser for npm
2
+ *
3
+ * Inputs:
4
+ * - directory containing a package.json and a yarn.lock
5
+ * - dependency name
6
+ * - target dependency version
7
+ *
8
+ * Outputs:
9
+ * - An array of objects with conflicting dependencies
10
+ */
11
+
12
+ const Arborist = require("@npmcli/arborist");
13
+ const semver = require("semver");
14
+
15
+ async function findConflictingDependencies(directory, depName, targetVersion) {
16
+ const arb = new Arborist({
17
+ path: directory,
18
+ dryRun: true,
19
+ ignoreScripts: true,
20
+ });
21
+
22
+ return await arb.loadVirtual().then((tree) => {
23
+ const parents = [];
24
+ for (const node of tree.inventory.query("name", depName)) {
25
+ for (const edge of node.edgesIn) {
26
+ if (!semver.satisfies(targetVersion, edge.spec)) {
27
+ findTopLevelEdges(edge).forEach((topLevel) => {
28
+ explanation = buildExplanation(node, edge, topLevel);
29
+
30
+ parents.push({
31
+ explanation: explanation,
32
+ name: edge.from.name,
33
+ version: edge.from.version,
34
+ requirement: edge.spec,
35
+ });
36
+ });
37
+ }
38
+ }
39
+ }
40
+
41
+ return parents;
42
+ });
43
+ }
44
+
45
+ function buildExplanation(node, directEdge, topLevelEdge) {
46
+ if (directEdge.from === topLevelEdge.to) {
47
+ // The nodes parent is top-level
48
+ return `${directEdge.from.name}@${directEdge.from.version} requires ${directEdge.to.name}@${directEdge.spec}`;
49
+ } else if (topLevelEdge.to.edgesOut.has(directEdge.from.name)) {
50
+ // The nodes parent is a direct dependency of the top-level dependency
51
+ return (
52
+ `${topLevelEdge.to.name}@${topLevelEdge.to.version} requires ${directEdge.to.name}@${directEdge.spec} ` +
53
+ `via ${directEdge.from.name}@${directEdge.from.version}`
54
+ );
55
+ } else {
56
+ // The nodes parent is a transitive dependency of the top-level dependency
57
+ return (
58
+ `${topLevelEdge.to.name}@${topLevelEdge.to.version} requires ${directEdge.to.name}@${directEdge.spec} ` +
59
+ `via a transitive dependency on ${directEdge.from.name}@${directEdge.from.version}`
60
+ );
61
+ }
62
+ }
63
+
64
+ function findTopLevelEdges(edge, parents = []) {
65
+ edge.from.edgesIn.forEach((parent) => {
66
+ if (parent.from.edgesIn.size === 0) {
67
+ if (!parents.includes(parent)) {
68
+ parents.push(parent);
69
+ }
70
+ } else {
71
+ findTopLevelEdges(parent, parents);
72
+ }
73
+ });
74
+
75
+ return parents;
76
+ }
77
+
78
+ module.exports = { findConflictingDependencies };
@@ -0,0 +1,9 @@
1
+ const conflictingDependencyParser = require("./conflicting-dependency-parser");
2
+ const vulnerabilityAuditor = require("./vulnerability-auditor");
3
+
4
+ module.exports = {
5
+ findConflictingDependencies:
6
+ conflictingDependencyParser.findConflictingDependencies,
7
+ vulnerabilityAuditor:
8
+ vulnerabilityAuditor.findVulnerableDependencies,
9
+ };
@@ -0,0 +1,291 @@
1
+ /* Vulnerability auditor for npm
2
+ *
3
+ * Inputs:
4
+ * - path to directory containing a package.json and a package-lock.json
5
+ * - array of security advisories to audit for,
6
+ * [
7
+ * { dependency_name: string, affected_versions: [string, ...] },
8
+ * ...
9
+ * ]
10
+ *
11
+ * Outputs:
12
+ * - object indicating whether a fix is available and how to achieve it,
13
+ * {
14
+ * dependency_name: string,
15
+ * current_version: string,
16
+ * target_version: string,
17
+ * fix_available: boolean | object,
18
+ * fix_updates: [
19
+ * {
20
+ * dependency_name: string,
21
+ * current_version: string,
22
+ * target_version: string
23
+ * },
24
+ * ...
25
+ * ]
26
+ * }
27
+ */
28
+
29
+ const Arborist = require('@npmcli/arborist')
30
+ const nock = require('nock')
31
+ const { promisify } = require('util');
32
+ const exec = promisify(require('child_process').exec)
33
+
34
+ async function findVulnerableDependencies(directory, advisories) {
35
+ const npmConfig = await loadNpmConfig()
36
+ const caCerts = loadCACerts(npmConfig)
37
+ const registryOpts = extractRegistryOptions(npmConfig)
38
+ const registryCreds = loadNpmConfigCredentials(directory)
39
+
40
+ const arb = new Arborist({
41
+ path: directory,
42
+ auditRegistry: 'http://localhost:9999',
43
+ ca: caCerts,
44
+ force: true,
45
+ dryRun: true,
46
+ ignoreScripts: true,
47
+ ...registryOpts,
48
+ ...registryCreds,
49
+ })
50
+
51
+ const scope = nock('http://localhost:9999')
52
+ .persist()
53
+ .post('/-/npm/v1/security/advisories/bulk')
54
+ .reply(200, convertAdvisoriesToRegistryBulkFormat(advisories))
55
+
56
+ if (!nock.isActive()) {
57
+ nock.activate()
58
+ }
59
+
60
+ try {
61
+ const name = advisories[0].dependency_name
62
+ const response = {
63
+ dependency_name: name,
64
+ fix_updates: [],
65
+ top_level_ancestors: [],
66
+ }
67
+ const auditReport = await arb.audit()
68
+ if (!auditReport.has(name)) {
69
+ if (auditReport.tree.children.has(name)) {
70
+ response.current_version = auditReport.tree.children.get(name).version
71
+ }
72
+ response.fix_available = false
73
+ return response
74
+ }
75
+ const vuln = auditReport.get(name)
76
+ const version = [...vuln.nodes][0].version
77
+ const fixAvailable = vuln.fixAvailable
78
+
79
+ response.current_version = version
80
+ response.fix_available = Boolean(fixAvailable)
81
+
82
+ if (!Boolean(fixAvailable)) {
83
+ return response
84
+ }
85
+
86
+ const chains = buildDependencyChains(auditReport, name)
87
+
88
+ // In order for the vuln dependency in question to be considered fixable,
89
+ // all dependency chains originating from it must be fixable.
90
+ if (chains.some((chain) => !Boolean(chain.fixAvailable))) {
91
+ response.fix_available = false
92
+ return response
93
+ }
94
+
95
+ const groupedFixUpdateChains = groupBy(chains, (chain) => chain.nodes[0].pkgid)
96
+ let topLevelAncestors = new Set()
97
+
98
+ for (const group of groupedFixUpdateChains.values()) {
99
+ const fixUpdateNode = group[0].nodes[0]
100
+ const groupTopLevelAncestors = group.reduce((ancestor, chain) => {
101
+ const topLevelNode = chain.nodes[chain.nodes.length - 1]
102
+ return ancestor.add(topLevelNode.name)
103
+ }, new Set())
104
+
105
+ // Add group's top-level ancestors to the set of all top-level ancestors of
106
+ // the vuln dependency in question.
107
+ topLevelAncestors = new Set([...topLevelAncestors, ...groupTopLevelAncestors])
108
+
109
+ // If a chain consists of only one node, chain.nodes[0].name == chain.nodes[chain.nodes.length-1].name.
110
+ // In such cases, don't include the fix update node as an ancestor of itself.
111
+ const fixUpdateNodeTopLevelAncestors =
112
+ [...groupTopLevelAncestors].filter((nodeName) => nodeName !== fixUpdateNode.name).sort()
113
+
114
+ response.fix_updates.push({
115
+ dependency_name: fixUpdateNode.name,
116
+ current_version: fixUpdateNode.version,
117
+ top_level_ancestors: fixUpdateNodeTopLevelAncestors,
118
+ })
119
+ }
120
+
121
+ response.top_level_ancestors = [...topLevelAncestors].sort()
122
+
123
+ const fixTree = await arb.audit({
124
+ fix: true,
125
+ })
126
+
127
+ response.target_version = fixTree.children.get(name)?.version
128
+
129
+ for (const update of response.fix_updates) {
130
+ update.target_version =
131
+ fixTree.children.get(update.dependency_name)?.version
132
+ }
133
+
134
+ return response
135
+ } finally {
136
+ nock.cleanAll()
137
+ nock.restore()
138
+ }
139
+ }
140
+
141
+ function convertAdvisoriesToRegistryBulkFormat(advisories) {
142
+ return advisories.reduce((formattedAdvisories, advisory) => {
143
+ if (!formattedAdvisories[advisory.dependency_name]) {
144
+ formattedAdvisories[advisory.dependency_name] = []
145
+ }
146
+ let formattedVersions =
147
+ advisory.affected_versions.reduce((memo, version) => {
148
+ memo.push({
149
+ id: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
150
+ vulnerable_versions: version
151
+ })
152
+ return memo
153
+ }, [])
154
+ formattedAdvisories[advisory.dependency_name].push(...formattedVersions)
155
+ return formattedAdvisories
156
+ }, {})
157
+ }
158
+
159
+ /* Returns an array of dependency chains rooted in the named dependency,
160
+ * [
161
+ * {
162
+ * fixAvailable: true | false | object,
163
+ * nodes: [
164
+ * ArboristNode {
165
+ * name: 'foo',
166
+ * version: '1.0.0',
167
+ * ...
168
+ * },
169
+ * ...
170
+ * ]
171
+ * },
172
+ * ...
173
+ * ]
174
+ *
175
+ * The first node in each chain is the innermost dependency affected by the vuln;
176
+ * the `fixAvailable` field applies to this dependency. The last node in each
177
+ * chain is always a top-level dependency.
178
+ */
179
+ function buildDependencyChains(auditReport, name) {
180
+ const helper = (node, chain, visited) => {
181
+ if (!node) {
182
+ return []
183
+ }
184
+ if (visited.has(node.name)) {
185
+ // We've already seen this node; end path.
186
+ return []
187
+ }
188
+ if (auditReport.has(node.name)) {
189
+ const vuln = auditReport.get(node.name)
190
+ if (vuln.isVulnerable(node)) {
191
+ return [{ fixAvailable: vuln.fixAvailable, nodes: [node, ...chain.nodes] }]
192
+ } else if (node.name == name) {
193
+ // This is a non-vulnerable version of the advisory dependency; end path.
194
+ return []
195
+ }
196
+ }
197
+ if (!node.edgesOut.size) {
198
+ // This is a leaf node that is unaffected by the vuln; end path.
199
+ return []
200
+ }
201
+ return [...node.edgesOut.values()].reduce((chains, { to }) => {
202
+ // Only prepend current node to chain/visited if it's not the project root.
203
+ const newChain = node.isProjectRoot ? chain : { nodes: [node, ...chain.nodes] }
204
+ const newVisited = node.isProjectRoot ? visited : new Set([node.name, ...visited])
205
+ return chains.concat(helper(to, newChain, newVisited))
206
+ }, [])
207
+ }
208
+ return helper(auditReport.tree, { nodes: [] }, new Set())
209
+ }
210
+
211
+ function groupBy(elems, fn) {
212
+ const groups = new Map()
213
+ for (const [index, elem] of [...elems].entries()) {
214
+ const key = fn(elem, index, elems)
215
+ groups.set(key, (groups.get(key) || []).concat([elem]))
216
+ }
217
+ return groups
218
+ }
219
+
220
+ async function loadNpmConfig() {
221
+ const configOutput = await exec('npm config ls --json')
222
+ return JSON.parse(configOutput.stdout)
223
+ }
224
+
225
+ function extractRegistryOptions(npmConfig) {
226
+ const opts = []
227
+ for (const [key, value] of Object.entries(npmConfig)) {
228
+ if (key == "registry" || key.endsWith(":registry")) {
229
+ opts.push([key, value])
230
+ }
231
+ }
232
+ return Object.fromEntries(opts)
233
+ }
234
+
235
+ // loadNpmConfig doesn't return registry credentials so we need to manually extract them. If available,
236
+ // Dependabot will have written them to the project's .npmrc file.
237
+ const ini = require('ini')
238
+ const path = require('path')
239
+
240
+ const credKeys = ['token', '_authToken', '_auth']
241
+
242
+ function loadNpmConfigCredentials(projectDir) {
243
+ const projectNpmrc = maybeReadFile(path.join(projectDir, '.npmrc'))
244
+ if (!projectNpmrc) {
245
+ return {}
246
+ }
247
+
248
+ const credentials = []
249
+ const config = ini.parse(projectNpmrc)
250
+ for (const [key, value] of Object.entries(config)) {
251
+ if (credKeys.includes(key) || credKeys.some((credKey) => key.endsWith(':' + credKey))) {
252
+ credentials.push([key, value])
253
+ }
254
+ }
255
+ return Object.fromEntries(credentials)
256
+ }
257
+
258
+ // sourced from npm's cli/lib/utils/config/definitions.js for reading certs from the cafile option
259
+ const fs = require('fs')
260
+ const maybeReadFile = file => {
261
+ try {
262
+ return fs.readFileSync(file, 'utf8')
263
+ } catch (er) {
264
+ if (er.code !== 'ENOENT') {
265
+ throw er
266
+ }
267
+ return null
268
+ }
269
+ }
270
+
271
+ function loadCACerts(npmConfig) {
272
+ if (npmConfig.ca) {
273
+ return npmConfig.ca
274
+ }
275
+
276
+ if (!npmConfig.cafile) {
277
+ return
278
+ }
279
+
280
+ const raw = maybeReadFile(npmConfig.cafile)
281
+ if (!raw) {
282
+ return
283
+ }
284
+
285
+ const delim = '-----END CERTIFICATE-----'
286
+ return raw.replace(/\r\n/g, '\n').split(delim)
287
+ .filter(section => section.trim())
288
+ .map(section => section.trimStart() + delim)
289
+ }
290
+
291
+ module.exports = { findVulnerableDependencies }
@@ -0,0 +1,25 @@
1
+ function runAsync(obj, method, args) {
2
+ return new Promise((resolve, reject) => {
3
+ const cb = (err, ...returnValues) => {
4
+ if (err) {
5
+ reject(err);
6
+ } else {
7
+ resolve(returnValues);
8
+ }
9
+ };
10
+ method.apply(obj, [...args, cb]);
11
+ });
12
+ }
13
+
14
+ function muteStderr() {
15
+ const original = process.stderr.write;
16
+ process.stderr.write = () => {};
17
+ return () => {
18
+ process.stderr.write = original;
19
+ };
20
+ }
21
+
22
+ module.exports = {
23
+ runAsync,
24
+ muteStderr,
25
+ };
@@ -0,0 +1,9 @@
1
+ const updater = require("./updater");
2
+ const peerDependencyChecker = require("./peer-dependency-checker");
3
+ const subdependencyUpdater = require("./subdependency-updater");
4
+
5
+ module.exports = {
6
+ update: updater.updateDependencyFiles,
7
+ updateSubdependency: subdependencyUpdater.updateDependencyFile,
8
+ checkPeerDependencies: peerDependencyChecker.checkPeerDependencies,
9
+ };
@@ -0,0 +1,111 @@
1
+ /* PEER DEPENDENCY CHECKER
2
+ *
3
+ * Inputs:
4
+ * - directory containing a package.json and a yarn.lock
5
+ * - dependency name
6
+ * - new dependency version
7
+ * - requirements for this dependency
8
+ *
9
+ * Outputs:
10
+ * - successful completion, or an error if there are peer dependency warnings
11
+ */
12
+
13
+ const npm = require("npm");
14
+ const installer = require("npm/lib/install");
15
+ const { muteStderr, runAsync } = require("./helpers.js");
16
+
17
+ function installArgsWithVersion(depName, desiredVersion, reqs) {
18
+ const source = (reqs.find((req) => req.source) || {}).source;
19
+
20
+ if (source && source.type === "git") {
21
+ return [`${depName}@${source.url}#${desiredVersion}`];
22
+ } else {
23
+ return [`${depName}@${desiredVersion}`];
24
+ }
25
+ }
26
+
27
+ async function checkPeerDependencies(
28
+ directory,
29
+ depName,
30
+ desiredVersion,
31
+ requirements,
32
+ topLevelDependencies
33
+ ) {
34
+ // `force: true` ignores checks for platform (os, cpu) and engines
35
+ // in npm/lib/install/validate-args.js
36
+ // Platform is checked and raised from (EBADPLATFORM):
37
+ // https://github.com/npm/npm-install-checks
38
+ //
39
+ // `'prefer-offline': true` sets fetch() cache key to `force-cache`
40
+ // https://github.com/npm/npm-registry-fetch
41
+ //
42
+ // `'ignore-scripts': true` used to disable prepare and prepack scripts
43
+ // which are run when installing git dependencies
44
+ await runAsync(npm, npm.load, [
45
+ {
46
+ loglevel: "silent",
47
+ force: true,
48
+ audit: false,
49
+ "prefer-offline": true,
50
+ "ignore-scripts": true,
51
+ save: false,
52
+ },
53
+ ]);
54
+
55
+ const dryRun = true;
56
+
57
+ // Returns dep name and version for npm install, example: ["react@16.6.0"]
58
+ let args = installArgsWithVersion(depName, desiredVersion, requirements);
59
+
60
+ // To check peer dependencies requirements in all top level dependencies we
61
+ // need to explicitly tell npm to fetch all manifests by specifying the
62
+ // existing dependency name and version in npm install
63
+
64
+ // For example, if we have "react@15.6.2" and "react-dom@15.6.2" installed
65
+ // and we want to install react@16.6.0, we need get the existing version of
66
+ // react-dom and pass this to npm install along with the new version react,
67
+ // this way npm fetches the manifest for react-dom and determines that we
68
+ // can't install react@16.6.0 due to the peer dependency requirement in
69
+ // react-dom
70
+
71
+ // If we only pass the new dep@version to npm install, e.g. "react@16.6.0" npm
72
+ // will only fetch the manifest for react and not know that react-dom enforces
73
+ // a peerDependency on react
74
+
75
+ // Returns dep name and version for npm install, example: ["react-dom@15.6.2"]
76
+ // - given react and react-dom in top level deps
77
+ const otherDeps = (topLevelDependencies || [])
78
+ .filter((dep) => dep.name !== depName && dep.version)
79
+ .map((dep) =>
80
+ installArgsWithVersion(dep.name, dep.version, dep.requirements)
81
+ )
82
+ .reduce((acc, dep) => acc.concat(dep), []);
83
+
84
+ args = args.concat(otherDeps);
85
+
86
+ const initialInstaller = new installer.Installer(directory, dryRun, args, {
87
+ packageLockOnly: true,
88
+ });
89
+
90
+ // Skip printing the success message
91
+ initialInstaller.printInstalled = (cb) => cb();
92
+
93
+ // There are some hard-to-prevent bits of output.
94
+ // This is horrible, but works.
95
+ const unmute = muteStderr();
96
+ try {
97
+ await runAsync(initialInstaller, initialInstaller.run, []);
98
+ } finally {
99
+ unmute();
100
+ }
101
+
102
+ const peerDependencyWarnings = initialInstaller.idealTree.warnings
103
+ .filter((warning) => warning.code === "EPEERINVALID")
104
+ .map((warning) => warning.message);
105
+
106
+ if (peerDependencyWarnings.length) {
107
+ throw new Error(peerDependencyWarnings.join("\n"));
108
+ }
109
+ }
110
+
111
+ module.exports = { checkPeerDependencies };
@@ -0,0 +1,22 @@
1
+ // Recursively removes all dependencies matching on name
2
+ function removeDependenciesFromLockfile(lockfile, dependencyNames) {
3
+ if (!lockfile.dependencies) return lockfile;
4
+
5
+ const dependencies = Object.entries(lockfile.dependencies).reduce(
6
+ (acc, [depName, packageValue]) => {
7
+ if (!dependencyNames.includes(depName)) {
8
+ acc[depName] = removeDependenciesFromLockfile(
9
+ packageValue,
10
+ dependencyNames
11
+ );
12
+ }
13
+
14
+ return acc;
15
+ },
16
+ {}
17
+ );
18
+
19
+ return Object.assign({}, lockfile, { dependencies });
20
+ }
21
+
22
+ module.exports = removeDependenciesFromLockfile;