@code-pushup/utils 0.26.1 → 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
@@ -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,43 +974,83 @@ 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 h2(text) {
1008
+ return headline(text, 2);
1009
+ }
1010
+ function h3(text) {
1011
+ return headline(text, 3);
1012
+ }
1013
+
1014
+ // packages/utils/src/lib/reports/md/link.ts
1015
+ function link2(href, text) {
1016
+ return `[${text || href}](${href})`;
1017
+ }
1018
+
1019
+ // packages/utils/src/lib/reports/md/list.ts
1020
+ function li(text, order = "unordered") {
1021
+ const style2 = order === "unordered" ? "-" : "- [ ]";
1022
+ return `${style2} ${text}`;
1023
+ }
1024
+
1025
+ // packages/utils/src/lib/reports/md/table.ts
1026
+ var alignString = /* @__PURE__ */ new Map([
1027
+ ["l", ":--"],
1028
+ ["c", ":--:"],
1029
+ ["r", "--:"]
1030
+ ]);
1031
+ function tableMd(data, align) {
1032
+ if (data.length === 0) {
1033
+ throw new Error("Data can't be empty");
1034
+ }
1035
+ const alignmentSetting = align ?? data[0]?.map(() => "c");
1036
+ const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1037
+ const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1038
+ return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1039
+ }
1040
+ function tableHtml(data) {
1041
+ if (data.length === 0) {
1042
+ throw new Error("Data can't be empty");
1043
+ }
1044
+ const tableContent = data.map((arr, index) => {
1045
+ if (index === 0) {
1046
+ const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1047
+ return `<tr>${headerRow}</tr>`;
1048
+ }
1049
+ const row = arr.map((s) => `<td>${s}</td>`).join("");
1050
+ return `<tr>${row}</tr>`;
1051
+ });
1052
+ return `<table>${tableContent.join("")}</table>`;
1053
+ }
805
1054
 
806
1055
  // packages/utils/src/lib/reports/utils.ts
807
1056
  function formatReportScore(score) {
@@ -998,7 +1247,7 @@ var ProcessError = class extends Error {
998
1247
  }
999
1248
  };
1000
1249
  function executeProcess(cfg) {
1001
- const { observer, cwd, command, args } = cfg;
1250
+ const { observer, cwd, command, args, alwaysResolve = false } = cfg;
1002
1251
  const { onStdout, onError, onComplete } = observer ?? {};
1003
1252
  const date = (/* @__PURE__ */ new Date()).toISOString();
1004
1253
  const start = performance.now();
@@ -1018,7 +1267,7 @@ function executeProcess(cfg) {
1018
1267
  });
