@openrewrite/recipes-nodejs 0.37.0-20260106-083133 → 0.37.0-20260106-170728

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 (36) hide show
  1. package/dist/security/dependency-vulnerability-check.d.ts +8 -54
  2. package/dist/security/dependency-vulnerability-check.d.ts.map +1 -1
  3. package/dist/security/dependency-vulnerability-check.js +176 -287
  4. package/dist/security/dependency-vulnerability-check.js.map +1 -1
  5. package/dist/security/index.d.ts +3 -0
  6. package/dist/security/index.d.ts.map +1 -1
  7. package/dist/security/index.js +3 -0
  8. package/dist/security/index.js.map +1 -1
  9. package/dist/security/npm-utils.d.ts +8 -2
  10. package/dist/security/npm-utils.d.ts.map +1 -1
  11. package/dist/security/npm-utils.js +114 -14
  12. package/dist/security/npm-utils.js.map +1 -1
  13. package/dist/security/override-utils.d.ts +23 -0
  14. package/dist/security/override-utils.d.ts.map +1 -0
  15. package/dist/security/override-utils.js +169 -0
  16. package/dist/security/override-utils.js.map +1 -0
  17. package/dist/security/remove-redundant-overrides.d.ts +1 -10
  18. package/dist/security/remove-redundant-overrides.d.ts.map +1 -1
  19. package/dist/security/remove-redundant-overrides.js +4 -152
  20. package/dist/security/remove-redundant-overrides.js.map +1 -1
  21. package/dist/security/types.d.ts +42 -0
  22. package/dist/security/types.d.ts.map +1 -0
  23. package/dist/security/types.js +7 -0
  24. package/dist/security/types.js.map +1 -0
  25. package/dist/security/version-utils.d.ts +13 -0
  26. package/dist/security/version-utils.d.ts.map +1 -0
  27. package/dist/security/version-utils.js +173 -0
  28. package/dist/security/version-utils.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/security/dependency-vulnerability-check.ts +300 -525
  31. package/src/security/index.ts +3 -0
  32. package/src/security/npm-utils.ts +172 -37
  33. package/src/security/override-utils.ts +253 -0
  34. package/src/security/remove-redundant-overrides.ts +9 -211
  35. package/src/security/types.ts +115 -0
  36. package/src/security/version-utils.ts +198 -0
@@ -49,8 +49,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
49
49
  };
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
51
  exports.DependencyVulnerabilityCheck = void 0;
52
- exports.extractVersionPrefix = extractVersionPrefix;
53
- exports.applyVersionPrefix = applyVersionPrefix;
54
52
  const rewrite_1 = require("@openrewrite/rewrite");
55
53
  const json_1 = require("@openrewrite/rewrite/json");
56
54
  const text_1 = require("@openrewrite/rewrite/text");
@@ -60,9 +58,9 @@ const semver = __importStar(require("semver"));
60
58
  const path = __importStar(require("path"));
61
59
  const vulnerability_1 = require("./vulnerability");
62
60
  const npm_utils_1 = require("./npm-utils");
