@openrewrite/recipes-nodejs 0.37.0-20260104-170507 → 0.37.0-20260106-082310

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.
@@ -59,6 +59,7 @@ const javascript_1 = require("@openrewrite/rewrite/javascript");
59
59
  const semver = __importStar(require("semver"));
60
60
  const path = __importStar(require("path"));
61
61
  const vulnerability_1 = require("./vulnerability");
62
+ const npm_utils_1 = require("./npm-utils");
62
63
  const ALL_DEPENDENCY_SCOPES = [
63
64
  'dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'
64
65
  ];
@@ -159,7 +160,7 @@ __decorate([
159
160
  ], VulnerabilityReportRow.prototype, "dependencyPath", void 0);
160
161
  class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
161
162
  constructor(options) {
162
- var _a, _b, _c;
163
+ var _a, _b, _c, _d, _e;
163
164
  super(options);
164
165
  this.name = "org.openrewrite.node.dependency-vulnerability-check";
165
166
  this.displayName = "Find and fix vulnerable npm dependencies";
@@ -169,9 +170,11 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
169
170
  "If a minor or major upgrade is required to reach the fixed version, this can be controlled using the `maximumUpgradeDelta` option. " +
170
171
  "Vulnerability information comes from the GitHub Security Advisory Database.";
171
172
  this.vulnerabilityReport = new rewrite_1.DataTable("org.openrewrite.nodejs.table.VulnerabilityReport", "Vulnerability Report", "Lists all vulnerabilities found in project dependencies.", VulnerabilityReportRow);
172
- (_a = this.maximumUpgradeDelta) !== null && _a !== void 0 ? _a : (this.maximumUpgradeDelta = 'patch');
173
- (_b = this.minimumSeverity) !== null && _b !== void 0 ? _b : (this.minimumSeverity = vulnerability_1.Severity.LOW);
174
- (_c = this.fixDeclaredVersions) !== null && _c !== void 0 ? _c : (this.fixDeclaredVersions = false);
173
+ (_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);
175
178
  if (options === null || options === void 0 ? void 0 : options.minimumSeverity) {
176
179
  this.minimumSeverity = (0, vulnerability_1.parseSeverity)(options.minimumSeverity);
177
180
  }
@@ -179,10 +182,47 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
179
182
  try {
180
183
  this.cvePatternRegex = new RegExp(this.cvePattern);
181
184
  }
182
- catch (_d) {
185
+ catch (_f) {
183
186
  }
184
187
  }
185
188
  }
189
+ shouldScanTransitives() {
190
+ return this.transitiveFixStrategy !== 'report';
191
+ }
192
+ shouldFixTransitives() {
193
+ return this.transitiveFixStrategy !== 'report';
194
+ }
195
+ shouldVerifyTransitiveFixes() {
196
+ return this.transitiveFixStrategy === 'override' ||
197
+ this.transitiveFixStrategy === 'prefer-direct-upgrade';
198
+ }
199
+ filterRemainingTransitiveFixes(fixes, lockFileContent, packageManager, db) {
200
+ const result = [];
201
+ for (const fix of fixes) {
202
+ if (!fix.isTransitive || fix.fixViaDirectUpgrade) {
203
+ result.push(fix);
204
+ continue;
205
+ }
206
+ const resolvedVersion = (0, npm_utils_1.extractVersionFromLockFile)(lockFileContent, fix.packageName, packageManager);
207
+ if (!resolvedVersion) {
208
+ continue;
209
+ }
210
+ const isStillVulnerable = this.isVersionStillVulnerable(fix.packageName, resolvedVersion, fix.cves, db);
211
+ if (isStillVulnerable) {
212
+ result.push(fix);
213
+ }
214
+ }
215
+ return result;
216
+ }
217
+ isVersionStillVulnerable(packageName, version, cves, db) {
218
+ const vulns = db.getVulnerabilities(packageName);
219
+ for (const vuln of vulns) {
220
+ if (cves.includes(vuln.cve) && this.isVersionAffected(version, vuln)) {
221
+ return true;
222
+ }
223
+ }
224
+ return false;
225
+ }
186
226
  initialValue(_ctx) {
187
227
  return Object.assign(Object.assign({}, (0, javascript_1.createDependencyRecipeAccumulator)()), { db: vulnerability_1.VulnerabilityDatabase.load(), vulnerableByProject: new Map(), fixesByProject: new Map(), originalLockFiles: new Map(), allPackageJsonContents: new Map(), workspaceRoots: new Map(), modifiedWorkspaceMemberContents: new Map(), workspaceDetectionComplete: false });
188
228
  }
