@code-pushup/js-packages-plugin 0.27.1 → 0.29.0-alpha

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.
@@ -0,0 +1,10 @@
1
+ # Contributing to js-packages
2
+
3
+ ## Adding new package managers
4
+
5
+ In order to add a support for a new package manager, one needs to do the following.
6
+
7
+ 1. Expand `packageManagerSchema` in `config.ts`.
8
+ 2. Expand `<command>Args` in `runner/<command>/constants.ts` with a set of arguments to be run for a given package manager command.
9
+ 3. Create a custom type in `runner/<command>/types.ts` with relevant properties based on expected command JSON output.
10
+ 4. Create a function in `runner/<command>/unify-type.ts` that will transform JSON output into a normalized type `OutdatedResult` or `AuditResult` and add it to `normalized<command>Mapper` in `runner/<command>/constants.ts`.
package/README.md CHANGED
@@ -10,9 +10,14 @@ This plugin checks for known vulnerabilities and outdated dependencies.
10
10
  It supports the following package managers:
11
11
 
12
12
  - [NPM](https://docs.npmjs.com/)
13
- - [Yarn v1](https://classic.yarnpkg.com/docs/) & [Yarn v2+](https://yarnpkg.com/getting-started)
13
+ - [Yarn v1](https://classic.yarnpkg.com/docs/)
14
+ - [Yarn v2+](https://yarnpkg.com/getting-started)
15
+ - In order to check outdated dependencies for Yarn v2+, you need to install [`yarn-plugin-outdated`](https://github.com/mskelton/yarn-plugin-outdated).
14
16
  - [PNPM](https://pnpm.io/pnpm-cli)
15
17
 
18
+ > ![NOTE]
19
+ > As of now, Yarn v2 does not support security audit of optional dependencies. Only production and dev dependencies audits will be included in the report.
20
+
16
21
  ## Getting started
17
22
 
18
23
  1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file.
@@ -97,7 +102,7 @@ The plugin accepts the following parameters:
97
102
 
98
103
  ### Audits and group
99
104
 
100
- This plugin provides a group per check for a convenient declaration in your config.
105
+ This plugin provides a group per check for a convenient declaration in your config. Each group contains audits for all supported groups of dependencies (`prod`, `dev` and `optional`).
101
106
 
102
107
  ```ts
103
108
  // ...
package/bin.js CHANGED
@@ -657,6 +657,20 @@ import chalk2 from "chalk";
657
657
  import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
658
658
  import { join } from "node:path";
659
659
 
660
+ // packages/utils/src/lib/formatting.ts
661
+ function pluralize(text, amount) {
662
+ if (amount != null && Math.abs(amount) === 1) {
663
+ return text;
664
+ }
665
+ if (text.endsWith("y")) {
666
+ return `${text.slice(0, -1)}ies`;
667
+ }
668
+ if (text.endsWith("s")) {
669
+ return `${text}es`;
670
+ }
671
+ return `${text}s`;
672
+ }
673
+
660
674
  // packages/utils/src/lib/guards.ts
661
675
  function isPromiseFulfilledResult(result) {
662
676
  return result.status === "fulfilled";
@@ -723,7 +737,7 @@ function pluginWorkDir(slug) {
723
737
 
724
738
  // packages/utils/src/lib/reports/utils.ts
725
739
  function calcDuration(start, stop) {
726
- return Math.floor((stop ?? performance.now()) - start);
740
+ return Math.round((stop ?? performance.now()) - start);
727
741
  }
728
742
 
729
743
  // packages/utils/src/lib/execute-process.ts
@@ -739,7 +753,7 @@ var ProcessError = class extends Error {
739
753
  }
740
754
  };
741
755
  function executeProcess(cfg) {
742
- const { observer, cwd, command, args, alwaysResolve = false } = cfg;
756
+ const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
743
757
  const { onStdout, onError, onComplete } = observer ?? {};
744
758
  const date = (/* @__PURE__ */ new Date()).toISOString();
745
759
  const start = performance.now();
@@ -759,7 +773,7 @@ function executeProcess(cfg) {
759
773
  });
760
774
  process2.on("close", (code) => {
761
775
  const timings = { date, duration: calcDuration(start) };
762
- if (code === 0 || alwaysResolve) {
776
+ if (code === 0 || ignoreExitCode) {
763
777
  onComplete?.();
764
778
  resolve({ code, stdout, stderr, ...timings });
765
779
  } else {
@@ -775,9 +789,25 @@ function executeProcess(cfg) {
775
789
  import { simpleGit } from "simple-git";
776
790
 
777
791
  // packages/utils/src/lib/transform.ts
792
+ import { platform } from "node:os";
778
793
  function objectToEntries(obj) {
779
794
  return Object.entries(obj);
780
795
  }
796
+ function objectFromEntries(entries) {
797
+ return Object.fromEntries(entries);
798
+ }
799
+ function toUnixNewlines(text) {
800
+ return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
801
+ }
802
+ function fromJsonLines(jsonLines) {
803
+ const unifiedNewLines = toUnixNewlines(jsonLines).trim();
804
+ return JSON.parse(`[${unifiedNewLines.split("\n").join(",")}]`);
805
+ }
806
+ function apostrophize(text, upperCase) {
807
+ const lastCharMatch = text.match(/(\w)\W*$/);
808
+ const lastChar = lastCharMatch?.[1] ?? "";
809
+ return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
810
+ }
781
811
 
782
812
  // packages/utils/src/lib/progress.ts
783
813
  import chalk3 from "chalk";
@@ -797,6 +827,17 @@ var defaultAuditLevelMapping = {
797
827
  low: "warning",
798
828
  info: "info"
799
829
  };
830
+ var dependencyGroupToLong = {
831
+ prod: "dependencies",
832
+ dev: "devDependencies",
833
+ optional: "optionalDependencies"
834
+ };
835
+ var pkgManagerCommands = {
836
+ npm: "npm",
837
+ "yarn-classic": "yarn",
838
+ "yarn-modern": "yarn",
839
+ pnpm: "pnpm"
840
+ };
800
841
 
801
842
  // packages/plugin-js-packages/src/lib/config.ts
802
843
  var dependencyGroups = ["prod", "dev", "optional"];
@@ -834,6 +875,173 @@ var jsPackagesPluginConfigSchema = z15.object({
834
875
  }).default(defaultAuditLevelMapping).transform(fillAuditLevelMapping)
835
876
  });
836
877
 
878
+ // packages/plugin-js-packages/src/lib/runner/utils.ts
879
+ function filterAuditResult(result, key, referenceResult) {
880
+ if (result.vulnerabilities.length === 0) {
881
+ return result;
882
+ }
883
+ const uniqueResult = result.vulnerabilities.reduce(
884
+ (acc, ref) => {
885
+ const matchReference = referenceResult ?? acc;
886
+ const isMatch = matchReference.vulnerabilities.map((vulnerability) => vulnerability[key]).includes(ref[key]);
887
+ if (isMatch) {
888
+ return {
889
+ vulnerabilities: acc.vulnerabilities,
890
+ summary: {
891
+ ...acc.summary,
892
+ [ref.severity]: acc.summary[ref.severity] - 1,
893
+ total: acc.summary.total - 1
894
+ }
895
+ };
896
+ }
897
+ return {
898
+ vulnerabilities: [...acc.vulnerabilities, ref],
899
+ summary: acc.summary
900
+ };
901
+ },
902
+ { vulnerabilities: [], summary: result.summary }
903
+ );
904
+ return {
905
+ vulnerabilities: uniqueResult.vulnerabilities,
906
+ summary: uniqueResult.summary
907
+ };
908
+ }
909
+
910
+ // packages/plugin-js-packages/src/lib/runner/audit/unify-type.ts
911
+ function npmToAuditResult(output) {
912
+ const npmAudit = JSON.parse(output);
913
+ const vulnerabilities = objectToEntries(npmAudit.vulnerabilities).map(
914
+ ([name, detail]) => {
915
+ const advisory = npmToAdvisory(name, npmAudit.vulnerabilities);
916
+ return {
917
+ name: name.toString(),
918
+ severity: detail.severity,
919
+ versionRange: detail.range,
920
+ directDependency: detail.isDirect ? true : detail.effects[0] ?? "",
921
+ fixInformation: npmToFixInformation(detail.fixAvailable),
922
+ ...advisory != null && {
923
+ title: advisory.title,
924
+ url: advisory.url
925
+ }
926
+ };
927
+ }
928
+ );
929
+ return {
930
+ vulnerabilities,
931
+ summary: npmAudit.metadata.vulnerabilities
932
+ };
933
+ }
934
+ function npmToFixInformation(fixAvailable) {
935
+ if (typeof fixAvailable === "boolean") {
936
+ return fixAvailable ? "Fix is available." : "";
937
+ }
938
+ return `Fix available: Update \`${fixAvailable.name}\` to version **${fixAvailable.version}**${fixAvailable.isSemVerMajor ? " (breaking change)." : "."}`;
939
+ }
940
+ function npmToAdvisory(name, vulnerabilities, prevNodes = /* @__PURE__ */ new Set()) {
941
+ const advisory = vulnerabilities[name]?.via;
942
+ if (Array.isArray(advisory) && advisory.length > 0 && typeof advisory[0] === "object") {
943
+ return { title: advisory[0].title, url: advisory[0].url };
944
+ }
945
+ if (Array.isArray(advisory) && advisory.length > 0 && advisory.every((value) => typeof value === "string")) {
946
+ let advisoryInfo = null;
947
+ let newReferences = [];
948
+ let advisoryInfoFound = false;
949
+ for (const via of advisory) {
950
+ if (!prevNodes.has(via)) {
951
+ newReferences.push(via);
952
+ }
953
+ }
954
+ while (newReferences.length > 0 && !advisoryInfoFound) {
955
+ const ref = newReferences.pop();
956
+ prevNodes.add(ref);
957
+ const result = npmToAdvisory(ref, vulnerabilities, prevNodes);
958
+ if (result != null) {
959
+ advisoryInfo = { title: result.title, url: result.url };
960
+ advisoryInfoFound = true;
961
+ }
962
+ }
963
+ return advisoryInfo;
964
+ }
965
+ return null;
966
+ }
967
+ function yarnv1ToAuditResult(output) {
968
+ const yarnv1Result = fromJsonLines(output);
969
+ const [yarnv1Advisory, yarnv1Summary] = validateYarnv1Result(yarnv1Result);
970
+ const vulnerabilities = yarnv1Advisory.map(
971
+ ({ data: { resolution, advisory } }) => {
972
+ const { id, path } = resolution;
973
+ const directDependency = path.slice(0, path.indexOf(">"));
974
+ const {
975
+ module_name: name,
976
+ title,
977
+ url,
978
+ severity,
979
+ vulnerable_versions: versionRange,
980
+ recommendation: fixInformation
981
+ } = advisory;
982
+ return {
983
+ name,
984
+ title,
985
+ id,
986
+ url,
987
+ severity,
988
+ versionRange,
989
+ directDependency: name === directDependency ? true : directDependency,
990
+ fixInformation
991
+ };
992
+ }
993
+ );
994
+ const summary = {
995
+ ...yarnv1Summary.data.vulnerabilities,
996
+ total: Object.values(yarnv1Summary.data.vulnerabilities).reduce(
997
+ (acc, amount) => acc + amount,
998
+ 0
999
+ )
1000
+ };
1001
+ return filterAuditResult({ vulnerabilities, summary }, "id");
1002
+ }
1003
+ function validateYarnv1Result(result) {
1004
+ const summary = result.at(-1);
1005
+ if (summary?.type !== "auditSummary") {
1006
+ throw new Error("Invalid Yarn v1 audit result - no summary found.");
1007
+ }
1008
+ const vulnerabilities = result.filter(
1009
+ (item) => item.type === "auditAdvisory"
1010
+ );
1011
+ return [vulnerabilities, summary];
1012
+ }
1013
+ function yarnv2ToAuditResult(output) {
1014
+ const yarnv2Audit = JSON.parse(output);
1015
+ const vulnerabilities = Object.values(yarnv2Audit.advisories).map(
1016
+ ({
1017
+ module_name: name,
1018
+ severity,
1019
+ title,
1020
+ url,
1021
+ vulnerable_versions: versionRange,
1022
+ recommendation: fixInformation,
1023
+ findings
1024
+ }) => {
1025
+ const directDep = findings[0]?.paths[0];
1026
+ return {
1027
+ name,
1028
+ severity,
1029
+ title,
1030
+ url,
1031
+ versionRange,
1032
+ fixInformation,
1033
+ directDependency: directDep != null && directDep !== name ? directDep : true
1034
+ };
1035
+ }
1036
+ );
1037
+ const total = Object.values(yarnv2Audit.metadata.vulnerabilities).reduce(
1038
+ (acc, value) => acc + value,
1039
+ 0
1040
+ );
1041
+ const summary = { ...yarnv2Audit.metadata.vulnerabilities, total };
1042
+ return { vulnerabilities, summary };
1043
+ }
1044
+
837
1045
  // packages/plugin-js-packages/src/lib/runner/audit/constants.ts
838
1046
  var auditScoreModifiers = {
839
1047
  critical: 1,
@@ -842,20 +1050,47 @@ var auditScoreModifiers = {
842
1050
  low: 0.02,
843
1051
  info: 0.01
844
1052
  };
1053
+ var normalizeAuditMapper = {
1054
+ npm: npmToAuditResult,
1055
+ "yarn-classic": yarnv1ToAuditResult,
1056
+ "yarn-modern": yarnv2ToAuditResult,
1057
+ pnpm: () => {
1058
+ throw new Error("PNPM audit is not supported yet.");
1059
+ }
1060
+ };
1061
+ var npmDependencyOptions = {
1062
+ prod: ["--omit=dev", "--omit=optional"],
1063
+ dev: ["--include=dev", "--omit=optional"],
1064
+ optional: ["--include=optional", "--omit=dev"]
1065
+ };
1066
+ var yarnv2EnvironmentOptions = {
1067
+ prod: "production",
1068
+ dev: "development",
1069
+ optional: ""
1070
+ };
1071
+ var auditArgs = (groupDep) => ({
1072
+ npm: [...npmDependencyOptions[groupDep], "--json", "--audit-level=none"],
1073
+ "yarn-classic": ["--json", "--groups", dependencyGroupToLong[groupDep]],
1074
+ "yarn-modern": [
1075
+ "--json",
1076
+ "--environment",
1077
+ yarnv2EnvironmentOptions[groupDep]
1078
+ ],
1079
+ // TODO: Add once PNPM is supported.
1080
+ pnpm: []
1081
+ });
845
1082
 
846
1083
  // packages/plugin-js-packages/src/lib/runner/audit/transform.ts
847
- function auditResultToAuditOutput(result, dependenciesType, auditLevelMapping) {
1084
+ function auditResultToAuditOutput(result, packageManager, dependenciesType, auditLevelMapping) {
848
1085
  const issues = vulnerabilitiesToIssues(
849
1086
  result.vulnerabilities,
850
1087
  auditLevelMapping
851
1088
  );
852
1089
  return {
853
- slug: `npm-audit-${dependenciesType}`,
854
- score: calculateAuditScore(result.metadata.vulnerabilities),
855
- value: result.metadata.vulnerabilities.total,
856
- displayValue: vulnerabilitiesToDisplayValue(
857
- result.metadata.vulnerabilities
858
- ),
1090
+ slug: `${packageManager}-audit-${dependenciesType}`,
1091
+ score: calculateAuditScore(result.summary),
1092
+ value: result.summary.total,
1093
+ displayValue: summaryToDisplayValue(result.summary),
859
1094
  ...issues.length > 0 && { details: { issues } }
860
1095
  };
861
1096
  }
@@ -874,31 +1109,26 @@ function calculateAuditScore(stats) {
874
1109
  1
875
1110
  );
876
1111
  }
877
- function vulnerabilitiesToDisplayValue(vulnerabilities) {
878
- if (vulnerabilities.total === 0) {
1112
+ function summaryToDisplayValue(summary) {
1113
+ if (summary.total === 0) {
879
1114
  return "0 vulnerabilities";
880
1115
  }
881
- const vulnerabilityStats = packageAuditLevels.map(
882
- (level) => vulnerabilities[level] > 0 ? `${vulnerabilities[level]} ${level}` : ""
883
- ).filter((text) => text !== "").join(", ");
884
- return `${vulnerabilities.total} ${vulnerabilities.total === 1 ? "vulnerability" : "vulnerabilities"} (${vulnerabilityStats})`;
1116
+ const vulnerabilityStats = packageAuditLevels.map((level) => summary[level] > 0 ? `${summary[level]} ${level}` : "").filter((text) => text !== "").join(", ");
1117
+ return `${summary.total} ${summary.total === 1 ? "vulnerability" : "vulnerabilities"} (${vulnerabilityStats})`;
885
1118
  }
886
1119
  function vulnerabilitiesToIssues(vulnerabilities, auditLevelMapping) {
887
- if (Object.keys(vulnerabilities).length === 0) {
1120
+ if (vulnerabilities.length === 0) {
888
1121
  return [];
889
1122
  }
890
- return Object.values(vulnerabilities).map((detail) => {
891
- const versionRange = detail.range === "*" ? "**all** versions" : `versions **${detail.range}**`;
892
- const vulnerabilitySummary = `\`${detail.name}\` dependency has a **${detail.severity}** vulnerability in ${versionRange}.`;
893
- const fixInformation = typeof detail.fixAvailable === "boolean" ? `Fix is ${detail.fixAvailable ? "" : "not "}available.` : `Fix available: Update \`${detail.fixAvailable.name}\` to version **${detail.fixAvailable.version}**${detail.fixAvailable.isSemVerMajor ? " (breaking change)." : "."}`;
894
- if (Array.isArray(detail.via) && detail.via.length > 0 && typeof detail.via[0] === "object") {
895
- return {
896
- message: `${vulnerabilitySummary} ${fixInformation} More information: [${detail.via[0].title}](${detail.via[0].url})`,
897
- severity: auditLevelMapping[detail.severity]
898
- };
899
- }
1123
+ return vulnerabilities.map((detail) => {
1124
+ const versionRange = detail.versionRange === "*" ? "**all** versions" : `versions **${detail.versionRange}**`;
1125
+ const directDependency = typeof detail.directDependency === "string" ? `\`${detail.directDependency}\`` : "";
1126
+ const depHierarchy = directDependency === "" ? `\`${detail.name}\` dependency` : `${apostrophize(directDependency)} dependency \`${detail.name}\``;
1127
+ const vulnerabilitySummary = `has a **${detail.severity}** vulnerability in ${versionRange}.`;
1128
+ const fixInfo = detail.fixInformation ? ` ${detail.fixInformation}` : "";
1129
+ const additionalInfo = detail.title != null && detail.url != null ? ` More information: [${detail.title}](${detail.url})` : "";
900
1130
  return {
901
- message: `${vulnerabilitySummary} ${fixInformation}`,
1131
+ message: `${depHierarchy} ${vulnerabilitySummary}${fixInfo}${additionalInfo}`,
902
1132
  severity: auditLevelMapping[detail.severity]
903
1133
  };
904
1134
  });
@@ -914,67 +1144,129 @@ var PLUGIN_CONFIG_PATH = join2(
914
1144
  "plugin-config.json"
915
1145
  );
916
1146
 
1147
+ // packages/plugin-js-packages/src/lib/runner/outdated/unify-type.ts
1148
+ function npmToOutdatedResult(output) {
1149
+ const npmOutdated = JSON.parse(output);
1150
+ return objectToEntries(npmOutdated).filter(
1151
+ (entry) => entry[1].current != null
1152
+ ).map(([name, overview]) => ({
1153
+ name,
1154
+ current: overview.current,
1155
+ latest: overview.latest,
1156
+ type: overview.type,
1157
+ ...overview.homepage != null && { url: overview.homepage }
1158
+ }));
1159
+ }
1160
+ function yarnv1ToOutdatedResult(output) {
1161
+ const yarnv1Outdated = fromJsonLines(output);
1162
+ const dependencies = yarnv1Outdated[1].data.body;
1163
+ return dependencies.map(([name, current, _, latest, __, type, url]) => ({
1164
+ name,
1165
+ current,
1166
+ latest,
1167
+ type,
1168
+ url
1169
+ }));
1170
+ }
1171
+ function yarnv2ToOutdatedResult(output) {
1172
+ const npmOutdated = JSON.parse(output);
1173
+ return npmOutdated.map(({ name, current, latest, type }) => ({
1174
+ name,
1175
+ current,
1176
+ latest,
1177
+ type
1178
+ }));
1179
+ }
1180
+
917
1181
  // packages/plugin-js-packages/src/lib/runner/outdated/constants.ts
918
1182
  var outdatedSeverity = {
919
1183
  major: "error",
920
1184
  minor: "warning",
921
1185
  patch: "info"
922
1186
  };
1187
+ var outdatedArgs = {
1188
+ npm: ["--json", "--long"],
1189
+ "yarn-classic": ["--json"],
1190
+ "yarn-modern": ["--json"],
1191
+ pnpm: []
1192
+ };
1193
+ var normalizeOutdatedMapper = {
1194
+ npm: npmToOutdatedResult,
1195
+ "yarn-classic": yarnv1ToOutdatedResult,
1196
+ "yarn-modern": yarnv2ToOutdatedResult,
1197
+ pnpm: (_) => []
1198
+ };
1199
+
1200
+ // packages/plugin-js-packages/src/lib/runner/outdated/types.ts
1201
+ var versionType = ["major", "minor", "patch"];
923
1202
 
924
1203
  // packages/plugin-js-packages/src/lib/runner/outdated/transform.ts
925
- function outdatedResultToAuditOutput(result, dependenciesType) {
926
- const validDependencies = objectToEntries(result).filter(
927
- (entry) => entry[1].current != null
928
- ).filter(
929
- ([, detail]) => dependenciesType === "prod" ? detail.type === "dependencies" : detail.type === `${dependenciesType}Dependencies`
1204
+ function outdatedResultToAuditOutput(result, packageManager, dependencyGroup) {
1205
+ const relevantDependencies = result.filter(
1206
+ (dep) => dep.type === dependencyGroupToLong[dependencyGroup]
930
1207
  );
931
- const outdatedDependencies = validDependencies.filter(
932
- ([, versions]) => versions.current !== versions.wanted
1208
+ const outdatedDependencies = relevantDependencies.filter(
1209
+ (dep) => dep.current !== dep.latest
1210
+ );
1211
+ const outdatedStats = outdatedDependencies.reduce(
1212
+ (acc, dep) => {
1213
+ const outdatedLevel = getOutdatedLevel(dep.current, dep.latest);
1214
+ return { ...acc, [outdatedLevel]: acc[outdatedLevel] + 1 };
1215
+ },
1216
+ { major: 0, minor: 0, patch: 0 }
933
1217
  );
934
- const majorOutdatedAmount = outdatedDependencies.filter(
935
- ([, versions]) => getOutdatedLevel(versions.current, versions.wanted) === "major"
936
- ).length;
937
1218
  const issues = outdatedDependencies.length === 0 ? [] : outdatedToIssues(outdatedDependencies);
938
1219
  return {
939
- slug: `npm-outdated-${dependenciesType}`,
1220
+ slug: `${packageManager}-outdated-${dependencyGroup}`,
940
1221
  score: calculateOutdatedScore(
941
- majorOutdatedAmount,
942
- validDependencies.length
1222
+ outdatedStats.major,
1223
+ relevantDependencies.length
943
1224
  ),
944
1225
  value: outdatedDependencies.length,
945
- displayValue: outdatedToDisplayValue(
946
- majorOutdatedAmount,
947
- outdatedDependencies.length
948
- ),
1226
+ displayValue: outdatedToDisplayValue(outdatedStats),
949
1227
  ...issues.length > 0 && { details: { issues } }
950
1228
  };
951
1229
  }
952
1230
  function calculateOutdatedScore(majorOutdated, totalDeps) {
953
1231
  return totalDeps > 0 ? (totalDeps - majorOutdated) / totalDeps : 1;
954
1232
  }
955
- function outdatedToDisplayValue(majorOutdated, totalOutdated) {
956
- return totalOutdated === 0 ? "all dependencies are up to date" : majorOutdated > 0 ? `${majorOutdated} out of ${totalOutdated} outdated dependencies require major update` : `${totalOutdated} outdated ${totalOutdated === 1 ? "dependency" : "dependencies"}`;
1233
+ function outdatedToDisplayValue(stats) {
1234
+ const total = stats.major + stats.minor + stats.patch;
1235
+ const versionBreakdown = versionType.map((version) => stats[version] > 0 ? `${stats[version]} ${version}` : "").filter((text) => text !== "");
1236
+ if (versionBreakdown.length === 0) {
1237
+ return "all dependencies are up to date";
1238
+ }
1239
+ if (versionBreakdown.length > 1) {
1240
+ return `${total} outdated package versions (${versionBreakdown.join(
1241
+ ", "
1242
+ )})`;
1243
+ }
1244
+ return `${versionBreakdown[0]} outdated package ${pluralize(
1245
+ "version",
1246
+ total
1247
+ )}`;
957
1248
  }
958
1249
  function outdatedToIssues(dependencies) {
959
- return dependencies.map(([name, versions]) => {
960
- const outdatedLevel = getOutdatedLevel(versions.current, versions.wanted);
961
- const packageReference = versions.homepage == null ? `\`${name}\`` : `[\`${name}\`](${versions.homepage})`;
1250
+ return dependencies.map((dep) => {
1251
+ const { name, current, latest, url } = dep;
1252
+ const outdatedLevel = getOutdatedLevel(current, latest);
1253
+ const packageReference = url == null ? `\`${name}\`` : `[\`${name}\`](${url})`;
962
1254
  return {
963
- message: `Package ${packageReference} requires a **${outdatedLevel}** update from **${versions.current}** to **${versions.wanted}**.`,
1255
+ message: `Package ${packageReference} requires a **${outdatedLevel}** update from **${current}** to **${latest}**.`,
964
1256
  severity: outdatedSeverity[outdatedLevel]
965
1257
  };
966
1258
  });
967
1259
  }
968
- function getOutdatedLevel(currentFullVersion, wantedFullVersion) {
1260
+ function getOutdatedLevel(currentFullVersion, latestFullVersion) {
969
1261
  const current = splitPackageVersion(currentFullVersion);
970
- const wanted = splitPackageVersion(wantedFullVersion);
971
- if (current.major < wanted.major) {
1262
+ const latest = splitPackageVersion(latestFullVersion);
1263
+ if (current.major < latest.major) {
972
1264
  return "major";
973
1265
  }
974
- if (current.minor < wanted.minor) {
1266
+ if (current.minor < latest.minor) {
975
1267
  return "minor";
976
1268
  }
977
- if (current.patch < wanted.patch) {
1269
+ if (current.patch < latest.patch) {
978
1270
  return "patch";
979
1271
  }
980
1272
  throw new Error("Package is not outdated.");
@@ -998,42 +1290,68 @@ async function executeRunner() {
998
1290
  }
999
1291
  async function processOutdated(packageManager) {
1000
1292
  const { stdout } = await executeProcess({
1001
- command: packageManager,
1002
- args: ["outdated", "--json", "--long"],
1003
- alwaysResolve: true
1293
+ command: pkgManagerCommands[packageManager],
1294
+ args: ["outdated", ...outdatedArgs[packageManager]],
1295
+ cwd: process.cwd(),
1296
+ ignoreExitCode: true
1004
1297
  // npm outdated returns exit code 1 when outdated dependencies are found
1005
1298
  });
1006
- const outdatedResult = JSON.parse(stdout);
1299
+ const normalizedResult = normalizeOutdatedMapper[packageManager](stdout);
1007
1300
  return dependencyGroups.map(
1008
- (dep) => outdatedResultToAuditOutput(outdatedResult, dep)
1301
+ (dep) => outdatedResultToAuditOutput(normalizedResult, packageManager, dep)
1009
1302
  );
1010
1303
  }
1011
1304
  async function processAudit(packageManager, auditLevelMapping) {
1305
+ const supportedDepGroups = packageManager === "yarn-modern" ? dependencyGroups.filter((dep) => dep !== "optional") : dependencyGroups;
1012
1306
  const auditResults = await Promise.allSettled(
1013
- dependencyGroups.map(async (dep) => {
1014
- const { stdout } = await executeProcess({
1015
- command: packageManager,
1016
- args: ["audit", ...getNpmAuditOptions(dep)]
1017
- });
1018
- const auditResult = JSON.parse(stdout);
1019
- return auditResultToAuditOutput(auditResult, dep, auditLevelMapping);
1020
- })
1307
+ supportedDepGroups.map(
1308
+ async (dep) => {
1309
+ const { stdout } = await executeProcess({
1310
+ command: pkgManagerCommands[packageManager],
1311
+ args: getAuditCommandArgs(packageManager, dep),
1312
+ cwd: process.cwd(),
1313
+ ignoreExitCode: packageManager === "yarn-classic"
1314
+ // yarn v1 does not have exit code configuration
1315
+ });
1316
+ return [dep, normalizeAuditMapper[packageManager](stdout)];
1317
+ }
1318
+ )
1021
1319
  );
1022
1320
  const rejected = auditResults.filter(isPromiseRejectedResult);
1023
1321
  if (rejected.length > 0) {
1024
1322
  rejected.map((result) => {
1025
1323
  console.error(result.reason);
1026
1324
  });
1027
- throw new Error(`JS Packages plugin: Running npm audit failed.`);
1325
+ throw new Error(
1326
+ `JS Packages plugin: Running ${pkgManagerCommands[packageManager]} audit failed.`
1327
+ );
1028
1328
  }
1029
- return auditResults.filter(isPromiseFulfilledResult).map((x) => x.value);
1329
+ const fulfilled = objectFromEntries(
1330
+ auditResults.filter(isPromiseFulfilledResult).map((x) => x.value)
1331
+ );
1332
+ const uniqueResults = packageManager === "npm" ? filterNpmAuditResults(fulfilled) : fulfilled;
1333
+ return supportedDepGroups.map(
1334
+ (group) => auditResultToAuditOutput(
1335
+ uniqueResults[group],
1336
+ packageManager,
1337
+ group,
1338
+ auditLevelMapping
1339
+ )
1340
+ );
1030
1341
  }
1031
- function getNpmAuditOptions(currentDep) {
1032
- const flags = [
1033
- `--include=${currentDep}`,
1034
- ...dependencyGroups.filter((dep) => dep !== currentDep).map((dep) => `--omit=${dep}`)
1342
+ function getAuditCommandArgs(packageManager, group) {
1343
+ return [
1344
+ ...packageManager === "yarn-modern" ? ["npm"] : [],
1345
+ "audit",
1346
+ ...auditArgs(group)[packageManager]
1035
1347
  ];
1036
- return [...flags, "--json", "--audit-level=none"];
1348
+ }
1349
+ function filterNpmAuditResults(results) {
1350
+ return {
1351
+ prod: results.prod,
1352
+ dev: filterAuditResult(results.dev, "name", results.prod),
1353
+ optional: filterAuditResult(results.optional, "name", results.prod)
1354
+ };
1037
1355
  }
1038
1356
 
1039
1357
  // packages/plugin-js-packages/src/bin.ts
package/index.js CHANGED
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
 
5
5
  // packages/plugin-js-packages/package.json
6
6
  var name = "@code-pushup/js-packages-plugin";
7
- var version = "0.27.1";
7
+ var version = "0.29.0";
8
8
 
9
9
  // packages/plugin-js-packages/src/lib/config.ts
10
10
  import { z as z15 } from "zod";
@@ -834,9 +834,9 @@ async function jsPackagesPlugin(config) {
834
834
  );
835
835
  return {
836
836
  slug: "js-packages",
837
- title: "Plugin for JS packages",
837
+ title: "JS Packages",
838
838
  icon: pkgManagerIcons[pkgManager],
839
- description: "This plugin runs audit to uncover vulnerabilities and lists outdated dependencies. It supports npm, yarn classic and berry, pnpm package managers.",
839
+ description: "This plugin runs audit to uncover vulnerabilities and lists outdated dependencies. It supports npm, yarn classic, yarn modern, and pnpm package managers.",
840
840
  docsUrl: pkgManagerDocs[pkgManager],
841
841
  packageName: name,
842
842
  version,
@@ -854,9 +854,15 @@ function createGroups(pkgManager, checks) {
854
854
  docsUrl: auditDocs[pkgManager],
855
855
  refs: [
856
856
  // eslint-disable-next-line no-magic-numbers
857
- { slug: `${pkgManager}-audit-prod`, weight: 8 },
857
+ { slug: `${pkgManager}-audit-prod`, weight: 3 },
858
858
  { slug: `${pkgManager}-audit-dev`, weight: 1 },
859
- { slug: `${pkgManager}-audit-optional`, weight: 1 }
859
+ // Yarn v2 does not support audit for optional dependencies
860
+ ...pkgManager === "yarn-modern" ? [] : [
861
+ {
862
+ slug: `${pkgManager}-audit-optional`,
863
+ weight: 1
864
+ }
865
+ ]
860
866
  ]
861
867
  },
862
868
  outdated: {
@@ -866,7 +872,7 @@ function createGroups(pkgManager, checks) {
866
872
  docsUrl: outdatedDocs[pkgManager],
867
873
  refs: [
868
874
  // eslint-disable-next-line no-magic-numbers
869
- { slug: `${pkgManager}-outdated-prod`, weight: 8 },
875
+ { slug: `${pkgManager}-outdated-prod`, weight: 3 },
870
876
  { slug: `${pkgManager}-outdated-dev`, weight: 1 },
871
877
  { slug: `${pkgManager}-outdated-optional`, weight: 1 }
872
878
  ]
@@ -888,12 +894,15 @@ function createAudits(pkgManager, checks) {
888
894
  description: getAuditDescription(check, "dev"),
889
895
  docsUrl: dependencyDocs.dev
890
896
  },
891
- {
892
- slug: `${pkgManager}-${check}-optional`,
893
- title: getAuditTitle(pkgManager, check, "optional"),
894
- description: getAuditDescription(check, "optional"),
895
- docsUrl: dependencyDocs.optional
896
- }
897
+ // Yarn v2 does not support audit for optional dependencies
898
+ ...pkgManager === "yarn-modern" && check === "audit" ? [] : [
899
+ {
900
+ slug: `${pkgManager}-${check}-optional`,
901
+ title: getAuditTitle(pkgManager, check, "optional"),
902
+ description: getAuditDescription(check, "optional"),
903
+ docsUrl: dependencyDocs.optional
904
+ }
905
+ ]
897
906
  ]);
898
907
  }
899
908
  function getAuditTitle(pkgManager, check, dependencyType) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-pushup/js-packages-plugin",
3
- "version": "0.27.1",
3
+ "version": "0.29.0-alpha",
4
4
  "dependencies": {
5
5
  "@code-pushup/models": "*",
6
6
  "@code-pushup/utils": "*",
@@ -9,15 +9,16 @@ export type PackageManager = z.infer<typeof packageManagerSchema>;
9
9
  export declare const packageAuditLevels: readonly ["critical", "high", "moderate", "low", "info"];
10
10
  declare const packageAuditLevelSchema: z.ZodEnum<["critical", "high", "moderate", "low", "info"]>;
11
11
  export type PackageAuditLevel = z.infer<typeof packageAuditLevelSchema>;
12
- export declare function fillAuditLevelMapping(mapping: Partial<Record<PackageAuditLevel, IssueSeverity>>): Record<PackageAuditLevel, IssueSeverity>;
12
+ export type AuditSeverity = Record<PackageAuditLevel, IssueSeverity>;
13
+ export declare function fillAuditLevelMapping(mapping: Partial<AuditSeverity>): AuditSeverity;
13
14
  export declare const jsPackagesPluginConfigSchema: z.ZodObject<{
14
15
  checks: z.ZodDefault<z.ZodArray<z.ZodEnum<["audit", "outdated"]>, "many">>;
15
16
  packageManager: z.ZodEnum<["npm", "yarn-classic", "yarn-modern", "pnpm"]>;
16
- auditLevelMapping: z.ZodEffects<z.ZodDefault<z.ZodRecord<z.ZodEnum<["critical", "high", "moderate", "low", "info"]>, z.ZodEnum<["info", "warning", "error"]>>>, Record<"info" | "critical" | "high" | "moderate" | "low", "error" | "info" | "warning">, Partial<Record<"info" | "critical" | "high" | "moderate" | "low", "error" | "info" | "warning">> | undefined>;
17
+ auditLevelMapping: z.ZodEffects<z.ZodDefault<z.ZodRecord<z.ZodEnum<["critical", "high", "moderate", "low", "info"]>, z.ZodEnum<["info", "warning", "error"]>>>, AuditSeverity, Partial<Record<"info" | "critical" | "high" | "moderate" | "low", "error" | "info" | "warning">> | undefined>;
17
18
  }, "strip", z.ZodTypeAny, {
18
19
  checks: ("audit" | "outdated")[];
19
20
  packageManager: "npm" | "pnpm" | "yarn-classic" | "yarn-modern";
20
- auditLevelMapping: Record<"info" | "critical" | "high" | "moderate" | "low", "error" | "info" | "warning">;
21
+ auditLevelMapping: AuditSeverity;
21
22
  }, {
22
23
  packageManager: "npm" | "pnpm" | "yarn-classic" | "yarn-modern";
23
24
  checks?: ("audit" | "outdated")[] | undefined;
@@ -1,6 +1,9 @@
1
1
  import { IssueSeverity, MaterialIcon } from '@code-pushup/models';
2
2
  import type { DependencyGroup, PackageAuditLevel, PackageManager } from './config';
3
+ import { DependencyGroupLong } from './runner/outdated/types';
3
4
  export declare const defaultAuditLevelMapping: Record<PackageAuditLevel, IssueSeverity>;
5
+ export declare const dependencyGroupToLong: Record<DependencyGroup, DependencyGroupLong>;
6
+ export declare const pkgManagerCommands: Record<PackageManager, string>;
4
7
  export declare const pkgManagerNames: Record<PackageManager, string>;
5
8
  export declare const pkgManagerIcons: Record<PackageManager, MaterialIcon>;
6
9
  export declare const pkgManagerDocs: Record<PackageManager, string>;
@@ -1,2 +1,5 @@
1
- import { PackageAuditLevel } from '../../config';
1
+ import { DependencyGroup, PackageAuditLevel, PackageManager } from '../../config';
2
+ import { AuditResult } from './types';
2
3
  export declare const auditScoreModifiers: Record<PackageAuditLevel, number>;
4
+ export declare const normalizeAuditMapper: Record<PackageManager, (output: string) => AuditResult>;
5
+ export declare const auditArgs: (groupDep: DependencyGroup) => Record<PackageManager, string[]>;
@@ -1,7 +1,7 @@
1
- import type { AuditOutput, Issue, IssueSeverity } from '@code-pushup/models';
2
- import { DependencyGroup, PackageAuditLevel } from '../../config';
3
- import { NpmAuditResultJson, Vulnerabilities } from './types';
4
- export declare function auditResultToAuditOutput(result: NpmAuditResultJson, dependenciesType: DependencyGroup, auditLevelMapping: Record<PackageAuditLevel, IssueSeverity>): AuditOutput;
5
- export declare function calculateAuditScore(stats: Record<PackageAuditLevel | 'total', number>): number;
6
- export declare function vulnerabilitiesToDisplayValue(vulnerabilities: Record<PackageAuditLevel | 'total', number>): string;
7
- export declare function vulnerabilitiesToIssues(vulnerabilities: Vulnerabilities, auditLevelMapping: Record<PackageAuditLevel, IssueSeverity>): Issue[];
1
+ import type { AuditOutput, Issue } from '@code-pushup/models';
2
+ import { AuditSeverity, DependencyGroup, PackageManager } from '../../config';
3
+ import { AuditResult, AuditSummary, Vulnerability } from './types';
4
+ export declare function auditResultToAuditOutput(result: AuditResult, packageManager: PackageManager, dependenciesType: DependencyGroup, auditLevelMapping: AuditSeverity): AuditOutput;
5
+ export declare function calculateAuditScore(stats: AuditSummary): number;
6
+ export declare function summaryToDisplayValue(summary: AuditSummary): string;
7
+ export declare function vulnerabilitiesToIssues(vulnerabilities: Vulnerability[], auditLevelMapping: AuditSeverity): Issue[];
@@ -1,27 +1,85 @@
1
1
  import type { PackageAuditLevel } from '../../config';
2
- type Advisory = {
2
+ export type Vulnerability = {
3
+ name: string;
4
+ id?: number;
5
+ title?: string;
6
+ url?: string;
7
+ severity: PackageAuditLevel;
8
+ versionRange: string;
9
+ directDependency: string | true;
10
+ fixInformation: string | false;
11
+ };
12
+ export type AuditSummary = Record<PackageAuditLevel | 'total', number>;
13
+ export type AuditResult = {
14
+ vulnerabilities: Vulnerability[];
15
+ summary: AuditSummary;
16
+ };
17
+ export type NpmAdvisory = {
3
18
  title: string;
4
19
  url: string;
5
20
  };
6
- type FixInformation = {
21
+ export type NpmFixInformation = {
7
22
  name: string;
8
23
  version: string;
9
24
  isSemVerMajor: boolean;
10
25
  };
11
- export type Vulnerability = {
26
+ export type NpmVulnerability = {
12
27
  name: string;
13
28
  severity: PackageAuditLevel;
14
- via: Advisory[] | string[];
29
+ isDirect: boolean;
30
+ effects: string[];
31
+ via: NpmAdvisory[] | string[];
15
32
  range: string;
16
- fixAvailable: boolean | FixInformation;
17
- };
18
- export type Vulnerabilities = {
19
- [key: string]: Vulnerability;
33
+ fixAvailable: boolean | NpmFixInformation;
20
34
  };
35
+ export type NpmVulnerabilities = Record<string, NpmVulnerability>;
21
36
  export type NpmAuditResultJson = {
22
- vulnerabilities: Vulnerabilities;
37
+ vulnerabilities: NpmVulnerabilities;
38
+ metadata: {
39
+ vulnerabilities: AuditSummary;
40
+ };
41
+ };
42
+ export type Yarnv1AuditAdvisory = {
43
+ type: 'auditAdvisory';
44
+ data: {
45
+ resolution: {
46
+ id: number;
47
+ path: string;
48
+ };
49
+ advisory: {
50
+ module_name: string;
51
+ severity: PackageAuditLevel;
52
+ vulnerable_versions: string;
53
+ recommendation: string;
54
+ title: string;
55
+ url: string;
56
+ };
57
+ };
58
+ };
59
+ export type Yarnv1AuditSummary = {
60
+ type: 'auditSummary';
61
+ data: {
62
+ vulnerabilities: Record<PackageAuditLevel, number>;
63
+ };
64
+ };
65
+ export type Yarnv1AuditResultJson = [
66
+ ...Yarnv1AuditAdvisory[],
67
+ Yarnv1AuditSummary
68
+ ];
69
+ export type Yarnv2AuditAdvisory = {
70
+ module_name: string;
71
+ severity: PackageAuditLevel;
72
+ vulnerable_versions: string;
73
+ recommendation: string;
74
+ title: string;
75
+ url: string;
76
+ findings: {
77
+ paths: string[];
78
+ }[];
79
+ };
80
+ export type Yarnv2AuditResultJson = {
81
+ advisories: Record<string, Yarnv2AuditAdvisory>;
23
82
  metadata: {
24
- vulnerabilities: Record<PackageAuditLevel | 'total', number>;
83
+ vulnerabilities: Record<PackageAuditLevel, number>;
25
84
  };
26
85
  };
27
- export {};
@@ -0,0 +1,6 @@
1
+ import { AuditResult, NpmAdvisory, NpmFixInformation, NpmVulnerabilities } from './types';
2
+ export declare function npmToAuditResult(output: string): AuditResult;
3
+ export declare function npmToFixInformation(fixAvailable: boolean | NpmFixInformation): string;
4
+ export declare function npmToAdvisory(name: string, vulnerabilities: NpmVulnerabilities, prevNodes?: Set<string>): NpmAdvisory | null;
5
+ export declare function yarnv1ToAuditResult(output: string): AuditResult;
6
+ export declare function yarnv2ToAuditResult(output: string): AuditResult;
@@ -1,3 +1,6 @@
1
1
  import { IssueSeverity } from '@code-pushup/models';
2
- import { VersionType } from './types';
2
+ import { PackageManager } from '../../config';
3
+ import { OutdatedResult, VersionType } from './types';
3
4
  export declare const outdatedSeverity: Record<VersionType, IssueSeverity>;
5
+ export declare const outdatedArgs: Record<PackageManager, string[]>;
6
+ export declare const normalizeOutdatedMapper: Record<PackageManager, (output: string) => OutdatedResult>;
@@ -1,7 +1,7 @@
1
1
  import { Issue } from '@code-pushup/models';
2
- import { DependencyGroup } from '../../config';
3
- import { NormalizedOutdatedEntries, NpmOutdatedResultJson, PackageVersion, VersionType } from './types';
4
- export declare function outdatedResultToAuditOutput(result: NpmOutdatedResultJson, dependenciesType: DependencyGroup): {
2
+ import { DependencyGroup, PackageManager } from '../../config';
3
+ import { OutdatedResult, PackageVersion, VersionType } from './types';
4
+ export declare function outdatedResultToAuditOutput(result: OutdatedResult, packageManager: PackageManager, dependencyGroup: DependencyGroup): {
5
5
  details?: {
6
6
  issues: {
7
7
  message: string;
@@ -23,7 +23,7 @@ export declare function outdatedResultToAuditOutput(result: NpmOutdatedResultJso
23
23
  displayValue: string;
24
24
  };
25
25
  export declare function calculateOutdatedScore(majorOutdated: number, totalDeps: number): number;
26
- export declare function outdatedToDisplayValue(majorOutdated: number, totalOutdated: number): string;
27
- export declare function outdatedToIssues(dependencies: NormalizedOutdatedEntries): Issue[];
28
- export declare function getOutdatedLevel(currentFullVersion: string, wantedFullVersion: string): VersionType;
26
+ export declare function outdatedToDisplayValue(stats: Record<VersionType, number>): string;
27
+ export declare function outdatedToIssues(dependencies: OutdatedResult): Issue[];
28
+ export declare function getOutdatedLevel(currentFullVersion: string, latestFullVersion: string): VersionType;
29
29
  export declare function splitPackageVersion(fullVersion: string): PackageVersion;
@@ -1,15 +1,48 @@
1
- export type VersionType = 'major' | 'minor' | 'patch';
1
+ export declare const versionType: readonly ["major", "minor", "patch"];
2
+ export type VersionType = (typeof versionType)[number];
2
3
  export type PackageVersion = Record<VersionType, number>;
3
- export type VersionOverview = {
4
+ export type DependencyGroupLong = 'dependencies' | 'devDependencies' | 'optionalDependencies';
5
+ export type OutdatedResult = {
6
+ name: string;
7
+ current: string;
8
+ latest: string;
9
+ type: DependencyGroupLong;
10
+ url?: string;
11
+ }[];
12
+ export type NpmVersionOverview = {
4
13
  current?: string;
5
- wanted: string;
6
- type: 'dependencies' | 'devDependencies' | 'optionalDependencies';
14
+ latest: string;
15
+ type: DependencyGroupLong;
7
16
  homepage?: string;
8
17
  };
9
- export type NormalizedVersionOverview = Omit<VersionOverview, 'current'> & {
18
+ export type NpmNormalizedOverview = Omit<NpmVersionOverview, 'current'> & {
10
19
  current: string;
11
20
  };
12
- export type NormalizedOutdatedEntries = [string, NormalizedVersionOverview][];
13
- export type NpmOutdatedResultJson = {
14
- [key: string]: VersionOverview;
21
+ export type NpmOutdatedResultJson = Record<string, NpmVersionOverview>;
22
+ export type Yarnv1VersionOverview = [
23
+ string,
24
+ string,
25
+ string,
26
+ string,
27
+ string,
28
+ DependencyGroupLong,
29
+ string
30
+ ];
31
+ type Yarnv1Info = {
32
+ type: 'info';
33
+ };
34
+ type Yarnv1Table = {
35
+ type: 'table';
36
+ data: {
37
+ body: Yarnv1VersionOverview[];
38
+ };
39
+ };
40
+ export type Yarnv1OutdatedResultJson = [Yarnv1Info, Yarnv1Table];
41
+ export type Yarnv2VersionOverview = {
42
+ current: string;
43
+ latest: string;
44
+ name: string;
45
+ type: DependencyGroupLong;
15
46
  };
47
+ export type Yarnv2OutdatedResultJson = Yarnv2VersionOverview[];
48
+ export {};
@@ -0,0 +1,4 @@
1
+ import { OutdatedResult } from './types';
2
+ export declare function npmToOutdatedResult(output: string): OutdatedResult;
3
+ export declare function yarnv1ToOutdatedResult(output: string): OutdatedResult;
4
+ export declare function yarnv2ToOutdatedResult(output: string): OutdatedResult;
@@ -0,0 +1,2 @@
1
+ import { AuditResult, Vulnerability } from './audit/types';
2
+ export declare function filterAuditResult(result: AuditResult, key: keyof Vulnerability, referenceResult?: AuditResult): AuditResult;