@code-pushup/cli 0.26.0 → 0.27.1

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
@@ -57,7 +57,7 @@ function getMissingRefsForCategories(categories, plugins) {
57
57
  ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
58
58
  );
59
59
  const groupRefsFromPlugins = plugins.flatMap(
60
- ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
60
+ ({ groups: groups2, slug: pluginSlug }) => Array.isArray(groups2) ? groups2.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
61
61
  );
62
62
  const missingGroupRefs = hasMissingStrings(
63
63
  groupRefsFromCategory,
@@ -92,6 +92,9 @@ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(
92
92
  var urlSchema = z.string().url();
93
93
  var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
94
94
  var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
95
+ var scoreSchema = z.number({
96
+ description: "Value between 0 and 1"
97
+ }).min(0).max(1);
95
98
  function metaSchema(options2) {
96
99
  const {
97
100
  descriptionDescription,
@@ -223,6 +226,8 @@ var issueSchema = z3.object(
223
226
  );
224
227
 
225
228
  // packages/models/src/lib/audit-output.ts
229
+ var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
230
+ var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
226
231
  var auditDetailsSchema = z4.object(
227
232
  {
228
233
  issues: z4.array(issueSchema, { description: "List of findings" })
@@ -232,11 +237,9 @@ var auditDetailsSchema = z4.object(
232
237
  var auditOutputSchema = z4.object(
233
238
  {
234
239
  slug: slugSchema.describe("Reference to audit"),
235
- displayValue: z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional(),
236
- value: nonnegativeIntSchema.describe("Raw numeric value"),
237
- score: z4.number({
238
- description: "Value between 0 and 1"
239
- }).min(0).max(1),
240
+ displayValue: auditDisplayValueSchema,
241
+ value: auditValueSchema,
242
+ score: scoreSchema,
240
243
  details: auditDetailsSchema.optional()
241
244
  },
242
245
  { description: "Audit information" }
@@ -375,28 +378,28 @@ var groupSchema = scorableSchema(
375
378
  var groupsSchema = z8.array(groupSchema, {
376
379
  description: "List of groups"
377
380
  }).optional().refine(
378
- (groups) => !getDuplicateSlugsInGroups(groups),
379
- (groups) => ({
380
- message: duplicateSlugsInGroupsErrorMsg(groups)
381
+ (groups2) => !getDuplicateSlugsInGroups(groups2),
382
+ (groups2) => ({
383
+ message: duplicateSlugsInGroupsErrorMsg(groups2)
381
384
  })
382
385
  );
383
- function duplicateRefsInGroupsErrorMsg(groups) {
384
- const duplicateRefs = getDuplicateRefsInGroups(groups);
386
+ function duplicateRefsInGroupsErrorMsg(groups2) {
387
+ const duplicateRefs = getDuplicateRefsInGroups(groups2);
385
388
  return `In plugin groups the following references are not unique: ${errorItems(
386
389
  duplicateRefs
387
390
  )}`;
388
391
  }
389
- function getDuplicateRefsInGroups(groups) {
390
- return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
392
+ function getDuplicateRefsInGroups(groups2) {
393
+ return hasDuplicateStrings(groups2.map(({ slug: ref }) => ref).filter(exists));
391
394
  }
392
- function duplicateSlugsInGroupsErrorMsg(groups) {
393
- const duplicateRefs = getDuplicateSlugsInGroups(groups);
395
+ function duplicateSlugsInGroupsErrorMsg(groups2) {
396
+ const duplicateRefs = getDuplicateSlugsInGroups(groups2);
394
397
  return `In groups the following slugs are not unique: ${errorItems(
395
398
  duplicateRefs
396
399
  )}`;
397
400
  }
398
- function getDuplicateSlugsInGroups(groups) {
399
- return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
401
+ function getDuplicateSlugsInGroups(groups2) {
402
+ return Array.isArray(groups2) ? hasDuplicateStrings(groups2.map(({ slug }) => slug)) : false;
400
403
  }
401
404
 
402
405
  // packages/models/src/lib/runner-config.ts
@@ -527,15 +530,15 @@ var pluginReportSchema = pluginMetaSchema.merge(
527
530
  )
528
531
  })
529
532
  );
530
- function missingRefsFromGroupsErrorMsg2(audits, groups) {
531
- const missingRefs = getMissingRefsFromGroups2(audits, groups);
533
+ function missingRefsFromGroupsErrorMsg2(audits, groups2) {
534
+ const missingRefs = getMissingRefsFromGroups2(audits, groups2);
532
535
  return `group references need to point to an existing audit in this plugin report: ${errorItems(
533
536
  missingRefs
534
537
  )}`;
535
538
  }
536
- function getMissingRefsFromGroups2(audits, groups) {
539
+ function getMissingRefsFromGroups2(audits, groups2) {
537
540
  return hasMissingStrings(
538
- groups.flatMap(
541
+ groups2.flatMap(
539
542
  ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
540
543
  ),
541
544
  audits.map(({ slug }) => slug)
@@ -568,6 +571,138 @@ var reportSchema = packageVersionSchema({
568
571
  })
569
572
  );
570
573
 
574
+ // packages/models/src/lib/reports-diff.ts
575
+ import { z as z14 } from "zod";
576
+ function makeComparisonSchema(schema) {
577
+ const sharedDescription = schema.description || "Result";
578
+ return z14.object({
579
+ before: schema.describe(`${sharedDescription} (source commit)`),
580
+ after: schema.describe(`${sharedDescription} (target commit)`)
581
+ });
582
+ }
583
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
584
+ return z14.object(
585
+ {
586
+ changed: z14.array(diffSchema),
587
+ unchanged: z14.array(resultSchema),
588
+ added: z14.array(resultSchema),
589
+ removed: z14.array(resultSchema)
590
+ },
591
+ { description }
592
+ );
593
+ }
594
+ var scorableMetaSchema = z14.object({ slug: slugSchema, title: titleSchema });
595
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
596
+ z14.object({
597
+ plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
598
+ })
599
+ );
600
+ var scorableDiffSchema = scorableMetaSchema.merge(
601
+ z14.object({
602
+ scores: makeComparisonSchema(scoreSchema).merge(
603
+ z14.object({
604
+ diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
605
+ })
606
+ ).describe("Score comparison")
607
+ })
608
+ );
609
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
610
+ scorableWithPluginMetaSchema
611
+ );
612
+ var categoryDiffSchema = scorableDiffSchema;
613
+ var groupDiffSchema = scorableWithPluginDiffSchema;
614
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
615
+ z14.object({
616
+ values: makeComparisonSchema(auditValueSchema).merge(
617
+ z14.object({
618
+ diff: z14.number().int().describe("Value change (`values.after - values.before`)")
619
+ })
620
+ ).describe("Audit `value` comparison"),
621
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
622
+ "Audit `displayValue` comparison"
623
+ )
624
+ })
625
+ );
626
+ var categoryResultSchema = scorableMetaSchema.merge(
627
+ z14.object({ score: scoreSchema })
628
+ );
629
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
630
+ z14.object({ score: scoreSchema })
631
+ );
632
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
633
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
634
+ );
635
+ var reportsDiffSchema = z14.object({
636
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
637
+ categories: makeArraysComparisonSchema(
638
+ categoryDiffSchema,
639
+ categoryResultSchema,
640
+ "Changes affecting categories"
641
+ ),
642
+ groups: makeArraysComparisonSchema(
643
+ groupDiffSchema,
644
+ groupResultSchema,
645
+ "Changes affecting groups"
646
+ ),
647
+ audits: makeArraysComparisonSchema(
648
+ auditDiffSchema,
649
+ auditResultSchema,
650
+ "Changes affecting audits"
651
+ )
652
+ }).merge(
653
+ packageVersionSchema({
654
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
655
+ required: true
656
+ })
657
+ ).merge(
658
+ executionMetaSchema({
659
+ descriptionDate: "Start date and time of the compare run",
660
+ descriptionDuration: "Duration of the compare run in ms"
661
+ })
662
+ );
663
+
664
+ // packages/utils/src/lib/diff.ts
665
+ function matchArrayItemsByKey({
666
+ before,
667
+ after,
668
+ key
669
+ }) {
670
+ const pairs = [];
671
+ const added = [];
672
+ const afterKeys = /* @__PURE__ */ new Set();
673
+ const keyFn = typeof key === "function" ? key : (item) => item[key];
674
+ for (const afterItem of after) {
675
+ const afterKey = keyFn(afterItem);
676
+ afterKeys.add(afterKey);
677
+ const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
678
+ if (match) {
679
+ pairs.push({ before: match, after: afterItem });
680
+ } else {
681
+ added.push(afterItem);
682
+ }
683
+ }
684
+ const removed = before.filter(
685
+ (beforeItem) => !afterKeys.has(keyFn(beforeItem))
686
+ );
687
+ return {
688
+ pairs,
689
+ added,
690
+ removed
691
+ };
692
+ }
693
+ function comparePairs(pairs, equalsFn) {
694
+ return pairs.reduce(
695
+ (acc, pair) => ({
696
+ ...acc,
697
+ ...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
698
+ }),
699
+ {
700
+ changed: [],
701
+ unchanged: []
702
+ }
703
+ );
704
+ }
705
+
571
706
  // packages/utils/src/lib/execute-process.ts
572
707
  import { spawn } from "node:child_process";
573
708
 
@@ -576,7 +711,7 @@ import { join } from "node:path";
576
711
 
577
712
  // packages/utils/src/lib/file-system.ts
578
713
  import { bundleRequire } from "bundle-require";
579
- import chalk from "chalk";
714
+ import chalk2 from "chalk";
580
715
  import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
581
716
 
582
717
  // packages/utils/src/lib/formatting.ts
@@ -621,33 +756,106 @@ function isPromiseRejectedResult(result) {
621
756
  return result.status === "rejected";
622
757
  }
623
758
 
759
+ // packages/utils/src/lib/logging.ts
760
+ import isaacs_cliui from "@isaacs/cliui";
761
+ import { cliui } from "@poppinss/cliui";
762
+ import chalk from "chalk";
763
+
764
+ // packages/utils/src/lib/reports/constants.ts
765
+ var TERMINAL_WIDTH = 80;
766
+ var NEW_LINE = "\n";
767
+ var SCORE_COLOR_RANGE = {
768
+ GREEN_MIN: 0.9,
769
+ YELLOW_MIN: 0.5
770
+ };
771
+ var FOOTER_PREFIX = "Made with \u2764 by";
772
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
773
+ var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
774
+ var reportHeadlineText = "Code PushUp Report";
775
+ var reportOverviewTableHeaders = [
776
+ "\u{1F3F7} Category",
777
+ "\u2B50 Score",
778
+ "\u{1F6E1} Audits"
779
+ ];
780
+ var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
781
+ var reportMetaTableHeaders = [
782
+ "Commit",
783
+ "Version",
784
+ "Duration",
785
+ "Plugins",
786
+ "Categories",
787
+ "Audits"
788
+ ];
789
+ var pluginMetaTableHeaders = [
790
+ "Plugin",
791
+ "Audits",
792
+ "Version",
793
+ "Duration"
794
+ ];
795
+ var detailsTableHeaders = [
796
+ "Severity",
797
+ "Message",
798
+ "Source file",
799
+ "Line(s)"
800
+ ];
801
+
802
+ // packages/utils/src/lib/logging.ts
803
+ var singletonUiInstance;
804
+ function ui() {
805
+ if (singletonUiInstance === void 0) {
806
+ singletonUiInstance = cliui();
807
+ }
808
+ return {
809
+ ...singletonUiInstance,
810
+ row: (args) => {
811
+ logListItem(args);
812
+ }
813
+ };
814
+ }
815
+ var singletonisaacUi;
816
+ function logListItem(args) {
817
+ if (singletonisaacUi === void 0) {
818
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
819
+ }
820
+ singletonisaacUi.div(...args);
821
+ const content = singletonisaacUi.toString();
822
+ singletonisaacUi.rows = [];
823
+ singletonUiInstance?.logger.log(content);
824
+ }
825
+ function link(text) {
826
+ return chalk.underline(chalk.blueBright(text));
827
+ }
828
+
624
829
  // packages/utils/src/lib/log-results.ts
625
- function logMultipleResults(results, messagePrefix, succeededCallback, failedCallback) {
626
- if (succeededCallback) {
830
+ function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
831
+ if (succeededTransform) {
627
832
  const succeededResults = results.filter(isPromiseFulfilledResult);
628
833
  logPromiseResults(
629
834
  succeededResults,
630
835
  `${messagePrefix} successfully: `,
631
- succeededCallback
836
+ succeededTransform
632
837
  );
633
838
  }
634
- if (failedCallback) {
839
+ if (failedTransform) {
635
840
  const failedResults = results.filter(isPromiseRejectedResult);
636
841
  logPromiseResults(
637
842
  failedResults,
638
843
  `${messagePrefix} failed: `,
639
- failedCallback
844
+ failedTransform
640
845
  );
641
846
  }
642
847
  }
643
- function logPromiseResults(results, logMessage, callback) {
848
+ function logPromiseResults(results, logMessage, getMsg) {
644
849
  if (results.length > 0) {
645
- if (results[0]?.status === "fulfilled") {
646
- console.info(logMessage);
647
- } else {
648
- console.warn(logMessage);
649
- }
650
- results.forEach(callback);
850
+ const log2 = results[0]?.status === "fulfilled" ? (m) => {
851
+ ui().logger.success(m);
852
+ } : (m) => {
853
+ ui().logger.warning(m);
854
+ };
855
+ log2(logMessage);
856
+ results.forEach((result) => {
857
+ log2(getMsg(result));
858
+ });
651
859
  }
652
860
  }
653
861
 
@@ -681,26 +889,24 @@ async function ensureDirectoryExists(baseDir) {
681
889
  await mkdir(baseDir, { recursive: true });
682
890
  return;
683
891
  } catch (error) {
684
- console.error(error.message);
892
+ ui().logger.error(error.message);
685
893
  if (error.code !== "EEXIST") {
686
894
  throw error;
687
895
  }
688
896
  }
689
897
  }
690
898
  function logMultipleFileResults(fileResults, messagePrefix) {
691
- const succeededCallback = (result) => {
899
+ const succeededTransform = (result) => {
692
900
  const [fileName, size] = result.value;
693
- const formattedSize = size ? ` (${chalk.gray(formatBytes(size))})` : "";
694
- console.info(`- ${chalk.bold(fileName)}${formattedSize}`);
695
- };
696
- const failedCallback = (result) => {
697
- console.warn(`- ${chalk.bold(result.reason)}`);
901
+ const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
902
+ return `- ${chalk2.bold(fileName)}${formattedSize}`;
698
903
  };
904
+ const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
699
905
  logMultipleResults(
700
906
  fileResults,
701
907
  messagePrefix,
702
- succeededCallback,
703
- failedCallback
908
+ succeededTransform,
909
+ failedTransform
704
910
  );
705
911
  }
706
912
  var NoExportError = class extends Error {
@@ -719,43 +925,83 @@ async function importEsmModule(options2) {
719
925
  return mod.default;
720
926
  }
721
927
 
722
- // packages/utils/src/lib/reports/constants.ts
723
- var TERMINAL_WIDTH = 80;
724
- var NEW_LINE = "\n";
725
- var SCORE_COLOR_RANGE = {
726
- GREEN_MIN: 0.9,
727
- YELLOW_MIN: 0.5
928
+ // packages/utils/src/lib/reports/md/details.ts
929
+ function details(title, content, cfg = { open: false }) {
930
+ return `<details${cfg.open ? " open" : ""}>
931
+ <summary>${title}</summary>
932
+
933
+ ${content}
934
+
935
+ </details>
936
+ `;
937
+ }
938
+
939
+ // packages/utils/src/lib/reports/md/font-style.ts
940
+ var stylesMap = {
941
+ i: "_",
942
+ // italic
943
+ b: "**",
944
+ // bold
945
+ s: "~",
946
+ // strike through
947
+ c: "`"
948
+ // code
728
949
  };
729
- var FOOTER_PREFIX = "Made with \u2764 by";
730
- var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
731
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
732
- var reportHeadlineText = "Code PushUp Report";
733
- var reportOverviewTableHeaders = [
734
- "\u{1F3F7} Category",
735
- "\u2B50 Score",
736
- "\u{1F6E1} Audits"
737
- ];
738
- var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
739
- var reportMetaTableHeaders = [
740
- "Commit",
741
- "Version",
742
- "Duration",
743
- "Plugins",
744
- "Categories",
745
- "Audits"
746
- ];
747
- var pluginMetaTableHeaders = [
748
- "Plugin",
749
- "Audits",
750
- "Version",
751
- "Duration"
752
- ];
753
- var detailsTableHeaders = [
754
- "Severity",
755
- "Message",
756
- "Source file",
757
- "Line(s)"
758
- ];
950
+ function style(text, styles = ["b"]) {
951
+ return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
952
+ }
953
+
954
+ // packages/utils/src/lib/reports/md/headline.ts
955
+ function headline(text, hierarchy = 1) {
956
+ return `${"#".repeat(hierarchy)} ${text}`;
957
+ }
958
+ function h2(text) {
959
+ return headline(text, 2);
960
+ }
961
+ function h3(text) {
962
+ return headline(text, 3);
963
+ }
964
+
965
+ // packages/utils/src/lib/reports/md/link.ts
966
+ function link2(href, text) {
967
+ return `[${text || href}](${href})`;
968
+ }
969
+
970
+ // packages/utils/src/lib/reports/md/list.ts
971
+ function li(text, order = "unordered") {
972
+ const style2 = order === "unordered" ? "-" : "- [ ]";
973
+ return `${style2} ${text}`;
974
+ }
975
+
976
+ // packages/utils/src/lib/reports/md/table.ts
977
+ var alignString = /* @__PURE__ */ new Map([
978
+ ["l", ":--"],
979
+ ["c", ":--:"],
980
+ ["r", "--:"]
981
+ ]);
982
+ function tableMd(data, align) {
983
+ if (data.length === 0) {
984
+ throw new Error("Data can't be empty");
985
+ }
986
+ const alignmentSetting = align ?? data[0]?.map(() => "c");
987
+ const tableContent = data.map((arr) => `|${arr.join("|")}|`);
988
+ const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
989
+ return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
990
+ }
991
+ function tableHtml(data) {
992
+ if (data.length === 0) {
993
+ throw new Error("Data can't be empty");
994
+ }
995
+ const tableContent = data.map((arr, index) => {
996
+ if (index === 0) {
997
+ const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
998
+ return `<tr>${headerRow}</tr>`;
999
+ }
1000
+ const row = arr.map((s) => `<td>${s}</td>`).join("");
1001
+ return `<tr>${row}</tr>`;
1002
+ });
1003
+ return `<table>${tableContent.join("")}</table>`;
1004
+ }
759
1005
 
760
1006
  // packages/utils/src/lib/reports/utils.ts
761
1007
  function formatReportScore(score) {
@@ -952,7 +1198,7 @@ var ProcessError = class extends Error {
952
1198
  }
953
1199
  };
954
1200
  function executeProcess(cfg) {
955
- const { observer, cwd, command, args } = cfg;
1201
+ const { observer, cwd, command, args, alwaysResolve = false } = cfg;
956
1202
  const { onStdout, onError, onComplete } = observer ?? {};
957
1203
  const date = (/* @__PURE__ */ new Date()).toISOString();
958
1204
  const start = performance.now();
@@ -972,7 +1218,7 @@ function executeProcess(cfg) {
972
1218
  });
973
1219
  process2.on("close", (code) => {
974
1220
  const timings = { date, duration: calcDuration(start) };
975
- if (code === 0) {
1221
+ if (code === 0 || alwaysResolve) {
976
1222
  onComplete?.();
977
1223
  resolve({ code, stdout, stderr, ...timings });
978
1224
  } else {
@@ -984,6 +1230,14 @@ function executeProcess(cfg) {
984
1230
  });
985
1231
  }
986
1232
 
1233
+ // packages/utils/src/lib/filter.ts
1234
+ function filterItemRefsBy(items, refFilterFn) {
1235
+ return items.map((item) => ({
1236
+ ...item,
1237
+ refs: item.refs.filter(refFilterFn)
1238
+ })).filter((item) => item.refs.length);
1239
+ }
1240
+
987
1241
  // packages/utils/src/lib/git.ts
988
1242
  import { isAbsolute, join as join2, relative } from "node:path";
989
1243
  import { simpleGit } from "simple-git";
@@ -1001,14 +1255,14 @@ function toUnixPath(path) {
1001
1255
 
1002
1256
  // packages/utils/src/lib/git.ts
1003
1257
  async function getLatestCommit(git = simpleGit()) {
1004
- const log = await git.log({
1258
+ const log2 = await git.log({
1005
1259
  maxCount: 1,
1006
1260
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1007
1261
  });
1008
- if (!log.latest) {
1262
+ if (!log2.latest) {
1009
1263
  return null;
1010
1264
  }
1011
- return commitSchema.parse(log.latest);
1265
+ return commitSchema.parse(log2.latest);
1012
1266
  }
1013
1267
  function getGitRoot(git = simpleGit()) {
1014
1268
  return git.revparse("--show-toplevel");
@@ -1027,12 +1281,6 @@ function groupByStatus(results) {
1027
1281
  );
1028
1282
  }
1029
1283
 
1030
- // packages/utils/src/lib/logging.ts
1031
- import chalk2 from "chalk";
1032
- function link(text) {
1033
- return chalk2.underline(chalk2.blueBright(text));
1034
- }
1035
-
1036
1284
  // packages/utils/src/lib/progress.ts
1037
1285
  import chalk3 from "chalk";
1038
1286
  import { MultiProgressBars } from "multi-progress-bars";
@@ -1088,80 +1336,105 @@ function getProgressBar(taskName) {
1088
1336
  };
1089
1337
  }
1090
1338
 
1091
- // packages/utils/src/lib/reports/md/details.ts
1092
- function details(title, content, cfg = { open: false }) {
1093
- return `<details${cfg.open ? " open" : ""}>
1094
- <summary>${title}</summary>
1095
- ${content}
1096
- </details>
1097
- `;
1098
- }
1099
-
1100
- // packages/utils/src/lib/reports/md/font-style.ts
1101
- var stylesMap = {
1102
- i: "_",
1103
- // italic
1104
- b: "**",
1105
- // bold
1106
- s: "~",
1107
- // strike through
1108
- c: "`"
1109
- // code
1110
- };
1111
- function style(text, styles = ["b"]) {
1112
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
1113
- }
1114
-
1115
- // packages/utils/src/lib/reports/md/headline.ts
1116
- function headline(text, hierarchy = 1) {
1117
- return `${"#".repeat(hierarchy)} ${text}`;
1339
+ // packages/utils/src/lib/reports/log-stdout-summary.ts
1340
+ import chalk4 from "chalk";
1341
+ function log(msg = "") {
1342
+ ui().logger.log(msg);
1118
1343
  }
1119
- function h2(text) {
1120
- return headline(text, 2);
1344
+ function logStdoutSummary(report) {
1345
+ const printCategories = report.categories.length > 0;
1346
+ log(reportToHeaderSection(report));
1347
+ log();
1348
+ logPlugins(report);
1349
+ if (printCategories) {
1350
+ logCategories(report);
1351
+ }
1352
+ log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1353
+ log();
1121
1354
  }
1122
- function h3(text) {
1123
- return headline(text, 3);
1355
+ function reportToHeaderSection(report) {
1356
+ const { packageName, version: version2 } = report;
1357
+ return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1124
1358
  }
1125
-
1126
- // packages/utils/src/lib/reports/md/link.ts
1127
- function link2(href, text) {
1128
- return `[${text || href}](${href})`;
1359
+ function logPlugins(report) {
1360
+ const { plugins } = report;
1361
+ plugins.forEach((plugin) => {
1362
+ const { title, audits } = plugin;
1363
+ log();
1364
+ log(chalk4.magentaBright.bold(`${title} audits`));
1365
+ log();
1366
+ audits.forEach((audit) => {
1367
+ ui().row([
1368
+ {
1369
+ text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
1370
+ width: 2,
1371
+ padding: [0, 1, 0, 0]
1372
+ },
1373
+ {
1374
+ text: audit.title,
1375
+ // eslint-disable-next-line no-magic-numbers
1376
+ padding: [0, 3, 0, 0]
1377
+ },
1378
+ {
1379
+ text: chalk4.cyanBright(audit.displayValue || `${audit.value}`),
1380
+ width: 10,
1381
+ padding: [0, 0, 0, 0]
1382
+ }
1383
+ ]);
1384
+ });
1385
+ log();
1386
+ });
1129
1387
  }
1130
-
1131
- // packages/utils/src/lib/reports/md/list.ts
1132
- function li(text, order = "unordered") {
1133
- const style2 = order === "unordered" ? "-" : "- [ ]";
1134
- return `${style2} ${text}`;
1388
+ function logCategories({ categories, plugins }) {
1389
+ const hAlign = (idx) => idx === 0 ? "left" : "right";
1390
+ const rows = categories.map(({ title, score, refs }) => [
1391
+ title,
1392
+ applyScoreColor({ score }),
1393
+ countCategoryAudits(refs, plugins)
1394
+ ]);
1395
+ const table = ui().table();
1396
+ table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1397
+ table.head(
1398
+ reportRawOverviewTableHeaders.map((heading, idx) => ({
1399
+ content: chalk4.cyan(heading),
1400
+ hAlign: hAlign(idx)
1401
+ }))
1402
+ );
1403
+ rows.forEach(
1404
+ (row) => table.row(
1405
+ row.map((content, idx) => ({
1406
+ content: content.toString(),
1407
+ hAlign: hAlign(idx)
1408
+ }))
1409
+ )
1410
+ );
1411
+ log(chalk4.magentaBright.bold("Categories"));
1412
+ log();
1413
+ table.render();
1414
+ log();
1135
1415
  }
1136
-
1137
- // packages/utils/src/lib/reports/md/table.ts
1138
- var alignString = /* @__PURE__ */ new Map([
1139
- ["l", ":--"],
1140
- ["c", ":--:"],
1141
- ["r", "--:"]
1142
- ]);
1143
- function tableMd(data, align) {
1144
- if (data.length === 0) {
1145
- throw new Error("Data can't be empty");
1416
+ function applyScoreColor({ score, text }) {
1417
+ const formattedScore = text ?? formatReportScore(score);
1418
+ const style2 = text ? chalk4 : chalk4.bold;
1419
+ if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1420
+ return style2.green(formattedScore);
1146
1421
  }
1147
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1148
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1149
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1150
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1151
- }
1152
- function tableHtml(data) {
1153
- if (data.length === 0) {
1154
- throw new Error("Data can't be empty");
1422
+ if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1423
+ return style2.yellow(formattedScore);
1155
1424
  }
1156
- const tableContent = data.map((arr, index) => {
1157
- if (index === 0) {
1158
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1159
- return `<tr>${headerRow}</tr>`;
1160
- }
1161
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1162
- return `<tr>${row}</tr>`;
1163
- });
1164
- return `<table>${tableContent.join("")}</table>`;
1425
+ return style2.red(formattedScore);
1426
+ }
1427
+
1428
+ // packages/utils/src/lib/reports/flatten-plugins.ts
1429
+ function listGroupsFromAllPlugins(report) {
1430
+ return report.plugins.flatMap(
1431
+ (plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
1432
+ );
1433
+ }
1434
+ function listAuditsFromAllPlugins(report) {
1435
+ return report.plugins.flatMap(
1436
+ (plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
1437
+ );
1165
1438
  }
1166
1439
 
1167
1440
  // packages/utils/src/lib/reports/generate-md-report.ts
@@ -1170,7 +1443,7 @@ function generateMdReport(report) {
1170
1443
  return (
1171
1444
  // header section
1172
1445
  // eslint-disable-next-line prefer-template
1173
- reportToHeaderSection() + NEW_LINE + // categories overview section
1446
+ reportToHeaderSection2() + NEW_LINE + // categories overview section
1174
1447
  (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1175
1448
  (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1176
1449
  reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
@@ -1178,7 +1451,7 @@ function generateMdReport(report) {
1178
1451
  `${FOOTER_PREFIX} ${link2(README_LINK, "Code PushUp")}`
1179
1452
  );
1180
1453
  }
1181
- function reportToHeaderSection() {
1454
+ function reportToHeaderSection2() {
1182
1455
  return headline(reportHeadlineText) + NEW_LINE;
1183
1456
  }
1184
1457
  function reportToOverviewSection(report) {
@@ -1351,82 +1624,6 @@ function getAuditResult(audit, isHtml = false) {
1351
1624
  return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1352
1625
  }
1353
1626
 
1354
- // packages/utils/src/lib/reports/generate-stdout-summary.ts
1355
- import cliui from "@isaacs/cliui";
1356
- import chalk4 from "chalk";
1357
- import CliTable3 from "cli-table3";
1358
- function addLine(line = "") {
1359
- return line + NEW_LINE;
1360
- }
1361
- function generateStdoutSummary(report) {
1362
- const printCategories = report.categories.length > 0;
1363
- return addLine(reportToHeaderSection2(report)) + addLine() + addLine(reportToDetailSection(report)) + (printCategories ? addLine(reportToOverviewSection2(report)) : "") + addLine(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1364
- }
1365
- function reportToHeaderSection2(report) {
1366
- const { packageName, version: version2 } = report;
1367
- return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1368
- }
1369
- function reportToDetailSection(report) {
1370
- const { plugins } = report;
1371
- return plugins.reduce((acc, plugin) => {
1372
- const { title, audits } = plugin;
1373
- const ui2 = cliui({ width: TERMINAL_WIDTH });
1374
- audits.forEach((audit) => {
1375
- ui2.div(
1376
- {
1377
- text: withColor({ score: audit.score, text: "\u25CF" }),
1378
- width: 2,
1379
- padding: [0, 1, 0, 0]
1380
- },
1381
- {
1382
- text: audit.title,
1383
- // eslint-disable-next-line no-magic-numbers
1384
- padding: [0, 3, 0, 0]
1385
- },
1386
- {
1387
- text: chalk4.cyanBright(audit.displayValue || `${audit.value}`),
1388
- width: 10,
1389
- padding: [0, 0, 0, 0]
1390
- }
1391
- );
1392
- });
1393
- return acc + addLine() + addLine(chalk4.magentaBright.bold(`${title} audits`)) + addLine() + addLine(ui2.toString()) + addLine();
1394
- }, "");
1395
- }
1396
- function reportToOverviewSection2({
1397
- categories,
1398
- plugins
1399
- }) {
1400
- const table = new CliTable3({
1401
- // eslint-disable-next-line no-magic-numbers
1402
- colWidths: [TERMINAL_WIDTH - 7 - 8 - 4, 7, 8],
1403
- head: reportRawOverviewTableHeaders,
1404
- colAligns: ["left", "right", "right"],
1405
- style: {
1406
- head: ["cyan"]
1407
- }
1408
- });
1409
- table.push(
1410
- ...categories.map(({ title, score, refs }) => [
1411
- title,
1412
- withColor({ score }),
1413
- countCategoryAudits(refs, plugins)
1414
- ])
1415
- );
1416
- return addLine(chalk4.magentaBright.bold("Categories")) + addLine() + addLine(table.toString());
1417
- }
1418
- function withColor({ score, text }) {
1419
- const formattedScore = text ?? formatReportScore(score);
1420
- const style2 = text ? chalk4 : chalk4.bold;
1421
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1422
- return style2.green(formattedScore);
1423
- }
1424
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1425
- return style2.yellow(formattedScore);
1426
- }
1427
- return style2.red(formattedScore);
1428
- }
1429
-
1430
1627
  // packages/utils/src/lib/reports/scoring.ts
1431
1628
  var GroupRefInvalidError = class extends Error {
1432
1629
  constructor(auditSlug, pluginSlug) {
@@ -1438,7 +1635,7 @@ var GroupRefInvalidError = class extends Error {
1438
1635
  function scoreReport(report) {
1439
1636
  const allScoredAuditsAndGroups = /* @__PURE__ */ new Map();
1440
1637
  const scoredPlugins = report.plugins.map((plugin) => {
1441
- const { groups, ...pluginProps } = plugin;
1638
+ const { groups: groups2, ...pluginProps } = plugin;
1442
1639
  plugin.audits.forEach((audit) => {
1443
1640
  allScoredAuditsAndGroups.set(`${plugin.slug}-${audit.slug}-audit`, audit);
1444
1641
  });
@@ -1451,7 +1648,7 @@ function scoreReport(report) {
1451
1648
  }
1452
1649
  return score;
1453
1650
  }
1454
- const scoredGroups = groups?.map((group) => ({
1651
+ const scoredGroups = groups2?.map((group) => ({
1455
1652
  ...group,
1456
1653
  score: calculateScore(group.refs, groupScoreFn)
1457
1654
  })) ?? [];
@@ -1518,7 +1715,7 @@ function parseScoringParameters(refs, scoreFn) {
1518
1715
  function sortReport(report) {
1519
1716
  const { categories, plugins } = report;
1520
1717
  const sortedCategories = categories.map((category) => {
1521
- const { audits, groups } = category.refs.reduce(
1718
+ const { audits, groups: groups2 } = category.refs.reduce(
1522
1719
  (acc, ref) => ({
1523
1720
  ...acc,
1524
1721
  ...ref.type === "group" ? {
@@ -1529,7 +1726,7 @@ function sortReport(report) {
1529
1726
  }),
1530
1727
  { groups: [], audits: [] }
1531
1728
  );
1532
- const sortedAuditsAndGroups = [...audits, ...groups].sort(
1729
+ const sortedAuditsAndGroups = [...audits, ...groups2].sort(
1533
1730
  compareCategoryAuditsAndGroups
1534
1731
  );
1535
1732
  const sortedRefs = [...category.refs].sort((a, b) => {
@@ -1566,9 +1763,9 @@ function sortPlugins(plugins) {
1566
1763
 
1567
1764
  // packages/utils/src/lib/verbose-utils.ts
1568
1765
  function getLogVerbose(verbose = false) {
1569
- return (...args) => {
1766
+ return (msg) => {
1570
1767
  if (verbose) {
1571
- console.info(...args);
1768
+ ui().logger.info(msg);
1572
1769
  }
1573
1770
  };
1574
1771
  }
@@ -1586,7 +1783,7 @@ var verboseUtils = (verbose = false) => ({
1586
1783
 
1587
1784
  // packages/core/package.json
1588
1785
  var name = "@code-pushup/core";
1589
- var version = "0.26.0";
1786
+ var version = "0.27.1";
1590
1787
 
1591
1788
  // packages/core/src/lib/implementation/execute-plugin.ts
1592
1789
  import chalk5 from "chalk";
@@ -1656,7 +1853,7 @@ async function executePlugin(pluginConfig, onProgress) {
1656
1853
  audits: pluginConfigAudits,
1657
1854
  description,
1658
1855
  docsUrl,
1659
- groups,
1856
+ groups: groups2,
1660
1857
  ...pluginMeta
1661
1858
  } = pluginConfig;
1662
1859
  const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
@@ -1678,7 +1875,7 @@ async function executePlugin(pluginConfig, onProgress) {
1678
1875
  audits: auditReports,
1679
1876
  ...description && { description },
1680
1877
  ...docsUrl && { docsUrl },
1681
- ...groups && { groups }
1878
+ ...groups2 && { groups: groups2 }
1682
1879
  };
1683
1880
  }
1684
1881
  async function executePlugins(plugins, options2) {
@@ -1699,11 +1896,9 @@ async function executePlugins(plugins, options2) {
1699
1896
  }
1700
1897
  }, Promise.resolve([]));
1701
1898
  progressBar?.endProgress("Done running plugins");
1702
- const errorsCallback = ({ reason }) => {
1703
- console.error(reason);
1704
- };
1899
+ const errorsTransform = ({ reason }) => String(reason);
1705
1900
  const results = await Promise.allSettled(pluginsResult);
1706
- logMultipleResults(results, "Plugins", void 0, errorsCallback);
1901
+ logMultipleResults(results, "Plugins", void 0, errorsTransform);
1707
1902
  const { fulfilled, rejected } = groupByStatus(results);
1708
1903
  if (rejected.length > 0) {
1709
1904
  const errorMessages = rejected.map(({ reason }) => String(reason)).join(", ");
@@ -1758,7 +1953,7 @@ var PersistError = class extends Error {
1758
1953
  async function persistReport(report, options2) {
1759
1954
  const { outputDir, filename, format } = options2;
1760
1955
  const sortedScoredReport = sortReport(scoreReport(report));
1761
- console.info(generateStdoutSummary(sortedScoredReport));
1956
+ logStdoutSummary(sortedScoredReport);
1762
1957
  const results = format.map((reportType) => {
1763
1958
  switch (reportType) {
1764
1959
  case "json":
@@ -1777,7 +1972,7 @@ async function persistReport(report, options2) {
1777
1972
  try {
1778
1973
  await mkdir2(outputDir, { recursive: true });
1779
1974
  } catch (error) {
1780
- console.warn(error);
1975
+ ui().logger.warning(error.toString());
1781
1976
  throw new PersistDirError(outputDir);
1782
1977
  }
1783
1978
  }
@@ -1792,7 +1987,7 @@ async function persistReport(report, options2) {
1792
1987
  }
1793
1988
  async function persistResult(reportPath, content) {
1794
1989
  return writeFile(reportPath, content).then(() => stat2(reportPath)).then((stats) => [reportPath, stats.size]).catch((error) => {
1795
- console.warn(error);
1990
+ ui().logger.warning(error.toString());
1796
1991
  throw new PersistError(reportPath);
1797
1992
  });
1798
1993
  }
@@ -1813,6 +2008,189 @@ async function collectAndPersistReports(options2) {
1813
2008
  });
1814
2009
  }
1815
2010
 
2011
+ // packages/core/src/lib/compare.ts
2012
+ import { writeFile as writeFile2 } from "node:fs/promises";
2013
+
2014
+ // packages/core/src/lib/implementation/compare-scorables.ts
2015
+ function compareCategories(reports) {
2016
+ const { pairs, added, removed } = matchArrayItemsByKey({
2017
+ before: reports.before.categories,
2018
+ after: reports.after.categories,
2019
+ key: "slug"
2020
+ });
2021
+ const { changed, unchanged } = comparePairs(
2022
+ pairs,
2023
+ ({ before, after }) => before.score === after.score
2024
+ );
2025
+ return {
2026
+ changed: changed.map(categoryPairToDiff),
2027
+ unchanged: unchanged.map(categoryToResult),
2028
+ added: added.map(categoryToResult),
2029
+ removed: removed.map(categoryToResult)
2030
+ };
2031
+ }
2032
+ function compareGroups(reports) {
2033
+ const { pairs, added, removed } = matchArrayItemsByKey({
2034
+ before: listGroupsFromAllPlugins(reports.before),
2035
+ after: listGroupsFromAllPlugins(reports.after),
2036
+ key: ({ plugin, group }) => `${plugin.slug}/${group.slug}`
2037
+ });
2038
+ const { changed, unchanged } = comparePairs(
2039
+ pairs,
2040
+ ({ before, after }) => before.group.score === after.group.score
2041
+ );
2042
+ return {
2043
+ changed: changed.map(pluginGroupPairToDiff),
2044
+ unchanged: unchanged.map(pluginGroupToResult),
2045
+ added: added.map(pluginGroupToResult),
2046
+ removed: removed.map(pluginGroupToResult)
2047
+ };
2048
+ }
2049
+ function compareAudits2(reports) {
2050
+ const { pairs, added, removed } = matchArrayItemsByKey({
2051
+ before: listAuditsFromAllPlugins(reports.before),
2052
+ after: listAuditsFromAllPlugins(reports.after),
2053
+ key: ({ plugin, audit }) => `${plugin.slug}/${audit.slug}`
2054
+ });
2055
+ const { changed, unchanged } = comparePairs(
2056
+ pairs,
2057
+ ({ before, after }) => before.audit.value === after.audit.value && before.audit.score === after.audit.score
2058
+ );
2059
+ return {
2060
+ changed: changed.map(pluginAuditPairToDiff),
2061
+ unchanged: unchanged.map(pluginAuditToResult),
2062
+ added: added.map(pluginAuditToResult),
2063
+ removed: removed.map(pluginAuditToResult)
2064
+ };
2065
+ }
2066
+ function categoryToResult(category) {
2067
+ return {
2068
+ slug: category.slug,
2069
+ title: category.title,
2070
+ score: category.score
2071
+ };
2072
+ }
2073
+ function categoryPairToDiff({
2074
+ before,
2075
+ after
2076
+ }) {
2077
+ return {
2078
+ slug: after.slug,
2079
+ title: after.title,
2080
+ scores: {
2081
+ before: before.score,
2082
+ after: after.score,
2083
+ diff: after.score - before.score
2084
+ }
2085
+ };
2086
+ }
2087
+ function pluginGroupToResult({ group, plugin }) {
2088
+ return {
2089
+ slug: group.slug,
2090
+ title: group.title,
2091
+ plugin: {
2092
+ slug: plugin.slug,
2093
+ title: plugin.title
2094
+ },
2095
+ score: group.score
2096
+ };
2097
+ }
2098
+ function pluginGroupPairToDiff({
2099
+ before,
2100
+ after
2101
+ }) {
2102
+ return {
2103
+ slug: after.group.slug,
2104
+ title: after.group.title,
2105
+ plugin: {
2106
+ slug: after.plugin.slug,
2107
+ title: after.plugin.title
2108
+ },
2109
+ scores: {
2110
+ before: before.group.score,
2111
+ after: after.group.score,
2112
+ diff: after.group.score - before.group.score
2113
+ }
2114
+ };
2115
+ }
2116
+ function pluginAuditToResult({ audit, plugin }) {
2117
+ return {
2118
+ slug: audit.slug,
2119
+ title: audit.title,
2120
+ plugin: {
2121
+ slug: plugin.slug,
2122
+ title: plugin.title
2123
+ },
2124
+ score: audit.score,
2125
+ value: audit.value,
2126
+ displayValue: audit.displayValue
2127
+ };
2128
+ }
2129
+ function pluginAuditPairToDiff({
2130
+ before,
2131
+ after
2132
+ }) {
2133
+ return {
2134
+ slug: after.audit.slug,
2135
+ title: after.audit.title,
2136
+ plugin: {
2137
+ slug: after.plugin.slug,
2138
+ title: after.plugin.title
2139
+ },
2140
+ scores: {
2141
+ before: before.audit.score,
2142
+ after: after.audit.score,
2143
+ diff: after.audit.score - before.audit.score
2144
+ },
2145
+ values: {
2146
+ before: before.audit.value,
2147
+ after: after.audit.value,
2148
+ diff: after.audit.value - before.audit.value
2149
+ },
2150
+ displayValues: {
2151
+ before: before.audit.displayValue,
2152
+ after: after.audit.displayValue
2153
+ }
2154
+ };
2155
+ }
2156
+
2157
+ // packages/core/src/lib/compare.ts
2158
+ async function compareReportFiles(inputPaths, outputPath) {
2159
+ const [reportBefore, reportAfter] = await Promise.all([
2160
+ readJsonFile(inputPaths.before),
2161
+ readJsonFile(inputPaths.after)
2162
+ ]);
2163
+ const reports = {
2164
+ before: reportSchema.parse(reportBefore),
2165
+ after: reportSchema.parse(reportAfter)
2166
+ };
2167
+ const reportsDiff = compareReports(reports);
2168
+ await writeFile2(outputPath, JSON.stringify(reportsDiff, null, 2));
2169
+ }
2170
+ function compareReports(reports) {
2171
+ const start = performance.now();
2172
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2173
+ const commits = reports.before.commit != null && reports.after.commit != null ? { before: reports.before.commit, after: reports.after.commit } : null;
2174
+ const scoredReports = {
2175
+ before: scoreReport(reports.before),
2176
+ after: scoreReport(reports.after)
2177
+ };
2178
+ const categories = compareCategories(scoredReports);
2179
+ const groups2 = compareGroups(scoredReports);
2180
+ const audits = compareAudits2(scoredReports);
2181
+ const duration = calcDuration(start);
2182
+ return {
2183
+ commits,
2184
+ categories,
2185
+ groups: groups2,
2186
+ audits,
2187
+ packageName: name,
2188
+ version,
2189
+ date,
2190
+ duration
2191
+ };
2192
+ }
2193
+
1816
2194
  // packages/core/src/lib/implementation/read-rc-file.ts
1817
2195
  import { join as join5 } from "node:path";
1818
2196
  var ConfigPathError = class extends Error {
@@ -1986,15 +2364,7 @@ var CLI_NAME = "Code PushUp CLI";
1986
2364
  var CLI_SCRIPT_NAME = "code-pushup";
1987
2365
 
1988
2366
  // packages/cli/src/lib/implementation/logging.ts
1989
- import { cliui as cliui2 } from "@poppinss/cliui";
1990
2367
  import chalk6 from "chalk";
1991
- var singletonUiInstance;
1992
- function ui() {
1993
- if (singletonUiInstance === void 0) {
1994
- singletonUiInstance = cliui2();
1995
- }
1996
- return singletonUiInstance;
1997
- }
1998
2368
  function renderConfigureCategoriesHint() {
1999
2369
  ui().logger.info(
2000
2370
  chalk6.gray(
@@ -2031,51 +2401,12 @@ function renderIntegratePortalHint() {
2031
2401
  ).render();
2032
2402
  }
2033
2403
 
2034
- // packages/cli/src/lib/implementation/global.utils.ts
2035
- function filterKebabCaseKeys(obj) {
2036
- return Object.entries(obj).filter(([key]) => !key.includes("-")).reduce(
2037
- (acc, [key, value]) => typeof value === "string" || typeof value === "object" && Array.isArray(obj[key]) ? { ...acc, [key]: value } : typeof value === "object" && !Array.isArray(value) && value != null ? {
2038
- ...acc,
2039
- [key]: filterKebabCaseKeys(value)
2040
- } : { ...acc, [key]: value },
2041
- {}
2042
- );
2043
- }
2044
- function logErrorBeforeThrow(fn) {
2045
- return async (...args) => {
2046
- try {
2047
- return await fn(...args);
2048
- } catch (error) {
2049
- console.error(error);
2050
- await new Promise((resolve) => process.stdout.write("", resolve));
2051
- throw error;
2052
- }
2053
- };
2054
- }
2055
- function coerceArray(param) {
2056
- return [...new Set(toArray(param).flatMap((f) => f.split(",")))];
2057
- }
2058
-
2059
- // packages/cli/src/lib/implementation/only-plugins.options.ts
2060
- var onlyPluginsOption = {
2061
- describe: "List of plugins to run. If not set all plugins are run.",
2062
- type: "array",
2063
- default: [],
2064
- coerce: coerceArray
2065
- };
2066
- function yargsOnlyPluginsOptionsDefinition() {
2067
- return {
2068
- onlyPlugins: onlyPluginsOption
2069
- };
2070
- }
2071
-
2072
2404
  // packages/cli/src/lib/autorun/autorun-command.ts
2073
2405
  function yargsAutorunCommandObject() {
2074
2406
  const command = "autorun";
2075
2407
  return {
2076
2408
  command,
2077
2409
  describe: "Shortcut for running collect followed by upload",
2078
- builder: yargsOnlyPluginsOptionsDefinition(),
2079
2410
  handler: async (args) => {
2080
2411
  ui().logger.log(chalk7.bold(CLI_NAME));
2081
2412
  ui().logger.info(chalk7.gray(`Run ${command}...`));
@@ -2112,7 +2443,6 @@ function yargsCollectCommandObject() {
2112
2443
  return {
2113
2444
  command,
2114
2445
  describe: "Run Plugins and collect results",
2115
- builder: yargsOnlyPluginsOptionsDefinition(),
2116
2446
  handler: async (args) => {
2117
2447
  const options2 = args;
2118
2448
  ui().logger.log(chalk8.bold(CLI_NAME));
@@ -2149,15 +2479,79 @@ function renderUploadAutorunHint() {
2149
2479
  ).render();
2150
2480
  }
2151
2481
 
2482
+ // packages/cli/src/lib/compare/compare-command.ts
2483
+ import chalk9 from "chalk";
2484
+ import { join as join6 } from "node:path";
2485
+
2486
+ // packages/cli/src/lib/implementation/compare.options.ts
2487
+ function yargsCompareOptionsDefinition() {
2488
+ return {
2489
+ before: {
2490
+ describe: "Path to source report.json",
2491
+ type: "string",
2492
+ demandOption: true
2493
+ },
2494
+ after: {
2495
+ describe: "Path to target report.json",
2496
+ type: "string",
2497
+ demandOption: true
2498
+ }
2499
+ };
2500
+ }
2501
+
2502
+ // packages/cli/src/lib/compare/compare-command.ts
2503
+ function yargsCompareCommandObject() {
2504
+ const command = "compare";
2505
+ return {
2506
+ command,
2507
+ describe: "Compare 2 report files and create a diff file",
2508
+ builder: yargsCompareOptionsDefinition(),
2509
+ handler: async (args) => {
2510
+ ui().logger.log(chalk9.bold(CLI_NAME));
2511
+ ui().logger.info(chalk9.gray(`Run ${command}...`));
2512
+ const options2 = args;
2513
+ const { before, after, persist } = options2;
2514
+ const outputPath = join6(
2515
+ persist.outputDir,
2516
+ `${persist.filename}-diff.json`
2517
+ );
2518
+ await compareReportFiles({ before, after }, outputPath);
2519
+ ui().logger.info(`Reports diff written to ${chalk9.bold(outputPath)}`);
2520
+ }
2521
+ };
2522
+ }
2523
+
2524
+ // packages/cli/src/lib/implementation/global.utils.ts
2525
+ function filterKebabCaseKeys(obj) {
2526
+ return Object.entries(obj).filter(([key]) => !key.includes("-")).reduce(
2527
+ (acc, [key, value]) => typeof value === "string" || typeof value === "object" && Array.isArray(obj[key]) ? { ...acc, [key]: value } : typeof value === "object" && !Array.isArray(value) && value != null ? {
2528
+ ...acc,
2529
+ [key]: filterKebabCaseKeys(value)
2530
+ } : { ...acc, [key]: value },
2531
+ {}
2532
+ );
2533
+ }
2534
+ function logErrorBeforeThrow(fn) {
2535
+ return async (...args) => {
2536
+ try {
2537
+ return await fn(...args);
2538
+ } catch (error) {
2539
+ console.error(error);
2540
+ await new Promise((resolve) => process.stdout.write("", resolve));
2541
+ throw error;
2542
+ }
2543
+ };
2544
+ }
2545
+ function coerceArray(param) {
2546
+ return [...new Set(toArray(param).flatMap((f) => f.split(",")))];
2547
+ }
2548
+
2152
2549
  // packages/cli/src/lib/print-config/print-config-command.ts
2153
2550
  function yargsConfigCommandObject() {
2154
2551
  const command = "print-config";
2155
2552
  return {
2156
2553
  command,
2157
2554
  describe: "Print config",
2158
- builder: {
2159
- onlyPlugins: onlyPluginsOption
2160
- },
2161
2555
  handler: (yargsArgs) => {
2162
2556
  const { _, $0, ...args } = yargsArgs;
2163
2557
  const cleanArgs = filterKebabCaseKeys(args);
@@ -2167,15 +2561,15 @@ function yargsConfigCommandObject() {
2167
2561
  }
2168
2562
 
2169
2563
  // packages/cli/src/lib/upload/upload-command.ts
2170
- import chalk9 from "chalk";
2564
+ import chalk10 from "chalk";
2171
2565
  function yargsUploadCommandObject() {
2172
2566
  const command = "upload";
2173
2567
  return {
2174
2568
  command,
2175
2569
  describe: "Upload report results to the portal",
2176
2570
  handler: async (args) => {
2177
- ui().logger.log(chalk9.bold(CLI_NAME));
2178
- ui().logger.info(chalk9.gray(`Run ${command}...`));
2571
+ ui().logger.log(chalk10.bold(CLI_NAME));
2572
+ ui().logger.info(chalk10.gray(`Run ${command}...`));
2179
2573
  const options2 = args;
2180
2574
  if (options2.upload == null) {
2181
2575
  renderIntegratePortalHint();
@@ -2196,6 +2590,7 @@ var commands = [
2196
2590
  yargsAutorunCommandObject(),
2197
2591
  yargsCollectCommandObject(),
2198
2592
  yargsUploadCommandObject(),
2593
+ yargsCompareCommandObject(),
2199
2594
  yargsConfigCommandObject()
2200
2595
  ];
2201
2596
 
@@ -2234,58 +2629,64 @@ async function coreConfigMiddleware(processArgs) {
2234
2629
  }
2235
2630
 
2236
2631
  // packages/cli/src/lib/implementation/only-plugins.utils.ts
2237
- import chalk10 from "chalk";
2238
- function filterPluginsBySlug(plugins, { onlyPlugins }) {
2239
- if (!onlyPlugins?.length) {
2240
- return plugins;
2241
- }
2242
- return plugins.filter((plugin) => onlyPlugins.includes(plugin.slug));
2243
- }
2244
- function filterCategoryByPluginSlug(categories, {
2245
- onlyPlugins,
2632
+ import chalk11 from "chalk";
2633
+ function validateOnlyPluginsOption({
2634
+ plugins,
2635
+ categories
2636
+ }, {
2637
+ onlyPlugins = [],
2246
2638
  verbose = false
2247
- }) {
2248
- if (categories.length === 0) {
2249
- return categories;
2250
- }
2251
- if (!onlyPlugins?.length) {
2252
- return categories;
2253
- }
2254
- return categories.filter(
2255
- (category) => category.refs.every((ref) => {
2256
- const isNotSkipped = onlyPlugins.includes(ref.plugin);
2257
- if (!isNotSkipped && verbose) {
2258
- console.info(
2259
- `${chalk10.yellow("\u26A0")} Category "${category.title}" is ignored because it references audits from skipped plugin "${ref.plugin}"`
2260
- );
2261
- }
2262
- return isNotSkipped;
2263
- })
2639
+ } = {}) {
2640
+ const onlyPluginsSet = new Set(onlyPlugins);
2641
+ const missingPlugins = onlyPlugins.filter(
2642
+ (plugin) => !plugins.some(({ slug }) => slug === plugin)
2264
2643
  );
2265
- }
2266
- function validateOnlyPluginsOption(plugins, {
2267
- onlyPlugins,
2268
- verbose = false
2269
- }) {
2270
- const missingPlugins = onlyPlugins?.length ? onlyPlugins.filter((plugin) => !plugins.some(({ slug }) => slug === plugin)) : [];
2271
2644
  if (missingPlugins.length > 0 && verbose) {
2272
- console.warn(
2273
- `${chalk10.yellow(
2645
+ ui().logger.info(
2646
+ `${chalk11.yellow(
2274
2647
  "\u26A0"
2275
2648
  )} The --onlyPlugin argument references plugins with "${missingPlugins.join(
2276
2649
  '", "'
2277
2650
  )}" slugs, but no such plugins are present in the configuration. Expected one of the following plugin slugs: "${plugins.map(({ slug }) => slug).join('", "')}".`
2278
2651
  );
2279
2652
  }
2653
+ if (categories.length > 0) {
2654
+ const removedCategorieSlugs = filterItemRefsBy(
2655
+ categories,
2656
+ ({ plugin }) => !onlyPluginsSet.has(plugin)
2657
+ ).map(({ slug }) => slug);
2658
+ ui().logger.info(
2659
+ `The --onlyPlugin argument removed categories with "${removedCategorieSlugs.join(
2660
+ '", "'
2661
+ )}" slugs.
2662
+ `
2663
+ );
2664
+ }
2280
2665
  }
2281
2666
 
2282
2667
  // packages/cli/src/lib/implementation/only-plugins.middleware.ts
2283
- function onlyPluginsMiddleware(processArgs) {
2284
- validateOnlyPluginsOption(processArgs.plugins, processArgs);
2668
+ function onlyPluginsMiddleware(originalProcessArgs) {
2669
+ const { categories = [], onlyPlugins: originalOnlyPlugins } = originalProcessArgs;
2670
+ if (originalOnlyPlugins && originalOnlyPlugins.length > 0) {
2671
+ const { verbose, plugins, onlyPlugins } = originalProcessArgs;
2672
+ validateOnlyPluginsOption(
2673
+ { plugins, categories },
2674
+ { onlyPlugins, verbose }
2675
+ );
2676
+ const onlyPluginsSet = new Set(onlyPlugins);
2677
+ return {
2678
+ ...originalProcessArgs,
2679
+ plugins: plugins.filter(({ slug }) => onlyPluginsSet.has(slug)),
2680
+ categories: filterItemRefsBy(
2681
+ categories,
2682
+ ({ plugin }) => onlyPluginsSet.has(plugin)
2683
+ )
2684
+ };
2685
+ }
2285
2686
  return {
2286
- ...processArgs,
2287
- plugins: filterPluginsBySlug(processArgs.plugins, processArgs),
2288
- categories: filterCategoryByPluginSlug(processArgs.categories, processArgs)
2687
+ ...originalProcessArgs,
2688
+ // if undefined fill categories with empty array
2689
+ categories
2289
2690
  };
2290
2691
  }
2291
2692
 
@@ -2369,20 +2770,44 @@ function yargsGlobalOptionsDefinition() {
2369
2770
  };
2370
2771
  }
2371
2772
 
2773
+ // packages/cli/src/lib/implementation/only-plugins.options.ts
2774
+ var onlyPluginsOption = {
2775
+ describe: "List of plugins to run. If not set all plugins are run.",
2776
+ type: "array",
2777
+ default: [],
2778
+ coerce: coerceArray
2779
+ };
2780
+ function yargsOnlyPluginsOptionsDefinition() {
2781
+ return {
2782
+ onlyPlugins: onlyPluginsOption
2783
+ };
2784
+ }
2785
+
2372
2786
  // packages/cli/src/lib/options.ts
2373
2787
  var options = {
2374
2788
  ...yargsGlobalOptionsDefinition(),
2375
- ...yargsCoreConfigOptionsDefinition()
2789
+ ...yargsCoreConfigOptionsDefinition(),
2790
+ ...yargsOnlyPluginsOptionsDefinition()
2791
+ };
2792
+ var groups = {
2793
+ "Global Options:": [
2794
+ ...Object.keys(yargsGlobalOptionsDefinition()),
2795
+ ...Object.keys(yargsOnlyPluginsOptionsDefinition())
2796
+ ],
2797
+ "Persist Options:": Object.keys(yargsPersistConfigOptionsDefinition()),
2798
+ "Upload Options:": Object.keys(yargsUploadConfigOptionsDefinition())
2376
2799
  };
2377
2800
 
2378
2801
  // packages/cli/src/lib/yargs-cli.ts
2379
- import chalk11 from "chalk";
2802
+ import chalk12 from "chalk";
2380
2803
  import yargs from "yargs";
2381
2804
  function yargsCli(argv, cfg) {
2382
2805
  const { usageMessage, scriptName, noExitProcess } = cfg;
2383
2806
  const commands2 = cfg.commands ?? [];
2384
2807
  const middlewares2 = cfg.middlewares ?? [];
2385
2808
  const options2 = cfg.options ?? {};
2809
+ const groups2 = cfg.groups ?? {};
2810
+ const examples = cfg.examples ?? [];
2386
2811
  const cli2 = yargs(argv);
2387
2812
  cli2.help().version(false).alias("h", "help").check((args) => {
2388
2813
  const persist = args["persist"];
@@ -2394,11 +2819,17 @@ function yargsCli(argv, cfg) {
2394
2819
  (config) => Array.isArray(config) ? config.at(-1) : config
2395
2820
  ).options(options2).wrap(TERMINAL_WIDTH);
2396
2821
  if (usageMessage) {
2397
- cli2.usage(chalk11.bold(usageMessage));
2822
+ cli2.usage(chalk12.bold(usageMessage));
2398
2823
  }
2399
2824
  if (scriptName) {
2400
2825
  cli2.scriptName(scriptName);
2401
2826
  }
2827
+ examples.forEach(
2828
+ ([exampleName, description]) => cli2.example(exampleName, description)
2829
+ );
2830
+ Object.entries(groups2).forEach(
2831
+ ([groupName, optionNames]) => cli2.group(optionNames, groupName)
2832
+ );
2402
2833
  middlewares2.forEach(({ middlewareFunction, applyBeforeValidation }) => {
2403
2834
  cli2.middleware(
2404
2835
  logErrorBeforeThrow(middlewareFunction),
@@ -2439,6 +2870,29 @@ var cli = (args) => yargsCli(args, {
2439
2870
  usageMessage: CLI_NAME,
2440
2871
  scriptName: CLI_SCRIPT_NAME,
2441
2872
  options,
2873
+ groups,
2874
+ examples: [
2875
+ [
2876
+ "code-pushup",
2877
+ "Run collect followed by upload based on configuration from code-pushup.config.* file."
2878
+ ],
2879
+ [
2880
+ "code-pushup collect --tsconfig=tsconfig.base.json",
2881
+ "Run collect using custom tsconfig to parse code-pushup.config.ts file."
2882
+ ],
2883
+ [
2884
+ "code-pushup collect --onlyPlugins=coverage",
2885
+ "Run collect with only coverage plugin, other plugins from config file will be skipped."
2886
+ ],
2887
+ [
2888
+ "code-pushup upload --persist.outputDir=dist --persist.filename=cp-report --upload.apiKey=$CP_API_KEY",
2889
+ "Upload dist/cp-report.json to portal using API key from environment variable"
2890
+ ],
2891
+ [
2892
+ "code-pushup print-config --config code-pushup.config.test.js",
2893
+ "Print resolved config object parsed from custom config location"
2894
+ ]
2895
+ ],
2442
2896
  middlewares,
2443
2897
  commands
2444
2898
  });