@@ -324,7 +364,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
324
364
  });
325
365
  }
326
366
  }
327
- if (this.overrideTransitive) {
367
+ if (this.shouldScanTransitives()) {
328
368
  const transitives = [
329
369
  ...(resolved.dependencies || []),
330
370
  ...(resolved.devDependencies || []),
@@ -352,6 +392,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
352
392
  continue;
353
393
  const vulns = db.getVulnerabilities(dep.name);
354
394
  const affectedCves = [];
395
+ const affectedCveSummaries = new Map();
355
396
  let highestFixVersion;
356
397
  for (const vuln of vulns) {
357
398
  if ((0, vulnerability_1.severityOrdinal)(vuln.severity) < (0, vulnerability_1.severityOrdinal)(this.minimumSeverity)) {
@@ -363,6 +404,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
363
404
  if (this.isVersionAffected(declaredMinVersion, vuln) &&
364
405
  !this.isVersionAffected(dep.resolved.version, vuln)) {
365
406
  affectedCves.push(vuln.cve);
407
+ affectedCveSummaries.set(vuln.cve, vuln.summary);
366
408
  const fixVersion = vuln.fixedVersion;
367
409
  if (fixVersion && (!highestFixVersion || semver.gt(fixVersion, highestFixVersion))) {
368
410
  highestFixVersion = fixVersion;
@@ -378,6 +420,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
378
420
  scope,
379
421
  isTransitive: false,
380
422
  cves: affectedCves,
423
+ cveSummaries: affectedCveSummaries,
381
424
  originalMajorVersion: majorVersion
382
425
  });
383
426
  }
@@ -454,76 +497,145 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
454
497
  return false;
455
498
  }
456
499
  }
457
- computeFixes(vulnerabilities, db) {
458
- var _a, _b, _c, _d, _e, _f, _g;
459
- if (this.isReportOnly()) {
460
- return [];
461
- }
462
- const byPackageAndMajor = new Map();
463
- for (const vuln of vulnerabilities) {
464
- const parsed = semver.parse(vuln.resolved.version);
465
- const major = (_a = parsed === null || parsed === void 0 ? void 0 : parsed.major) !== null && _a !== void 0 ? _a : 0;
466
- const key = `${vuln.resolved.name}@${major}`;
467
- const existing = byPackageAndMajor.get(key) || [];
468
- existing.push(vuln);
469
- byPackageAndMajor.set(key, existing);
470
- }
471
- const majorVersionsByPackage = new Map();
472
- for (const vuln of vulnerabilities) {
473
- const parsed = semver.parse(vuln.resolved.version);
474
- const major = (_b = parsed === null || parsed === void 0 ? void 0 : parsed.major) !== null && _b !== void 0 ? _b : 0;
475
- const existing = majorVersionsByPackage.get(vuln.resolved.name) || new Set();
476
- existing.add(major);
477
- majorVersionsByPackage.set(vuln.resolved.name, existing);
478
- }
479
- const fixes = [];
480
- for (const [key, vulns] of byPackageAndMajor) {
481
- const packageName = vulns[0].resolved.name;
482
- const originalMajor = (_d = (_c = semver.parse(vulns[0].resolved.version)) === null || _c === void 0 ? void 0 : _c.major) !== null && _d !== void 0 ? _d : 0;
483
- const hasMultipleMajorVersions = ((_f = (_e = majorVersionsByPackage.get(packageName)) === null || _e === void 0 ? void 0 : _e.size) !== null && _f !== void 0 ? _f : 0) > 1;
484
- let highestFixVersion;
485
- const cves = [];
486
- let isTransitive = true;
487
- let scope;
488
- let originalVersion;
489
- for (const vuln of vulns) {
490
- if (!originalVersion) {
491
- originalVersion = vuln.resolved.version;
492
- }
493
- if (!this.isUpgradeableWithinDelta(vuln.resolved.version, vuln.vulnerability)) {
494
- continue;
495
- }
496
- const fixVersion = this.getUpgradeVersion(vuln.vulnerability);
497
- if (fixVersion) {
498
- const fixMajor = (_g = semver.parse(fixVersion)) === null || _g === void 0 ? void 0 : _g.major;
499
- const shouldConsiderFix = hasMultipleMajorVersions
500
- ? fixMajor === originalMajor
501
- : true;
502
- if (shouldConsiderFix) {
503
- if (!highestFixVersion || semver.gt(fixVersion, highestFixVersion)) {
504
- highestFixVersion = fixVersion;
500
+ computeFixes(vulnerabilities, db, projectContext) {
501
+ return __awaiter(this, void 0, void 0, function* () {
502
+ var _a, _b, _c, _d, _e, _f, _g;
503
+ if (this.isReportOnly()) {
504
+ return [];
505
+ }
506
+ const byPackageAndMajor = new Map();
507
+ for (const vuln of vulnerabilities) {
508
+ const parsed = semver.parse(vuln.resolved.version);
509
+ const major = (_a = parsed === null || parsed === void 0 ? void 0 : parsed.major) !== null && _a !== void 0 ? _a : 0;
510
+ const key = `${vuln.resolved.name}@${major}`;
511
+ const existing = byPackageAndMajor.get(key) || [];
512
+ existing.push(vuln);
513
+ byPackageAndMajor.set(key, existing);
514
+ }
515
+ const majorVersionsByPackage = new Map();
516
+ for (const vuln of vulnerabilities) {
517
+ const parsed = semver.parse(vuln.resolved.version);
518
+ const major = (_b = parsed === null || parsed === void 0 ? void 0 : parsed.major) !== null && _b !== void 0 ? _b : 0;
519
+ const existing = majorVersionsByPackage.get(vuln.resolved.name) || new Set();
520
+ existing.add(major);
521
+ majorVersionsByPackage.set(vuln.resolved.name, existing);
522
+ }
523
+ const fixes = [];
524
+ for (const [key, vulns] of byPackageAndMajor) {
525
+ const packageName = vulns[0].resolved.name;
526
+ const originalMajor = (_d = (_c = semver.parse(vulns[0].resolved.version)) === null || _c === void 0 ? void 0 : _c.major) !== null && _d !== void 0 ? _d : 0;
527
+ const hasMultipleMajorVersions = ((_f = (_e = majorVersionsByPackage.get(packageName)) === null || _e === void 0 ? void 0 : _e.size) !== null && _f !== void 0 ? _f : 0) > 1;
528
+ let highestFixVersion;
529
+ const cves = [];
530
+ const cveSummaries = new Map();
531
+ let isTransitive = true;
532
+ let scope;
533
+ let originalVersion;
534
+ let directDepInfo;
535
+ for (const vuln of vulns) {
536
+ if (!originalVersion) {
537
+ originalVersion = vuln.resolved.version;
538
+ }
539
+ if (!vuln.isDirect && vuln.path.length > 0 && !directDepInfo) {
540
+ const directDep = vuln.path[0];
541
+ directDepInfo = {
542
+ name: directDep.name,
543
+ version: directDep.version,
544
+ scope: vuln.scope || 'dependencies'
545
+ };
546
+ }
547
+ if (!this.isUpgradeableWithinDelta(vuln.resolved.version, vuln.vulnerability)) {
548
+ continue;
549
+ }
550
+ const fixVersion = this.getUpgradeVersion(vuln.vulnerability);
551
+ if (fixVersion) {
552
+ const fixMajor = (_g = semver.parse(fixVersion)) === null || _g === void 0 ? void 0 : _g.major;
553
+ const shouldConsiderFix = hasMultipleMajorVersions
554
+ ? fixMajor === originalMajor
555
+ : true;
556
+ if (shouldConsiderFix) {
557
+ if (!highestFixVersion || semver.gt(fixVersion, highestFixVersion)) {
558
+ highestFixVersion = fixVersion;
559
+ }
505
560
  }
506
561
  }
562
+ cves.push(vuln.vulnerability.cve);
563
+ cveSummaries.set(vuln.vulnerability.cve, vuln.vulnerability.summary);
564
+ if (vuln.isDirect) {
565
+ isTransitive = false;
566
+ scope = vuln.scope;
567
+ }
507
568
  }
508
- cves.push(vuln.vulnerability.cve);
509
- if (vuln.isDirect) {
510
- isTransitive = false;
511
- scope = vuln.scope;
569
+ if (highestFixVersion && cves.length > 0 && originalVersion) {
570
+ const safeVersion = this.findHighestSafeVersion(packageName, originalVersion, highestFixVersion, db);
571
+ const fix = {
572
+ packageName,
573
+ newVersion: safeVersion || highestFixVersion,
574
+ isTransitive,
575
+ cves,
576
+ cveSummaries,
577
+ scope,
578
+ originalMajorVersion: originalMajor,
579
+ directDepInfo: isTransitive && directDepInfo ? {
580
+ name: directDepInfo.name,
581
+ version: directDepInfo.version,
582
+ scope: directDepInfo.scope
583
+ } : undefined
584
+ };
585
+ fixes.push(fix);
512
586
  }
513
587
  }
514
- if (highestFixVersion && cves.length > 0 && originalVersion) {
515
- const safeVersion = this.findHighestSafeVersion(packageName, originalVersion, highestFixVersion, db);
516
- fixes.push({
517
- packageName,
518
- newVersion: safeVersion || highestFixVersion,
519
- isTransitive,
520
- cves,
521
- scope,
522
- originalMajorVersion: originalMajor
523
- });
588
+ return fixes;
589
+ });
590
+ }
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;
524
607
  }
525
- }
526
- return fixes;
608
+ catch (_a) {
609
+ return undefined;
610
+ }
611
+ });
612
+ }
613
+ tryDirectUpgradesForTransitives(fixes, updateInfo, db) {
614
+ return __awaiter(this, void 0, void 0, function* () {
615
+ const result = [];
616
+ for (const fix of fixes) {
617
+ if (!fix.directDepInfo) {
618
+ result.push(fix);
619
+ continue;
620
+ }
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,
629
+ directDepVersion: directUpgradeVersion,
630
+ directDepScope: fix.directDepInfo.scope
631
+ } }));
632
+ }
633
+ else {
634
+ result.push(fix);
635
+ }
636
+ }
637
+ return result;
638
+ });
527
639
  }
