dependabot-bun 0.296.2 → 0.296.3

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.
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;