@code-pushup/utils 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" }
@@ -551,6 +554,138 @@ var reportSchema = packageVersionSchema({
551
554
  })
552
555
  );
553
556
 
557
+ // packages/models/src/lib/reports-diff.ts
558
+ import { z as z14 } from "zod";
559
+ function makeComparisonSchema(schema) {
560
+ const sharedDescription = schema.description || "Result";
561
+ return z14.object({
562
+ before: schema.describe(`${sharedDescription} (source commit)`),
563
+ after: schema.describe(`${sharedDescription} (target commit)`)
564
+ });
565
+ }
566
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
567
+ return z14.object(
568
+ {
569
+ changed: z14.array(diffSchema),
570
+ unchanged: z14.array(resultSchema),
571
+ added: z14.array(resultSchema),
572
+ removed: z14.array(resultSchema)
573
+ },
574
+ { description }
575
+ );
576
+ }
577
+ var scorableMetaSchema = z14.object({ slug: slugSchema, title: titleSchema });
578
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
579
+ z14.object({
580
+ plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
581
+ })
582
+ );
583
+ var scorableDiffSchema = scorableMetaSchema.merge(
584
+ z14.object({
585
+ scores: makeComparisonSchema(scoreSchema).merge(
586
+ z14.object({
587
+ diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
588
+ })
589
+ ).describe("Score comparison")
590
+ })
591
+ );
592
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
593
+ scorableWithPluginMetaSchema
594
+ );
595
+ var categoryDiffSchema = scorableDiffSchema;
596
+ var groupDiffSchema = scorableWithPluginDiffSchema;
597
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
598
+ z14.object({
599
+ values: makeComparisonSchema(auditValueSchema).merge(
600
+ z14.object({
601
+ diff: z14.number().int().describe("Value change (`values.after - values.before`)")
602
+ })
603
+ ).describe("Audit `value` comparison"),
604
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
605
+ "Audit `displayValue` comparison"
606
+ )
607
+ })
608
+ );
609
+ var categoryResultSchema = scorableMetaSchema.merge(
610
+ z14.object({ score: scoreSchema })
611
+ );
612
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
613
+ z14.object({ score: scoreSchema })
614
+ );
615
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
616
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
617
+ );
618
+ var reportsDiffSchema = z14.object({
619
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
620
+ categories: makeArraysComparisonSchema(
621
+ categoryDiffSchema,
622
+ categoryResultSchema,
623
+ "Changes affecting categories"
624
+ ),
625
+ groups: makeArraysComparisonSchema(
626
+ groupDiffSchema,
627
+ groupResultSchema,
628
+ "Changes affecting groups"
629
+ ),
630
+ audits: makeArraysComparisonSchema(
631
+ auditDiffSchema,
632
+ auditResultSchema,
633
+ "Changes affecting audits"
634
+ )
635
+ }).merge(
636
+ packageVersionSchema({
637
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
638
+ required: true
639
+ })
640
+ ).merge(
641
+ executionMetaSchema({
642
+ descriptionDate: "Start date and time of the compare run",
643
+ descriptionDuration: "Duration of the compare run in ms"
644
+ })
645
+ );
646
+
647
+ // packages/utils/src/lib/diff.ts
648
+ function matchArrayItemsByKey({
649
+ before,
650
+ after,
651
+ key
652
+ }) {
653
+ const pairs = [];
654
+ const added = [];
655
+ const afterKeys = /* @__PURE__ */ new Set();
656
+ const keyFn = typeof key === "function" ? key : (item) => item[key];
657
+ for (const afterItem of after) {
658
+ const afterKey = keyFn(afterItem);
659
+ afterKeys.add(afterKey);
660
+ const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
661
+ if (match) {
662
+ pairs.push({ before: match, after: afterItem });
663
+ } else {
664
+ added.push(afterItem);
665
+ }
666
+ }
667
+ const removed = before.filter(
668
+ (beforeItem) => !afterKeys.has(keyFn(beforeItem))
669
+ );
670
+ return {
671
+ pairs,
672
+ added,
673
+ removed
674
+ };
675
+ }
676
+ function comparePairs(pairs, equalsFn) {
677
+ return pairs.reduce(
678
+ (acc, pair) => ({
679
+ ...acc,
680
+ ...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
681
+ }),
682
+ {
683
+ changed: [],
684
+ unchanged: []
685
+ }
686
+ );
687
+ }
688
+
554
689
  // packages/utils/src/lib/execute-process.ts
555
690
  import { spawn } from "node:child_process";
556
691
 
@@ -559,7 +694,7 @@ import { join as join2 } from "node:path";
559
694
 
560
695
  // packages/utils/src/lib/file-system.ts
561
696
  import { bundleRequire } from "bundle-require";
562
- import chalk from "chalk";
697
+ import chalk2 from "chalk";
563
698
  import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
564
699
  import { join } from "node:path";
565
700
 
@@ -567,7 +702,10 @@ import { join } from "node:path";
567
702
  function slugify(text) {
568
703
  return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
569
704
  }