63
- const ALL_DEPENDENCY_SCOPES = [
64
- 'dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'
65
- ];
61
+ const version_utils_1 = require("./version-utils");
62
+ const types_1 = require("./types");
63
+ const override_utils_1 = require("./override-utils");
66
64
  class VulnerabilityReportRow {
67
65
  constructor(sourcePath, cve, packageName, version, fixedVersion, lastAffectedVersion, upgradeable, summary, severity, depth, cwes, isDirect, dependencyPath) {
68
66
  this.sourcePath = sourcePath;
@@ -160,7 +158,7 @@ __decorate([
160
158
  ], VulnerabilityReportRow.prototype, "dependencyPath", void 0);
161
159
  class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
162
160
  constructor(options) {
163
- var _a, _b, _c, _d, _e;
161
+ var _a, _b, _c, _d, _e, _f;
164
162
  super(options);
165
163
  this.name = "org.openrewrite.node.dependency-vulnerability-check";
166
164
  this.displayName = "Find and fix vulnerable npm dependencies";
@@ -171,10 +169,11 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
171
169
  "Vulnerability information comes from the GitHub Security Advisory Database.";
172
170
  this.vulnerabilityReport = new rewrite_1.DataTable("org.openrewrite.nodejs.table.VulnerabilityReport", "Vulnerability Report", "Lists all vulnerabilities found in project dependencies.", VulnerabilityReportRow);
173
171
  (_a = this.transitiveFixStrategy) !== null && _a !== void 0 ? _a : (this.transitiveFixStrategy = 'report');
174
- (_b = this.maximumUpgradeDelta) !== null && _b !== void 0 ? _b : (this.maximumUpgradeDelta = 'patch');
175
- (_c = this.minimumSeverity) !== null && _c !== void 0 ? _c : (this.minimumSeverity = vulnerability_1.Severity.LOW);
176
- (_d = this.fixDeclaredVersions) !== null && _d !== void 0 ? _d : (this.fixDeclaredVersions = false);
177
- (_e = this.addOverrideComments) !== null && _e !== void 0 ? _e : (this.addOverrideComments = true);
172
+ (_b = this.preferDirectUpgrade) !== null && _b !== void 0 ? _b : (this.preferDirectUpgrade = true);
173
+ (_c = this.maximumUpgradeDelta) !== null && _c !== void 0 ? _c : (this.maximumUpgradeDelta = 'patch');
174
+ (_d = this.minimumSeverity) !== null && _d !== void 0 ? _d : (this.minimumSeverity = vulnerability_1.Severity.LOW);
175
+ (_e = this.fixDeclaredVersions) !== null && _e !== void 0 ? _e : (this.fixDeclaredVersions = false);
176
+ (_f = this.addOverrideComments) !== null && _f !== void 0 ? _f : (this.addOverrideComments = true);
178
177
  if (options === null || options === void 0 ? void 0 : options.minimumSeverity) {
179
178
  this.minimumSeverity = (0, vulnerability_1.parseSeverity)(options.minimumSeverity);
180
179
  }
@@ -182,33 +181,29 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
182
181
  try {
183
182
  this.cvePatternRegex = new RegExp(this.cvePattern);
184
183
  }
185
- catch (_f) {
184
+ catch (_g) {
186
185
  }
187
186
  }
188
187
  }
189
188
  shouldScanTransitives() {
190
- return this.transitiveFixStrategy !== 'report';
191
- }
192
- shouldFixTransitives() {
193
- return this.transitiveFixStrategy !== 'report';
189
+ return true;
194
190
  }
195
191
  shouldVerifyTransitiveFixes() {
196
- return this.transitiveFixStrategy === 'override' ||
197
- this.transitiveFixStrategy === 'prefer-direct-upgrade';
192
+ return this.preferDirectUpgrade === true;
198
193
  }
199
194
  filterRemainingTransitiveFixes(fixes, lockFileContent, packageManager, db) {
200
195
  const result = [];
201
196
  for (const fix of fixes) {
202
- if (!fix.isTransitive || fix.fixViaDirectUpgrade) {
197
+ if (!fix.isTransitive || (fix.fixViaDirectUpgrades && fix.fixViaDirectUpgrades.length > 0)) {
203
198
  result.push(fix);
204
199
  continue;
205
200
  }
206
- const resolvedVersion = (0, npm_utils_1.extractVersionFromLockFile)(lockFileContent, fix.packageName, packageManager);
207
- if (!resolvedVersion) {
201
+ const resolvedVersions = (0, npm_utils_1.extractAllVersionsFromLockFile)(lockFileContent, fix.packageName, packageManager);
202
+ if (resolvedVersions.length === 0) {
208
203
  continue;
209
204
  }
210
- const isStillVulnerable = this.isVersionStillVulnerable(fix.packageName, resolvedVersion, fix.cves, db);
211
- if (isStillVulnerable) {
205
+ const anyVersionVulnerable = resolvedVersions.some(version => this.isVersionStillVulnerable(fix.packageName, version, fix.cves, db));
206
+ if (anyVersionVulnerable) {
212
207
  result.push(fix);
213
208
  }
214
209
  }
@@ -217,7 +212,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
217
212
  isVersionStillVulnerable(packageName, version, cves, db) {
218
213
  const vulns = db.getVulnerabilities(packageName);
219
214
  for (const vuln of vulns) {
220
- if (cves.includes(vuln.cve) && this.isVersionAffected(version, vuln)) {
215
+ if (cves.includes(vuln.cve) && (0, version_utils_1.isVersionAffected)(version, vuln)) {
221
216
  return true;
222
217
  }
223
218
  }
@@ -235,100 +230,6 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
235
230
  }
236
231
  return this.cvePatternRegex.test(vulnerability.cve);
237
232
  }
238
- isVersionAffected(version, vulnerability) {
239
- try {
240
- const v = semver.parse(version);
241
- if (!v)
242
- return false;
243
- if (vulnerability.introducedVersion && vulnerability.introducedVersion !== '0') {
244
- const introduced = semver.parse(vulnerability.introducedVersion);
245
- if (introduced && semver.lt(v, introduced)) {
246
- return false;
247
- }
248
- }
249
- if (vulnerability.fixedVersion) {
250
- const fixed = semver.parse(vulnerability.fixedVersion);
251
- if (fixed && semver.gte(v, fixed)) {
252
- return false;
253
- }
254
- return true;
255
- }
256
- if (vulnerability.lastAffectedVersion) {
257
- const lastAffected = semver.parse(vulnerability.lastAffectedVersion);
258
- if (lastAffected && semver.gt(v, lastAffected)) {
259
- return false;
260
- }
261
- return true;
262
- }
263
- return true;
264
- }
265
- catch (_a) {
266
- return false;
267
- }
268
- }
269
- isUpgradeableWithinDelta(currentVersion, vulnerability) {
270
- if (this.isReportOnly()) {
271
- return false;
272
- }
273
- try {
274
- const current = semver.parse(currentVersion);
275
- if (!current)
276
- return false;
277
- const delta = this.maximumUpgradeDelta;
278
- if (vulnerability.fixedVersion) {
279
- const fixed = semver.parse(vulnerability.fixedVersion);
280
- if (!fixed)
281
- return false;
282
- switch (delta) {
283
- case 'patch':
284
- return current.major === fixed.major && current.minor === fixed.minor;
285
- case 'minor':
286
- return current.major === fixed.major;
287
- case 'major':
288
- return true;
289
- case 'none':
290
- return false;
291
- }
292
- }
293
- if (vulnerability.lastAffectedVersion) {
294
- const lastAffected = semver.parse(vulnerability.lastAffectedVersion);
295
- if (!lastAffected)
296
- return false;
297
- switch (delta) {
298
- case 'patch':
299
- return current.major === lastAffected.major &&
300
- current.minor === lastAffected.minor;
301
- case 'minor':
302
- return current.major === lastAffected.major;
303
- case 'major':
304
- return true;
305
- case 'none':
306
- return false;
307
- }
308
- }
309
- return false;
310
- }
311
- catch (_a) {
312
- return false;
313
- }
314
- }
315
- getUpgradeVersion(vulnerability) {
316
- if (vulnerability.fixedVersion) {
317
- return vulnerability.fixedVersion;
318
- }
319
- return undefined;
320
- }
321
- getVersionPrefixForDelta() {
322
- switch (this.maximumUpgradeDelta) {
323
- case 'patch':
324
- return '~';
325
- case 'minor':
326
- case 'major':
327
- return '^';
328
- default:
329
- return '';
330
- }
331
- }
332
233
  renderPath(scope, path) {
333
234
  const parts = [];
334
235
  if (scope) {
@@ -339,6 +240,39 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
339
240
  }
340
241
  return parts.join(' > ');
341
242
  }
243
+ findAllDirectDepsForTransitive(marker, transitivePackageName, scopesToCheck) {
244
+ const results = [];
245
+ for (const scope of scopesToCheck) {
246
+ const deps = marker[scope] || [];
247
+ for (const dep of deps) {
248
+ if (dep.resolved && this.hasTransitiveInTree(dep.resolved, transitivePackageName, new Set())) {
249
+ results.push({
250
+ name: dep.resolved.name,
251
+ version: dep.resolved.version,
252
+ scope
253
+ });
254
+ }
255
+ }
256
+ }
257
+ return results;
258
+ }
259
+ hasTransitiveInTree(resolved, targetPackageName, visited) {
260
+ const key = `${resolved.name}@${resolved.version}`;
261
+ if (visited.has(key))
262
+ return false;
263
+ visited.add(key);
264
+ if (resolved.dependencies) {
265
+ for (const child of resolved.dependencies) {
266
+ if (child.name === targetPackageName) {
267
+ return true;
268
+ }
269
+ if (child.resolved && this.hasTransitiveInTree(child.resolved, targetPackageName, visited)) {
270
+ return true;
271
+ }
272
+ }
273
+ }
274
+ return false;
275
+ }
342
276
  findVulnerabilities(resolved, db, depth, isDirect, scope, path, visited, results) {
343
277
  const key = `${resolved.name}@${resolved.version}`;
344
278
  if (visited.has(key))
@@ -353,7 +287,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
353
287
  if (!this.matchesCvePattern(vuln)) {
354
288
  continue;
355
289
  }
356
- if (this.isVersionAffected(resolved.version, vuln)) {
290
+ if ((0, version_utils_1.isVersionAffected)(resolved.version, vuln)) {
357
291
  results.push({
358
292
  resolved,
359
293
  vulnerability: vuln,
@@ -385,7 +319,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
385
319
  for (const dep of deps) {
386
320
  if (!dep.resolved)
387
321
  continue;
388
- const declaredMinVersion = this.extractMinimumVersion(dep.versionConstraint);
322
+ const declaredMinVersion = (0, version_utils_1.extractMinimumVersion)(dep.versionConstraint);
389
323
  if (!declaredMinVersion)
390
324
  continue;
391
325
  if (declaredMinVersion === dep.resolved.version)
@@ -401,8 +335,8 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
401
335
  if (!this.matchesCvePattern(vuln)) {
402
336
  continue;
403
337
  }
404
- if (this.isVersionAffected(declaredMinVersion, vuln) &&
405
- !this.isVersionAffected(dep.resolved.version, vuln)) {
338
+ if ((0, version_utils_1.isVersionAffected)(declaredMinVersion, vuln) &&
339
+ !(0, version_utils_1.isVersionAffected)(dep.resolved.version, vuln)) {
406
340
  affectedCves.push(vuln.cve);
407
341
  affectedCveSummaries.set(vuln.cve, vuln.summary);
408
342
  const fixVersion = vuln.fixedVersion;
@@ -433,20 +367,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
433
367
  if (this.isReportOnly()) {
434
368
  return false;
435
369
  }
436
- return this.isVersionWithinDelta(fromVersion, toVersion);
437
- }
438
- extractMinimumVersion(constraint) {
439
- if (!constraint)
440
- return undefined;
441
- if (semver.valid(constraint)) {
442
- return constraint;
443
- }
444
- const match = constraint.match(/^[~^>=<]*\s*(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)/);
445
- if (match && semver.valid(match[1])) {
446
- return match[1];
447
- }
448
- const coerced = semver.coerce(constraint);
449
- return coerced === null || coerced === void 0 ? void 0 : coerced.version;
370
+ return (0, version_utils_1.isVersionWithinDelta)(fromVersion, toVersion, this.maximumUpgradeDelta);
450
371
  }
451
372
  findHighestSafeVersion(packageName, originalVersion, initialFixVersion, db, visited = new Set()) {
452
373
  if (visited.has(initialFixVersion)) {
@@ -454,15 +375,15 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
454
375
  }
455
376
  visited.add(initialFixVersion);
456
377
  const vulnsInFixVersion = db.getVulnerabilities(packageName)
457
- .filter(v => this.isVersionAffected(initialFixVersion, v));
378
+ .filter(v => (0, version_utils_1.isVersionAffected)(initialFixVersion, v));
458
379
  if (vulnsInFixVersion.length === 0) {
459
380
  return initialFixVersion;
460
381
  }
461
382
  let highestFixVersion = initialFixVersion;
462
383
  for (const vuln of vulnsInFixVersion) {
463
- const fixVersion = this.getUpgradeVersion(vuln);
384
+ const fixVersion = (0, version_utils_1.getUpgradeVersion)(vuln);
464
385
  if (fixVersion && semver.valid(fixVersion)) {
465
- if (this.isVersionWithinDelta(originalVersion, fixVersion)) {
386
+ if ((0, version_utils_1.isVersionWithinDelta)(originalVersion, fixVersion, this.maximumUpgradeDelta)) {
466
387
  if (semver.gt(fixVersion, highestFixVersion)) {
467
388
  highestFixVersion = fixVersion;
468
389
  }
@@ -474,29 +395,6 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
474
395
  }
475
396
  return initialFixVersion;
476
397
  }
477
- isVersionWithinDelta(originalVersion, targetVersion) {
478
- try {
479
- const original = semver.parse(originalVersion);
480
- const target = semver.parse(targetVersion);
481
- if (!original || !target)
482
- return false;
483
- switch (this.maximumUpgradeDelta) {
484
- case 'patch':
485
- return original.major === target.major && original.minor === target.minor;
486
- case 'minor':
487
- return original.major === target.major;
488
- case 'major':
489
- return true;
490
- case 'none':
491
- return false;
492
- default:
493
- return false;
494
- }
495
- }
496
- catch (_a) {
497
- return false;
498
- }
499
- }
500
398
  computeFixes(vulnerabilities, db, projectContext) {
501
399
  return __awaiter(this, void 0, void 0, function* () {
502
400
  var _a, _b, _c, _d, _e, _f, _g;
@@ -531,23 +429,28 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
531
429
  let isTransitive = true;
532
430
  let scope;
533
431
  let originalVersion;
534
- let directDepInfo;
432
+ const directDepInfosMap = new Map();
535
433
  for (const vuln of vulns) {
536
434
  if (!originalVersion) {
537
435
  originalVersion = vuln.resolved.version;
538
436
  }
539
- if (!vuln.isDirect && vuln.path.length > 0 && !directDepInfo) {
437
+ if (!vuln.isDirect && vuln.path.length > 0) {
540
438
  const directDep = vuln.path[0];
541
- directDepInfo = {
542
- name: directDep.name,
543
- version: directDep.version,
544
- scope: vuln.scope || 'dependencies'
545
- };
439
+ const depScope = vuln.scope || 'dependencies';
440
+ const key = `${directDep.name}@${depScope}`;
441
+ if (!directDepInfosMap.has(key)) {
442
+ directDepInfosMap.set(key, {
443
+ name: directDep.name,
444
+ version: directDep.version,
445
+ scope: depScope
446
+ });
447
+ }
546
448
  }
547
- if (!this.isUpgradeableWithinDelta(vuln.resolved.version, vuln.vulnerability)) {
449
+ const upgradeCheck = (0, version_utils_1.isUpgradeableWithinDelta)(vuln.resolved.version, vuln.vulnerability, this.maximumUpgradeDelta);
450
+ if (this.isReportOnly() || !upgradeCheck) {
548
451
  continue;
549
452
  }
550
- const fixVersion = this.getUpgradeVersion(vuln.vulnerability);
453
+ const fixVersion = (0, version_utils_1.getUpgradeVersion)(vuln.vulnerability);
551
454
  if (fixVersion) {
552
455
  const fixMajor = (_g = semver.parse(fixVersion)) === null || _g === void 0 ? void 0 : _g.major;
553
456
  const shouldConsiderFix = hasMultipleMajorVersions
@@ -566,8 +469,18 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
566
469
  scope = vuln.scope;
567
470
  }
568
471
  }
472
+ if (isTransitive && (projectContext === null || projectContext === void 0 ? void 0 : projectContext.marker) && (projectContext === null || projectContext === void 0 ? void 0 : projectContext.scopesToCheck)) {
473
+ const allDirectDeps = this.findAllDirectDepsForTransitive(projectContext.marker, packageName, projectContext.scopesToCheck);
474
+ for (const dep of allDirectDeps) {
475
+ const key = `${dep.name}@${dep.scope}`;
476
+ if (!directDepInfosMap.has(key)) {
477
+ directDepInfosMap.set(key, dep);
478
+ }
479
+ }
480
+ }
569
481
  if (highestFixVersion && cves.length > 0 && originalVersion) {
570
482
  const safeVersion = this.findHighestSafeVersion(packageName, originalVersion, highestFixVersion, db);
483
+ const directDepInfosArray = Array.from(directDepInfosMap.values());
571
484
  const fix = {
572
485
  packageName,
573
486
  newVersion: safeVersion || highestFixVersion,
@@ -576,11 +489,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
576
489
  cveSummaries,
577
490
  scope,
578
491
  originalMajorVersion: originalMajor,
579
- directDepInfo: isTransitive && directDepInfo ? {
580
- name: directDepInfo.name,
581
- version: directDepInfo.version,
582
- scope: directDepInfo.scope
583
- } : undefined
492
+ directDepInfos: isTransitive && directDepInfosArray.length > 0 ? directDepInfosArray : undefined
584
493
  };
585
494
  fixes.push(fix);
586
495
  }
@@ -588,47 +497,50 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
588
497
  return fixes;
589
498
  });
590
499
  }
591
- tryFindDirectDepUpgrade(transitivePackageName, requiredFixVersion, directDepInfo, projectContext, db) {
592
- return __awaiter(this, void 0, void 0, function* () {
593
- const isVulnerable = (version) => {
594
- try {
595
- return semver.lt(version, requiredFixVersion);
596
- }
597
- catch (_a) {
598
- return true;
599
- }
600
- };
601
- const isWithinDelta = (from, to) => {
602
- return this.isVersionWithinDelta(from, to);
603
- };
604
- try {
605
- const upgradeVersion = yield (0, npm_utils_1.findDirectUpgradeThatFixesTransitive)(projectContext.packageManager, directDepInfo.name, directDepInfo.version, transitivePackageName, isVulnerable, isWithinDelta, projectContext.originalPackageJson, directDepInfo.scope, projectContext.configFiles);
606
- return upgradeVersion;
607
- }
608
- catch (_a) {
609
- return undefined;
610
- }
611
- });
612
- }
613
- tryDirectUpgradesForTransitives(fixes, updateInfo, db) {
500
+ tryDirectUpgradesForTransitives(fixes, updateInfo) {
614
501
  return __awaiter(this, void 0, void 0, function* () {
615
502
  const result = [];
616
503
  for (const fix of fixes) {
617
- if (!fix.directDepInfo) {
504
+ if (!fix.directDepInfos || fix.directDepInfos.length === 0) {
618
505
  result.push(fix);
619
506
  continue;
620
507
  }
621
- const directUpgradeVersion = yield this.tryFindDirectDepUpgrade(fix.packageName, fix.newVersion, fix.directDepInfo, {
622
- packageManager: updateInfo.packageManager,
623
- originalPackageJson: updateInfo.originalPackageJson,
624
- configFiles: updateInfo.configFiles
625
- }, db);
626
- if (directUpgradeVersion) {
627
- result.push(Object.assign(Object.assign({}, fix), { fixViaDirectUpgrade: {
628
- directDepName: fix.directDepInfo.name,
508
+ const candidateUpgrades = [];
509
+ const isVulnerable = (version) => {
510
+ try {
511
+ return semver.lt(version, fix.newVersion);
512
+ }
513
+ catch (_a) {
514
+ return true;
515
+ }
516
+ };
517
+ const isWithinDelta = (from, to) => {
518
+ return (0, version_utils_1.isVersionWithinDelta)(from, to, this.maximumUpgradeDelta);
519
+ };
520
+ for (const directDepInfo of fix.directDepInfos) {
521
+ const directUpgradeVersion = yield (0, npm_utils_1.findDirectUpgradeWithSafeTransitiveInIsolation)(updateInfo.packageManager, directDepInfo.name, directDepInfo.version, fix.packageName, isVulnerable, isWithinDelta, directDepInfo.scope, updateInfo.configFiles);
522
+ if (directUpgradeVersion) {
523
+ candidateUpgrades.push({
524
+ directDepName: directDepInfo.name,
629
525
  directDepVersion: directUpgradeVersion,
630
- directDepScope: fix.directDepInfo.scope
631
- } }));
526
+ directDepScope: directDepInfo.scope
527
+ });
528
+ }
529
+ }
530
+ if (candidateUpgrades.length > 0) {
531
+ const foundAllUpgrades = candidateUpgrades.length === fix.directDepInfos.length;
532
+ if (foundAllUpgrades) {
533
+ const allFixed = yield (0, npm_utils_1.verifyAllUpgradesFixTransitive)(updateInfo.packageManager, candidateUpgrades.map(u => ({
534
+ name: u.directDepName,
535
+ version: u.directDepVersion,
536
+ scope: u.directDepScope
537
+ })), fix.packageName, isVulnerable, updateInfo.originalPackageJson, updateInfo.configFiles);
538
+ if (allFixed) {
539
+ result.push(Object.assign(Object.assign({}, fix), { fixViaDirectUpgrades: candidateUpgrades }));
540
+ continue;
541
+ }
542
+ }
543
+ result.push(Object.assign(Object.assign({}, fix), { fixViaDirectUpgrades: candidateUpgrades }));
632
544
  }
633
545
  else {
634
546
  result.push(fix);
@@ -680,7 +592,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
680
592
  const visited = new Set();
681
593
  const scopesToCheck = recipe.scope
682
594
  ? [recipe.scope]
683
- : ALL_DEPENDENCY_SCOPES;
595
+ : types_1.ALL_DEPENDENCY_SCOPES;
684
596
  for (const scope of scopesToCheck) {
685
597
  const deps = marker[scope] || [];
686
598
  for (const dep of deps) {
@@ -713,7 +625,9 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
713
625
  fixes = yield recipe.computeFixes(vulnerabilities, acc.db, {
714
626
  packageManager: pm,
715
627
  originalPackageJson,
716
- configFiles: Object.keys(configFiles).length > 0 ? configFiles : undefined
628
+ configFiles: Object.keys(configFiles).length > 0 ? configFiles : undefined,
629
+ marker,
630
+ scopesToCheck
717
631
  });
718
632
  }
719
633
  if (recipe.fixDeclaredVersions) {
@@ -844,7 +758,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
844
758
  const vulnerabilities = acc.vulnerableByProject.get(doc.sourcePath);
845
759
  if (vulnerabilities && vulnerabilities.length > 0) {
846
760
  for (const vuln of vulnerabilities) {
847
- const upgradeable = recipe.isUpgradeableWithinDelta(vuln.resolved.version, vuln.vulnerability);
761
+ const upgradeable = !recipe.isReportOnly() && (0, version_utils_1.isUpgradeableWithinDelta)(vuln.resolved.version, vuln.vulnerability, recipe.maximumUpgradeDelta);
848
762
  recipe.vulnerabilityReport.insertRow(ctx, new VulnerabilityReportRow(doc.sourcePath, vuln.vulnerability.cve, vuln.resolved.name, vuln.resolved.version, vuln.vulnerability.fixedVersion || '', vuln.vulnerability.lastAffectedVersion || '', upgradeable, vuln.vulnerability.summary, vuln.vulnerability.severity, vuln.depth, vuln.vulnerability.cwes, vuln.isDirect, recipe.renderPath(vuln.scope, vuln.path)));
849
763
  }
850
764
  }
@@ -1011,8 +925,8 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1011
925
  }
1012
926
  runPackageManagerInstall(acc, updateInfo, fixes) {
1013
927
  return __awaiter(this, void 0, void 0, function* () {
1014
- const directFixes = fixes.filter(f => !f.isTransitive || f.fixViaDirectUpgrade);
1015
- const transitiveFixes = fixes.filter(f => f.isTransitive && !f.fixViaDirectUpgrade);
928
+ const directFixes = fixes.filter(f => !f.isTransitive || (f.fixViaDirectUpgrades && f.fixViaDirectUpgrades.length > 0));
929
+ const transitiveFixes = fixes.filter(f => f.isTransitive && (!f.fixViaDirectUpgrades || f.fixViaDirectUpgrades.length === 0));
1016
930
  if (this.shouldVerifyTransitiveFixes() && transitiveFixes.length > 0) {
1017
931
  const phase1PackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, directFixes, updateInfo.packageManager);
1018
932
  const phase1Result = yield (0, javascript_1.runInstallInTempDir)(updateInfo.packageManager, phase1PackageJson, {
@@ -1031,8 +945,8 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1031
945
  (0, javascript_1.storeInstallResult)(phase1Result, acc, updateInfo, phase1PackageJson);
1032
946
  return;
1033
947
  }
1034
- if (this.transitiveFixStrategy === 'prefer-direct-upgrade') {
1035
- remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, updateInfo, acc.db);
948
+ if (this.preferDirectUpgrade) {
949
+ remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, updateInfo);
1036
950
  }
1037
951
  const finalFixes = [...directFixes, ...remainingTransitiveFixes];
1038
952
  const finalPackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, finalFixes, updateInfo.packageManager);
@@ -1053,21 +967,18 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1053
967
  return __awaiter(this, void 0, void 0, function* () {
1054
968
  const memberPaths = acc.workspaceRoots.get(rootPath) || [];
1055
969
  const pm = rootUpdateInfo.packageManager;
1056
- const allDirectFixes = [];
1057
970
  const allTransitiveFixes = [];
1058
971
  const rootFixes = acc.fixesByProject.get(rootPath) || [];
1059
- const rootDirectFixes = rootFixes.filter(f => !f.isTransitive || f.fixViaDirectUpgrade);
1060
- const rootTransitiveFixes = rootFixes.filter(f => f.isTransitive && !f.fixViaDirectUpgrade);
1061
- allDirectFixes.push(...rootDirectFixes);
972
+ const rootDirectFixes = rootFixes.filter(f => !f.isTransitive || (f.fixViaDirectUpgrades && f.fixViaDirectUpgrades.length > 0));
973
+ const rootTransitiveFixes = rootFixes.filter(f => f.isTransitive && (!f.fixViaDirectUpgrades || f.fixViaDirectUpgrades.length === 0));
1062
974
  allTransitiveFixes.push(...rootTransitiveFixes);
1063
975
  const memberDirectFixes = new Map();
1064
976
  for (const memberPath of memberPaths) {
1065
977
  const memberFixes = acc.fixesByProject.get(memberPath) || [];
1066
- const directFixes = memberFixes.filter(f => !f.isTransitive || f.fixViaDirectUpgrade);
1067
- const transitiveFixes = memberFixes.filter(f => f.isTransitive && !f.fixViaDirectUpgrade);
978
+ const directFixes = memberFixes.filter(f => !f.isTransitive || (f.fixViaDirectUpgrades && f.fixViaDirectUpgrades.length > 0));
979
+ const transitiveFixes = memberFixes.filter(f => f.isTransitive && (!f.fixViaDirectUpgrades || f.fixViaDirectUpgrades.length === 0));
1068
980
  if (directFixes.length > 0) {
1069
981
  memberDirectFixes.set(memberPath, directFixes);
1070
- allDirectFixes.push(...directFixes);
1071
982
  }
1072
983
  allTransitiveFixes.push(...transitiveFixes);
1073
984
  }
@@ -1109,8 +1020,8 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1109
1020
  (0, javascript_1.storeInstallResult)(phase1Result, acc, rootUpdateInfo, phase1RootPackageJson);
1110
1021
  return;
1111
1022
  }
1112
- if (this.transitiveFixStrategy === 'prefer-direct-upgrade') {
1113
- remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, rootUpdateInfo, acc.db);
1023
+ if (this.preferDirectUpgrade) {
1024
+ remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, rootUpdateInfo);
1114
1025
  }
1115
1026
  const finalRootPackageJson = this.createModifiedPackageJson(rootOriginalContent, [...rootDirectFixes, ...remainingTransitiveFixes], pm);
1116
1027
  const finalResult = yield (0, javascript_1.runWorkspaceInstallInTempDir)(pm, finalRootPackageJson, {
@@ -1134,7 +1045,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1134
1045
  if (!fix.isTransitive && fix.scope) {
1135
1046
  if (packageJson[fix.scope] && packageJson[fix.scope][fix.packageName]) {
1136
1047
  const originalVersion = packageJson[fix.scope][fix.packageName];
1137
- packageJson[fix.scope][fix.packageName] = applyVersionPrefix(originalVersion, fix.newVersion);
1048
+ packageJson[fix.scope][fix.packageName] = (0, version_utils_1.applyVersionPrefix)(originalVersion, fix.newVersion);
1138
1049
  }
1139
1050
  }
1140
1051
  }
@@ -1154,19 +1065,24 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1154
1065
  if (!fix.isTransitive && fix.scope) {
1155
1066
  if (packageJson[fix.scope] && packageJson[fix.scope][fix.packageName]) {
1156
1067
  const originalVersion = packageJson[fix.scope][fix.packageName];
1157
- packageJson[fix.scope][fix.packageName] = applyVersionPrefix(originalVersion, fix.newVersion);
1068
+ packageJson[fix.scope][fix.packageName] = (0, version_utils_1.applyVersionPrefix)(originalVersion, fix.newVersion);
1158
1069
  }
1159
1070
  }
1160
1071
  else if (fix.isTransitive) {
1161
- if (fix.fixViaDirectUpgrade) {
1162
- const { directDepName, directDepVersion, directDepScope } = fix.fixViaDirectUpgrade;
1163
- if ((_a = packageJson[directDepScope]) === null || _a === void 0 ? void 0 : _a[directDepName]) {
1164
- const originalVersion = packageJson[directDepScope][directDepName];
1165
- packageJson[directDepScope][directDepName] = applyVersionPrefix(originalVersion, directDepVersion);
1166
- continue;
1072
+ if (this.transitiveFixStrategy === 'lock-file') {
1073
+ continue;
1074
+ }
1075
+ if (fix.fixViaDirectUpgrades && fix.fixViaDirectUpgrades.length > 0) {
1076
+ for (const upgrade of fix.fixViaDirectUpgrades) {
1077
+ const { directDepName, directDepVersion, directDepScope } = upgrade;
1078
+ if ((_a = packageJson[directDepScope]) === null || _a === void 0 ? void 0 : _a[directDepName]) {
1079
+ const originalVersion = packageJson[directDepScope][directDepName];
1080
+ packageJson[directDepScope][directDepName] = (0, version_utils_1.applyVersionPrefix)(originalVersion, directDepVersion);
1081
+ }
1167
1082
  }
1083
+ continue;
1168
1084
  }
1169
- const directDepScope = findDirectDependencyScope(packageJson, fix.packageName);
1085
+ const directDepScope = (0, override_utils_1.findDirectDependencyScope)(packageJson, fix.packageName);
1170
1086
  if (directDepScope) {
1171
1087
  const directVersion = packageJson[directDepScope][fix.packageName];
1172
1088
  const directMajor = semver.major(semver.coerce(directVersion) || '0.0.0');
@@ -1182,13 +1098,13 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1182
1098
  }
1183
1099
  continue;
1184
1100
  }
1185
- packageJson[directDepScope][fix.packageName] = applyVersionPrefix(directVersion, fix.newVersion);
1101
+ packageJson[directDepScope][fix.packageName] = (0, version_utils_1.applyVersionPrefix)(directVersion, fix.newVersion);
1186
1102
  continue;
1187
1103
  }
1188
1104
  const packageFixes = fixesByPackage.get(fix.packageName) || [];
1189
1105
  const hasMultipleMajorVersions = packageFixes.length > 1 &&
1190
1106
  new Set(packageFixes.map(f => f.originalMajorVersion)).size > 1;
1191
- const existingOverrides = getOverridesFromPackageJson(packageJson, packageManager);
1107
+ const existingOverrides = (0, override_utils_1.getOverridesFromPackageJson)(packageJson, packageManager);
1192
1108
  const hasExistingVersionSpecificOverrides = existingOverrides &&
1193
1109
  Object.keys(existingOverrides).some(key => key.startsWith(`${fix.packageName}@`));
1194
1110
  const isYarn = packageManager === "YarnClassic" ||
@@ -1262,7 +1178,7 @@ __decorate([
1262
1178
  (0, rewrite_1.Option)({
1263
1179
  displayName: "Scope",
1264
1180
  description: "Match dependencies with the specified scope. Default includes all scopes. " +
1265
- "Use 'dependencies' for production dependencies, 'devDependencies' for development only, etc.",
1181
+ "Use `dependencies` for production dependencies, `devDependencies` for development only, etc.",
1266
1182
  required: false,
1267
1183
  example: "dependencies",
1268
1184
  valid: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]
@@ -1272,20 +1188,31 @@ __decorate([
1272
1188
  (0, rewrite_1.Option)({
1273
1189
  displayName: "Transitive fix strategy",
1274
1190
  description: "Strategy for handling transitive dependency vulnerabilities. " +
1275
- "'report' only reports them without fixing. " +
1276
- "'override' adds overrides only for transitives not fixed by direct upgrades (runs extra npm install to verify). " +
1277
- "'prefer-direct-upgrade' tries to find higher direct dependency versions that fix transitives, falls back to overrides (queries npm registry). " +
1278
- "Default is 'report'.",
1191
+ "`report` only reports them without fixing. " +
1192
+ "`override` adds overrides/resolutions for transitive vulnerabilities. " +
1193
+ "`lock-file` updates the lock file to resolve safe versions without modifying `package.json` (similar to Dependabot). " +
1194
+ "Default is `report`.",
1279
1195
  required: false,
1280
1196
  example: "override",
1281
- valid: ["report", "override", "prefer-direct-upgrade"]
1197
+ valid: ["report", "override", "lock-file"]
1282
1198
  })
1283
1199
  ], DependencyVulnerabilityCheck.prototype, "transitiveFixStrategy", void 0);
1200
+ __decorate([
1201
+ (0, rewrite_1.Option)({
1202
+ displayName: "Prefer direct upgrade",
1203
+ description: "When fixing transitive vulnerabilities, first try to find higher versions of " +
1204
+ "direct dependencies that include safe transitive versions. Falls back to the " +
1205
+ "`transitiveFixStrategy` if no suitable direct upgrade exists. Queries npm registry. " +
1206
+ "Default is `true`.",
1207
+ required: false,
1208
+ example: "false"
1209
+ })
1210
+ ], DependencyVulnerabilityCheck.prototype, "preferDirectUpgrade", void 0);
1284
1211
  __decorate([
1285
1212
  (0, rewrite_1.Option)({
1286
1213
  displayName: "Maximum upgrade delta",
1287
1214
  description: "The maximum difference to allow when upgrading a dependency version. " +
1288
- "Use 'none' to only report vulnerabilities without making any changes. " +
1215
+ "Use `none` to only report vulnerabilities without making any changes. " +
1289
1216
  "Patch version upgrades are the default and safest option. " +
1290
1217
  "Minor version upgrades can introduce new features but typically no breaking changes. " +
1291
1218
  "Major version upgrades may require code changes.",
@@ -1298,8 +1225,8 @@ __decorate([
1298
1225
  (0, rewrite_1.Option)({
1299
1226
  displayName: "Minimum severity",
1300
1227
  description: "Only fix vulnerabilities with a severity level equal to or higher than the specified minimum. " +
1301
- "Vulnerabilities are classified as LOW, MODERATE, HIGH, or CRITICAL based on their potential impact. " +
1302
- "Default is LOW, which includes all severity levels.",
1228
+ "Vulnerabilities are classified as `LOW`, `MODERATE`, `HIGH`, or `CRITICAL` based on their potential impact. " +
1229
+ "Default is `LOW`, which includes all severity levels.",
1303
1230
  required: false,
1304
1231
  example: "MODERATE",
1305
1232
  valid: ["LOW", "MODERATE", "HIGH", "CRITICAL"]
@@ -1319,11 +1246,11 @@ __decorate([
1319
1246
  __decorate([
1320
1247
  (0, rewrite_1.Option)({
1321
1248
  displayName: "Fix declared versions",
1322
- description: "When enabled, also upgrades version specifiers declared in package.json that specify vulnerable versions, " +
1249
+ description: "When enabled, also upgrades version specifiers declared in `package.json` that specify vulnerable versions, " +
1323
1250
  "even if the lock file already resolves to a safe version. This is a preventive measure to ensure that " +
1324
1251
  "future installs (e.g., on a different machine or after lock file changes) won't install vulnerable versions. " +
1325
1252
  "These preventive upgrades are NOT reported in the vulnerability data table since there's no actual vulnerability. " +
1326
- "Default is false.",
1253
+ "Default is `false`.",
1327
1254
  required: false,
1328
1255
  example: "true"
1329
1256
  })
@@ -1331,49 +1258,11 @@ __decorate([
1331
1258
  __decorate([
1332
1259
  (0, rewrite_1.Option)({
1333
1260
  displayName: "Add override comments",
1334
- description: "When enabled, adds a comment field (e.g., '//overrides') alongside overrides to document which CVEs " +
1261
+ description: "When enabled, adds a comment field (e.g., `//overrides`) alongside overrides to document which CVEs " +
1335
1262
  "each override is fixing. This helps with auditing and knowing when overrides can be removed. " +
1336
- "Default is true.",
1263
+ "Default is `true`.",
1337
1264
  required: false,
1338
1265
  example: "true"
1339
1266
  })
1340
1267
  ], DependencyVulnerabilityCheck.prototype, "addOverrideComments", void 0);
1341
- function extractVersionPrefix(versionString) {
1342
- const match = versionString.match(/^([~^]|>=?|<=?|=)?(.*)$/);
1343
- if (match) {
1344
- return {
1345
- prefix: match[1] || '',
1346
- version: match[2]
1347
- };
1348
- }
1349
- return { prefix: '', version: versionString };
1350
- }
1351
- function applyVersionPrefix(originalVersion, newVersion) {
1352
- const { prefix } = extractVersionPrefix(originalVersion);
1353
- return prefix + newVersion;
1354
- }
1355
- function findDirectDependencyScope(packageJson, packageName) {
1356
- var _a;
1357
- for (const scope of ALL_DEPENDENCY_SCOPES) {
1358
- if ((_a = packageJson[scope]) === null || _a === void 0 ? void 0 : _a[packageName]) {
1359
- return scope;
1360
- }
1361
- }
1362
- return undefined;
1363
- }
1364
- function getOverridesFromPackageJson(packageJson, packageManager) {
1365
- var _a;
1366
- switch (packageManager) {
1367
- case "Npm":
1368
- case "Bun":
1369
- return packageJson.overrides;
1370
- case "Pnpm":
1371
- return (_a = packageJson.pnpm) === null || _a === void 0 ? void 0 : _a.overrides;
1372
- case "YarnClassic":
1373
- case "YarnBerry":
1374
- return packageJson.resolutions;
1375
- default:
1376
- return undefined;
1377
- }
1378
- }
1379
1268
  //# sourceMappingURL=dependency-vulnerability-check.js.map