1019
1268
  process2.on("close", (code) => {
1020
1269
  const timings = { date, duration: calcDuration(start) };
1021
- if (code === 0) {
1270
+ if (code === 0 || alwaysResolve) {
1022
1271
  onComplete?.();
1023
1272
  resolve({ code, stdout, stderr, ...timings });
1024
1273
  } else {
@@ -1133,14 +1382,14 @@ function toOrdinal(value) {
1133
1382
 
1134
1383
  // packages/utils/src/lib/git.ts
1135
1384
  async function getLatestCommit(git = simpleGit()) {
1136
- const log = await git.log({
1385
+ const log2 = await git.log({
1137
1386
  maxCount: 1,
1138
1387
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1139
1388
  });
1140
- if (!log.latest) {
1389
+ if (!log2.latest) {
1141
1390
  return null;
1142
1391
  }
1143
- return commitSchema.parse(log.latest);
1392
+ return commitSchema.parse(log2.latest);
1144
1393
  }
1145
1394
  function getGitRoot(git = simpleGit()) {
1146
1395
  return git.revparse("--show-toplevel");
@@ -1154,6 +1403,35 @@ async function toGitPath(path, git = simpleGit()) {
1154
1403
  const gitRoot = await getGitRoot(git);
1155
1404
  return formatGitPath(path, gitRoot);
1156
1405
  }
1406
+ async function guardAgainstLocalChanges(git = simpleGit()) {
1407
+ const isClean = await git.status(["-s"]).then((r) => r.files.length === 0);
1408
+ if (!isClean) {
1409
+ throw new Error(
1410
+ "Working directory needs to be clean before we you can proceed. Commit your local changes or stash them."
1411
+ );
1412
+ }
1413
+ }
1414
+ async function getCurrentBranchOrTag(git = simpleGit()) {
1415
+ try {
1416
+ const branch = await git.branch().then((r) => r.current);
1417
+ if (branch) {
1418
+ return branch;
1419
+ } else {
1420
+ return await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1421
+ }
1422
+ } catch {
1423
+ throw new Error("Could not get current tag or branch.");
1424
+ }
1425
+ }
1426
+ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1427
+ if (forceCleanStatus) {
1428
+ await git.raw(["reset", "--hard"]);
1429
+ await git.clean(["f", "d"]);
1430
+ ui().logger.info(`git status cleaned`);
1431
+ }
1432
+ await guardAgainstLocalChanges(git);
1433
+ await git.checkout(branchOrHash);
1434
+ }
1157
1435
 
1158
1436
  // packages/utils/src/lib/group-by-status.ts
1159
1437
  function groupByStatus(results) {
@@ -1163,12 +1441,6 @@ function groupByStatus(results) {
1163
1441
  );
1164
1442
  }
1165
1443
 
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
1444
  // packages/utils/src/lib/progress.ts
1173
1445
  import chalk3 from "chalk";
1174
1446
  import { MultiProgressBars } from "multi-progress-bars";
@@ -1224,80 +1496,105 @@ function getProgressBar(taskName) {
1224
1496
  };
1225
1497
  }
1226
1498
 
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);
1499
+ // packages/utils/src/lib/reports/log-stdout-summary.ts
1500
+ import chalk4 from "chalk";
1501
+ function log(msg = "") {
1502
+ ui().logger.log(msg);
1249
1503
  }
1250
-
1251
- // packages/utils/src/lib/reports/md/headline.ts
1252
- function headline(text, hierarchy = 1) {
1253
- return `${"#".repeat(hierarchy)} ${text}`;
1504
+ function logStdoutSummary(report) {
1505
+ const printCategories = report.categories.length > 0;
1506
+ log(reportToHeaderSection(report));
1507
+ log();
1508
+ logPlugins(report);
1509
+ if (printCategories) {
1510
+ logCategories(report);
1511
+ }
1512
+ log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1513
+ log();
1254
1514
  }
1255
- function h2(text) {
1256
- return headline(text, 2);
1515
+ function reportToHeaderSection(report) {
1516
+ const { packageName, version } = report;
1517
+ return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version}`;
1257
1518
  }
1258
- function h3(text) {
1259
- return headline(text, 3);
1519
+ function logPlugins(report) {
1520
+ const { plugins } = report;
1521
+ plugins.forEach((plugin) => {
1522
+ const { title, audits } = plugin;
1523
+ log();
1524
+ log(chalk4.magentaBright.bold(`${title} audits`));
1525
+ log();
1526
+ audits.forEach((audit) => {
1527
+ ui().row([
1528
+ {
1529
+ text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
1530
+ width: 2,
1531
+ padding: [0, 1, 0, 0]
1532
+ },
1533
+ {
1534
+ text: audit.title,
1535
+ // eslint-disable-next-line no-magic-numbers
1536
+ padding: [0, 3, 0, 0]
1537
+ },
1538
+ {
1539
+ text: chalk4.cyanBright(audit.displayValue || `${audit.value}`),
1540
+ width: 10,
1541
+ padding: [0, 0, 0, 0]
1542
+ }
1543
+ ]);
1544
+ });
1545
+ log();
1546
+ });
1260
1547
  }
1261
-
1262
- // packages/utils/src/lib/reports/md/link.ts
1263
- function link2(href, text) {
1264
- return `[${text || href}](${href})`;
1548
+ function logCategories({ categories, plugins }) {
1549
+ const hAlign = (idx) => idx === 0 ? "left" : "right";
1550
+ const rows = categories.map(({ title, score, refs }) => [
1551
+ title,
1552
+ applyScoreColor({ score }),
1553
+ countCategoryAudits(refs, plugins)
1554
+ ]);
1555
+ const table = ui().table();
1556
+ table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1557
+ table.head(
1558
+ reportRawOverviewTableHeaders.map((heading, idx) => ({
1559
+ content: chalk4.cyan(heading),
1560
+ hAlign: hAlign(idx)
1561
+ }))
1562
+ );
1563
+ rows.forEach(
1564
+ (row) => table.row(
1565
+ row.map((content, idx) => ({
1566
+ content: content.toString(),
1567
+ hAlign: hAlign(idx)
1568
+ }))
1569
+ )
1570
+ );
1571
+ log(chalk4.magentaBright.bold("Categories"));
1572
+ log();
1573
+ table.render();
1574
+ log();
1265
1575
  }
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}`;
1576
+ function applyScoreColor({ score, text }) {
1577
+ const formattedScore = text ?? formatReportScore(score);
1578
+ const style2 = text ? chalk4 : chalk4.bold;
1579
+ if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1580
+ return style2.green(formattedScore);
1581
+ }
1582
+ if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1583
+ return style2.yellow(formattedScore);
1584
+ }
1585
+ return style2.red(formattedScore);
1271
1586
  }
1272
1587
 
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);
1588
+ // packages/utils/src/lib/reports/flatten-plugins.ts
1589
+ function listGroupsFromAllPlugins(report) {
1590
+ return report.plugins.flatMap(
1591
+ (plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
1592
+ );
1287
1593
  }
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>`;
1594
+ function listAuditsFromAllPlugins(report) {
1595
+ return report.plugins.flatMap(
1596
+ (plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
1597
+ );
1301
1598
  }
1302
1599
 
1303
1600
  // packages/utils/src/lib/reports/generate-md-report.ts
@@ -1306,7 +1603,7 @@ function generateMdReport(report) {
1306
1603
  return (
1307
1604
  // header section
1308
1605
  // eslint-disable-next-line prefer-template
1309
- reportToHeaderSection() + NEW_LINE + // categories overview section
1606
+ reportToHeaderSection2() + NEW_LINE + // categories overview section
1310
1607
  (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1311
1608
  (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1312
1609
  reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
@@ -1314,7 +1611,7 @@ function generateMdReport(report) {
1314
1611
  `${FOOTER_PREFIX} ${link2(README_LINK, "Code PushUp")}`
1315
1612
  );
1316
1613
  }
1317
- function reportToHeaderSection() {
1614
+ function reportToHeaderSection2() {
1318
1615
  return headline(reportHeadlineText) + NEW_LINE;
1319
1616
  }
1320
1617
  function reportToOverviewSection(report) {
@@ -1487,82 +1784,6 @@ function getAuditResult(audit, isHtml = false) {
1487
1784
  return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1488
1785
  }
1489
1786
 
1490
- // packages/utils/src/lib/reports/generate-stdout-summary.ts
1491
- import cliui from "@isaacs/cliui";
1492
- import chalk4 from "chalk";
1493
- import CliTable3 from "cli-table3";
1494
- function addLine(line = "") {
1495
- return line + NEW_LINE;
1496
- }
1497
- function generateStdoutSummary(report) {
1498
- 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}`);
1500
- }
1501
- function reportToHeaderSection2(report) {
1502
- const { packageName, version } = report;
1503
- return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version}`;
1504
- }
1505
- function reportToDetailSection(report) {
1506
- const { plugins } = report;
1507
- return plugins.reduce((acc, plugin) => {
1508
- const { title, audits } = plugin;
1509
- const ui = cliui({ width: TERMINAL_WIDTH });
1510
- audits.forEach((audit) => {
1511
- ui.div(
1512
- {
1513
- text: withColor({ score: audit.score, text: "\u25CF" }),
1514
- width: 2,
1515
- padding: [0, 1, 0, 0]
1516
- },
1517
- {
1518
- text: audit.title,
1519
- // eslint-disable-next-line no-magic-numbers
1520
- padding: [0, 3, 0, 0]
1521
- },
1522
- {
1523
- text: chalk4.cyanBright(audit.displayValue || `${audit.value}`),
1524
- width: 10,
1525
- padding: [0, 0, 0, 0]
1526
- }
1527
- );
1528
- });
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
- }
1544
- });
1545
- table.push(
1546
- ...categories.map(({ title, score, refs }) => [
1547
- title,
1548
- withColor({ score }),
1549
- countCategoryAudits(refs, plugins)
1550
- ])
1551
- );
1552
- return addLine(chalk4.magentaBright.bold("Categories")) + addLine() + addLine(table.toString());
1553
- }
1554
- function withColor({ score, text }) {
1555
- const formattedScore = text ?? formatReportScore(score);
1556
- const style2 = text ? chalk4 : chalk4.bold;
1557
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1558
- return style2.green(formattedScore);
1559
- }
1560
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1561
- return style2.yellow(formattedScore);
1562
- }
1563
- return style2.red(formattedScore);
1564
- }
1565
-
1566
1787
  // packages/utils/src/lib/reports/scoring.ts
1567
1788
  var GroupRefInvalidError = class extends Error {
1568
1789
  constructor(auditSlug, pluginSlug) {
@@ -1702,9 +1923,9 @@ function sortPlugins(plugins) {
1702
1923
 
1703
1924
  // packages/utils/src/lib/verbose-utils.ts
1704
1925
  function getLogVerbose(verbose = false) {
1705
- return (...args) => {
1926
+ return (msg) => {
1706
1927
  if (verbose) {
1707
- console.info(...args);
1928
+ ui().logger.info(msg);
1708
1929
  }
1709
1930
  };
1710
1931
  }
@@ -1728,6 +1949,7 @@ export {
1728
1949
  calcDuration,
1729
1950
  capitalize,
1730
1951
  compareIssueSeverity,
1952
+ comparePairs,
1731
1953
  countOccurrences,
1732
1954
  crawlFileSystem,
1733
1955
  directoryExists,
@@ -1743,7 +1965,7 @@ export {
1743
1965
  formatDuration,
1744
1966
  formatGitPath,
1745
1967
  generateMdReport,
1746
- generateStdoutSummary,
1968
+ getCurrentBranchOrTag,
1747
1969
  getGitRoot,
1748
1970
  getLatestCommit,
1749
1971
  getProgressBar,
@@ -1752,9 +1974,13 @@ export {
1752
1974
  isPromiseFulfilledResult,
1753
1975
  isPromiseRejectedResult,
1754
1976
  link,
1977
+ listAuditsFromAllPlugins,
1978
+ listGroupsFromAllPlugins,
1755
1979
  loadReport,
1756
1980
  logMultipleFileResults,
1757
1981
  logMultipleResults,
1982
+ logStdoutSummary,
1983
+ matchArrayItemsByKey,
1758
1984
  objectToCliArgs,
1759
1985
  objectToEntries,
1760
1986
  objectToKeys,
@@ -1764,6 +1990,7 @@ export {
1764
1990
  readJsonFile,
1765
1991
  readTextFile,
1766
1992
  removeDirectoryIfExists,
1993
+ safeCheckout,
1767
1994
  scoreReport,
1768
1995
  slugify,
1769
1996
  sortReport,
@@ -1777,5 +2004,6 @@ export {
1777
2004
  truncateIssueMessage,
1778
2005
  truncateText,
1779
2006
  truncateTitle,
2007
+ ui,
1780
2008
  verboseUtils
1781
2009
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-pushup/utils",
3
- "version": "0.26.1",
3
+ "version": "0.27.1",
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,22 @@
1
1
  export { exists } from '@code-pushup/models';
2
+ export { Diff, matchArrayItemsByKey, comparePairs } 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, getGitRoot, getLatestCommit, toGitPath, getCurrentBranchOrTag, safeCheckout, } 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
11
  export { ProgressBar, getProgressBar } from './lib/progress';
12
+ export { logStdoutSummary } from './lib/reports/log-stdout-summary';
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';
15
16
  export { scoreReport } from './lib/reports/scoring';
16
17
  export { sortReport } from './lib/reports/sorting';
17
- export { ScoredReport } from './lib/reports/types';
18
+ export { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/reports/types';
18
19
  export { calcDuration, compareIssueSeverity, loadReport, } from './lib/reports/utils';
19
20
  export { CliArgsObject, capitalize, countOccurrences, distinct, factorOf, objectToCliArgs, objectToEntries, objectToKeys, toArray, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
20
21
  export { verboseUtils } from './lib/verbose-utils';
22
+ export { link, ui, CliUi, Column } from './lib/logging';
@@ -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;