@code-pushup/core 0.26.1 → 0.28.0

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.
package/index.js CHANGED
@@ -84,6 +84,9 @@ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(
84
84
  var urlSchema = z.string().url();
85
85
  var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
86
86
  var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
87
+ var scoreSchema = z.number({
88
+ description: "Value between 0 and 1"
89
+ }).min(0).max(1);
87
90
  function metaSchema(options) {
88
91
  const {
89
92
  descriptionDescription,
@@ -215,6 +218,8 @@ var issueSchema = z3.object(
215
218
  );
216
219
 
217
220
  // packages/models/src/lib/audit-output.ts
221
+ var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
222
+ var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
218
223
  var auditDetailsSchema = z4.object(
219
224
  {
220
225
  issues: z4.array(issueSchema, { description: "List of findings" })
@@ -224,11 +229,9 @@ var auditDetailsSchema = z4.object(
224
229
  var auditOutputSchema = z4.object(
225
230
  {
226
231
  slug: slugSchema.describe("Reference to audit"),
227
- displayValue: z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional(),
228
- value: nonnegativeIntSchema.describe("Raw numeric value"),
229
- score: z4.number({
230
- description: "Value between 0 and 1"
231
- }).min(0).max(1),
232
+ displayValue: auditDisplayValueSchema,
233
+ value: auditValueSchema,
234
+ score: scoreSchema,
232
235
  details: auditDetailsSchema.optional()
233
236
  },
234
237
  { description: "Audit information" }
@@ -555,6 +558,138 @@ var reportSchema = packageVersionSchema({
555
558
  })
556
559
  );
557
560
 
561
+ // packages/models/src/lib/reports-diff.ts
562
+ import { z as z14 } from "zod";
563
+ function makeComparisonSchema(schema) {
564
+ const sharedDescription = schema.description || "Result";
565
+ return z14.object({
566
+ before: schema.describe(`${sharedDescription} (source commit)`),
567
+ after: schema.describe(`${sharedDescription} (target commit)`)
568
+ });
569
+ }
570
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
571
+ return z14.object(
572
+ {
573
+ changed: z14.array(diffSchema),
574
+ unchanged: z14.array(resultSchema),
575
+ added: z14.array(resultSchema),
576
+ removed: z14.array(resultSchema)
577
+ },
578
+ { description }
579
+ );
580
+ }
581
+ var scorableMetaSchema = z14.object({ slug: slugSchema, title: titleSchema });
582
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
583
+ z14.object({
584
+ plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
585
+ })
586
+ );
587
+ var scorableDiffSchema = scorableMetaSchema.merge(
588
+ z14.object({
589
+ scores: makeComparisonSchema(scoreSchema).merge(
590
+ z14.object({
591
+ diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
592
+ })
593
+ ).describe("Score comparison")
594
+ })
595
+ );
596
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
597
+ scorableWithPluginMetaSchema
598
+ );
599
+ var categoryDiffSchema = scorableDiffSchema;
600
+ var groupDiffSchema = scorableWithPluginDiffSchema;
601
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
602
+ z14.object({
603
+ values: makeComparisonSchema(auditValueSchema).merge(
604
+ z14.object({
605
+ diff: z14.number().int().describe("Value change (`values.after - values.before`)")
606
+ })
607
+ ).describe("Audit `value` comparison"),
608
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
609
+ "Audit `displayValue` comparison"
610
+ )
611
+ })
612
+ );
613
+ var categoryResultSchema = scorableMetaSchema.merge(
614
+ z14.object({ score: scoreSchema })
615
+ );
616
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
617
+ z14.object({ score: scoreSchema })
618
+ );
619
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
620
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
621
+ );
622
+ var reportsDiffSchema = z14.object({
623
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
624
+ categories: makeArraysComparisonSchema(
625
+ categoryDiffSchema,
626
+ categoryResultSchema,
627
+ "Changes affecting categories"
628
+ ),
629
+ groups: makeArraysComparisonSchema(
630
+ groupDiffSchema,
631
+ groupResultSchema,
632
+ "Changes affecting groups"
633
+ ),
634
+ audits: makeArraysComparisonSchema(
635
+ auditDiffSchema,
636
+ auditResultSchema,
637
+ "Changes affecting audits"
638
+ )
639
+ }).merge(
640
+ packageVersionSchema({
641
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
642
+ required: true
643
+ })
644
+ ).merge(
645
+ executionMetaSchema({
646
+ descriptionDate: "Start date and time of the compare run",
647
+ descriptionDuration: "Duration of the compare run in ms"
648
+ })
649
+ );
650
+
651
+ // packages/utils/src/lib/diff.ts
652
+ function matchArrayItemsByKey({
653
+ before,
654
+ after,
655
+ key
656
+ }) {
657
+ const pairs = [];
658
+ const added = [];
659
+ const afterKeys = /* @__PURE__ */ new Set();
660
+ const keyFn = typeof key === "function" ? key : (item) => item[key];
661
+ for (const afterItem of after) {
662
+ const afterKey = keyFn(afterItem);
663
+ afterKeys.add(afterKey);
664
+ const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
665
+ if (match) {
666
+ pairs.push({ before: match, after: afterItem });
667
+ } else {
668
+ added.push(afterItem);
669
+ }
670
+ }
671
+ const removed = before.filter(
672
+ (beforeItem) => !afterKeys.has(keyFn(beforeItem))
673
+ );
674
+ return {
675
+ pairs,
676
+ added,
677
+ removed
678
+ };
679
+ }
680
+ function comparePairs(pairs, equalsFn) {
681
+ return pairs.reduce(
682
+ (acc, pair) => ({
683
+ ...acc,
684
+ ...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
685
+ }),
686
+ {
687
+ changed: [],
688
+ unchanged: []
689
+ }
690
+ );
691
+ }
692
+
558
693
  // packages/utils/src/lib/execute-process.ts
559
694
  import { spawn } from "node:child_process";
560
695
 
@@ -563,13 +698,25 @@ import { join } from "node:path";
563
698
 
564
699
  // packages/utils/src/lib/file-system.ts
565
700
  import { bundleRequire } from "bundle-require";
566
- import chalk from "chalk";
701
+ import chalk2 from "chalk";
567
702
  import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
568
703
 
569
704
  // packages/utils/src/lib/formatting.ts
