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
@@ -0,0 +1,78 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const npm = require("npm");
4
+ const installer = require("npm/lib/install");
5
+ const detectIndent = require("detect-indent");
6
+ const removeDependenciesFromLockfile = require("./remove-dependencies-from-lockfile");
7
+
8
+ const { muteStderr, runAsync } = require("./helpers.js");
9
+
10
+ async function updateDependencyFile(directory, lockfileName, dependencies) {
11
+ const readFile = (fileName) =>
12
+ fs.readFileSync(path.join(directory, fileName)).toString();
13
+
14
+ const lockfile = readFile(lockfileName);
15
+ const indent = detectIndent(lockfile).indent || " ";
16
+ const lockfileObject = JSON.parse(lockfile);
17
+ // Remove the dependency we want to update from the lockfile and let
18
+ // npm find the latest resolvable version and fix the lockfile
19
+ const updatedLockfileObject = removeDependenciesFromLockfile(
20
+ lockfileObject,
21
+ dependencies.map((dep) => dep.name)
22
+ );
23
+ fs.writeFileSync(
24
+ path.join(directory, lockfileName),
25
+ JSON.stringify(updatedLockfileObject, null, indent)
26
+ );
27
+
28
+ // `force: true` ignores checks for platform (os, cpu) and engines
29
+ // in npm/lib/install/validate-args.js
30
+ // Platform is checked and raised from (EBADPLATFORM):
31
+ // https://github.com/npm/npm-install-checks
32
+ //
33
+ // `'prefer-offline': true` sets fetch() cache key to `force-cache`
34
+ // https://github.com/npm/npm-registry-fetch
35
+ //
36
+ // `'ignore-scripts': true` used to disable prepare and prepack scripts
37
+ // which are run when installing git dependencies
38
+ await runAsync(npm, npm.load, [
39
+ {
40
+ loglevel: "silent",
41
+ force: true,
42
+ audit: false,
43
+ "prefer-offline": true,
44
+ "ignore-scripts": true,
45
+ },
46
+ ]);
47
+
48
+ const dryRun = true;
49
+ const initialInstaller = new installer.Installer(directory, dryRun, [], {
50
+ packageLockOnly: true,
51
+ });
52
+
53
+ // A bug in npm means the initial install will remove any git dependencies
54
+ // from the lockfile. A subsequent install with no arguments fixes this.
55
+ const cleanupInstaller = new installer.Installer(directory, dryRun, [], {
56
+ packageLockOnly: true,
57
+ });
58
+
59
+ // Skip printing the success message
60
+ initialInstaller.printInstalled = (cb) => cb();
61
+ cleanupInstaller.printInstalled = (cb) => cb();
62
+
63
+ // There are some hard-to-prevent bits of output.
64
+ // This is horrible, but works.
65
+ const unmute = muteStderr();
66
+ try {
67
+ await runAsync(initialInstaller, initialInstaller.run, []);
68
+ await runAsync(cleanupInstaller, cleanupInstaller.run, []);
69
+ } finally {
70
+ unmute();
71
+ }
72
+
73
+ const updatedLockfile = readFile(lockfileName);
74
+
75
+ return { [lockfileName]: updatedLockfile };
76
+ }
77
+
78
+ module.exports = { updateDependencyFile };
@@ -0,0 +1,199 @@
1
+ /* DEPENDENCY FILE UPDATER
2
+ *
3
+ * Inputs:
4
+ * - directory containing an up-to-date package.json and a package-lock.json
5
+ * to be updated
6
+ * - name of the dependency to be updated
7
+ * - new dependency version
8
+ * - previous requirements for this dependency
9
+ * - the name of the lockfile (package-lock.json or npm-shrinkwrap.json)
10
+ *
11
+ * Outputs:
12
+ * - updated package.json and package-lock.json files
13
+ *
14
+ * Update the dependency to the version specified and rewrite the package.json
15
+ * and package-lock.json files.
16
+ */
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+ const npm = require("npm");
20
+ const installer = require("npm/lib/install");
21
+ const detectIndent = require("detect-indent");
22
+ const { muteStderr, runAsync } = require("./helpers.js");
23
+
24
+ async function updateDependencyFiles(directory, lockfileName, dependencies) {
25
+ const readFile = (fileName) =>
26
+ fs.readFileSync(path.join(directory, fileName)).toString();
27
+
28
+ // `force: true` ignores checks for platform (os, cpu) and engines
29
+ // in npm/lib/install/validate-args.js
30
+ // Platform is checked and raised from (EBADPLATFORM):
31
+ // https://github.com/npm/npm-install-checks
32
+ //
33
+ // `'prefer-offline': true` sets fetch() cache key to `force-cache`
34
+ // https://github.com/npm/npm-registry-fetch
35
+ //
36
+ // `'ignore-scripts': true` used to disable prepare and prepack scripts
37
+ // which are run when installing git dependencies
38
+ await runAsync(npm, npm.load, [
39
+ {
40
+ loglevel: "silent",
41
+ force: true,
42
+ audit: false,
43
+ "prefer-offline": true,
44
+ "ignore-scripts": true,
45
+ },
46
+ ]);
47
+ const manifest = JSON.parse(readFile("package.json"));
48
+
49
+ const dryRun = true;
50
+ const flattenedDependencies = flattenAllDependencies(manifest);
51
+ const args = dependencies.map((dependency) => {
52
+ const existingVersionRequirement = flattenedDependencies[dependency.name];
53
+ return installArgs(
54
+ dependency.name,
55
+ dependency.version,
56
+ dependency.requirements,
57
+ existingVersionRequirement
58
+ );
59
+ });
60
+ const initialInstaller = new installer.Installer(directory, dryRun, args, {
61
+ packageLockOnly: true,
62
+ });
63
+
64
+ // A bug in npm means the initial install will remove any git dependencies
65
+ // from the lockfile. A subsequent install with no arguments fixes this.
66
+ const cleanupInstaller = new installer.Installer(directory, dryRun, [], {
67
+ packageLockOnly: true,
68
+ });
69
+
70
+ // Skip printing the success message
71
+ initialInstaller.printInstalled = (cb) => cb();
72
+ cleanupInstaller.printInstalled = (cb) => cb();
73
+
74
+ // There are some hard-to-prevent bits of output.
75
+ // This is horrible, but works.
76
+ const unmute = muteStderr();
77
+ try {
78
+ // Fix already present git sub-dependency with invalid "from" and "requires"
79
+ updateLockfileWithValidGitUrls(path.join(directory, lockfileName));
80
+
81
+ await runAsync(initialInstaller, initialInstaller.run, []);
82
+
83
+ // Fix npm5 lockfiles where invalid "from" is introduced after first install
84
+ updateLockfileWithValidGitUrls(path.join(directory, lockfileName));
85
+
86
+ await runAsync(cleanupInstaller, cleanupInstaller.run, []);
87
+ } finally {
88
+ unmute();
89
+ }
90
+
91
+ const updatedLockfile = readFile(lockfileName);
92
+
93
+ return { [lockfileName]: updatedLockfile };
94
+ }
95
+
96
+ function updateLockfileWithValidGitUrls(lockfilePath) {
97
+ const lockfile = fs.readFileSync(lockfilePath).toString();
98
+ const indent = detectIndent(lockfile).indent || " ";
99
+ const updatedLockfileObject = removeInvalidGitUrls(JSON.parse(lockfile));
100
+ fs.writeFileSync(
101
+ lockfilePath,
102
+ JSON.stringify(updatedLockfileObject, null, indent)
103
+ );
104
+ }
105
+
106
+ function flattenAllDependencies(manifest) {
107
+ return Object.assign(
108
+ {},
109
+ manifest.optionalDependencies,
110
+ manifest.peerDependencies,
111
+ manifest.devDependencies,
112
+ manifest.dependencies
113
+ );
114
+ }
115
+
116
+ // NOTE: Reused in npm 7 updater
117
+ function installArgs(
118
+ depName,
119
+ desiredVersion,
120
+ requirements,
121
+ existingVersionRequirement
122
+ ) {
123
+ const source = (requirements.find((req) => req.source) || {}).source;
124
+
125
+ if (source && source.type === "git") {
126
+ if (!existingVersionRequirement) {
127
+ existingVersionRequirement = source.url;
128
+ }
129
+
130
+ // Git is configured to auth over https while updating
131
+ existingVersionRequirement = existingVersionRequirement.replace(
132
+ /git\+ssh:\/\/git@(.*?)[:/]/,
133
+ "git+https://$1/"
134
+ );
135
+
136
+ // Keep any semver range that has already been updated in the package
137
+ // requirement when installing the new version
138
+ if (existingVersionRequirement.match(desiredVersion)) {
139
+ return `${depName}@${existingVersionRequirement}`;
140
+ } else if (!existingVersionRequirement.includes("#")) {
141
+ return `${depName}@${existingVersionRequirement}`;
142
+ } else {
143
+ return `${depName}@${existingVersionRequirement.replace(
144
+ /#.*/,
145
+ ""
146
+ )}#${desiredVersion}`;
147
+ }
148
+ } else {
149
+ return `${depName}@${desiredVersion}`;
150
+ }
151
+ }
152
+
153
+ // Note: Fixes bugs introduced in npm 6.6.0 for the following cases:
154
+ //
155
+ // - Fails when a sub-dependency has a "from" field that includes the dependency
156
+ // name for git dependencies (e.g. "bignumber.js@git+https://gi...)
157
+ // - Fails when updating a npm@5 lockfile with git sub-dependencies, resulting
158
+ // in invalid "requires" that include the dependency name for git dependencies
159
+ // (e.g. "bignumber.js": "bignumber.js@git+https://gi...)
160
+ function removeInvalidGitUrls(lockfile) {
161
+ if (!lockfile.dependencies) return lockfile;
162
+
163
+ const dependencies = Object.keys(lockfile.dependencies).reduce((acc, key) => {
164
+ let value = removeInvalidGitUrlsInFrom(lockfile.dependencies[key], key);
165
+ value = removeInvalidGitUrlsInRequires(value);
166
+ acc[key] = removeInvalidGitUrls(value);
167
+ return acc;
168
+ }, {});
169
+
170
+ return Object.assign({}, lockfile, { dependencies });
171
+ }
172
+
173
+ function removeInvalidGitUrlsInFrom(value, dependencyName) {
174
+ const matchKey = new RegExp(`^${dependencyName}@`);
175
+ let from = value.from;
176
+ if (value.from && value.from.match(matchKey)) {
177
+ from = value.from.replace(matchKey, "");
178
+ }
179
+
180
+ return Object.assign({}, value, { from });
181
+ }
182
+
183
+ function removeInvalidGitUrlsInRequires(value) {
184
+ if (!value.requires) return value;
185
+
186
+ const requires = Object.keys(value.requires).reduce((acc, reqKey) => {
187
+ let reqValue = value.requires[reqKey];
188
+ const requiresMatchKey = new RegExp(`^${reqKey}@`);
189
+ if (reqValue && reqValue.match(requiresMatchKey)) {
190
+ reqValue = reqValue.replace(requiresMatchKey, "");
191
+ }
192
+ acc[reqKey] = reqValue;
193
+ return acc;
194
+ }, {});
195
+
196
+ return Object.assign({}, value, { requires });
197
+ }
198
+
199
+ module.exports = { updateDependencyFiles };
@@ -0,0 +1,5 @@
1
+ const lockfileParser = require("./lockfile-parser");
2
+
3
+ module.exports = {
4
+ parseLockfile: lockfileParser.parse,
5
+ };
@@ -0,0 +1,82 @@
1
+ /* PNPM-LOCK.YAML PARSER
2
+ *
3
+ * Inputs:
4
+ * - directory containing a pnpm-lock.yaml file
5
+ *
6
+ * Outputs:
7
+ * - JSON formatted information of dependencies (name, version, dependency-type)
8
+ */
9
+ const { readWantedLockfile } = require("@pnpm/lockfile-file");
10
+ const dependencyPath = require("@pnpm/dependency-path");
11
+
12
+ async function parse(directory) {
13
+ const lockfile = await readWantedLockfile(directory, {
14
+ ignoreIncompatible: true
15
+ });
16
+
17
+ return Object.entries(lockfile.packages ?? {})
18
+ .filter(([depPath, pkgSnapshot]) => {
19
+ let dp = dependencyPath.parse(depPath);
20
+ return dp && dp.name // null or undefined checked for dependency path (dp) and empty name dps are filtered.
21
+ })
22
+ .map(([depPath, pkgSnapshot]) => nameVerDevFromPkgSnapshot(depPath, pkgSnapshot, Object.values(lockfile.importers)))
23
+ }
24
+
25
+ function nameVerDevFromPkgSnapshot(depPath, pkgSnapshot, projectSnapshots) {
26
+ let name;
27
+ let version;
28
+
29
+ if (!pkgSnapshot.name) {
30
+ const pkgInfo = dependencyPath.parse(depPath);
31
+ name = pkgInfo.name;
32
+ version = pkgInfo.version;
33
+ } else {
34
+ name = pkgSnapshot.name;
35
+ version = pkgSnapshot.version;
36
+ }
37
+
38
+ let specifiers = [];
39
+ let aliased = false;
40
+
41
+ projectSnapshots.every(projectSnapshot => {
42
+ const projectSpecifiers = projectSnapshot.specifiers;
43
+
44
+ if (Object.values(projectSpecifiers).some(specifier => specifier.startsWith(`npm:${name}@`) || specifier == `npm:${name}`)) {
45
+ aliased = true;
46
+ return false;
47
+ }
48
+
49
+ currentSpecifier = projectSpecifiers[name];
50
+
51
+ if (!currentSpecifier) {
52
+ return true;
53
+ }
54
+
55
+ let specifierVersion = currentSpecifier.version;
56
+
57
+ if (!currentSpecifier.version) {
58
+ specifierVersion = projectSnapshot.dependencies?.[name] || projectSnapshot.devDependencies?.[name] || projectSnapshot.optionalDependencies?.[name]
59
+ }
60
+
61
+ if (
62
+ specifierVersion == version ||
63
+ specifierVersion.startsWith(`${version}_`) || // lockfileVersion 5.4
64
+ specifierVersion.startsWith(`${version}(`) // lockfileVersion 6.0
65
+ ) {
66
+ specifiers.push(currentSpecifier.specifier || currentSpecifier);
67
+ }
68
+
69
+ return true;
70
+ });
71
+
72
+ return {
73
+ name: name,
74
+ version: version,
75
+ resolved: pkgSnapshot.resolution.tarball,
76
+ dev: pkgSnapshot.dev,
77
+ specifiers: specifiers,
78
+ aliased: aliased
79
+ }
80
+ }
81
+
82
+ module.exports = { parse };
@@ -0,0 +1,176 @@
1
+ /* Conflicting dependency parser for yarn
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 fs = require("fs");
13
+ const path = require("path");
14
+ const semver = require("semver");
15
+ const { parse } = require("./lockfile-parser");
16
+ const { LOCKFILE_ENTRY_REGEX } = require("./helpers");
17
+
18
+ async function findConflictingDependencies(directory, depName, targetVersion) {
19
+ const lockfileJson = await parse(directory);
20
+ const packageJson = fs
21
+ .readFileSync(path.join(directory, "package.json"))
22
+ .toString();
23
+ const dependencyTypes = [
24
+ "dependencies",
25
+ "devDependencies",
26
+ "optionalDependencies",
27
+ ];
28
+ const topLevelDependencies = dependencyTypes.flatMap((type) => {
29
+ return Object.entries(JSON.parse(packageJson)[type] || {});
30
+ });
31
+
32
+ const conflictingParents = topLevelDependencies.flatMap(
33
+ ([topLevelDepName, topLevelRequirement]) => {
34
+ const topLevelSpec = {
35
+ name: topLevelDepName,
36
+ requirement: topLevelRequirement,
37
+ };
38
+
39
+ return Array.from(
40
+ findConflictingParentDependencies(
41
+ topLevelDepName,
42
+ topLevelRequirement,
43
+ depName,
44
+ targetVersion,
45
+ topLevelSpec,
46
+ lockfileJson
47
+ ).values()
48
+ );
49
+ }
50
+ );
51
+
52
+ return conflictingParents.map((parentSpec) => {
53
+ const explanation = buildExplanation(parentSpec, depName);
54
+ return {
55
+ explanation: explanation,
56
+ name: parentSpec.name,
57
+ version: parentSpec.version,
58
+ requirement: parentSpec.requirement,
59
+ };
60
+ });
61
+ }
62
+
63
+ function buildExplanation(parentSpec, targetDepName) {
64
+ if (
65
+ parentSpec.name === parentSpec.topLevelSpec.name &&
66
+ parentSpec.version === parentSpec.topLevelSpec.version
67
+ ) {
68
+ // The nodes parent is top-level
69
+ return (
70
+ `${parentSpec.name}@${parentSpec.version} requires ${targetDepName}` +
71
+ `@${parentSpec.requirement}`
72
+ );
73
+ } else if (
74
+ parentSpec.transitiveSpec.name === parentSpec.topLevelSpec.name &&
75
+ parentSpec.transitiveSpec.version === parentSpec.topLevelSpec.version
76
+ ) {
77
+ // The nodes parent is a direct dependency of the top-level dependency
78
+ return (
79
+ `${parentSpec.topLevelSpec.name}@${parentSpec.topLevelSpec.version} requires ` +
80
+ `${targetDepName}@${parentSpec.requirement} ` +
81
+ `via ${parentSpec.name}@${parentSpec.version}`
82
+ );
83
+ } else {
84
+ // The nodes parent is a transitive dependency of the top-level dependency
85
+ return (
86
+ `${parentSpec.topLevelSpec.name}@${parentSpec.topLevelSpec.version} requires ` +
87
+ `${targetDepName}@${parentSpec.requirement} ` +
88
+ `via a transitive dependency on ${parentSpec.name}@${parentSpec.version}`
89
+ );
90
+ }
91
+ }
92
+
93
+ function findConflictingParentDependencies(
94
+ dependency,
95
+ requirement,
96
+ targetDep,
97
+ targetversion,
98
+ topLevelSpec,
99
+ lockfileJson,
100
+ transitiveSpec = {},
101
+ checkedEntries = new Set(),
102
+ conflictingParents = new Map()
103
+ ) {
104
+ // Prevent infinite loops for circular dependencies by only checking each
105
+ // lockfile entry once
106
+ const checkedEntry = [dependency, requirement].join("@");
107
+ if (checkedEntries.has(checkedEntry)) {
108
+ return conflictingParents;
109
+ }
110
+
111
+ checkedEntries.add(checkedEntry);
112
+
113
+ for (const [entry, pkg] of Object.entries(lockfileJson)) {
114
+ const [_, parentDepName, parentDepRequirement] = entry.match(
115
+ LOCKFILE_ENTRY_REGEX
116
+ );
117
+ // Decorate the top-level dependency spec with an installed version as we
118
+ // only have the requirement from the package.json manifest
119
+ if (
120
+ topLevelSpec.name == parentDepName &&
121
+ topLevelSpec.requirement == parentDepRequirement
122
+ ) {
123
+ topLevelSpec.version = pkg.version;
124
+ }
125
+
126
+ if (
127
+ pkg.dependencies &&
128
+ dependency == parentDepName &&
129
+ requirement == parentDepRequirement
130
+ ) {
131
+ // Recursive check for sub-dependencies finding dependencies that don't
132
+ // allow the target version of the vulnerable dependency to be installed
133
+ for (const [subDepName, spec] of Object.entries(pkg.dependencies)) {
134
+ if (
135
+ subDepName === targetDep &&
136
+ !semver.satisfies(targetversion, spec)
137
+ ) {
138
+ // Only add the conflicting parent once per version preventing
139
+ // duplicate dependencies from circular graphs
140
+ const key = [parentDepName, pkg.version].join("@");
141
+ conflictingParents.set(key, {
142
+ name: parentDepName,
143
+ version: pkg.version,
144
+ requirement: spec,
145
+ transitiveSpec,
146
+ topLevelSpec,
147
+ });
148
+ } else {
149
+ // Keep track of the parent dependency as a way to check if the
150
+ // conflicting dependency ends up being a direct dependency of a
151
+ // top-level dependency
152
+ transitiveSpec = {
153
+ name: parentDepName,
154
+ version: pkg.version,
155
+ requirement: parentDepRequirement,
156
+ };
157
+ findConflictingParentDependencies(
158
+ subDepName,
159
+ spec,
160
+ targetDep,
161
+ targetversion,
162
+ topLevelSpec,
163
+ lockfileJson,
164
+ transitiveSpec,
165
+ checkedEntries,
166
+ conflictingParents
167
+ );
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return conflictingParents;
174
+ }
175
+
176
+ module.exports = { findConflictingDependencies };
@@ -0,0 +1,80 @@
1
+ const parse = require("@dependabot/yarn-lib/lib/lockfile/parse").default;
2
+ const stringify = require("@dependabot/yarn-lib/lib/lockfile/stringify")
3
+ .default;
4
+ const semver = require("semver");
5
+ const { LOCKFILE_ENTRY_REGEX } = require("./helpers");
6
+
7
+ function flattenIndirectDependencies(packages) {
8
+ return (packages || []).reduce((acc, { pkg }) => {
9
+ if ("dependencies" in pkg) {
10
+ return acc.concat(Object.keys(pkg.dependencies));
11
+ }
12
+ return acc;
13
+ }, []);
14
+ }
15
+
16
+ // Inspired by yarn-deduplicate. Altered to ensure the latest version is always used
17
+ // for version ranges which allow it.
18
+ module.exports = (data, updatedDependencyName) => {
19
+ if (!updatedDependencyName) {
20
+ throw new Error("Yarn fix duplicates: must provide dependency name");
21
+ }
22
+
23
+ const json = parse(data).object;
24
+ const enableLockfileVersions = Boolean(data.match(/^# yarn v/m));
25
+ const noHeader = !Boolean(data.match(/^# THIS IS AN AU/m));
26
+
27
+ const packages = {};
28
+
29
+ Object.entries(json).forEach(([name, pkg]) => {
30
+ if (name.match(LOCKFILE_ENTRY_REGEX)) {
31
+ const [_, packageName, requestedVersion] = name.match(
32
+ LOCKFILE_ENTRY_REGEX
33
+ );
34
+ packages[packageName] = packages[packageName] || [];
35
+ packages[packageName].push(
36
+ Object.assign({}, { name, pkg, packageName, requestedVersion })
37
+ );
38
+ }
39
+ });
40
+
41
+ const packageEntries = Object.entries(packages);
42
+
43
+ const updatedPackageEntry = packageEntries.filter(([name]) => {
44
+ return updatedDependencyName === name;
45
+ });
46
+
47
+ const updatedDependencyPackage =
48
+ updatedPackageEntry[0] && updatedPackageEntry[0][1];
49
+
50
+ const indirectDependencies = flattenIndirectDependencies(
51
+ updatedDependencyPackage
52
+ );
53
+
54
+ const packagesToDedupe = [updatedDependencyName, ...indirectDependencies];
55
+
56
+ packageEntries
57
+ .filter(([name]) => packagesToDedupe.includes(name))
58
+ .forEach(([name, packages]) => {
59
+ // Reverse sort, so we'll find the maximum satisfying version first
60
+ const versions = packages.map((p) => p.pkg.version).sort(semver.rcompare);
61
+ const ranges = packages.map((p) => p.requestedVersion);
62
+
63
+ // Dedup each package to its maxSatisfying version
64
+ packages.forEach((p) => {
65
+ const targetVersion = semver.maxSatisfying(
66
+ versions,
67
+ p.requestedVersion
68
+ );
69
+ if (targetVersion === null) return;
70
+ if (targetVersion !== p.pkg.version) {
71
+ const dedupedPackage = packages.find(
72
+ (p) => p.pkg.version === targetVersion
73
+ );
74
+ json[`${name}@${p.requestedVersion}`] = dedupedPackage.pkg;
75
+ }
76
+ });
77
+ });
78
+
79
+ return stringify(json, noHeader, enableLockfileVersions);
80
+ };
@@ -0,0 +1,54 @@
1
+ const { Add } = require("@dependabot/yarn-lib/lib/cli/commands/add");
2
+ const { Install } = require("@dependabot/yarn-lib/lib/cli/commands/install");
3
+
4
+ function isString(value) {
5
+ return Object.prototype.toString.call(value) === "[object String]";
6
+ }
7
+
8
+ // Add is a subclass of the Install CLI command, which is responsible for
9
+ // adding packages to a package.json and yarn.lock. Upgrading a package is
10
+ // exactly the same as adding, except the package already exists in the
11
+ // manifests.
12
+ //
13
+ // Usually, calling Add.init() would execute a series of steps: resolve, fetch,
14
+ // link, run lifecycle scripts, cleanup, then save new manifest (package.json).
15
+ // We only care about the first and last steps: resolve, then save the new
16
+ // manifest. Fortunately, overriding bailout() gives us an opportunity to skip
17
+ // over the intermediate steps in a relatively painless fashion.
18
+ class LightweightAdd extends Add {
19
+ // This method is called by init() at the end of the resolve step, and is
20
+ // responsible for checking if any dependencies need to be updated locally.
21
+ // If everything is up to date, it'll save a new lockfile and return true,
22
+ // which causes init() to skip over the next few steps (fetching and
23
+ // installing packages). If there are packages that need updating, it'll
24
+ // return false, and init() will continue on to the fetching and installing
25
+ // steps.
26
+ //
27
+ // Add overrides Install's implementation to always return false - meaning
28
+ // that it will always continue to the fetch and install steps. We want to
29
+ // do the opposite - just save the new lockfile and stop there.
30
+ async bailout(patterns, workspaceLayout) {
31
+ // This is the only part of the original bailout implementation that
32
+ // matters: saving the new lockfile
33
+ await this.saveLockfileAndIntegrity(patterns, workspaceLayout);
34
+
35
+ // Skip over the unnecessary steps - fetching and linking packages, etc.
36
+ return true;
37
+ }
38
+ }
39
+
40
+ class LightweightInstall extends Install {
41
+ async bailout(patterns, workspaceLayout) {
42
+ await this.saveLockfileAndIntegrity(patterns, workspaceLayout);
43
+ return true;
44
+ }
45
+ }
46
+
47
+ const LOCKFILE_ENTRY_REGEX = /^(.*)@([^@]*?)$/;
48
+
49
+ module.exports = {
50
+ isString,
51
+ LightweightAdd,
52
+ LightweightInstall,
53
+ LOCKFILE_ENTRY_REGEX,
54
+ };
@@ -0,0 +1,14 @@
1
+ const lockfileParser = require("./lockfile-parser");
2
+ const updater = require("./updater");
3
+ const subdependencyUpdater = require("./subdependency-updater");
4
+ const peerDependencyChecker = require("./peer-dependency-checker");
5
+ const conflictingDependencyParser = require("./conflicting-dependency-parser");
6
+
7
+ module.exports = {
8
+ parseLockfile: lockfileParser.parse,
9
+ update: updater.updateDependencyFiles,
10
+ updateSubdependency: subdependencyUpdater.updateDependencyFile,
11
+ checkPeerDependencies: peerDependencyChecker.checkPeerDependencies,
12
+ findConflictingDependencies:
13
+ conflictingDependencyParser.findConflictingDependencies,
14
+ };