528
640
  scanner(acc) {
529
641
  return __awaiter(this, void 0, void 0, function* () {
@@ -597,7 +709,12 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
597
709
  let fixes = [];
598
710
  if (vulnerabilities.length > 0) {
599
711
  acc.vulnerableByProject.set(doc.sourcePath, vulnerabilities);
600
- fixes = recipe.computeFixes(vulnerabilities, acc.db);
712
+ const originalPackageJson = storedContent || (yield rewrite_1.TreePrinters.print(doc));
713
+ fixes = yield recipe.computeFixes(vulnerabilities, acc.db, {
714
+ packageManager: pm,
715
+ originalPackageJson,
716
+ configFiles: Object.keys(configFiles).length > 0 ? configFiles : undefined
717
+ });
601
718
  }
602
719
  if (recipe.fixDeclaredVersions) {
603
720
  const preventiveFixes = recipe.findPreventiveFixes(marker, scopesToCheck, acc.db);
@@ -894,6 +1011,37 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
894
1011
  }
895
1012
  runPackageManagerInstall(acc, updateInfo, fixes) {
896
1013
  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);
1016
+ if (this.shouldVerifyTransitiveFixes() && transitiveFixes.length > 0) {
1017
+ const phase1PackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, directFixes, updateInfo.packageManager);
1018
+ const phase1Result = yield (0, javascript_1.runInstallInTempDir)(updateInfo.packageManager, phase1PackageJson, {
1019
+ configFiles: updateInfo.configFiles
1020
+ });
1021
+ if (!phase1Result.success || !phase1Result.lockFileContent) {
1022
+ const modifiedPackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, fixes, updateInfo.packageManager);
1023
+ const result = yield (0, javascript_1.runInstallInTempDir)(updateInfo.packageManager, modifiedPackageJson, {
1024
+ configFiles: updateInfo.configFiles
1025
+ });
1026
+ (0, javascript_1.storeInstallResult)(result, acc, updateInfo, modifiedPackageJson);
1027
+ return;
1028
+ }
1029
+ let remainingTransitiveFixes = this.filterRemainingTransitiveFixes(transitiveFixes, phase1Result.lockFileContent, updateInfo.packageManager, acc.db);
1030
+ if (remainingTransitiveFixes.length === 0) {
1031
+ (0, javascript_1.storeInstallResult)(phase1Result, acc, updateInfo, phase1PackageJson);
1032
+ return;
1033
+ }
1034
+ if (this.transitiveFixStrategy === 'prefer-direct-upgrade') {
1035
+ remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, updateInfo, acc.db);
1036
+ }
1037
+ const finalFixes = [...directFixes, ...remainingTransitiveFixes];
1038
+ const finalPackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, finalFixes, updateInfo.packageManager);
1039
+ const finalResult = yield (0, javascript_1.runInstallInTempDir)(updateInfo.packageManager, finalPackageJson, {
1040
+ configFiles: updateInfo.configFiles
1041
+ });
1042
+ (0, javascript_1.storeInstallResult)(finalResult, acc, updateInfo, finalPackageJson);
1043
+ return;
1044
+ }
897
1045
  const modifiedPackageJson = this.createModifiedPackageJson(updateInfo.originalPackageJson, fixes, updateInfo.packageManager);