570
705
  function slugify(text) {
571
706
  return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
572
707
  }
708
+ function pluralize(text, amount) {
709
+ if (amount != null && Math.abs(amount) === 1) {
710
+ return text;
711
+ }
712
+ if (text.endsWith("y")) {
713
+ return `${text.slice(0, -1)}ies`;
714
+ }
715
+ if (text.endsWith("s")) {
716
+ return `${text}es`;
717
+ }
718
+ return `${text}s`;
719
+ }
573
720
  function formatBytes(bytes, decimals = 2) {
574
721
  const positiveBytes = Math.max(bytes, 0);
575
722
  if (positiveBytes === 0) {
@@ -581,6 +728,9 @@ function formatBytes(bytes, decimals = 2) {
581
728
  const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
582
729
  return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
583
730
  }
731
+ function pluralizeToken(token, times) {
732
+ return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
733
+ }
584
734
  function formatDuration(duration) {
585
735
  if (duration < 1e3) {
586
736
  return `${duration} ms`;
@@ -608,33 +758,103 @@ function isPromiseRejectedResult(result) {
608
758
  return result.status === "rejected";
609
759
  }
610
760
 
761
+ // packages/utils/src/lib/logging.ts
762
+ import isaacs_cliui from "@isaacs/cliui";
763
+ import { cliui } from "@poppinss/cliui";
764
+ import chalk from "chalk";
765
+
766
+ // packages/utils/src/lib/reports/constants.ts
767
+ var TERMINAL_WIDTH = 80;
768
+ var NEW_LINE = "\n";
769
+ var SCORE_COLOR_RANGE = {
770
+ GREEN_MIN: 0.9,
771
+ YELLOW_MIN: 0.5
772
+ };
773
+ var FOOTER_PREFIX = "Made with \u2764 by";
774
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
775
+ var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
776
+ var reportHeadlineText = "Code PushUp Report";
777
+ var reportOverviewTableHeaders = [
778
+ "\u{1F3F7} Category",
779
+ "\u2B50 Score",
780
+ "\u{1F6E1} Audits"
781
+ ];
782
+ var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
783
+ var reportMetaTableHeaders = [
784
+ "Commit",
785
+ "Version",
786
+ "Duration",
787
+ "Plugins",
788
+ "Categories",
789
+ "Audits"
790
+ ];
791
+ var pluginMetaTableHeaders = [
792
+ "Plugin",
793
+ "Audits",
794
+ "Version",
795
+ "Duration"
796
+ ];
797
+ var detailsTableHeaders = [
798
+ "Severity",
799
+ "Message",
800
+ "Source file",
801
+ "Line(s)"
802
+ ];
803
+
804
+ // packages/utils/src/lib/logging.ts
805
+ var singletonUiInstance;
806
+ function ui() {
807
+ if (singletonUiInstance === void 0) {
808
+ singletonUiInstance = cliui();
809
+ }
810
+ return {
811
+ ...singletonUiInstance,
812
+ row: (args) => {
813
+ logListItem(args);
814
+ }
815
+ };
816
+ }
817
+ var singletonisaacUi;
818
+ function logListItem(args) {
819
+ if (singletonisaacUi === void 0) {
820
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
821
+ }
822
+ singletonisaacUi.div(...args);
823
+ const content = singletonisaacUi.toString();
824
+ singletonisaacUi.rows = [];
825
+ singletonUiInstance?.logger.log(content);
826
+ }
827
+
611
828
  // packages/utils/src/lib/log-results.ts
612
- function logMultipleResults(results, messagePrefix, succeededCallback, failedCallback) {
613
- if (succeededCallback) {
829
+ function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
830
+ if (succeededTransform) {
614
831
  const succeededResults = results.filter(isPromiseFulfilledResult);
615
832
  logPromiseResults(
616
833
  succeededResults,
617
834
  `${messagePrefix} successfully: `,
618
- succeededCallback
835
+ succeededTransform
619
836
  );
620
837
  }
621
- if (failedCallback) {
838
+ if (failedTransform) {
622
839
  const failedResults = results.filter(isPromiseRejectedResult);
623
840
  logPromiseResults(
624
841
  failedResults,
625
842
  `${messagePrefix} failed: `,
626
- failedCallback
843
+ failedTransform
627
844
  );
628
845
  }
629
846
  }
630
- function logPromiseResults(results, logMessage, callback) {
847
+ function logPromiseResults(results, logMessage, getMsg) {
631
848
  if (results.length > 0) {
632
- if (results[0]?.status === "fulfilled") {
633
- console.info(logMessage);
634
- } else {
635
- console.warn(logMessage);
636
- }
637
- results.forEach(callback);
849
+ const log2 = results[0]?.status === "fulfilled" ? (m) => {
850
+ ui().logger.success(m);
851
+ } : (m) => {
852
+ ui().logger.warning(m);
853
+ };
854
+ log2(logMessage);
855
+ results.forEach((result) => {
856
+ log2(getMsg(result));
857
+ });
638
858
  }
639
859
  }
640
860
 
@@ -668,26 +888,24 @@ async function ensureDirectoryExists(baseDir) {
668
888
  await mkdir(baseDir, { recursive: true });
669
889
  return;
670
890
  } catch (error) {
671
- console.error(error.message);
891
+ ui().logger.error(error.message);
672
892
  if (error.code !== "EEXIST") {
673
893
  throw error;
674
894
  }
675
895
  }
676
896
  }
677
897
  function logMultipleFileResults(fileResults, messagePrefix) {
678
- const succeededCallback = (result) => {
898
+ const succeededTransform = (result) => {
679
899
  const [fileName, size] = result.value;
680
- const formattedSize = size ? ` (${chalk.gray(formatBytes(size))})` : "";
681
- console.info(`- ${chalk.bold(fileName)}${formattedSize}`);
682
- };
683
- const failedCallback = (result) => {
684
- console.warn(`- ${chalk.bold(result.reason)}`);
900
+ const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
901
+ return `- ${chalk2.bold(fileName)}${formattedSize}`;
685
902
  };
903
+ const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
686
904
  logMultipleResults(
687
905
  fileResults,
688
906
  messagePrefix,
689
- succeededCallback,
690
- failedCallback
907
+ succeededTransform,
908
+ failedTransform
691
909
  );
692
910
  }
693
911
  var NoExportError = class extends Error {
@@ -706,48 +924,105 @@ async function importEsmModule(options) {
706
924
  return mod.default;
707
925
  }
708
926
 
709
- // packages/utils/src/lib/reports/constants.ts
710
- var TERMINAL_WIDTH = 80;
711
- var NEW_LINE = "\n";
712
- var SCORE_COLOR_RANGE = {
713
- GREEN_MIN: 0.9,
714
- YELLOW_MIN: 0.5
927
+ // packages/utils/src/lib/reports/md/details.ts
928
+ function details(title, content, cfg = { open: false }) {
929
+ return `<details${cfg.open ? " open" : ""}>
930
+ <summary>${title}</summary>
931
+
932
+ ${content}
933
+
934
+ </details>
935
+ `;
936
+ }
937
+
938
+ // packages/utils/src/lib/reports/md/font-style.ts
939
+ var stylesMap = {
940
+ i: "_",
941
+ // italic
942
+ b: "**",
943
+ // bold
944
+ s: "~",
945
+ // strike through
946
+ c: "`"
947
+ // code
715
948
  };
716
- var FOOTER_PREFIX = "Made with \u2764 by";
717
- var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
718
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
719
- var reportHeadlineText = "Code PushUp Report";
720
- var reportOverviewTableHeaders = [
721
- "\u{1F3F7} Category",
722
- "\u2B50 Score",
723
- "\u{1F6E1} Audits"
724
- ];
725
- var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
726
- var reportMetaTableHeaders = [
727
- "Commit",
728
- "Version",
729
- "Duration",
730
- "Plugins",
731
- "Categories",
732
- "Audits"
733
- ];
734
- var pluginMetaTableHeaders = [
735
- "Plugin",
736
- "Audits",
737
- "Version",
738
- "Duration"
739
- ];
740
- var detailsTableHeaders = [
741
- "Severity",
742
- "Message",
743
- "Source file",
744
- "Line(s)"
745
- ];
949
+ function style(text, styles = ["b"]) {
950
+ return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
951
+ }
952
+
953
+ // packages/utils/src/lib/reports/md/headline.ts
954
+ function headline(text, hierarchy = 1) {
955
+ return `${"#".repeat(hierarchy)} ${text}`;
956
+ }
957
+ function h1(text) {
958
+ return headline(text, 1);
959
+ }
960
+ function h2(text) {
961
+ return headline(text, 2);
962
+ }
963
+ function h3(text) {
964
+ return headline(text, 3);
965
+ }
966
+
967
+ // packages/utils/src/lib/reports/md/image.ts
968
+ function image(src, alt) {
969
+ return `![${alt}](${src})`;
970
+ }
971
+
972
+ // packages/utils/src/lib/reports/md/link.ts
973
+ function link(href, text) {
974
+ return `[${text || href}](${href})`;
975
+ }
976
+
977
+ // packages/utils/src/lib/reports/md/list.ts
978
+ function li(text, order = "unordered") {
979
+ const style2 = order === "unordered" ? "-" : "- [ ]";
980
+ return `${style2} ${text}`;
981
+ }
982
+
983
+ // packages/utils/src/lib/reports/md/paragraphs.ts
984
+ function paragraphs(...sections) {
985
+ return sections.filter(Boolean).join("\n\n");
986
+ }
987
+
988
+ // packages/utils/src/lib/reports/md/table.ts
989
+ var alignString = /* @__PURE__ */ new Map([
990
+ ["l", ":--"],
991
+ ["c", ":--:"],
992
+ ["r", "--:"]
993
+ ]);
994
+ function tableMd(data, align) {
995
+ if (data.length === 0) {
996
+ throw new Error("Data can't be empty");
997
+ }
998
+ const alignmentSetting = align ?? data[0]?.map(() => "c");
999
+ const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1000
+ const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1001
+ return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1002
+ }
1003
+ function tableHtml(data) {
1004
+ if (data.length === 0) {
1005
+ throw new Error("Data can't be empty");
1006
+ }
1007
+ const tableContent = data.map((arr, index) => {
1008
+ if (index === 0) {
1009
+ const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1010
+ return `<tr>${headerRow}</tr>`;
1011
+ }
1012
+ const row = arr.map((s) => `<td>${s}</td>`).join("");
1013
+ return `<tr>${row}</tr>`;
1014
+ });
1015
+ return `<table>${tableContent.join("")}</table>`;
1016
+ }
746
1017
 
747
1018
  // packages/utils/src/lib/reports/utils.ts
748
1019
  function formatReportScore(score) {
749
1020
  return Math.round(score * 100).toString();
750
1021
  }
1022
+ function formatScoreWithColor(score, options) {
1023
+ const styledNumber = options?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1024
+ return `${getRoundScoreMarker(score)} ${styledNumber}`;
1025
+ }
751
1026
  function getRoundScoreMarker(score) {
752
1027
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
753
1028
  return "\u{1F7E2}";
@@ -766,6 +1041,30 @@ function getSquaredScoreMarker(score) {
766
1041
  }
767
1042
  return "\u{1F7E5}";
768
1043
  }
1044
+ function getDiffMarker(diff) {
1045
+ if (diff > 0) {
1046
+ return "\u2191";
1047
+ }
1048
+ if (diff < 0) {
1049
+ return "\u2193";
1050
+ }
1051
+ return "";
1052
+ }
1053
+ function colorByScoreDiff(text, diff) {
1054
+ const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
1055
+ return shieldsBadge(text, color);
1056
+ }
1057
+ function shieldsBadge(text, color) {
1058
+ return image(
1059
+ `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1060
+ text
1061
+ );
1062
+ }
1063
+ function formatDiffNumber(diff) {
1064
+ const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
1065
+ const sign = diff < 0 ? "\u2212" : "+";
1066
+ return `${sign}${number}`;
1067
+ }
769
1068
  function getSeverityIcon(severity) {
770
1069
  if (severity === "error") {
771
1070
  return "\u{1F6A8}";
@@ -776,7 +1075,7 @@ function getSeverityIcon(severity) {
776
1075
  return "\u2139\uFE0F";
777
1076
  }
778
1077
  function calcDuration(start, stop) {
779
- return Math.floor((stop ?? performance.now()) - start);
1078
+ return Math.round((stop ?? performance.now()) - start);
780
1079
  }
781
1080
  function countCategoryAudits(refs, plugins) {
782
1081
  const groupLookup = plugins.reduce(
@@ -939,7 +1238,7 @@ var ProcessError = class extends Error {
939
1238
  }
940
1239
  };
941
1240
  function executeProcess(cfg) {
942
- const { observer, cwd, command, args } = cfg;
1241
+ const { observer, cwd, command, args, alwaysResolve = false } = cfg;
943
1242
  const { onStdout, onError, onComplete } = observer ?? {};
944
1243
  const date = (/* @__PURE__ */ new Date()).toISOString();
945
1244
  const start = performance.now();
@@ -959,7 +1258,7 @@ function executeProcess(cfg) {
959
1258
  });
960
1259
  process2.on("close", (code) => {
961
1260
  const timings = { date, duration: calcDuration(start) };
962
- if (code === 0) {
1261
+ if (code === 0 || alwaysResolve) {
963
1262
  onComplete?.();
964
1263
  resolve({ code, stdout, stderr, ...timings });
965
1264
  } else {
@@ -976,6 +1275,9 @@ import { isAbsolute, join as join2, relative } from "node:path";
976
1275
  import { simpleGit } from "simple-git";
977
1276
 
978
1277
  // packages/utils/src/lib/transform.ts
1278
+ function objectToEntries(obj) {
1279
+ return Object.entries(obj);
1280
+ }
979
1281
  function deepClone(obj) {
980
1282
  return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
981
1283
  }
@@ -985,14 +1287,14 @@ function toUnixPath(path) {
985
1287
 
986
1288
  // packages/utils/src/lib/git.ts
987
1289
  async function getLatestCommit(git = simpleGit()) {
988
- const log = await git.log({
1290
+ const log2 = await git.log({
989
1291
  maxCount: 1,
990
1292
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
991
1293
  });
992
- if (!log.latest) {
1294
+ if (!log2.latest) {
993
1295
  return null;
994
1296
  }
995
- return commitSchema.parse(log.latest);
1297
+ return commitSchema.parse(log2.latest);
996
1298
  }
997
1299
  function getGitRoot(git = simpleGit()) {
998
1300
  return git.revparse("--show-toplevel");
@@ -1011,9 +1313,6 @@ function groupByStatus(results) {
1011
1313
  );
1012
1314
  }
1013
1315
 
1014
- // packages/utils/src/lib/logging.ts
1015
- import chalk2 from "chalk";
1016
-
1017
1316
  // packages/utils/src/lib/progress.ts
1018
1317
  import chalk3 from "chalk";
1019
1318
  import { MultiProgressBars } from "multi-progress-bars";
@@ -1069,80 +1368,16 @@ function getProgressBar(taskName) {
1069
1368
  };
1070
1369
  }
1071
1370
 
1072
- // packages/utils/src/lib/reports/md/details.ts
1073
- function details(title, content, cfg = { open: false }) {
1074
- return `<details${cfg.open ? " open" : ""}>
1075
- <summary>${title}</summary>
1076
- ${content}
1077
- </details>
1078
- `;
1079
- }
1080
-
1081
- // packages/utils/src/lib/reports/md/font-style.ts
1082
- var stylesMap = {
1083
- i: "_",
1084
- // italic
1085
- b: "**",
1086
- // bold
1087
- s: "~",
1088
- // strike through
1089
- c: "`"
1090
- // code
1091
- };
1092
- function style(text, styles = ["b"]) {
1093
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
1094
- }
1095
-
1096
- // packages/utils/src/lib/reports/md/headline.ts
1097
- function headline(text, hierarchy = 1) {
1098
- return `${"#".repeat(hierarchy)} ${text}`;
1099
- }
1100
- function h2(text) {
1101
- return headline(text, 2);
1102
- }
1103
- function h3(text) {
1104
- return headline(text, 3);
1105
- }
1106
-
1107
- // packages/utils/src/lib/reports/md/link.ts
1108
- function link(href, text) {
1109
- return `[${text || href}](${href})`;
1110
- }
1111
-
1112
- // packages/utils/src/lib/reports/md/list.ts
1113
- function li(text, order = "unordered") {
1114
- const style2 = order === "unordered" ? "-" : "- [ ]";
1115
- return `${style2} ${text}`;
1116
- }
1117
-
1118
- // packages/utils/src/lib/reports/md/table.ts
1119
- var alignString = /* @__PURE__ */ new Map([
1120
- ["l", ":--"],
1121
- ["c", ":--:"],
1122
- ["r", "--:"]
1123
- ]);
1124
- function tableMd(data, align) {
1125
- if (data.length === 0) {
1126
- throw new Error("Data can't be empty");
1127
- }
1128
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1129
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1130
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1131
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1371
+ // packages/utils/src/lib/reports/flatten-plugins.ts
1372
+ function listGroupsFromAllPlugins(report) {
1373
+ return report.plugins.flatMap(
1374
+ (plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
1375
+ );
1132
1376
  }
1133
- function tableHtml(data) {
1134
- if (data.length === 0) {
1135
- throw new Error("Data can't be empty");
1136
- }
1137
- const tableContent = data.map((arr, index) => {
1138
- if (index === 0) {
1139
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1140
- return `<tr>${headerRow}</tr>`;
1141
- }
1142
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1143
- return `<tr>${row}</tr>`;
1144
- });
1145
- return `<table>${tableContent.join("")}</table>`;
1377
+ function listAuditsFromAllPlugins(report) {
1378
+ return report.plugins.flatMap(
1379
+ (plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
1380
+ );
1146
1381
  }
1147
1382
 
1148
1383
  // packages/utils/src/lib/reports/generate-md-report.ts
@@ -1332,30 +1567,261 @@ function getAuditResult(audit, isHtml = false) {
1332
1567
  return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1333
1568
  }
1334
1569
 
1335
- // packages/utils/src/lib/reports/generate-stdout-summary.ts
1336
- import cliui from "@isaacs/cliui";
1570
+ // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1571
+ var MAX_ROWS = 100;
1572
+ function generateMdReportsDiff(diff) {
1573
+ return paragraphs(
1574
+ formatDiffHeaderSection(diff),
1575
+ formatDiffCategoriesSection(diff),
1576
+ formatDiffGroupsSection(diff),
1577
+ formatDiffAuditsSection(diff)
1578
+ );
1579
+ }
1580
+ function formatDiffHeaderSection(diff) {
1581
+ const outcomeTexts = {
1582
+ positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1583
+ negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1584
+ mixed: `\u{1F928} Code PushUp report has both ${style(
1585
+ "improvements and regressions"
1586
+ )}`,
1587
+ unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
1588
+ };
1589
+ const outcome = mergeDiffOutcomes(
1590
+ changesToDiffOutcomes([
1591
+ ...diff.categories.changed,
1592
+ ...diff.groups.changed,
1593
+ ...diff.audits.changed
1594
+ ])
1595
+ );
1596
+ const styleCommit = (commit) => style(commit.hash.slice(0, 7), ["c"]);
1597
+ const styleCommits = (commits) => {
1598
+ const src = styleCommit(commits.before);
1599
+ const tgt = styleCommit(commits.after);
1600
+ return `compared target commit ${tgt} with source commit ${src}`;
1601
+ };
1602
+ return paragraphs(
1603
+ h1("Code PushUp"),
1604
+ diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1605
+ );
1606
+ }
1607
+ function formatDiffCategoriesSection(diff) {
1608
+ const { changed, unchanged, added } = diff.categories;
1609
+ const categoriesCount = changed.length + unchanged.length + added.length;
1610
+ const hasChanges = unchanged.length < categoriesCount;
1611
+ if (categoriesCount === 0) {
1612
+ return "";
1613
+ }
1614
+ return paragraphs(
1615
+ h2("\u{1F3F7}\uFE0F Categories"),
1616
+ categoriesCount > 0 && tableMd(
1617
+ [
1618
+ [
1619
+ "\u{1F3F7}\uFE0F Category",
1620
+ hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1621
+ "\u2B50 Previous score",
1622
+ "\u{1F504} Score change"
1623
+ ],
1624
+ ...sortChanges(changed).map((category) => [
1625
+ category.title,
1626
+ formatScoreWithColor(category.scores.after),
1627
+ formatScoreWithColor(category.scores.before, { skipBold: true }),
1628
+ formatScoreChange(category.scores.diff)
1629
+ ]),
1630
+ ...added.map((category) => [
1631
+ category.title,
1632
+ formatScoreWithColor(category.score),
1633
+ style("n/a (\\*)", ["i"]),
1634
+ style("n/a (\\*)", ["i"])
1635
+ ]),
1636
+ ...unchanged.map((category) => [
1637
+ category.title,
1638
+ formatScoreWithColor(category.score),
1639
+ formatScoreWithColor(category.score, { skipBold: true }),
1640
+ "\u2013"
1641
+ ])
1642
+ ].map((row) => hasChanges ? row : row.slice(0, 2)),
1643
+ hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1644
+ ),
1645
+ added.length > 0 && style("(\\*) New category.", ["i"])
1646
+ );
1647
+ }
1648
+ function formatDiffGroupsSection(diff) {
1649
+ if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1650
+ return "";
1651
+ }
1652
+ return paragraphs(
1653
+ h2("\u{1F397}\uFE0F Groups"),
1654
+ formatGroupsOrAuditsDetails("group", diff.groups, {
1655
+ headings: [
1656
+ "\u{1F50C} Plugin",
1657
+ "\u{1F5C3}\uFE0F Group",
1658
+ "\u2B50 Current score",
1659
+ "\u2B50 Previous score",
1660
+ "\u{1F504} Score change"
1661
+ ],
1662
+ rows: sortChanges(diff.groups.changed).map((group) => [
1663
+ group.plugin.title,
1664
+ group.title,
1665
+ formatScoreWithColor(group.scores.after),
1666
+ formatScoreWithColor(group.scores.before, { skipBold: true }),
1667
+ formatScoreChange(group.scores.diff)
1668
+ ]),
1669
+ align: ["l", "l", "c", "c", "c"]
1670
+ })
1671
+ );
1672
+ }
1673
+ function formatDiffAuditsSection(diff) {
1674
+ return paragraphs(
1675
+ h2("\u{1F6E1}\uFE0F Audits"),
1676
+ formatGroupsOrAuditsDetails("audit", diff.audits, {
1677
+ headings: [
1678
+ "\u{1F50C} Plugin",
1679
+ "\u{1F6E1}\uFE0F Audit",
1680
+ "\u{1F4CF} Current value",
1681
+ "\u{1F4CF} Previous value",
1682
+ "\u{1F504} Value change"
1683
+ ],
1684
+ rows: sortChanges(diff.audits.changed).map((audit) => [
1685
+ audit.plugin.title,
1686
+ audit.title,
1687
+ `${getSquaredScoreMarker(audit.scores.after)} ${style(
1688
+ audit.displayValues.after || audit.values.after.toString()
1689
+ )}`,
1690
+ `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1691
+ formatValueChange(audit)
1692
+ ]),
1693
+ align: ["l", "l", "c", "c", "c"]
1694
+ })
1695
+ );
1696
+ }
1697
+ function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1698
+ return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
1699
+ summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1700
+ paragraphs(
1701
+ tableMd(
1702
+ [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1703
+ table.align
1704
+ ),
1705
+ changed.length > MAX_ROWS && style(
1706
+ `Only the ${MAX_ROWS} most affected ${pluralize(
1707
+ token
1708
+ )} are listed above for brevity.`,
1709
+ ["i"]
1710
+ ),
1711
+ unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1712
+ )
1713
+ );
1714
+ }
1715
+ function formatScoreChange(diff) {
1716
+ const marker = getDiffMarker(diff);
1717
+ const text = formatDiffNumber(Math.round(diff * 100));
1718
+ return colorByScoreDiff(`${marker} ${text}`, diff);
1719
+ }
1720
+ function formatValueChange({
1721
+ values,
1722
+ scores
1723
+ }) {
1724
+ const marker = getDiffMarker(values.diff);
1725
+ const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
1726
+ const text = `${formatDiffNumber(percentage)}\u2009%`;
1727
+ return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1728
+ }
1729
+ function summarizeUnchanged(token, { changed, unchanged }) {
1730
+ return [
1731
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1732
+ unchanged.length === 1 ? "is" : "are",
1733
+ "unchanged."
1734
+ ].join(" ");
1735
+ }
1736
+ function summarizeDiffOutcomes(outcomes, token) {
1737
+ return objectToEntries(countDiffOutcomes(outcomes)).filter(
1738
+ (entry) => entry[0] !== "unchanged" && entry[1] > 0
1739
+ ).map(([outcome, count]) => {
1740
+ const formattedCount = `<strong>${count}</strong> ${pluralize(
1741
+ token,
1742
+ count
1743
+ )}`;
1744
+ switch (outcome) {
1745
+ case "positive":
1746
+ return `\u{1F44D} ${formattedCount} improved`;
1747
+ case "negative":
1748
+ return `\u{1F44E} ${formattedCount} regressed`;
1749
+ case "mixed":
1750
+ return `${formattedCount} changed without impacting score`;
1751
+ }
1752
+ }).join(", ");
1753
+ }
1754
+ function sortChanges(changes) {
1755
+ return [...changes].sort(
1756
+ (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
1757
+ );
1758
+ }
1759
+ function changesToDiffOutcomes(changes) {
1760
+ return changes.map((change) => {
1761
+ if (change.scores.diff > 0) {
1762
+ return "positive";
1763
+ }
1764
+ if (change.scores.diff < 0) {
1765
+ return "negative";
1766
+ }
1767
+ if (change.values != null && change.values.diff !== 0) {
1768
+ return "mixed";
1769
+ }
1770
+ return "unchanged";
1771
+ });
1772
+ }
1773
+ function mergeDiffOutcomes(outcomes) {
1774
+ if (outcomes.every((outcome) => outcome === "unchanged")) {
1775
+ return "unchanged";
1776
+ }
1777
+ if (outcomes.includes("positive") && !outcomes.includes("negative")) {
1778
+ return "positive";
1779
+ }
1780
+ if (outcomes.includes("negative") && !outcomes.includes("positive")) {
1781
+ return "negative";
1782
+ }
1783
+ return "mixed";
1784
+ }
1785
+ function countDiffOutcomes(outcomes) {
1786
+ return {
1787
+ positive: outcomes.filter((outcome) => outcome === "positive").length,
1788
+ negative: outcomes.filter((outcome) => outcome === "negative").length,
1789
+ mixed: outcomes.filter((outcome) => outcome === "mixed").length,
1790
+ unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
1791
+ };
1792
+ }
1793
+
1794
+ // packages/utils/src/lib/reports/log-stdout-summary.ts
1337
1795
  import chalk4 from "chalk";
1338
- import CliTable3 from "cli-table3";
1339
- function addLine(line = "") {
1340
- return line + NEW_LINE;
1796
+ function log(msg = "") {
1797
+ ui().logger.log(msg);
1341
1798
  }
1342
- function generateStdoutSummary(report) {
1799
+ function logStdoutSummary(report) {
1343
1800
  const printCategories = report.categories.length > 0;
1344
- return addLine(reportToHeaderSection2(report)) + addLine() + addLine(reportToDetailSection(report)) + (printCategories ? addLine(reportToOverviewSection2(report)) : "") + addLine(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1801
+ log(reportToHeaderSection2(report));
1802
+ log();
1803
+ logPlugins(report);
1804
+ if (printCategories) {
1805
+ logCategories(report);
1806
+ }
1807
+ log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1808
+ log();
1345
1809
  }
1346
1810
  function reportToHeaderSection2(report) {
1347
1811
  const { packageName, version: version2 } = report;
1348
1812
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1349
1813
  }
1350
- function reportToDetailSection(report) {
1814
+ function logPlugins(report) {
1351
1815
  const { plugins } = report;
1352
- return plugins.reduce((acc, plugin) => {
1816
+ plugins.forEach((plugin) => {
1353
1817
  const { title, audits } = plugin;
1354
- const ui = cliui({ width: TERMINAL_WIDTH });
1818
+ log();
1819
+ log(chalk4.magentaBright.bold(`${title} audits`));
1820
+ log();
1355
1821
  audits.forEach((audit) => {
1356
- ui.div(
1822
+ ui().row([
1357
1823
  {
1358
- text: withColor({ score: audit.score, text: "\u25CF" }),
1824
+ text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
1359
1825
  width: 2,
1360
1826
  padding: [0, 1, 0, 0]
1361
1827
  },
@@ -1369,34 +1835,40 @@ function reportToDetailSection(report) {
1369
1835
  width: 10,
1370
1836
  padding: [0, 0, 0, 0]
1371
1837
  }
1372
- );
1838
+ ]);
1373
1839
  });
1374
- return acc + addLine() + addLine(chalk4.magentaBright.bold(`${title} audits`)) + addLine() + addLine(ui.toString()) + addLine();
1375
- }, "");
1376
- }
1377
- function reportToOverviewSection2({
1378
- categories,
1379
- plugins
1380
- }) {
1381
- const table = new CliTable3({
1382
- // eslint-disable-next-line no-magic-numbers
1383
- colWidths: [TERMINAL_WIDTH - 7 - 8 - 4, 7, 8],
1384
- head: reportRawOverviewTableHeaders,
1385
- colAligns: ["left", "right", "right"],
1386
- style: {
1387
- head: ["cyan"]
1388
- }
1840
+ log();
1389
1841
  });
1390
- table.push(
1391
- ...categories.map(({ title, score, refs }) => [
1392
- title,
1393
- withColor({ score }),
1394
- countCategoryAudits(refs, plugins)
1395
- ])
1842
+ }
1843
+ function logCategories({ categories, plugins }) {
1844
+ const hAlign = (idx) => idx === 0 ? "left" : "right";
1845
+ const rows = categories.map(({ title, score, refs }) => [
1846
+ title,
1847
+ applyScoreColor({ score }),
1848
+ countCategoryAudits(refs, plugins)
1849
+ ]);
1850
+ const table = ui().table();
1851
+ table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1852
+ table.head(
1853
+ reportRawOverviewTableHeaders.map((heading, idx) => ({
1854
+ content: chalk4.cyan(heading),
1855
+ hAlign: hAlign(idx)
1856
+ }))
1396
1857
  );
1397
- return addLine(chalk4.magentaBright.bold("Categories")) + addLine() + addLine(table.toString());
1858
+ rows.forEach(
1859
+ (row) => table.row(
1860
+ row.map((content, idx) => ({
1861
+ content: content.toString(),
1862
+ hAlign: hAlign(idx)
1863
+ }))
1864
+ )
1865
+ );
1866
+ log(chalk4.magentaBright.bold("Categories"));
1867
+ log();
1868
+ table.render();
1869
+ log();
1398
1870
  }
1399
- function withColor({ score, text }) {
1871
+ function applyScoreColor({ score, text }) {
1400
1872
  const formattedScore = text ?? formatReportScore(score);
1401
1873
  const style2 = text ? chalk4 : chalk4.bold;
1402
1874
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
@@ -1547,9 +2019,9 @@ function sortPlugins(plugins) {
1547
2019
 
1548
2020
  // packages/utils/src/lib/verbose-utils.ts
1549
2021
  function getLogVerbose(verbose = false) {
1550
- return (...args) => {
2022
+ return (msg) => {
1551
2023
  if (verbose) {
1552
- console.info(...args);
2024
+ ui().logger.info(msg);
1553
2025
  }
1554
2026
  };
1555
2027
  }
@@ -1567,7 +2039,7 @@ var verboseUtils = (verbose = false) => ({
1567
2039
 
1568
2040
  // packages/core/package.json
1569
2041
  var name = "@code-pushup/core";
1570
- var version = "0.26.1";
2042
+ var version = "0.28.0";
1571
2043
 
1572
2044
  // packages/core/src/lib/implementation/execute-plugin.ts
1573
2045
  import chalk5 from "chalk";
@@ -1680,11 +2152,9 @@ async function executePlugins(plugins, options) {
1680
2152
  }
1681
2153
  }, Promise.resolve([]));
1682
2154
  progressBar?.endProgress("Done running plugins");
1683
- const errorsCallback = ({ reason }) => {
1684
- console.error(reason);
1685
- };
2155
+ const errorsTransform = ({ reason }) => String(reason);
1686
2156
  const results = await Promise.allSettled(pluginsResult);
1687
- logMultipleResults(results, "Plugins", void 0, errorsCallback);
2157
+ logMultipleResults(results, "Plugins", void 0, errorsTransform);
1688
2158
  const { fulfilled, rejected } = groupByStatus(results);
1689
2159
  if (rejected.length > 0) {
1690
2160
  const errorMessages = rejected.map(({ reason }) => String(reason)).join(", ");
@@ -1739,7 +2209,7 @@ var PersistError = class extends Error {
1739
2209
  async function persistReport(report, options) {
1740
2210
  const { outputDir, filename, format } = options;
1741
2211
  const sortedScoredReport = sortReport(scoreReport(report));
1742
- console.info(generateStdoutSummary(sortedScoredReport));
2212
+ logStdoutSummary(sortedScoredReport);
1743
2213
  const results = format.map((reportType) => {
1744
2214
  switch (reportType) {
1745
2215
  case "json":
@@ -1758,7 +2228,7 @@ async function persistReport(report, options) {
1758
2228
  try {
1759
2229
  await mkdir2(outputDir, { recursive: true });
1760
2230
  } catch (error) {
1761
- console.warn(error);
2231
+ ui().logger.warning(error.toString());
1762
2232
  throw new PersistDirError(outputDir);
1763
2233
  }
1764
2234
  }
@@ -1773,7 +2243,7 @@ async function persistReport(report, options) {
1773
2243
  }
1774
2244
  async function persistResult(reportPath, content) {
1775
2245
  return writeFile(reportPath, content).then(() => stat2(reportPath)).then((stats) => [reportPath, stats.size]).catch((error) => {
1776
- console.warn(error);
2246
+ ui().logger.warning(error.toString());
1777
2247
  throw new PersistError(reportPath);
1778
2248
  });
1779
2249
  }
@@ -1794,8 +2264,209 @@ async function collectAndPersistReports(options) {
1794
2264
  });
1795
2265
  }
1796
2266
 
1797
- // packages/core/src/lib/implementation/read-rc-file.ts
2267
+ // packages/core/src/lib/compare.ts
2268
+ import { writeFile as writeFile2 } from "node:fs/promises";
1798
2269
  import { join as join5 } from "node:path";
2270
+
2271
+ // packages/core/src/lib/implementation/compare-scorables.ts
2272
+ function compareCategories(reports) {
2273
+ const { pairs, added, removed } = matchArrayItemsByKey({
2274
+ before: reports.before.categories,
2275
+ after: reports.after.categories,
2276
+ key: "slug"
2277
+ });
2278
+ const { changed, unchanged } = comparePairs(
2279
+ pairs,
2280
+ ({ before, after }) => before.score === after.score
2281
+ );
2282
+ return {
2283
+ changed: changed.map(categoryPairToDiff),
2284
+ unchanged: unchanged.map(categoryToResult),
2285
+ added: added.map(categoryToResult),
2286
+ removed: removed.map(categoryToResult)
2287
+ };
2288
+ }
2289
+ function compareGroups(reports) {
2290
+ const { pairs, added, removed } = matchArrayItemsByKey({
2291
+ before: listGroupsFromAllPlugins(reports.before),
2292
+ after: listGroupsFromAllPlugins(reports.after),
2293
+ key: ({ plugin, group }) => `${plugin.slug}/${group.slug}`
2294
+ });
2295
+ const { changed, unchanged } = comparePairs(
2296
+ pairs,
2297
+ ({ before, after }) => before.group.score === after.group.score
2298
+ );
2299
+ return {
2300
+ changed: changed.map(pluginGroupPairToDiff),
2301
+ unchanged: unchanged.map(pluginGroupToResult),
2302
+ added: added.map(pluginGroupToResult),
2303
+ removed: removed.map(pluginGroupToResult)
2304
+ };
2305
+ }
2306
+ function compareAudits2(reports) {
2307
+ const { pairs, added, removed } = matchArrayItemsByKey({
2308
+ before: listAuditsFromAllPlugins(reports.before),
2309
+ after: listAuditsFromAllPlugins(reports.after),
2310
+ key: ({ plugin, audit }) => `${plugin.slug}/${audit.slug}`
2311
+ });
2312
+ const { changed, unchanged } = comparePairs(
2313
+ pairs,
2314
+ ({ before, after }) => before.audit.value === after.audit.value && before.audit.score === after.audit.score
2315
+ );
2316
+ return {
2317
+ changed: changed.map(pluginAuditPairToDiff),
2318
+ unchanged: unchanged.map(pluginAuditToResult),
2319
+ added: added.map(pluginAuditToResult),
2320
+ removed: removed.map(pluginAuditToResult)
2321
+ };
2322
+ }
2323
+ function categoryToResult(category) {
2324
+ return {
2325
+ slug: category.slug,
2326
+ title: category.title,
2327
+ score: category.score
2328
+ };
2329
+ }
2330
+ function categoryPairToDiff({
2331
+ before,
2332
+ after
2333
+ }) {
2334
+ return {
2335
+ slug: after.slug,
2336
+ title: after.title,
2337
+ scores: {
2338
+ before: before.score,
2339
+ after: after.score,
2340
+ diff: after.score - before.score
2341
+ }
2342
+ };
2343
+ }
2344
+ function pluginGroupToResult({ group, plugin }) {
2345
+ return {
2346
+ slug: group.slug,
2347
+ title: group.title,
2348
+ plugin: {
2349
+ slug: plugin.slug,
2350
+ title: plugin.title
2351
+ },
2352
+ score: group.score
2353
+ };
2354
+ }
2355
+ function pluginGroupPairToDiff({
2356
+ before,
2357
+ after
2358
+ }) {
2359
+ return {
2360
+ slug: after.group.slug,
2361
+ title: after.group.title,
2362
+ plugin: {
2363
+ slug: after.plugin.slug,
2364
+ title: after.plugin.title
2365
+ },
2366
+ scores: {
2367
+ before: before.group.score,
2368
+ after: after.group.score,
2369
+ diff: after.group.score - before.group.score
2370
+ }
2371
+ };
2372
+ }
2373
+ function pluginAuditToResult({ audit, plugin }) {
2374
+ return {
2375
+ slug: audit.slug,
2376
+ title: audit.title,
2377
+ plugin: {
2378
+ slug: plugin.slug,
2379
+ title: plugin.title
2380
+ },
2381
+ score: audit.score,
2382
+ value: audit.value,
2383
+ displayValue: audit.displayValue
2384
+ };
2385
+ }
2386
+ function pluginAuditPairToDiff({
2387
+ before,
2388
+ after
2389
+ }) {
2390
+ return {
2391
+ slug: after.audit.slug,
2392
+ title: after.audit.title,
2393
+ plugin: {
2394
+ slug: after.plugin.slug,
2395
+ title: after.plugin.title
2396
+ },
2397
+ scores: {
2398
+ before: before.audit.score,
2399
+ after: after.audit.score,
2400
+ diff: after.audit.score - before.audit.score
2401
+ },
2402
+ values: {
2403
+ before: before.audit.value,
2404
+ after: after.audit.value,
2405
+ diff: after.audit.value - before.audit.value
2406
+ },
2407
+ displayValues: {
2408
+ before: before.audit.displayValue,
2409
+ after: after.audit.displayValue
2410
+ }
2411
+ };
2412
+ }
2413
+
2414
+ // packages/core/src/lib/compare.ts
2415
+ async function compareReportFiles(inputPaths, persistConfig) {
2416
+ const { outputDir, filename, format } = persistConfig;
2417
+ const [reportBefore, reportAfter] = await Promise.all([
2418
+ readJsonFile(inputPaths.before),
2419
+ readJsonFile(inputPaths.after)
2420
+ ]);
2421
+ const reports = {
2422
+ before: reportSchema.parse(reportBefore),
2423
+ after: reportSchema.parse(reportAfter)
2424
+ };
2425
+ const reportsDiff = compareReports(reports);
2426
+ return Promise.all(
2427
+ format.map(async (fmt) => {
2428
+ const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
2429
+ const content = reportsDiffToFileContent(reportsDiff, fmt);
2430
+ await ensureDirectoryExists(outputDir);
2431
+ await writeFile2(outputPath, content);
2432
+ return outputPath;
2433
+ })
2434
+ );
2435
+ }
2436
+ function compareReports(reports) {
2437
+ const start = performance.now();
2438
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2439
+ const commits = reports.before.commit != null && reports.after.commit != null ? { before: reports.before.commit, after: reports.after.commit } : null;
2440
+ const scoredReports = {
2441
+ before: scoreReport(reports.before),
2442
+ after: scoreReport(reports.after)
2443
+ };
2444
+ const categories = compareCategories(scoredReports);
2445
+ const groups = compareGroups(scoredReports);
2446
+ const audits = compareAudits2(scoredReports);
2447
+ const duration = calcDuration(start);
2448
+ return {
2449
+ commits,
2450
+ categories,
2451
+ groups,
2452
+ audits,
2453
+ packageName: name,
2454
+ version,
2455
+ date,
2456
+ duration
2457
+ };
2458
+ }
2459
+ function reportsDiffToFileContent(reportsDiff, format) {
2460
+ switch (format) {
2461
+ case "json":
2462
+ return JSON.stringify(reportsDiff, null, 2);
2463
+ case "md":
2464
+ return generateMdReportsDiff(reportsDiff);
2465
+ }
2466
+ }
2467
+
2468
+ // packages/core/src/lib/implementation/read-rc-file.ts
2469
+ import { join as join6 } from "node:path";
1799
2470
  var ConfigPathError = class extends Error {
1800
2471
  constructor(configPath) {
1801
2472
  super(`Provided path '${configPath}' is not valid.`);
@@ -1829,7 +2500,7 @@ async function autoloadRc(tsconfig) {
1829
2500
  );
1830
2501
  }
1831
2502
  return readRcByPath(
1832
- join5(process.cwd(), `${CONFIG_FILE_NAME}.${ext}`),
2503
+ join6(process.cwd(), `${CONFIG_FILE_NAME}.${ext}`),
1833
2504
  tsconfig
1834
2505
  );
1835
2506
  }
@@ -1969,6 +2640,8 @@ export {
1969
2640
  autoloadRc,
1970
2641
  collect,
1971
2642
  collectAndPersistReports,
2643
+ compareReportFiles,
2644
+ compareReports,
1972
2645
  executePlugin,
1973
2646
  executePlugins,
1974
2647
  persistReport,