570
- function pluralize(text) {
705
+ function pluralize(text, amount) {
706
+ if (amount != null && Math.abs(amount) === 1) {
707
+ return text;
708
+ }
571
709
  if (text.endsWith("y")) {
572
710
  return `${text.slice(0, -1)}ies`;
573
711
  }
@@ -587,7 +725,7 @@ function formatBytes(bytes, decimals = 2) {
587
725
  const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
588
726
  return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
589
727
  }
590
- function pluralizeToken(token, times = 0) {
728
+ function pluralizeToken(token, times) {
591
729
  return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
592
730
  }
593
731
  function formatDuration(duration) {
@@ -633,33 +771,106 @@ function isPromiseRejectedResult(result) {
633
771
  return result.status === "rejected";
634
772
  }
635
773
 
774
+ // packages/utils/src/lib/logging.ts
775
+ import isaacs_cliui from "@isaacs/cliui";
776
+ import { cliui } from "@poppinss/cliui";
777
+ import chalk from "chalk";
778
+
779
+ // packages/utils/src/lib/reports/constants.ts
780
+ var TERMINAL_WIDTH = 80;
781
+ var NEW_LINE = "\n";
782
+ var SCORE_COLOR_RANGE = {
783
+ GREEN_MIN: 0.9,
784
+ YELLOW_MIN: 0.5
785
+ };
786
+ var FOOTER_PREFIX = "Made with \u2764 by";
787
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
788
+ var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
789
+ var reportHeadlineText = "Code PushUp Report";
790
+ var reportOverviewTableHeaders = [
791
+ "\u{1F3F7} Category",
792
+ "\u2B50 Score",
793
+ "\u{1F6E1} Audits"
794
+ ];
795
+ var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
796
+ var reportMetaTableHeaders = [
797
+ "Commit",
798
+ "Version",
799
+ "Duration",
800
+ "Plugins",
801
+ "Categories",
802
+ "Audits"
803
+ ];
804
+ var pluginMetaTableHeaders = [
805
+ "Plugin",
806
+ "Audits",
807
+ "Version",
808
+ "Duration"
809
+ ];
810
+ var detailsTableHeaders = [
811
+ "Severity",
812
+ "Message",
813
+ "Source file",
814
+ "Line(s)"
815
+ ];
816
+
817
+ // packages/utils/src/lib/logging.ts
818
+ var singletonUiInstance;
819
+ function ui() {
820
+ if (singletonUiInstance === void 0) {
821
+ singletonUiInstance = cliui();
822
+ }
823
+ return {
824
+ ...singletonUiInstance,
825
+ row: (args) => {
826
+ logListItem(args);
827
+ }
828
+ };
829
+ }
830
+ var singletonisaacUi;
831
+ function logListItem(args) {
832
+ if (singletonisaacUi === void 0) {
833
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
834
+ }
835
+ singletonisaacUi.div(...args);
836
+ const content = singletonisaacUi.toString();
837
+ singletonisaacUi.rows = [];
838
+ singletonUiInstance?.logger.log(content);
839
+ }
840
+ function link(text) {
841
+ return chalk.underline(chalk.blueBright(text));
842
+ }
843
+
636
844
  // packages/utils/src/lib/log-results.ts
637
- function logMultipleResults(results, messagePrefix, succeededCallback, failedCallback) {
638
- if (succeededCallback) {
845
+ function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
846
+ if (succeededTransform) {
639
847
  const succeededResults = results.filter(isPromiseFulfilledResult);
640
848
  logPromiseResults(
641
849
  succeededResults,
642
850
  `${messagePrefix} successfully: `,
643
- succeededCallback
851
+ succeededTransform
644
852
  );
645
853
  }
646
- if (failedCallback) {
854
+ if (failedTransform) {
647
855
  const failedResults = results.filter(isPromiseRejectedResult);
648
856
  logPromiseResults(
649
857
  failedResults,
650
858
  `${messagePrefix} failed: `,
651
- failedCallback
859
+ failedTransform
652
860
  );
653
861
  }
654
862
  }
655
- function logPromiseResults(results, logMessage, callback) {
863
+ function logPromiseResults(results, logMessage, getMsg) {
656
864
  if (results.length > 0) {
657
- if (results[0]?.status === "fulfilled") {
658
- console.info(logMessage);
659
- } else {
660
- console.warn(logMessage);
661
- }
662
- results.forEach(callback);
865
+ const log2 = results[0]?.status === "fulfilled" ? (m) => {
866
+ ui().logger.success(m);
867
+ } : (m) => {
868
+ ui().logger.warning(m);
869
+ };
870
+ log2(logMessage);
871
+ results.forEach((result) => {
872
+ log2(getMsg(result));
873
+ });
663
874
  }
664
875
  }
665
876
 
@@ -693,7 +904,7 @@ async function ensureDirectoryExists(baseDir) {
693
904
  await mkdir(baseDir, { recursive: true });
694
905
  return;
695
906
  } catch (error) {
696
- console.error(error.message);
907
+ ui().logger.error(error.message);
697
908
  if (error.code !== "EEXIST") {
698
909
  throw error;
699
910
  }
@@ -705,19 +916,17 @@ async function removeDirectoryIfExists(dir) {
705
916
  }
706
917
  }
707
918
  function logMultipleFileResults(fileResults, messagePrefix) {
708
- const succeededCallback = (result) => {
919
+ const succeededTransform = (result) => {
709
920
  const [fileName, size] = result.value;
710
- const formattedSize = size ? ` (${chalk.gray(formatBytes(size))})` : "";
711
- console.info(`- ${chalk.bold(fileName)}${formattedSize}`);
712
- };
713
- const failedCallback = (result) => {
714
- console.warn(`- ${chalk.bold(result.reason)}`);
921
+ const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
922
+ return `- ${chalk2.bold(fileName)}${formattedSize}`;
715
923
  };
924
+ const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
716
925
  logMultipleResults(
717
926
  fileResults,
718
927
  messagePrefix,
719
- succeededCallback,
720
- failedCallback
928
+ succeededTransform,
929
+ failedTransform
721
930
  );
722
931
  }
723
932
  var NoExportError = class extends Error {
@@ -765,48 +974,105 @@ function findLineNumberInText(content, pattern) {
765
974
  return lineNumber === 0 ? null : lineNumber;
766
975
  }
767
976
 
768
- // packages/utils/src/lib/reports/constants.ts
769
- var TERMINAL_WIDTH = 80;
770
- var NEW_LINE = "\n";
771
- var SCORE_COLOR_RANGE = {
772
- GREEN_MIN: 0.9,
773
- YELLOW_MIN: 0.5
977
+ // packages/utils/src/lib/reports/md/details.ts
978
+ function details(title, content, cfg = { open: false }) {
979
+ return `<details${cfg.open ? " open" : ""}>
980
+ <summary>${title}</summary>
981
+
982
+ ${content}
983
+
984
+ </details>
985
+ `;
986
+ }
987
+
988
+ // packages/utils/src/lib/reports/md/font-style.ts
989
+ var stylesMap = {
990
+ i: "_",
991
+ // italic
992
+ b: "**",
993
+ // bold
994
+ s: "~",
995
+ // strike through
996
+ c: "`"
997
+ // code
774
998
  };
775
- var FOOTER_PREFIX = "Made with \u2764 by";
776
- var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
777
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
778
- var reportHeadlineText = "Code PushUp Report";
779
- var reportOverviewTableHeaders = [
780
- "\u{1F3F7} Category",
781
- "\u2B50 Score",
782
- "\u{1F6E1} Audits"
783
- ];
784
- var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
785
- var reportMetaTableHeaders = [
786
- "Commit",
787
- "Version",
788
- "Duration",
789
- "Plugins",
790
- "Categories",
791
- "Audits"
792
- ];
793
- var pluginMetaTableHeaders = [
794
- "Plugin",
795
- "Audits",
796
- "Version",
797
- "Duration"
798
- ];
799
- var detailsTableHeaders = [
800
- "Severity",
801
- "Message",
802
- "Source file",
803
- "Line(s)"
804
- ];
999
+ function style(text, styles = ["b"]) {
1000
+ return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
1001
+ }
1002
+
1003
+ // packages/utils/src/lib/reports/md/headline.ts
1004
+ function headline(text, hierarchy = 1) {
1005
+ return `${"#".repeat(hierarchy)} ${text}`;
1006
+ }
1007
+ function h1(text) {
1008
+ return headline(text, 1);
1009
+ }
1010
+ function h2(text) {
1011
+ return headline(text, 2);
1012
+ }
1013
+ function h3(text) {
1014
+ return headline(text, 3);
1015
+ }
1016
+
1017
+ // packages/utils/src/lib/reports/md/image.ts
1018
+ function image(src, alt) {
1019
+ return `![${alt}](${src})`;
1020
+ }
1021
+
1022
+ // packages/utils/src/lib/reports/md/link.ts
1023
+ function link2(href, text) {
1024
+ return `[${text || href}](${href})`;
1025
+ }
1026
+
1027
+ // packages/utils/src/lib/reports/md/list.ts
1028
+ function li(text, order = "unordered") {
1029
+ const style2 = order === "unordered" ? "-" : "- [ ]";
1030
+ return `${style2} ${text}`;
1031
+ }
1032
+
1033
+ // packages/utils/src/lib/reports/md/paragraphs.ts
1034
+ function paragraphs(...sections) {
1035
+ return sections.filter(Boolean).join("\n\n");
1036
+ }
1037
+
1038
+ // packages/utils/src/lib/reports/md/table.ts
1039
+ var alignString = /* @__PURE__ */ new Map([
1040
+ ["l", ":--"],
1041
+ ["c", ":--:"],
1042
+ ["r", "--:"]
1043
+ ]);
1044
+ function tableMd(data, align) {
1045
+ if (data.length === 0) {
1046
+ throw new Error("Data can't be empty");
1047
+ }
1048
+ const alignmentSetting = align ?? data[0]?.map(() => "c");
1049
+ const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1050
+ const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1051
+ return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1052
+ }
1053
+ function tableHtml(data) {
1054
+ if (data.length === 0) {
1055
+ throw new Error("Data can't be empty");
1056
+ }
1057
+ const tableContent = data.map((arr, index) => {
1058
+ if (index === 0) {
1059
+ const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1060
+ return `<tr>${headerRow}</tr>`;
1061
+ }
1062
+ const row = arr.map((s) => `<td>${s}</td>`).join("");
1063
+ return `<tr>${row}</tr>`;
1064
+ });
1065
+ return `<table>${tableContent.join("")}</table>`;
1066
+ }
805
1067
 
806
1068
  // packages/utils/src/lib/reports/utils.ts
807
1069
  function formatReportScore(score) {
808
1070
  return Math.round(score * 100).toString();
809
1071
  }
1072
+ function formatScoreWithColor(score, options) {
1073
+ const styledNumber = options?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1074
+ return `${getRoundScoreMarker(score)} ${styledNumber}`;
1075
+ }
810
1076
  function getRoundScoreMarker(score) {
811
1077
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
812
1078
  return "\u{1F7E2}";
@@ -825,6 +1091,30 @@ function getSquaredScoreMarker(score) {
825
1091
  }
826
1092
  return "\u{1F7E5}";
827
1093
  }
1094
+ function getDiffMarker(diff) {
1095
+ if (diff > 0) {
1096
+ return "\u2191";
1097
+ }
1098
+ if (diff < 0) {
1099
+ return "\u2193";
1100
+ }
1101
+ return "";
1102
+ }
1103
+ function colorByScoreDiff(text, diff) {
1104
+ const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
1105
+ return shieldsBadge(text, color);
1106
+ }
1107
+ function shieldsBadge(text, color) {
1108
+ return image(
1109
+ `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1110
+ text
1111
+ );
1112
+ }
1113
+ function formatDiffNumber(diff) {
1114
+ const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
1115
+ const sign = diff < 0 ? "\u2212" : "+";
1116
+ return `${sign}${number}`;
1117
+ }
828
1118
  function getSeverityIcon(severity) {
829
1119
  if (severity === "error") {
830
1120
  return "\u{1F6A8}";
@@ -835,7 +1125,7 @@ function getSeverityIcon(severity) {
835
1125
  return "\u2139\uFE0F";
836
1126
  }
837
1127
  function calcDuration(start, stop) {
838
- return Math.floor((stop ?? performance.now()) - start);
1128
+ return Math.round((stop ?? performance.now()) - start);
839
1129
  }
840
1130
  function countCategoryAudits(refs, plugins) {
841
1131
  const groupLookup = plugins.reduce(
@@ -998,7 +1288,7 @@ var ProcessError = class extends Error {
998
1288
  }
999
1289
  };
1000
1290
  function executeProcess(cfg) {
1001
- const { observer, cwd, command, args } = cfg;
1291
+ const { observer, cwd, command, args, alwaysResolve = false } = cfg;
1002
1292
  const { onStdout, onError, onComplete } = observer ?? {};
1003
1293
  const date = (/* @__PURE__ */ new Date()).toISOString();
1004
1294
  const start = performance.now();
@@ -1018,7 +1308,7 @@ function executeProcess(cfg) {
1018
1308
  });
1019
1309
  process2.on("close", (code) => {
1020
1310
  const timings = { date, duration: calcDuration(start) };
1021
- if (code === 0) {
1311
+ if (code === 0 || alwaysResolve) {
1022
1312
  onComplete?.();
1023
1313
  resolve({ code, stdout, stderr, ...timings });
1024
1314
  } else {
@@ -1133,14 +1423,14 @@ function toOrdinal(value) {
1133
1423
 
1134
1424
  // packages/utils/src/lib/git.ts
1135
1425
  async function getLatestCommit(git = simpleGit()) {
1136
- const log = await git.log({
1426
+ const log2 = await git.log({
1137
1427
  maxCount: 1,
1138
1428
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1139
1429
  });
1140
- if (!log.latest) {
1430
+ if (!log2.latest) {
1141
1431
  return null;
1142
1432
  }
1143
- return commitSchema.parse(log.latest);
1433
+ return commitSchema.parse(log2.latest);
1144
1434
  }
1145
1435
  function getGitRoot(git = simpleGit()) {
1146
1436
  return git.revparse("--show-toplevel");
@@ -1154,6 +1444,35 @@ async function toGitPath(path, git = simpleGit()) {
1154
1444
  const gitRoot = await getGitRoot(git);
1155
1445
  return formatGitPath(path, gitRoot);
1156
1446
  }
1447
+ async function guardAgainstLocalChanges(git = simpleGit()) {
1448
+ const isClean = await git.status(["-s"]).then((r) => r.files.length === 0);
1449
+ if (!isClean) {
1450
+ throw new Error(
1451
+ "Working directory needs to be clean before we you can proceed. Commit your local changes or stash them."
1452
+ );
1453
+ }
1454
+ }
1455
+ async function getCurrentBranchOrTag(git = simpleGit()) {
1456
+ try {
1457
+ const branch = await git.branch().then((r) => r.current);
1458
+ if (branch) {
1459
+ return branch;
1460
+ } else {
1461
+ return await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1462
+ }
1463
+ } catch {
1464
+ throw new Error("Could not get current tag or branch.");
1465
+ }
1466
+ }
1467
+ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1468
+ if (forceCleanStatus) {
1469
+ await git.raw(["reset", "--hard"]);
1470
+ await git.clean(["f", "d"]);
1471
+ ui().logger.info(`git status cleaned`);
1472
+ }
1473
+ await guardAgainstLocalChanges(git);
1474
+ await git.checkout(branchOrHash);
1475
+ }
1157
1476
 
1158
1477
  // packages/utils/src/lib/group-by-status.ts
1159
1478
  function groupByStatus(results) {
@@ -1163,12 +1482,6 @@ function groupByStatus(results) {
1163
1482
  );
1164
1483
  }
1165
1484
 
1166
- // packages/utils/src/lib/logging.ts
1167
- import chalk2 from "chalk";
1168
- function link(text) {
1169
- return chalk2.underline(chalk2.blueBright(text));
1170
- }
1171
-
1172
1485
  // packages/utils/src/lib/progress.ts
1173
1486
  import chalk3 from "chalk";
1174
1487
  import { MultiProgressBars } from "multi-progress-bars";
@@ -1224,80 +1537,16 @@ function getProgressBar(taskName) {
1224
1537
  };
1225
1538
  }
1226
1539
 
1227
- // packages/utils/src/lib/reports/md/details.ts
1228
- function details(title, content, cfg = { open: false }) {
1229
- return `<details${cfg.open ? " open" : ""}>
1230
- <summary>${title}</summary>
1231
- ${content}
1232
- </details>
1233
- `;
1234
- }
1235
-
1236
- // packages/utils/src/lib/reports/md/font-style.ts
1237
- var stylesMap = {
1238
- i: "_",
1239
- // italic
1240
- b: "**",
1241
- // bold
1242
- s: "~",
1243
- // strike through
1244
- c: "`"
1245
- // code
1246
- };
1247
- function style(text, styles = ["b"]) {
1248
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
1249
- }
1250
-
1251
- // packages/utils/src/lib/reports/md/headline.ts
1252
- function headline(text, hierarchy = 1) {
1253
- return `${"#".repeat(hierarchy)} ${text}`;
1254
- }
1255
- function h2(text) {
1256
- return headline(text, 2);
1257
- }
1258
- function h3(text) {
1259
- return headline(text, 3);
1260
- }
1261
-
1262
- // packages/utils/src/lib/reports/md/link.ts
1263
- function link2(href, text) {
1264
- return `[${text || href}](${href})`;
1265
- }
1266
-
1267
- // packages/utils/src/lib/reports/md/list.ts
1268
- function li(text, order = "unordered") {
1269
- const style2 = order === "unordered" ? "-" : "- [ ]";
1270
- return `${style2} ${text}`;
1271
- }
1272
-
1273
- // packages/utils/src/lib/reports/md/table.ts
1274
- var alignString = /* @__PURE__ */ new Map([
1275
- ["l", ":--"],
1276
- ["c", ":--:"],
1277
- ["r", "--:"]
1278
- ]);
1279
- function tableMd(data, align) {
1280
- if (data.length === 0) {
1281
- throw new Error("Data can't be empty");
1282
- }
1283
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1284
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1285
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1286
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1540
+ // packages/utils/src/lib/reports/flatten-plugins.ts
1541
+ function listGroupsFromAllPlugins(report) {
1542
+ return report.plugins.flatMap(
1543
+ (plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
1544
+ );
1287
1545
  }
1288
- function tableHtml(data) {
1289
- if (data.length === 0) {
1290
- throw new Error("Data can't be empty");
1291
- }
1292
- const tableContent = data.map((arr, index) => {
1293
- if (index === 0) {
1294
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1295
- return `<tr>${headerRow}</tr>`;
1296
- }
1297
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1298
- return `<tr>${row}</tr>`;
1299
- });
1300
- return `<table>${tableContent.join("")}</table>`;
1546
+ function listAuditsFromAllPlugins(report) {
1547
+ return report.plugins.flatMap(
1548
+ (plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
1549
+ );
1301
1550
  }
1302
1551
 
1303
1552
  // packages/utils/src/lib/reports/generate-md-report.ts
@@ -1487,30 +1736,261 @@ function getAuditResult(audit, isHtml = false) {
1487
1736
  return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1488
1737
  }
1489
1738
 
1490
- // packages/utils/src/lib/reports/generate-stdout-summary.ts
1491
- import cliui from "@isaacs/cliui";
1739
+ // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1740
+ var MAX_ROWS = 100;
1741
+ function generateMdReportsDiff(diff) {
1742
+ return paragraphs(
1743
+ formatDiffHeaderSection(diff),
1744
+ formatDiffCategoriesSection(diff),
1745
+ formatDiffGroupsSection(diff),
1746
+ formatDiffAuditsSection(diff)
1747
+ );
1748
+ }
1749
+ function formatDiffHeaderSection(diff) {
1750
+ const outcomeTexts = {
1751
+ positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1752
+ negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1753
+ mixed: `\u{1F928} Code PushUp report has both ${style(
1754
+ "improvements and regressions"
1755
+ )}`,
1756
+ unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
1757
+ };
1758
+ const outcome = mergeDiffOutcomes(
1759
+ changesToDiffOutcomes([
1760
+ ...diff.categories.changed,
1761
+ ...diff.groups.changed,
1762
+ ...diff.audits.changed
1763
+ ])
1764
+ );
1765
+ const styleCommit = (commit) => style(commit.hash.slice(0, 7), ["c"]);
1766
+ const styleCommits = (commits) => {
1767
+ const src = styleCommit(commits.before);
1768
+ const tgt = styleCommit(commits.after);
1769
+ return `compared target commit ${tgt} with source commit ${src}`;
1770
+ };
1771
+ return paragraphs(
1772
+ h1("Code PushUp"),
1773
+ diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1774
+ );
1775
+ }
1776
+ function formatDiffCategoriesSection(diff) {
1777
+ const { changed, unchanged, added } = diff.categories;
1778
+ const categoriesCount = changed.length + unchanged.length + added.length;
1779
+ const hasChanges = unchanged.length < categoriesCount;
1780
+ if (categoriesCount === 0) {
1781
+ return "";
1782
+ }
1783
+ return paragraphs(
1784
+ h2("\u{1F3F7}\uFE0F Categories"),
1785
+ categoriesCount > 0 && tableMd(
1786
+ [
1787
+ [
1788
+ "\u{1F3F7}\uFE0F Category",
1789
+ hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1790
+ "\u2B50 Previous score",
1791
+ "\u{1F504} Score change"
1792
+ ],
1793
+ ...sortChanges(changed).map((category) => [
1794
+ category.title,
1795
+ formatScoreWithColor(category.scores.after),
1796
+ formatScoreWithColor(category.scores.before, { skipBold: true }),
1797
+ formatScoreChange(category.scores.diff)
1798
+ ]),
1799
+ ...added.map((category) => [
1800
+ category.title,
1801
+ formatScoreWithColor(category.score),
1802
+ style("n/a (\\*)", ["i"]),
1803
+ style("n/a (\\*)", ["i"])
1804
+ ]),
1805
+ ...unchanged.map((category) => [
1806
+ category.title,
1807
+ formatScoreWithColor(category.score),
1808
+ formatScoreWithColor(category.score, { skipBold: true }),
1809
+ "\u2013"
1810
+ ])
1811
+ ].map((row) => hasChanges ? row : row.slice(0, 2)),
1812
+ hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1813
+ ),
1814
+ added.length > 0 && style("(\\*) New category.", ["i"])
1815
+ );
1816
+ }
1817
+ function formatDiffGroupsSection(diff) {
1818
+ if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1819
+ return "";
1820
+ }
1821
+ return paragraphs(
1822
+ h2("\u{1F397}\uFE0F Groups"),
1823
+ formatGroupsOrAuditsDetails("group", diff.groups, {
1824
+ headings: [
1825
+ "\u{1F50C} Plugin",
1826
+ "\u{1F5C3}\uFE0F Group",
1827
+ "\u2B50 Current score",
1828
+ "\u2B50 Previous score",
1829
+ "\u{1F504} Score change"
1830
+ ],
1831
+ rows: sortChanges(diff.groups.changed).map((group) => [
1832
+ group.plugin.title,
1833
+ group.title,
1834
+ formatScoreWithColor(group.scores.after),
1835
+ formatScoreWithColor(group.scores.before, { skipBold: true }),
1836
+ formatScoreChange(group.scores.diff)
1837
+ ]),
1838
+ align: ["l", "l", "c", "c", "c"]
1839
+ })
1840
+ );
1841
+ }
1842
+ function formatDiffAuditsSection(diff) {
1843
+ return paragraphs(
1844
+ h2("\u{1F6E1}\uFE0F Audits"),
1845
+ formatGroupsOrAuditsDetails("audit", diff.audits, {
1846
+ headings: [
1847
+ "\u{1F50C} Plugin",
1848
+ "\u{1F6E1}\uFE0F Audit",
1849
+ "\u{1F4CF} Current value",
1850
+ "\u{1F4CF} Previous value",
1851
+ "\u{1F504} Value change"
1852
+ ],
1853
+ rows: sortChanges(diff.audits.changed).map((audit) => [
1854
+ audit.plugin.title,
1855
+ audit.title,
1856
+ `${getSquaredScoreMarker(audit.scores.after)} ${style(
1857
+ audit.displayValues.after || audit.values.after.toString()
1858
+ )}`,
1859
+ `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1860
+ formatValueChange(audit)
1861
+ ]),
1862
+ align: ["l", "l", "c", "c", "c"]
1863
+ })
1864
+ );
1865
+ }
1866
+ function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1867
+ return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
1868
+ summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1869
+ paragraphs(
1870
+ tableMd(
1871
+ [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1872
+ table.align
1873
+ ),
1874
+ changed.length > MAX_ROWS && style(
1875
+ `Only the ${MAX_ROWS} most affected ${pluralize(
1876
+ token
1877
+ )} are listed above for brevity.`,
1878
+ ["i"]
1879
+ ),
1880
+ unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1881
+ )
1882
+ );
1883
+ }
1884
+ function formatScoreChange(diff) {
1885
+ const marker = getDiffMarker(diff);
1886
+ const text = formatDiffNumber(Math.round(diff * 100));
1887
+ return colorByScoreDiff(`${marker} ${text}`, diff);
1888
+ }
1889
+ function formatValueChange({
1890
+ values,
1891
+ scores
1892
+ }) {
1893
+ const marker = getDiffMarker(values.diff);
1894
+ const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
1895
+ const text = `${formatDiffNumber(percentage)}\u2009%`;
1896
+ return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1897
+ }
1898
+ function summarizeUnchanged(token, { changed, unchanged }) {
1899
+ return [
1900
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1901
+ unchanged.length === 1 ? "is" : "are",
1902
+ "unchanged."
1903
+ ].join(" ");
1904
+ }
1905
+ function summarizeDiffOutcomes(outcomes, token) {
1906
+ return objectToEntries(countDiffOutcomes(outcomes)).filter(
1907
+ (entry) => entry[0] !== "unchanged" && entry[1] > 0
1908
+ ).map(([outcome, count]) => {
1909
+ const formattedCount = `<strong>${count}</strong> ${pluralize(
1910
+ token,
1911
+ count
1912
+ )}`;
1913
+ switch (outcome) {
1914
+ case "positive":
1915
+ return `\u{1F44D} ${formattedCount} improved`;
1916
+ case "negative":
1917
+ return `\u{1F44E} ${formattedCount} regressed`;
1918
+ case "mixed":
1919
+ return `${formattedCount} changed without impacting score`;
1920
+ }
1921
+ }).join(", ");
1922
+ }
1923
+ function sortChanges(changes) {
1924
+ return [...changes].sort(
1925
+ (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
1926
+ );
1927
+ }
1928
+ function changesToDiffOutcomes(changes) {
1929
+ return changes.map((change) => {
1930
+ if (change.scores.diff > 0) {
1931
+ return "positive";
1932
+ }
1933
+ if (change.scores.diff < 0) {
1934
+ return "negative";
1935
+ }
1936
+ if (change.values != null && change.values.diff !== 0) {
1937
+ return "mixed";
1938
+ }
1939
+ return "unchanged";
1940
+ });
1941
+ }
1942
+ function mergeDiffOutcomes(outcomes) {
1943
+ if (outcomes.every((outcome) => outcome === "unchanged")) {
1944
+ return "unchanged";
1945
+ }
1946
+ if (outcomes.includes("positive") && !outcomes.includes("negative")) {
1947
+ return "positive";
1948
+ }
1949
+ if (outcomes.includes("negative") && !outcomes.includes("positive")) {
1950
+ return "negative";
1951
+ }
1952
+ return "mixed";
1953
+ }
1954
+ function countDiffOutcomes(outcomes) {
1955
+ return {
1956
+ positive: outcomes.filter((outcome) => outcome === "positive").length,
1957
+ negative: outcomes.filter((outcome) => outcome === "negative").length,
1958
+ mixed: outcomes.filter((outcome) => outcome === "mixed").length,
1959
+ unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
1960
+ };
1961
+ }
1962
+
1963
+ // packages/utils/src/lib/reports/log-stdout-summary.ts
1492
1964
  import chalk4 from "chalk";
1493
- import CliTable3 from "cli-table3";
1494
- function addLine(line = "") {
1495
- return line + NEW_LINE;
1965
+ function log(msg = "") {
1966
+ ui().logger.log(msg);
1496
1967
  }
1497
- function generateStdoutSummary(report) {
1968
+ function logStdoutSummary(report) {
1498
1969
  const printCategories = report.categories.length > 0;
1499
- return addLine(reportToHeaderSection2(report)) + addLine() + addLine(reportToDetailSection(report)) + (printCategories ? addLine(reportToOverviewSection2(report)) : "") + addLine(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1970
+ log(reportToHeaderSection2(report));
1971
+ log();
1972
+ logPlugins(report);
1973
+ if (printCategories) {
1974
+ logCategories(report);
1975
+ }
1976
+ log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1977
+ log();
1500
1978
  }
1501
1979
  function reportToHeaderSection2(report) {
1502
1980
  const { packageName, version } = report;
1503
1981
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version}`;
1504
1982
  }
1505
- function reportToDetailSection(report) {
1983
+ function logPlugins(report) {
1506
1984
  const { plugins } = report;
1507
- return plugins.reduce((acc, plugin) => {
1985
+ plugins.forEach((plugin) => {
1508
1986
  const { title, audits } = plugin;
1509
- const ui = cliui({ width: TERMINAL_WIDTH });
1987
+ log();
1988
+ log(chalk4.magentaBright.bold(`${title} audits`));
1989
+ log();
1510
1990
  audits.forEach((audit) => {
1511
- ui.div(
1991
+ ui().row([
1512
1992
  {
1513
- text: withColor({ score: audit.score, text: "\u25CF" }),
1993
+ text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
1514
1994
  width: 2,
1515
1995
  padding: [0, 1, 0, 0]
1516
1996
  },
@@ -1524,34 +2004,40 @@ function reportToDetailSection(report) {
1524
2004
  width: 10,
1525
2005
  padding: [0, 0, 0, 0]
1526
2006
  }
1527
- );
2007
+ ]);
1528
2008
  });
1529
- return acc + addLine() + addLine(chalk4.magentaBright.bold(`${title} audits`)) + addLine() + addLine(ui.toString()) + addLine();
1530
- }, "");
1531
- }
1532
- function reportToOverviewSection2({
1533
- categories,
1534
- plugins
1535
- }) {
1536
- const table = new CliTable3({
1537
- // eslint-disable-next-line no-magic-numbers
1538
- colWidths: [TERMINAL_WIDTH - 7 - 8 - 4, 7, 8],
1539
- head: reportRawOverviewTableHeaders,
1540
- colAligns: ["left", "right", "right"],
1541
- style: {
1542
- head: ["cyan"]
1543
- }
2009
+ log();
1544
2010
  });
1545
- table.push(
1546
- ...categories.map(({ title, score, refs }) => [
1547
- title,
1548
- withColor({ score }),
1549
- countCategoryAudits(refs, plugins)
1550
- ])
2011
+ }
2012
+ function logCategories({ categories, plugins }) {
2013
+ const hAlign = (idx) => idx === 0 ? "left" : "right";
2014
+ const rows = categories.map(({ title, score, refs }) => [
2015
+ title,
2016
+ applyScoreColor({ score }),
2017
+ countCategoryAudits(refs, plugins)
2018
+ ]);
2019
+ const table = ui().table();
2020
+ table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2021
+ table.head(
2022
+ reportRawOverviewTableHeaders.map((heading, idx) => ({
2023
+ content: chalk4.cyan(heading),
2024
+ hAlign: hAlign(idx)
2025
+ }))
2026
+ );
2027
+ rows.forEach(
2028
+ (row) => table.row(
2029
+ row.map((content, idx) => ({
2030
+ content: content.toString(),
2031
+ hAlign: hAlign(idx)
2032
+ }))
2033
+ )
1551
2034
  );
1552
- return addLine(chalk4.magentaBright.bold("Categories")) + addLine() + addLine(table.toString());
2035
+ log(chalk4.magentaBright.bold("Categories"));
2036
+ log();
2037
+ table.render();
2038
+ log();
1553
2039
  }
1554
- function withColor({ score, text }) {
2040
+ function applyScoreColor({ score, text }) {
1555
2041
  const formattedScore = text ?? formatReportScore(score);
1556
2042
  const style2 = text ? chalk4 : chalk4.bold;
1557
2043
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
@@ -1702,9 +2188,9 @@ function sortPlugins(plugins) {
1702
2188
 
1703
2189
  // packages/utils/src/lib/verbose-utils.ts
1704
2190
  function getLogVerbose(verbose = false) {
1705
- return (...args) => {
2191
+ return (msg) => {
1706
2192
  if (verbose) {
1707
- console.info(...args);
2193
+ ui().logger.info(msg);
1708
2194
  }
1709
2195
  };
1710
2196
  }
@@ -1728,6 +2214,7 @@ export {
1728
2214
  calcDuration,
1729
2215
  capitalize,
1730
2216
  compareIssueSeverity,
2217
+ comparePairs,
1731
2218
  countOccurrences,
1732
2219
  crawlFileSystem,
1733
2220
  directoryExists,
@@ -1743,7 +2230,8 @@ export {
1743
2230
  formatDuration,
1744
2231
  formatGitPath,
1745
2232
  generateMdReport,
1746
- generateStdoutSummary,
2233
+ generateMdReportsDiff,
2234
+ getCurrentBranchOrTag,
1747
2235
  getGitRoot,
1748
2236
  getLatestCommit,
1749
2237
  getProgressBar,
@@ -1752,9 +2240,13 @@ export {
1752
2240
  isPromiseFulfilledResult,
1753
2241
  isPromiseRejectedResult,
1754
2242
  link,
2243
+ listAuditsFromAllPlugins,
2244
+ listGroupsFromAllPlugins,
1755
2245
  loadReport,
1756
2246
  logMultipleFileResults,
1757
2247
  logMultipleResults,
2248
+ logStdoutSummary,
2249
+ matchArrayItemsByKey,
1758
2250
  objectToCliArgs,
1759
2251
  objectToEntries,
1760
2252
  objectToKeys,
@@ -1764,6 +2256,7 @@ export {
1764
2256
  readJsonFile,
1765
2257
  readTextFile,
1766
2258
  removeDirectoryIfExists,
2259
+ safeCheckout,
1767
2260
  scoreReport,
1768
2261
  slugify,
1769
2262
  sortReport,
@@ -1777,5 +2270,6 @@ export {
1777
2270
  truncateIssueMessage,
1778
2271
  truncateText,
1779
2272
  truncateTitle,
2273
+ ui,
1780
2274
  verboseUtils
1781
2275
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-pushup/utils",
3
- "version": "0.26.1",
3
+ "version": "0.28.0",
4
4
  "dependencies": {
5
5
  "@code-pushup/models": "*",
6
6
  "bundle-require": "^4.0.1",
@@ -8,7 +8,7 @@
8
8
  "@isaacs/cliui": "^8.0.2",
9
9
  "simple-git": "^3.20.0",
10
10
  "multi-progress-bars": "^5.0.3",
11
- "cli-table3": "^0.6.3"
11
+ "@poppinss/cliui": "^6.4.0"
12
12
  },
13
13
  "license": "MIT",
14
14
  "homepage": "https://github.com/code-pushup/cli#readme",
package/src/index.d.ts CHANGED
@@ -1,20 +1,23 @@
1
1
  export { exists } from '@code-pushup/models';
2
+ export { Diff, comparePairs, matchArrayItemsByKey } from './lib/diff';
2
3
  export { ProcessConfig, ProcessError, ProcessObserver, ProcessResult, executeProcess, } from './lib/execute-process';
3
4
  export { CrawlFileSystemOptions, FileResult, MultipleFileResults, crawlFileSystem, directoryExists, ensureDirectoryExists, fileExists, findLineNumberInText, importEsmModule, logMultipleFileResults, pluginWorkDir, readJsonFile, readTextFile, removeDirectoryIfExists, } from './lib/file-system';
4
5
  export { filterItemRefsBy } from './lib/filter';
5
6
  export { formatBytes, formatDuration, pluralize, pluralizeToken, slugify, truncateDescription, truncateIssueMessage, truncateText, truncateTitle, } from './lib/formatting';
6
- export { formatGitPath, getGitRoot, getLatestCommit, toGitPath, } from './lib/git';
7
+ export { formatGitPath, getCurrentBranchOrTag, getGitRoot, getLatestCommit, safeCheckout, toGitPath, } from './lib/git';
7
8
  export { groupByStatus } from './lib/group-by-status';
8
9
  export { isPromiseFulfilledResult, isPromiseRejectedResult, } from './lib/guards';
9
10
  export { logMultipleResults } from './lib/log-results';
10
- export { link } from './lib/logging';
11
+ export { CliUi, Column, link, ui } from './lib/logging';
11
12
  export { ProgressBar, getProgressBar } from './lib/progress';
12
13
  export { CODE_PUSHUP_DOMAIN, FOOTER_PREFIX, README_LINK, TERMINAL_WIDTH, } from './lib/reports/constants';
14
+ export { listAuditsFromAllPlugins, listGroupsFromAllPlugins, } from './lib/reports/flatten-plugins';
13
15
  export { generateMdReport } from './lib/reports/generate-md-report';
14
- export { generateStdoutSummary } from './lib/reports/generate-stdout-summary';
16
+ export { generateMdReportsDiff } from './lib/reports/generate-md-reports-diff';
17
+ export { logStdoutSummary } from './lib/reports/log-stdout-summary';
15
18
  export { scoreReport } from './lib/reports/scoring';
16
19
  export { sortReport } from './lib/reports/sorting';
17
- export { ScoredReport } from './lib/reports/types';
20
+ export { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/reports/types';
18
21
  export { calcDuration, compareIssueSeverity, loadReport, } from './lib/reports/utils';
19
22
  export { CliArgsObject, capitalize, countOccurrences, distinct, factorOf, objectToCliArgs, objectToEntries, objectToKeys, toArray, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
20
23
  export { verboseUtils } from './lib/verbose-utils';
@@ -0,0 +1,15 @@
1
+ export type Diff<T> = {
2
+ before: T;
3
+ after: T;
4
+ };
5
+ export declare function matchArrayItemsByKey<T>({ before, after, key, }: Diff<T[]> & {
6
+ key: keyof T | ((item: T) => unknown);
7
+ }): {
8
+ pairs: Diff<T>[];
9
+ added: T[];
10
+ removed: T[];
11
+ };
12
+ export declare function comparePairs<T>(pairs: Diff<T>[], equalsFn: (pair: Diff<T>) => boolean): {
13
+ changed: Diff<T>[];
14
+ unchanged: T[];
15
+ };
@@ -71,6 +71,7 @@ export type ProcessConfig = {
71
71
  args?: string[];
72
72
  cwd?: string;
73
73
  observer?: ProcessObserver;
74
+ alwaysResolve?: boolean;
74
75
  };
75
76
  /**
76
77
  * Process observer object. Contains the onStdout, error and complete function.
@@ -1,7 +1,7 @@
1
1
  export declare function slugify(text: string): string;
2
- export declare function pluralize(text: string): string;
2
+ export declare function pluralize(text: string, amount?: number): string;
3
3
  export declare function formatBytes(bytes: number, decimals?: number): string;
4
- export declare function pluralizeToken(token: string, times?: number): string;
4
+ export declare function pluralizeToken(token: string, times: number): string;
5
5
  export declare function formatDuration(duration: number): string;
6
6
  export declare function formatDate(date: Date): string;
7
7
  export declare function truncateText(text: string, maxChars: number): string;
package/src/lib/git.d.ts CHANGED
@@ -5,6 +5,4 @@ export declare function formatGitPath(path: string, gitRoot: string): string;
5
5
  export declare function toGitPath(path: string, git?: import("simple-git").SimpleGit): Promise<string>;
6
6
  export declare function guardAgainstLocalChanges(git?: import("simple-git").SimpleGit): Promise<void>;
7
7
  export declare function getCurrentBranchOrTag(git?: import("simple-git").SimpleGit): Promise<string>;
8
- export declare function safeCheckout(branchOrHash: string, options?: {
9
- forceCleanStatus?: true;
10
- }, git?: import("simple-git").SimpleGit): Promise<void>;
8
+ export declare function safeCheckout(branchOrHash: string, forceCleanStatus?: boolean, git?: import("simple-git").SimpleGit): Promise<void>;
@@ -1,2 +1,2 @@
1
- export declare function logMultipleResults<T>(results: PromiseSettledResult<T>[], messagePrefix: string, succeededCallback?: (result: PromiseFulfilledResult<T>) => void, failedCallback?: (result: PromiseRejectedResult) => void): void;
2
- export declare function logPromiseResults<T extends PromiseFulfilledResult<unknown> | PromiseRejectedResult>(results: T[], logMessage: string, callback: (result: T) => void): void;
1
+ export declare function logMultipleResults<T>(results: PromiseSettledResult<T>[], messagePrefix: string, succeededTransform?: (result: PromiseFulfilledResult<T>) => string, failedTransform?: (result: PromiseRejectedResult) => string): void;
2
+ export declare function logPromiseResults<T extends PromiseFulfilledResult<unknown> | PromiseRejectedResult>(results: T[], logMessage: string, getMsg: (result: T) => string): void;
@@ -1 +1,21 @@
1
+ import isaacs_cliui from '@isaacs/cliui';
2
+ import { cliui } from '@poppinss/cliui';
3
+ type ArgumentsType<T> = T extends (...args: infer U) => any ? U : never;
4
+ export type CliUiBase = ReturnType<typeof cliui>;
5
+ type UI = ReturnType<typeof isaacs_cliui>;
6
+ type CliExtension = {
7
+ row: (r: ArgumentsType<UI['div']>) => void;
8
+ };
9
+ export type Column = {
10
+ text: string;
11
+ width?: number;
12
+ align?: 'right' | 'left' | 'center';
13
+ padding: number[];
14
+ border?: boolean;
15
+ };
16
+ export type CliUi = CliUiBase & CliExtension;
17
+ export declare let singletonUiInstance: CliUiBase | undefined;
18
+ export declare function ui(): CliUi;
19
+ export declare function logListItem(args: ArgumentsType<UI['div']>): void;
1
20
  export declare function link(text: string): string;
21
+ export {};
@@ -0,0 +1,9 @@
1
+ import { Report } from '@code-pushup/models';
2
+ export declare function listGroupsFromAllPlugins<T extends Report>(report: T): {
3
+ plugin: T['plugins'][number];
4
+ group: NonNullable<T['plugins'][number]['groups']>[number];
5
+ }[];
6
+ export declare function listAuditsFromAllPlugins<T extends Report>(report: T): {
7
+ plugin: T['plugins'][number];
8
+ audit: T['plugins'][number]['audits'][number];
9
+ }[];
@@ -0,0 +1,2 @@
1
+ import { ReportsDiff } from '@code-pushup/models';
2
+ export declare function generateMdReportsDiff(diff: ReportsDiff): string;
@@ -0,0 +1,2 @@
1
+ import { ScoredReport } from './types';
2
+ export declare function logStdoutSummary(report: ScoredReport): void;
@@ -6,6 +6,7 @@ export type Hierarchy = 1 | 2 | 3 | 4 | 5 | 6;
6
6
  */
7
7
  export declare function headline(text: string, hierarchy?: Hierarchy): string;
8
8
  export declare function h(text: string, hierarchy?: Hierarchy): string;
9
+ export declare function h1(text: string): string;
9
10
  export declare function h2(text: string): string;
10
11
  export declare function h3(text: string): string;
11
12
  export declare function h4(text: string): string;
@@ -0,0 +1 @@
1
+ export declare function image(src: string, alt: string): string;
@@ -1,6 +1,8 @@
1
1
  export * from './details';
2
2
  export * from './font-style';
3
3
  export * from './headline';
4
+ export * from './image';
4
5
  export * from './link';
5
6
  export * from './list';
7
+ export * from './paragraphs';
6
8
  export * from './table';
@@ -0,0 +1 @@
1
+ export declare function paragraphs(...sections: (string | undefined | boolean)[]): string;
@@ -19,3 +19,4 @@ export type SortableAuditReport = AuditReport & {
19
19
  weight: number;
20
20
  plugin: string;
21
21
  };
22
+ export type DiffOutcome = 'positive' | 'negative' | 'mixed' | 'unchanged';
@@ -1,8 +1,15 @@
1
1
  import { AuditReport, CategoryRef, IssueSeverity as CliIssueSeverity, Format, Group, Issue, PersistConfig, Report } from '@code-pushup/models';
2
2
  import { ScoredReport, SortableAuditReport, SortableGroup } from './types';
3
3
  export declare function formatReportScore(score: number): string;
4
+ export declare function formatScoreWithColor(score: number, options?: {
5
+ skipBold?: boolean;
6
+ }): string;
4
7
  export declare function getRoundScoreMarker(score: number): string;
5
8
  export declare function getSquaredScoreMarker(score: number): string;
9
+ export declare function getDiffMarker(diff: number): string;
10
+ export declare function colorByScoreDiff(text: string, diff: number): string;
11
+ export declare function shieldsBadge(text: string, color: string): string;
12
+ export declare function formatDiffNumber(diff: number): string;
6
13
  export declare function getSeverityIcon(severity: 'info' | 'warning' | 'error'): string;
7
14
  export declare function calcDuration(start: number, stop?: number): number;
8
15
  export declare function countWeightedRefs(refs: CategoryRef[]): number;
@@ -1,4 +1,4 @@
1
1
  export declare const verboseUtils: (verbose?: boolean) => {
2
- log: (...args: unknown[]) => void;
2
+ log: (msg: string) => void;
3
3
  exec: (fn: () => unknown) => void;
4
4
  };
@@ -1,2 +0,0 @@
1
- import { ScoredReport } from './types';
2
- export declare function generateStdoutSummary(report: ScoredReport): string;