898
1046
  const result = yield (0, javascript_1.runInstallInTempDir)(updateInfo.packageManager, modifiedPackageJson, {
899
1047
  configFiles: updateInfo.configFiles
@@ -905,23 +1053,24 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
905
1053
  return __awaiter(this, void 0, void 0, function* () {
906
1054
  const memberPaths = acc.workspaceRoots.get(rootPath) || [];
907
1055
  const pm = rootUpdateInfo.packageManager;
1056
+ const allDirectFixes = [];
908
1057
  const allTransitiveFixes = [];
909
1058
  const rootFixes = acc.fixesByProject.get(rootPath) || [];
910
- const rootDirectFixes = rootFixes.filter(f => !f.isTransitive);
911
- const rootTransitiveFixes = rootFixes.filter(f => f.isTransitive);
1059
+ const rootDirectFixes = rootFixes.filter(f => !f.isTransitive || f.fixViaDirectUpgrade);
1060
+ const rootTransitiveFixes = rootFixes.filter(f => f.isTransitive && !f.fixViaDirectUpgrade);
1061
+ allDirectFixes.push(...rootDirectFixes);
912
1062
  allTransitiveFixes.push(...rootTransitiveFixes);
913
1063
  const memberDirectFixes = new Map();
914
1064
  for (const memberPath of memberPaths) {
915
1065
  const memberFixes = acc.fixesByProject.get(memberPath) || [];
916
- const directFixes = memberFixes.filter(f => !f.isTransitive);
917
- const transitiveFixes = memberFixes.filter(f => f.isTransitive);
1066
+ const directFixes = memberFixes.filter(f => !f.isTransitive || f.fixViaDirectUpgrade);
1067
+ const transitiveFixes = memberFixes.filter(f => f.isTransitive && !f.fixViaDirectUpgrade);
918
1068
  if (directFixes.length > 0) {
919
1069
  memberDirectFixes.set(memberPath, directFixes);
1070
+ allDirectFixes.push(...directFixes);
920
1071
  }
921
1072
  allTransitiveFixes.push(...transitiveFixes);
922
1073
  }
923
- const rootOriginalContent = acc.allPackageJsonContents.get(rootPath) || rootUpdateInfo.originalPackageJson;
924
- const modifiedRootPackageJson = this.createModifiedPackageJson(rootOriginalContent, [...rootDirectFixes, ...allTransitiveFixes], pm);
925
1074
  const workspacePackages = {};
926
1075
  for (const memberPath of memberPaths) {
927
1076
  const originalContent = acc.allPackageJsonContents.get(memberPath);
@@ -939,6 +1088,39 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
939
1088
  }
940
1089
  workspacePackages[memberPath] = modifiedContent;
941
1090
  }
1091
+ const rootOriginalContent = acc.allPackageJsonContents.get(rootPath) || rootUpdateInfo.originalPackageJson;
1092
+ if (this.shouldVerifyTransitiveFixes() && allTransitiveFixes.length > 0) {
1093
+ const phase1RootPackageJson = this.createModifiedPackageJson(rootOriginalContent, rootDirectFixes, pm);
1094
+ const phase1Result = yield (0, javascript_1.runWorkspaceInstallInTempDir)(pm, phase1RootPackageJson, {
1095
+ configFiles: rootUpdateInfo.configFiles,
1096
+ workspacePackages
1097
+ });
1098
+ if (!phase1Result.success || !phase1Result.lockFileContent) {
1099
+ const modifiedRootPackageJson = this.createModifiedPackageJson(rootOriginalContent, [...rootDirectFixes, ...allTransitiveFixes], pm);
1100
+ const result = yield (0, javascript_1.runWorkspaceInstallInTempDir)(pm, modifiedRootPackageJson, {
1101
+ configFiles: rootUpdateInfo.configFiles,
1102
+ workspacePackages
1103
+ });
1104
+ (0, javascript_1.storeInstallResult)(result, acc, rootUpdateInfo, modifiedRootPackageJson);
1105
+ return;
1106
+ }
1107
+ let remainingTransitiveFixes = this.filterRemainingTransitiveFixes(allTransitiveFixes, phase1Result.lockFileContent, pm, acc.db);
1108
+ if (remainingTransitiveFixes.length === 0) {
1109
+ (0, javascript_1.storeInstallResult)(phase1Result, acc, rootUpdateInfo, phase1RootPackageJson);
1110
+ return;
1111
+ }
1112
+ if (this.transitiveFixStrategy === 'prefer-direct-upgrade') {
1113
+ remainingTransitiveFixes = yield this.tryDirectUpgradesForTransitives(remainingTransitiveFixes, rootUpdateInfo, acc.db);
1114
+ }
1115
+ const finalRootPackageJson = this.createModifiedPackageJson(rootOriginalContent, [...rootDirectFixes, ...remainingTransitiveFixes], pm);
1116
+ const finalResult = yield (0, javascript_1.runWorkspaceInstallInTempDir)(pm, finalRootPackageJson, {
1117
+ configFiles: rootUpdateInfo.configFiles,
1118
+ workspacePackages
1119
+ });
1120
+ (0, javascript_1.storeInstallResult)(finalResult, acc, rootUpdateInfo, finalRootPackageJson);
1121
+ return;
1122
+ }
1123
+ const modifiedRootPackageJson = this.createModifiedPackageJson(rootOriginalContent, [...rootDirectFixes, ...allTransitiveFixes], pm);
942
1124
  const result = yield (0, javascript_1.runWorkspaceInstallInTempDir)(pm, modifiedRootPackageJson, {
943
1125
  configFiles: rootUpdateInfo.configFiles,
944
1126
  workspacePackages
@@ -959,7 +1141,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
959
1141
  return JSON.stringify(packageJson, null, 2);
960
1142
  }
961
1143
  createModifiedPackageJson(originalContent, fixes, packageManager) {
962
- var _a, _b, _c;
1144
+ var _a;
963
1145
  let packageJson = JSON.parse(originalContent);
964
1146
  const fixesByPackage = new Map();
965
1147
  for (const fix of fixes) {
@@ -967,6 +1149,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
967
1149
  existing.push(fix);
968
1150
  fixesByPackage.set(fix.packageName, existing);
969
1151
  }
1152
+ const overrideComments = new Map();
970
1153
  for (const fix of fixes) {
971
1154
  if (!fix.isTransitive && fix.scope) {
972
1155
  if (packageJson[fix.scope] && packageJson[fix.scope][fix.packageName]) {
@@ -975,6 +1158,14 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
975
1158
  }
976
1159
  }
977
1160
  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;
1167
+ }
1168
+ }
978
1169
  const directDepScope = findDirectDependencyScope(packageJson, fix.packageName);
979
1170
  if (directDepScope) {
980
1171
  const directVersion = packageJson[directDepScope][fix.packageName];
@@ -985,6 +1176,7 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
985
1176
  if (!isYarn) {
986
1177
  const versionSpecificKey = `${fix.packageName}@^${fix.originalMajorVersion}`;
987
1178
  packageJson = (0, javascript_1.applyOverrideToPackageJson)(packageJson, packageManager, versionSpecificKey, fix.newVersion);
1179
+ overrideComments.set(versionSpecificKey, this.generateOverrideComment(fix));
988
1180
  }
989
1181
  else {
990
1182
  }
@@ -1007,26 +1199,63 @@ class DependencyVulnerabilityCheck extends rewrite_1.ScanningRecipe {
1007
1199
  if (useVersionSpecificOverride) {
1008
1200
  const versionSpecificKey = `${fix.packageName}@^${fix.originalMajorVersion}`;
1009
1201
  packageJson = (0, javascript_1.applyOverrideToPackageJson)(packageJson, packageManager, versionSpecificKey, fix.newVersion);
1202
+ overrideComments.set(versionSpecificKey, this.generateOverrideComment(fix));
1010
1203
  }
1011
1204
  else {
1012
1205
  packageJson = (0, javascript_1.applyOverrideToPackageJson)(packageJson, packageManager, fix.packageName, fix.newVersion);
1013
- }
1014
- if (packageManager === "Pnpm") {
1015
- if (!packageJson.devDependencies) {
1016
- packageJson.devDependencies = {};
1017
- }
1018
- if (!((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a[fix.packageName]) &&
1019
- !packageJson.devDependencies[fix.packageName] &&
1020
- !((_b = packageJson.peerDependencies) === null || _b === void 0 ? void 0 : _b[fix.packageName]) &&
1021
- !((_c = packageJson.optionalDependencies) === null || _c === void 0 ? void 0 : _c[fix.packageName])) {
1022
- const prefix = this.getVersionPrefixForDelta();
1023
- packageJson.devDependencies[fix.packageName] = prefix + fix.newVersion;
1024
- }
1206
+ overrideComments.set(fix.packageName, this.generateOverrideComment(fix));
1025
1207
  }
1026
1208
  }
1027
1209
  }
1210
+ if (this.addOverrideComments && overrideComments.size > 0) {
1211
+ packageJson = this.addOverrideCommentsToPackageJson(packageJson, packageManager, overrideComments);
1212
+ }
1028
1213
  return JSON.stringify(packageJson, null, 2);
1029
1214
  }
1215
+ generateOverrideComment(fix) {
1216
+ if (!fix.cveSummaries || fix.cveSummaries.size === 0) {
1217
+ return fix.cves.join(', ');
1218
+ }
1219
+ const parts = [];
1220
+ for (const cve of fix.cves) {
1221
+ const summary = fix.cveSummaries.get(cve);
1222
+ if (summary) {
1223
+ const truncatedSummary = summary.length > 80
1224
+ ? summary.substring(0, 77) + '...'
1225
+ : summary;
1226
+ parts.push(`${cve}: ${truncatedSummary}`);
1227
+ }
1228
+ else {
1229
+ parts.push(cve);
1230
+ }
1231
+ }
1232
+ return parts.join('; ');
1233
+ }
1234
+ addOverrideCommentsToPackageJson(packageJson, packageManager, comments) {
1235
+ let commentFieldName;
1236
+ switch (packageManager) {
1237
+ case "Npm":
1238
+ case "Bun":
1239
+ commentFieldName = '//overrides';
1240
+ break;
1241
+ case "Pnpm":
1242
+ commentFieldName = '//pnpm.overrides';
1243
+ break;
1244
+ case "YarnClassic":
1245
+ case "YarnBerry":
1246
+ commentFieldName = '//resolutions';
1247
+ break;
1248
+ default:
1249
+ return packageJson;
1250
+ }
1251
+ const existingComments = packageJson[commentFieldName] || {};
1252
+ const mergedComments = Object.assign({}, existingComments);
1253
+ for (const [key, comment] of comments) {
1254
+ mergedComments[key] = comment;
1255
+ }
1256
+ packageJson[commentFieldName] = mergedComments;
1257
+ return packageJson;
1258
+ }
1030
1259
  }
1031
1260
  exports.DependencyVulnerabilityCheck = DependencyVulnerabilityCheck;
1032
1261
  __decorate([
@@ -1041,14 +1270,17 @@ __decorate([
1041
1270
  ], DependencyVulnerabilityCheck.prototype, "scope", void 0);
1042
1271
  __decorate([
1043
1272
  (0, rewrite_1.Option)({
1044
- displayName: "Override transitives",
1045
- description: "When enabled, transitive dependencies with vulnerabilities will have their versions overridden " +
1046
- "using npm overrides, yarn resolutions, or pnpm overrides. " +
1047
- "By default only direct dependencies have their version numbers upgraded.",
1273
+ displayName: "Transitive fix strategy",
1274
+ 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'.",
1048
1279
  required: false,
1049
- example: "true"
1280
+ example: "override",
1281
+ valid: ["report", "override", "prefer-direct-upgrade"]
1050
1282
  })
1051
- ], DependencyVulnerabilityCheck.prototype, "overrideTransitive", void 0);
1283
+ ], DependencyVulnerabilityCheck.prototype, "transitiveFixStrategy", void 0);
1052
1284
  __decorate([
1053
1285
  (0, rewrite_1.Option)({
1054
1286
  displayName: "Maximum upgrade delta",
@@ -1096,6 +1328,16 @@ __decorate([
1096
1328
  example: "true"
1097
1329
  })
1098
1330
  ], DependencyVulnerabilityCheck.prototype, "fixDeclaredVersions", void 0);
1331
+ __decorate([
1332
+ (0, rewrite_1.Option)({
1333
+ displayName: "Add override comments",
1334
+ description: "When enabled, adds a comment field (e.g., '//overrides') alongside overrides to document which CVEs " +
1335
+ "each override is fixing. This helps with auditing and knowing when overrides can be removed. " +
1336
+ "Default is true.",
1337
+ required: false,
1338
+ example: "true"
1339
+ })
1340
+ ], DependencyVulnerabilityCheck.prototype, "addOverrideComments", void 0);
1099
1341
  function extractVersionPrefix(versionString) {
1100
1342
  const match = versionString.match(/^([~^]|>=?|<=?|=)?(.*)$/);
1101
1343
  if (match) {