@code-pushup/cli 0.47.0 → 0.49.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
@@ -4,7 +4,7 @@
4
4
  import { hideBin } from "yargs/helpers";
5
5
 
6
6
  // packages/cli/src/lib/autorun/autorun-command.ts
7
- import chalk7 from "chalk";
7
+ import { bold as bold7, gray as gray4 } from "ansis";
8
8
 
9
9
  // packages/models/src/lib/implementation/schemas.ts
10
10
  import { MATERIAL_ICONS } from "vscode-material-icons";
@@ -756,11 +756,262 @@ function comparePairs(pairs, equalsFn) {
756
756
  import { spawn } from "node:child_process";
757
757
 
758
758
  // packages/utils/src/lib/reports/utils.ts
759
- import { join } from "node:path";
759
+ import ansis from "ansis";
760
+ import { md } from "build-md";
761
+
762
+ // packages/utils/src/lib/reports/constants.ts
763
+ var TERMINAL_WIDTH = 80;
764
+ var SCORE_COLOR_RANGE = {
765
+ GREEN_MIN: 0.9,
766
+ YELLOW_MIN: 0.5
767
+ };
768
+ var FOOTER_PREFIX = "Made with \u2764 by";
769
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
770
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
771
+ var REPORT_HEADLINE_TEXT = "Code PushUp Report";
772
+ var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
773
+ "Category",
774
+ "Score",
775
+ "Audits"
776
+ ];
777
+
778
+ // packages/utils/src/lib/reports/utils.ts
779
+ function formatReportScore(score) {
780
+ const scaledScore = score * 100;
781
+ const roundedScore = Math.round(scaledScore);
782
+ return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
783
+ }
784
+ function formatScoreWithColor(score, options2) {
785
+ const styledNumber = options2?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
786
+ return md`${scoreMarker(score)} ${styledNumber}`;
787
+ }
788
+ var MARKERS = {
789
+ circle: {
790
+ red: "\u{1F534}",
791
+ yellow: "\u{1F7E1}",
792
+ green: "\u{1F7E2}"
793
+ },
794
+ square: {
795
+ red: "\u{1F7E5}",
796
+ yellow: "\u{1F7E8}",
797
+ green: "\u{1F7E9}"
798
+ }
799
+ };
800
+ function scoreMarker(score, markerType = "circle") {
801
+ if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
802
+ return MARKERS[markerType].green;
803
+ }
804
+ if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
805
+ return MARKERS[markerType].yellow;
806
+ }
807
+ return MARKERS[markerType].red;
808
+ }
809
+ function getDiffMarker(diff) {
810
+ if (diff > 0) {
811
+ return "\u2191";
812
+ }
813
+ if (diff < 0) {
814
+ return "\u2193";
815
+ }
816
+ return "";
817
+ }
818
+ function colorByScoreDiff(text, diff) {
819
+ const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
820
+ return shieldsBadge(text, color);
821
+ }
822
+ function shieldsBadge(text, color) {
823
+ return md.image(
824
+ `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
825
+ text
826
+ );
827
+ }
828
+ function formatDiffNumber(diff) {
829
+ const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
830
+ const sign = diff < 0 ? "\u2212" : "+";
831
+ return `${sign}${number}`;
832
+ }
833
+ function severityMarker(severity) {
834
+ if (severity === "error") {
835
+ return "\u{1F6A8}";
836
+ }
837
+ if (severity === "warning") {
838
+ return "\u26A0\uFE0F";
839
+ }
840
+ return "\u2139\uFE0F";
841
+ }
842
+ function formatScoreChange(diff) {
843
+ const marker = getDiffMarker(diff);
844
+ const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
845
+ return colorByScoreDiff(`${marker} ${text}`, diff);
846
+ }
847
+ function formatValueChange({
848
+ values,
849
+ scores
850
+ }) {
851
+ const marker = getDiffMarker(values.diff);
852
+ const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
853
+ const text = `${formatDiffNumber(percentage)}\u2009%`;
854
+ return colorByScoreDiff(`${marker} ${text}`, scores.diff);
855
+ }
856
+ function calcDuration(start, stop) {
857
+ return Math.round((stop ?? performance.now()) - start);
858
+ }
859
+ function countCategoryAudits(refs, plugins) {
860
+ const groupLookup = plugins.reduce(
861
+ (lookup, plugin) => {
862
+ if (plugin.groups == null || plugin.groups.length === 0) {
863
+ return lookup;
864
+ }
865
+ return {
866
+ ...lookup,
867
+ [plugin.slug]: Object.fromEntries(
868
+ plugin.groups.map((group) => [group.slug, group])
869
+ )
870
+ };
871
+ },
872
+ {}
873
+ );
874
+ return refs.reduce((acc, ref) => {
875
+ if (ref.type === "group") {
876
+ const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
877
+ return acc + (groupRefs?.length ?? 0);
878
+ }
879
+ return acc + 1;
880
+ }, 0);
881
+ }
882
+ function compareCategoryAuditsAndGroups(a, b) {
883
+ if (a.weight !== b.weight) {
884
+ return b.weight - a.weight;
885
+ }
886
+ if (a.score !== b.score) {
887
+ return a.score - b.score;
888
+ }
889
+ if ("value" in a && "value" in b && a.value !== b.value) {
890
+ return b.value - a.value;
891
+ }
892
+ return a.title.localeCompare(b.title);
893
+ }
894
+ function compareAudits(a, b) {
895
+ if (a.score !== b.score) {
896
+ return a.score - b.score;
897
+ }
898
+ if (a.value !== b.value) {
899
+ return b.value - a.value;
900
+ }
901
+ return a.title.localeCompare(b.title);
902
+ }
903
+ function compareIssueSeverity(severity1, severity2) {
904
+ const levels = {
905
+ info: 0,
906
+ warning: 1,
907
+ error: 2
908
+ };
909
+ return levels[severity1] - levels[severity2];
910
+ }
911
+ function throwIsNotPresentError(itemName, presentPlace) {
912
+ throw new Error(`${itemName} is not present in ${presentPlace}`);
913
+ }
914
+ function getPluginNameFromSlug(slug, plugins) {
915
+ return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
916
+ }
917
+ function compareIssues(a, b) {
918
+ if (a.severity !== b.severity) {
919
+ return -compareIssueSeverity(a.severity, b.severity);
920
+ }
921
+ if (!a.source && b.source) {
922
+ return -1;
923
+ }
924
+ if (a.source && !b.source) {
925
+ return 1;
926
+ }
927
+ if (a.source?.file !== b.source?.file) {
928
+ return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
929
+ }
930
+ if (!a.source?.position && b.source?.position) {
931
+ return -1;
932
+ }
933
+ if (a.source?.position && !b.source?.position) {
934
+ return 1;
935
+ }
936
+ if (a.source?.position?.startLine !== b.source?.position?.startLine) {
937
+ return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
938
+ }
939
+ return 0;
940
+ }
941
+ function applyScoreColor({ score, text }, style = ansis) {
942
+ const formattedScore = text ?? formatReportScore(score);
943
+ if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
944
+ return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
945
+ }
946
+ if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
947
+ return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
948
+ }
949
+ return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
950
+ }
951
+ function targetScoreIcon(score, targetScore, options2 = {}) {
952
+ if (targetScore != null) {
953
+ const {
954
+ passIcon = "\u2705",
955
+ failIcon = "\u274C",
956
+ prefix = "",
957
+ postfix = ""
958
+ } = options2;
959
+ if (score >= targetScore) {
960
+ return `${prefix}${passIcon}${postfix}`;
961
+ }
962
+ return `${prefix}${failIcon}${postfix}`;
963
+ }
964
+ return "";
965
+ }
966
+
967
+ // packages/utils/src/lib/execute-process.ts
968
+ var ProcessError = class extends Error {
969
+ code;
970
+ stderr;
971
+ stdout;
972
+ constructor(result) {
973
+ super(result.stderr);
974
+ this.code = result.code;
975
+ this.stderr = result.stderr;
976
+ this.stdout = result.stdout;
977
+ }
978
+ };
979
+ function executeProcess(cfg) {
980
+ const { observer, cwd, command: command2, args, ignoreExitCode = false } = cfg;
981
+ const { onStdout, onError, onComplete } = observer ?? {};
982
+ const date = (/* @__PURE__ */ new Date()).toISOString();
983
+ const start = performance.now();
984
+ return new Promise((resolve, reject) => {
985
+ const process2 = spawn(command2, args, { cwd, shell: true });
986
+ let stdout = "";
987
+ let stderr = "";
988
+ process2.stdout.on("data", (data) => {
989
+ stdout += String(data);
990
+ onStdout?.(String(data));
991
+ });
992
+ process2.stderr.on("data", (data) => {
993
+ stderr += String(data);
994
+ });
995
+ process2.on("error", (err) => {
996
+ stderr += err.toString();
997
+ });
998
+ process2.on("close", (code2) => {
999
+ const timings = { date, duration: calcDuration(start) };
1000
+ if (code2 === 0 || ignoreExitCode) {
1001
+ onComplete?.();
1002
+ resolve({ code: code2, stdout, stderr, ...timings });
1003
+ } else {
1004
+ const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
1005
+ onError?.(errorMsg);
1006
+ reject(errorMsg);
1007
+ }
1008
+ });
1009
+ });
1010
+ }
760
1011
 
761
1012
  // packages/utils/src/lib/file-system.ts
1013
+ import { bold, gray } from "ansis";
762
1014
  import { bundleRequire } from "bundle-require";
763
- import chalk2 from "chalk";
764
1015
  import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
765
1016
 
766
1017
  // packages/utils/src/lib/formatting.ts
@@ -823,55 +1074,7 @@ function isPromiseRejectedResult(result) {
823
1074
  // packages/utils/src/lib/logging.ts
824
1075
  import isaacs_cliui from "@isaacs/cliui";
825
1076
  import { cliui } from "@poppinss/cliui";
826
- import chalk from "chalk";
827
-
828
- // packages/utils/src/lib/reports/constants.ts
829
- var TERMINAL_WIDTH = 80;
830
- var SCORE_COLOR_RANGE = {
831
- GREEN_MIN: 0.9,
832
- YELLOW_MIN: 0.5
833
- };
834
- var CATEGORIES_TITLE = "\u{1F3F7} Categories";
835
- var FOOTER_PREFIX = "Made with \u2764 by";
836
- var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
837
- var README_LINK = "https://github.com/code-pushup/cli#readme";
838
- var reportHeadlineText = "Code PushUp Report";
839
- var reportOverviewTableHeaders = [
840
- {
841
- key: "category",
842
- label: "\u{1F3F7} Category",
843
- align: "left"
844
- },
845
- {
846
- key: "score",
847
- label: "\u2B50 Score"
848
- },
849
- {
850
- key: "audits",
851
- label: "\u{1F6E1} Audits"
852
- }
853
- ];
854
- var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
855
- var issuesTableHeadings = [
856
- {
857
- key: "severity",
858
- label: "Severity"
859
- },
860
- {
861
- key: "message",
862
- label: "Message"
863
- },
864
- {
865
- key: "file",
866
- label: "Source file"
867
- },
868
- {
869
- key: "line",
870
- label: "Line(s)"
871
- }
872
- ];
873
-
874
- // packages/utils/src/lib/logging.ts
1077
+ import { underline } from "ansis";
875
1078
  var singletonUiInstance;
876
1079
  function ui() {
877
1080
  if (singletonUiInstance === void 0) {
@@ -895,7 +1098,7 @@ function logListItem(args) {
895
1098
  singletonUiInstance?.logger.log(content);
896
1099
  }
897
1100
  function link(text) {
898
- return chalk.underline(chalk.blueBright(text));
1101
+ return underline.blueBright(text);
899
1102
  }
900
1103
 
901
1104
  // packages/utils/src/lib/log-results.ts
@@ -970,582 +1173,23 @@ async function ensureDirectoryExists(baseDir) {
970
1173
  function logMultipleFileResults(fileResults, messagePrefix) {
971
1174
  const succeededTransform = (result) => {
972
1175
  const [fileName, size] = result.value;
973
- const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
974
- return `- ${chalk2.bold(fileName)}${formattedSize}`;
975
- };
976
- const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
977
- logMultipleResults(
978
- fileResults,
979
- messagePrefix,
980
- succeededTransform,
981
- failedTransform
982
- );
983
- }
984
- async function importModule(options2) {
985
- const { mod } = await bundleRequire(options2);
986
- if (typeof mod === "object" && "default" in mod) {
987
- return mod.default;
988
- }
989
- return mod;
990
- }
991
-
992
- // packages/utils/src/lib/text-formats/constants.ts
993
- var NEW_LINE = "\n";
994
- var TAB = " ";
995
- var SPACE = " ";
996
-
997
- // packages/utils/src/lib/text-formats/html/details.ts
998
- function details(title, content, cfg = { open: false }) {
999
- return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
1000
- NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
1001
- // ⚠️ The blank line ensure Markdown in content is rendered correctly.
1002
- NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
1003
- NEW_LINE}`;
1004
- }
1005
-
1006
- // packages/utils/src/lib/text-formats/html/font-style.ts
1007
- var boldElement = "b";
1008
- function bold(text) {
1009
- return `<${boldElement}>${text}</${boldElement}>`;
1010
- }
1011
- var italicElement = "i";
1012
- function italic(text) {
1013
- return `<${italicElement}>${text}</${italicElement}>`;
1014
- }
1015
- var codeElement = "code";
1016
- function code(text) {
1017
- return `<${codeElement}>${text}</${codeElement}>`;
1018
- }
1019
-
1020
- // packages/utils/src/lib/text-formats/html/link.ts
1021
- function link2(href, text) {
1022
- return `<a href="${href}">${text || href}</a>`;
1023
- }
1024
-
1025
- // packages/utils/src/lib/transform.ts
1026
- function toArray(val) {
1027
- return Array.isArray(val) ? val : [val];
1028
- }
1029
- function objectToEntries(obj) {
1030
- return Object.entries(obj);
1031
- }
1032
- function deepClone(obj) {
1033
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1034
- }
1035
- function toUnixPath(path) {
1036
- return path.replace(/\\/g, "/");
1037
- }
1038
- function capitalize(text) {
1039
- return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
1040
- 1
1041
- )}`;
1042
- }
1043
-
1044
- // packages/utils/src/lib/text-formats/table.ts
1045
- function rowToStringArray({ rows, columns = [] }) {
1046
- if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
1047
- throw new TypeError(
1048
- "Column can`t be object when rows are primitive values"
1049
- );
1050
- }
1051
- return rows.map((row) => {
1052
- if (Array.isArray(row)) {
1053
- return row.map(String);
1054
- }
1055
- const objectRow = row;
1056
- if (columns.length === 0 || typeof columns.at(0) === "string") {
1057
- return Object.values(objectRow).map(
1058
- (value) => value == null ? "" : String(value)
1059
- );
1060
- }
1061
- return columns.map(
1062
- ({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
1063
- );
1064
- });
1065
- }
1066
- function columnsToStringArray({
1067
- rows,
1068
- columns = []
1069
- }) {
1070
- const firstRow = rows.at(0);
1071
- const primitiveRows = Array.isArray(firstRow);
1072
- if (typeof columns.at(0) === "string" && !primitiveRows) {
1073
- throw new Error("invalid union type. Caught by model parsing.");
1074
- }
1075
- if (columns.length === 0) {
1076
- if (Array.isArray(firstRow)) {
1077
- return firstRow.map((_, idx) => String(idx));
1078
- }
1079
- return Object.keys(firstRow);
1080
- }
1081
- if (typeof columns.at(0) === "string") {
1082
- return columns.map(String);
1083
- }
1084
- const cols = columns;
1085
- return cols.map(({ label, key }) => label ?? capitalize(key));
1086
- }
1087
- function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
1088
- const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
1089
- if (typeof column === "string") {
1090
- return column;
1091
- } else if (typeof column === "object") {
1092
- return column.align ?? "center";
1093
- } else {
1094
- return "center";
1095
- }
1096
- }
1097
- function getColumnAlignmentForIndex(targetIdx, columns = []) {
1098
- const column = columns.at(targetIdx);
1099
- if (column == null) {
1100
- return "center";
1101
- } else if (typeof column === "string") {
1102
- return column;
1103
- } else if (typeof column === "object") {
1104
- return column.align ?? "center";
1105
- } else {
1106
- return "center";
1107
- }
1108
- }
1109
- function getColumnAlignments(tableData) {
1110
- const { rows, columns = [] } = tableData;
1111
- if (rows.at(0) == null) {
1112
- throw new Error("first row can`t be undefined.");
1113
- }
1114
- if (Array.isArray(rows.at(0))) {
1115
- const firstPrimitiveRow = rows.at(0);
1116
- return Array.from({ length: firstPrimitiveRow.length }).map(
1117
- (_, idx) => getColumnAlignmentForIndex(idx, columns)
1118
- );
1119
- }
1120
- const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
1121
- if (columns.length > 0) {
1122
- return columns.map(
1123
- (column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
1124
- column.key,
1125
- idx,
1126
- columns
1127
- )
1128
- );
1129
- }
1130
- return Object.keys(biggestRow ?? {}).map((_) => "center");
1131
- }
1132
-
1133
- // packages/utils/src/lib/text-formats/html/table.ts
1134
- function wrap(elem, content) {
1135
- return `<${elem}>${content}</${elem}>${NEW_LINE}`;
1136
- }
1137
- function wrapRow(content) {
1138
- const elem = "tr";
1139
- return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
1140
- }
1141
- function table(tableData) {
1142
- if (tableData.rows.length === 0) {
1143
- throw new Error("Data can't be empty");
1144
- }
1145
- const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
1146
- const tableHeaderRow = wrapRow(tableHeaderCols);
1147
- const tableBody = rowToStringArray(tableData).map((arr) => {
1148
- const columns = arr.map((s) => wrap("td", s)).join("");
1149
- return wrapRow(columns);
1150
- }).join("");
1151
- return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
1152
- }
1153
-
1154
- // packages/utils/src/lib/text-formats/md/font-style.ts
1155
- var boldWrap = "**";
1156
- function bold2(text) {
1157
- return `${boldWrap}${text}${boldWrap}`;
1158
- }
1159
- var italicWrap = "_";
1160
- function italic2(text) {
1161
- return `${italicWrap}${text}${italicWrap}`;
1162
- }
1163
- var strikeThroughWrap = "~";
1164
- function strikeThrough(text) {
1165
- return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
1166
- }
1167
- var codeWrap = "`";
1168
- function code2(text) {
1169
- return `${codeWrap}${text}${codeWrap}`;
1170
- }
1171
-
1172
- // packages/utils/src/lib/text-formats/md/headline.ts
1173
- function headline(text, hierarchy = 1) {
1174
- return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
1175
- }
1176
- function h(text, hierarchy = 1) {
1177
- return headline(text, hierarchy);
1178
- }
1179
- function h1(text) {
1180
- return headline(text, 1);
1181
- }
1182
- function h2(text) {
1183
- return headline(text, 2);
1184
- }
1185
- function h3(text) {
1186
- return headline(text, 3);
1187
- }
1188
- function h4(text) {
1189
- return headline(text, 4);
1190
- }
1191
- function h5(text) {
1192
- return headline(text, 5);
1193
- }
1194
- function h6(text) {
1195
- return headline(text, 6);
1196
- }
1197
-
1198
- // packages/utils/src/lib/text-formats/md/image.ts
1199
- function image(src, alt) {
1200
- return `![${alt}](${src})`;
1201
- }
1202
-
1203
- // packages/utils/src/lib/text-formats/md/link.ts
1204
- function link3(href, text) {
1205
- return `[${text || href}](${href})`;
1206
- }
1207
-
1208
- // packages/utils/src/lib/text-formats/md/list.ts
1209
- function li(text, order = "unordered") {
1210
- const style = order === "unordered" ? "-" : "- [ ]";
1211
- return `${style} ${text}`;
1212
- }
1213
- function indentation(text, level = 1) {
1214
- return `${TAB.repeat(level)}${text}`;
1215
- }
1216
-
1217
- // packages/utils/src/lib/text-formats/md/paragraphs.ts
1218
- function paragraphs(...sections) {
1219
- return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
1220
- }
1221
-
1222
- // packages/utils/src/lib/text-formats/md/section.ts
1223
- function section(...contents) {
1224
- return `${lines(...contents)}${NEW_LINE}`;
1225
- }
1226
- function lines(...contents) {
1227
- const filteredContent = contents.filter(
1228
- (value) => value != null && value !== "" && value !== false
1229
- );
1230
- return `${filteredContent.join(NEW_LINE)}`;
1231
- }
1232
-
1233
- // packages/utils/src/lib/text-formats/md/table.ts
1234
- var alignString = /* @__PURE__ */ new Map([
1235
- ["left", ":--"],
1236
- ["center", ":--:"],
1237
- ["right", "--:"]
1238
- ]);
1239
- function tableRow(rows) {
1240
- return `|${rows.join("|")}|`;
1241
- }
1242
- function table2(data) {
1243
- if (data.rows.length === 0) {
1244
- throw new Error("Data can't be empty");
1245
- }
1246
- const alignmentRow = getColumnAlignments(data).map(
1247
- (s) => alignString.get(s) ?? String(alignString.get("center"))
1248
- );
1249
- return section(
1250
- `${lines(
1251
- tableRow(columnsToStringArray(data)),
1252
- tableRow(alignmentRow),
1253
- ...rowToStringArray(data).map(tableRow)
1254
- )}`
1255
- );
1256
- }
1257
-
1258
- // packages/utils/src/lib/text-formats/index.ts
1259
- var md = {
1260
- bold: bold2,
1261
- italic: italic2,
1262
- strikeThrough,
1263
- code: code2,
1264
- link: link3,
1265
- image,
1266
- headline,
1267
- h,
1268
- h1,
1269
- h2,
1270
- h3,
1271
- h4,
1272
- h5,
1273
- h6,
1274
- indentation,
1275
- lines,
1276
- li,
1277
- section,
1278
- paragraphs,
1279
- table: table2
1280
- };
1281
- var html = {
1282
- bold,
1283
- italic,
1284
- code,
1285
- link: link2,
1286
- details,
1287
- table
1288
- };
1289
-
1290
- // packages/utils/src/lib/reports/utils.ts
1291
- var { image: image2, bold: boldMd } = md;
1292
- function formatReportScore(score) {
1293
- const scaledScore = score * 100;
1294
- const roundedScore = Math.round(scaledScore);
1295
- return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
1296
- }
1297
- function formatScoreWithColor(score, options2) {
1298
- const styledNumber = options2?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
1299
- return `${scoreMarker(score)} ${styledNumber}`;
1300
- }
1301
- var MARKERS = {
1302
- circle: {
1303
- red: "\u{1F534}",
1304
- yellow: "\u{1F7E1}",
1305
- green: "\u{1F7E2}"
1306
- },
1307
- square: {
1308
- red: "\u{1F7E5}",
1309
- yellow: "\u{1F7E8}",
1310
- green: "\u{1F7E9}"
1311
- }
1312
- };
1313
- function scoreMarker(score, markerType = "circle") {
1314
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1315
- return MARKERS[markerType].green;
1316
- }
1317
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1318
- return MARKERS[markerType].yellow;
1319
- }
1320
- return MARKERS[markerType].red;
1321
- }
1322
- function getDiffMarker(diff) {
1323
- if (diff > 0) {
1324
- return "\u2191";
1325
- }
1326
- if (diff < 0) {
1327
- return "\u2193";
1328
- }
1329
- return "";
1330
- }
1331
- function colorByScoreDiff(text, diff) {
1332
- const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
1333
- return shieldsBadge(text, color);
1334
- }
1335
- function shieldsBadge(text, color) {
1336
- return image2(
1337
- `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1338
- text
1339
- );
1340
- }
1341
- function formatDiffNumber(diff) {
1342
- const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
1343
- const sign = diff < 0 ? "\u2212" : "+";
1344
- return `${sign}${number}`;
1345
- }
1346
- function severityMarker(severity) {
1347
- if (severity === "error") {
1348
- return "\u{1F6A8}";
1349
- }
1350
- if (severity === "warning") {
1351
- return "\u26A0\uFE0F";
1352
- }
1353
- return "\u2139\uFE0F";
1354
- }
1355
- function calcDuration(start, stop) {
1356
- return Math.round((stop ?? performance.now()) - start);
1357
- }
1358
- function countCategoryAudits(refs, plugins) {
1359
- const groupLookup = plugins.reduce(
1360
- (lookup, plugin) => {
1361
- if (plugin.groups == null || plugin.groups.length === 0) {
1362
- return lookup;
1363
- }
1364
- return {
1365
- ...lookup,
1366
- [plugin.slug]: Object.fromEntries(
1367
- plugin.groups.map((group) => [group.slug, group])
1368
- )
1369
- };
1370
- },
1371
- {}
1372
- );
1373
- return refs.reduce((acc, ref) => {
1374
- if (ref.type === "group") {
1375
- const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
1376
- return acc + (groupRefs?.length ?? 0);
1377
- }
1378
- return acc + 1;
1379
- }, 0);
1380
- }
1381
- function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
1382
- const auditPlugin = plugins.find((p) => p.slug === plugin);
1383
- if (!auditPlugin) {
1384
- throwIsNotPresentError(`Plugin ${plugin}`, "report");
1385
- }
1386
- const audit = auditPlugin.audits.find(
1387
- ({ slug: auditSlug }) => auditSlug === slug
1388
- );
1389
- if (!audit) {
1390
- throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
1391
- }
1392
- return {
1393
- ...audit,
1394
- weight,
1395
- plugin
1396
- };
1397
- }
1398
- function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
1399
- const groupPlugin = plugins.find((p) => p.slug === plugin);
1400
- if (!groupPlugin) {
1401
- throwIsNotPresentError(`Plugin ${plugin}`, "report");
1402
- }
1403
- const group = groupPlugin.groups?.find(
1404
- ({ slug: groupSlug }) => groupSlug === slug
1405
- );
1406
- if (!group) {
1407
- throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
1408
- }
1409
- const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
1410
- const sortedAuditRefs = [...group.refs].sort((a, b) => {
1411
- const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
1412
- const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
1413
- return aIndex - bIndex;
1414
- });
1415
- return {
1416
- ...group,
1417
- refs: sortedAuditRefs,
1418
- plugin,
1419
- weight
1420
- };
1421
- }
1422
- function getSortedGroupAudits(group, plugin, plugins) {
1423
- return group.refs.map(
1424
- (ref) => getSortableAuditByRef(
1425
- {
1426
- plugin,
1427
- slug: ref.slug,
1428
- weight: ref.weight,
1429
- type: "audit"
1430
- },
1431
- plugins
1432
- )
1433
- ).sort(compareCategoryAuditsAndGroups);
1434
- }
1435
- function compareCategoryAuditsAndGroups(a, b) {
1436
- if (a.weight !== b.weight) {
1437
- return b.weight - a.weight;
1438
- }
1439
- if (a.score !== b.score) {
1440
- return a.score - b.score;
1441
- }
1442
- if ("value" in a && "value" in b && a.value !== b.value) {
1443
- return b.value - a.value;
1444
- }
1445
- return a.title.localeCompare(b.title);
1446
- }
1447
- function compareAudits(a, b) {
1448
- if (a.score !== b.score) {
1449
- return a.score - b.score;
1450
- }
1451
- if (a.value !== b.value) {
1452
- return b.value - a.value;
1453
- }
1454
- return a.title.localeCompare(b.title);
1455
- }
1456
- function compareIssueSeverity(severity1, severity2) {
1457
- const levels = {
1458
- info: 0,
1459
- warning: 1,
1460
- error: 2
1461
- };
1462
- return levels[severity1] - levels[severity2];
1463
- }
1464
- async function loadReport(options2) {
1465
- const { outputDir, filename, format } = options2;
1466
- await ensureDirectoryExists(outputDir);
1467
- const filePath = join(outputDir, `${filename}.${format}`);
1468
- if (format === "json") {
1469
- const content = await readJsonFile(filePath);
1470
- return reportSchema.parse(content);
1471
- }
1472
- const text = await readTextFile(filePath);
1473
- return text;
1474
- }
1475
- function throwIsNotPresentError(itemName, presentPlace) {
1476
- throw new Error(`${itemName} is not present in ${presentPlace}`);
1477
- }
1478
- function getPluginNameFromSlug(slug, plugins) {
1479
- return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
1480
- }
1481
- function compareIssues(a, b) {
1482
- if (a.severity !== b.severity) {
1483
- return -compareIssueSeverity(a.severity, b.severity);
1484
- }
1485
- if (!a.source && b.source) {
1486
- return -1;
1487
- }
1488
- if (a.source && !b.source) {
1489
- return 1;
1490
- }
1491
- if (a.source?.file !== b.source?.file) {
1492
- return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
1493
- }
1494
- if (!a.source?.position && b.source?.position) {
1495
- return -1;
1496
- }
1497
- if (a.source?.position && !b.source?.position) {
1498
- return 1;
1499
- }
1500
- if (a.source?.position?.startLine !== b.source?.position?.startLine) {
1501
- return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
1502
- }
1503
- return 0;
1504
- }
1505
-
1506
- // packages/utils/src/lib/execute-process.ts
1507
- var ProcessError = class extends Error {
1508
- code;
1509
- stderr;
1510
- stdout;
1511
- constructor(result) {
1512
- super(result.stderr);
1513
- this.code = result.code;
1514
- this.stderr = result.stderr;
1515
- this.stdout = result.stdout;
1176
+ const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
1177
+ return `- ${bold(fileName)}${formattedSize}`;
1178
+ };
1179
+ const failedTransform = (result) => `- ${bold(result.reason)}`;
1180
+ logMultipleResults(
1181
+ fileResults,
1182
+ messagePrefix,
1183
+ succeededTransform,
1184
+ failedTransform
1185
+ );
1186
+ }
1187
+ async function importModule(options2) {
1188
+ const { mod } = await bundleRequire(options2);
1189
+ if (typeof mod === "object" && "default" in mod) {
1190
+ return mod.default;
1516
1191
  }
1517
- };
1518
- function executeProcess(cfg) {
1519
- const { observer, cwd, command: command2, args, ignoreExitCode = false } = cfg;
1520
- const { onStdout, onError, onComplete } = observer ?? {};
1521
- const date = (/* @__PURE__ */ new Date()).toISOString();
1522
- const start = performance.now();
1523
- return new Promise((resolve, reject) => {
1524
- const process2 = spawn(command2, args, { cwd, shell: true });
1525
- let stdout = "";
1526
- let stderr = "";
1527
- process2.stdout.on("data", (data) => {
1528
- stdout += String(data);
1529
- onStdout?.(String(data));
1530
- });
1531
- process2.stderr.on("data", (data) => {
1532
- stderr += String(data);
1533
- });
1534
- process2.on("error", (err) => {
1535
- stderr += err.toString();
1536
- });
1537
- process2.on("close", (code3) => {
1538
- const timings = { date, duration: calcDuration(start) };
1539
- if (code3 === 0 || ignoreExitCode) {
1540
- onComplete?.();
1541
- resolve({ code: code3, stdout, stderr, ...timings });
1542
- } else {
1543
- const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
1544
- onError?.(errorMsg);
1545
- reject(errorMsg);
1546
- }
1547
- });
1548
- });
1192
+ return mod;
1549
1193
  }
1550
1194
 
1551
1195
  // packages/utils/src/lib/filter.ts
@@ -1557,13 +1201,34 @@ function filterItemRefsBy(items, refFilterFn) {
1557
1201
  }
1558
1202
 
1559
1203
  // packages/utils/src/lib/git/git.ts
1560
- import { isAbsolute, join as join2, relative } from "node:path";
1204
+ import { isAbsolute, join, relative } from "node:path";
1561
1205
  import { simpleGit } from "simple-git";
1206
+
1207
+ // packages/utils/src/lib/transform.ts
1208
+ function toArray(val) {
1209
+ return Array.isArray(val) ? val : [val];
1210
+ }
1211
+ function objectToEntries(obj) {
1212
+ return Object.entries(obj);
1213
+ }
1214
+ function deepClone(obj) {
1215
+ return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1216
+ }
1217
+ function toUnixPath(path) {
1218
+ return path.replace(/\\/g, "/");
1219
+ }
1220
+ function capitalize(text) {
1221
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
1222
+ 1
1223
+ )}`;
1224
+ }
1225
+
1226
+ // packages/utils/src/lib/git/git.ts
1562
1227
  function getGitRoot(git = simpleGit()) {
1563
1228
  return git.revparse("--show-toplevel");
1564
1229
  }
1565
1230
  function formatGitPath(path, gitRoot) {
1566
- const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
1231
+ const absolutePath = isAbsolute(path) ? path : join(process.cwd(), path);
1567
1232
  const relativePath = relative(gitRoot, absolutePath);
1568
1233
  return toUnixPath(relativePath);
1569
1234
  }
@@ -1735,17 +1400,17 @@ function groupByStatus(results) {
1735
1400
  }
1736
1401
 
1737
1402
  // packages/utils/src/lib/progress.ts
1738
- import chalk3 from "chalk";
1403
+ import { black, bold as bold2, gray as gray2, green } from "ansis";
1739
1404
  import { MultiProgressBars } from "multi-progress-bars";
1740
1405
  var barStyles = {
1741
- active: (s) => chalk3.green(s),
1742
- done: (s) => chalk3.gray(s),
1743
- idle: (s) => chalk3.gray(s)
1406
+ active: (s) => green(s),
1407
+ done: (s) => gray2(s),
1408
+ idle: (s) => gray2(s)
1744
1409
  };
1745
1410
  var messageStyles = {
1746
- active: (s) => chalk3.black(s),
1747
- done: (s) => chalk3.green(chalk3.bold(s)),
1748
- idle: (s) => chalk3.gray(s)
1411
+ active: (s) => black(s),
1412
+ done: (s) => bold2.green(s),
1413
+ idle: (s) => gray2(s)
1749
1414
  };
1750
1415
  var mpb;
1751
1416
  function getSingletonProgressBars(options2) {
@@ -1801,321 +1466,489 @@ function listAuditsFromAllPlugins(report) {
1801
1466
  );
1802
1467
  }
1803
1468
 
1469
+ // packages/utils/src/lib/reports/generate-md-report.ts
1470
+ import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
1471
+
1472
+ // packages/utils/src/lib/text-formats/constants.ts
1473
+ var HIERARCHY = {
1474
+ level_1: 1,
1475
+ level_2: 2,
1476
+ level_3: 3,
1477
+ level_4: 4,
1478
+ level_5: 5,
1479
+ level_6: 6
1480
+ };
1481
+
1482
+ // packages/utils/src/lib/text-formats/table.ts
1483
+ function rowToStringArray({ rows, columns = [] }) {
1484
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
1485
+ throw new TypeError(
1486
+ "Column can`t be object when rows are primitive values"
1487
+ );
1488
+ }
1489
+ return rows.map((row) => {
1490
+ if (Array.isArray(row)) {
1491
+ return row.map(String);
1492
+ }
1493
+ const objectRow = row;
1494
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
1495
+ return Object.values(objectRow).map(
1496
+ (value) => value == null ? "" : String(value)
1497
+ );
1498
+ }
1499
+ return columns.map(
1500
+ ({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
1501
+ );
1502
+ });
1503
+ }
1504
+ function columnsToStringArray({
1505
+ rows,
1506
+ columns = []
1507
+ }) {
1508
+ const firstRow = rows.at(0);
1509
+ const primitiveRows = Array.isArray(firstRow);
1510
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
1511
+ throw new Error("invalid union type. Caught by model parsing.");
1512
+ }
1513
+ if (columns.length === 0) {
1514
+ if (Array.isArray(firstRow)) {
1515
+ return firstRow.map((_, idx) => String(idx));
1516
+ }
1517
+ return Object.keys(firstRow);
1518
+ }
1519
+ if (typeof columns.at(0) === "string") {
1520
+ return columns.map(String);
1521
+ }
1522
+ const cols = columns;
1523
+ return cols.map(({ label, key }) => label ?? capitalize(key));
1524
+ }
1525
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
1526
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
1527
+ if (typeof column === "string") {
1528
+ return column;
1529
+ } else if (typeof column === "object") {
1530
+ return column.align ?? "center";
1531
+ } else {
1532
+ return "center";
1533
+ }
1534
+ }
1535
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
1536
+ const column = columns.at(targetIdx);
1537
+ if (column == null) {
1538
+ return "center";
1539
+ } else if (typeof column === "string") {
1540
+ return column;
1541
+ } else if (typeof column === "object") {
1542
+ return column.align ?? "center";
1543
+ } else {
1544
+ return "center";
1545
+ }
1546
+ }
1547
+ function getColumnAlignments(tableData) {
1548
+ const { rows, columns = [] } = tableData;
1549
+ if (rows.at(0) == null) {
1550
+ throw new Error("first row can`t be undefined.");
1551
+ }
1552
+ if (Array.isArray(rows.at(0))) {
1553
+ const firstPrimitiveRow = rows.at(0);
1554
+ return Array.from({ length: firstPrimitiveRow.length }).map(
1555
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
1556
+ );
1557
+ }
1558
+ const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
1559
+ if (columns.length > 0) {
1560
+ return columns.map(
1561
+ (column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
1562
+ column.key,
1563
+ idx,
1564
+ columns
1565
+ )
1566
+ );
1567
+ }
1568
+ return Object.keys(biggestRow ?? {}).map((_) => "center");
1569
+ }
1570
+
1804
1571
  // packages/utils/src/lib/reports/formatting.ts
1805
- var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
1572
+ import { MarkdownDocument, md as md2 } from "build-md";
1806
1573
  function tableSection(tableData, options2) {
1807
1574
  if (tableData.rows.length === 0) {
1808
- return "";
1575
+ return null;
1576
+ }
1577
+ const { level = HIERARCHY.level_4 } = options2 ?? {};
1578
+ const columns = columnsToStringArray(tableData);
1579
+ const alignments = getColumnAlignments(tableData);
1580
+ const rows = rowToStringArray(tableData);
1581
+ return new MarkdownDocument().heading(level, tableData.title).table(
1582
+ columns.map((heading, i) => {
1583
+ const alignment = alignments[i];
1584
+ if (alignment) {
1585
+ return { heading, alignment };
1586
+ }
1587
+ return heading;
1588
+ }),
1589
+ rows
1590
+ );
1591
+ }
1592
+ function metaDescription(audit) {
1593
+ const docsUrl = audit.docsUrl;
1594
+ const description = audit.description?.trim();
1595
+ if (docsUrl) {
1596
+ const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
1597
+ if (!description) {
1598
+ return docsLink;
1599
+ }
1600
+ const parsedDescription = description.endsWith("```") ? `${description}
1601
+
1602
+ ` : `${description} `;
1603
+ return md2`${parsedDescription}${docsLink}`;
1604
+ }
1605
+ if (description && description.trim().length > 0) {
1606
+ return description;
1607
+ }
1608
+ return "";
1609
+ }
1610
+
1611
+ // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1612
+ import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
1613
+
1614
+ // packages/utils/src/lib/reports/sorting.ts
1615
+ function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
1616
+ const auditPlugin = plugins.find((p) => p.slug === plugin);
1617
+ if (!auditPlugin) {
1618
+ throwIsNotPresentError(`Plugin ${plugin}`, "report");
1619
+ }
1620
+ const audit = auditPlugin.audits.find(
1621
+ ({ slug: auditSlug }) => auditSlug === slug
1622
+ );
1623
+ if (!audit) {
1624
+ throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
1625
+ }
1626
+ return {
1627
+ ...audit,
1628
+ weight,
1629
+ plugin
1630
+ };
1631
+ }
1632
+ function getSortedGroupAudits(group, plugin, plugins) {
1633
+ return group.refs.map(
1634
+ (ref) => getSortableAuditByRef(
1635
+ {
1636
+ plugin,
1637
+ slug: ref.slug,
1638
+ weight: ref.weight,
1639
+ type: "audit"
1640
+ },
1641
+ plugins
1642
+ )
1643
+ ).sort(compareCategoryAuditsAndGroups);
1644
+ }
1645
+ function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
1646
+ const groupPlugin = plugins.find((p) => p.slug === plugin);
1647
+ if (!groupPlugin) {
1648
+ throwIsNotPresentError(`Plugin ${plugin}`, "report");
1809
1649
  }
1810
- const { level = 4 } = options2 ?? {};
1811
- const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
1812
- return lines2(
1813
- tableData.title && render(tableData.title, level),
1814
- table3(tableData)
1650
+ const group = groupPlugin.groups?.find(
1651
+ ({ slug: groupSlug }) => groupSlug === slug
1815
1652
  );
1653
+ if (!group) {
1654
+ throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
1655
+ }
1656
+ const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
1657
+ const sortedAuditRefs = [...group.refs].sort((a, b) => {
1658
+ const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
1659
+ const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
1660
+ return aIndex - bIndex;
1661
+ });
1662
+ return {
1663
+ ...group,
1664
+ refs: sortedAuditRefs,
1665
+ plugin,
1666
+ weight
1667
+ };
1668
+ }
1669
+ function sortReport(report) {
1670
+ const { categories, plugins } = report;
1671
+ const sortedCategories = categories.map((category) => {
1672
+ const { audits, groups: groups2 } = category.refs.reduce(
1673
+ (acc, ref) => ({
1674
+ ...acc,
1675
+ ...ref.type === "group" ? {
1676
+ groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
1677
+ } : {
1678
+ audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
1679
+ }
1680
+ }),
1681
+ { groups: [], audits: [] }
1682
+ );
1683
+ const sortedAuditsAndGroups = [...audits, ...groups2].sort(
1684
+ compareCategoryAuditsAndGroups
1685
+ );
1686
+ const sortedRefs = [...category.refs].sort((a, b) => {
1687
+ const aIndex = sortedAuditsAndGroups.findIndex(
1688
+ (ref) => ref.slug === a.slug && ref.plugin === a.plugin
1689
+ );
1690
+ const bIndex = sortedAuditsAndGroups.findIndex(
1691
+ (ref) => ref.slug === b.slug && ref.plugin === b.plugin
1692
+ );
1693
+ return aIndex - bIndex;
1694
+ });
1695
+ return { ...category, refs: sortedRefs };
1696
+ });
1697
+ return {
1698
+ ...report,
1699
+ categories: sortedCategories,
1700
+ plugins: sortPlugins(plugins)
1701
+ };
1816
1702
  }
1817
- function metaDescription({
1818
- docsUrl,
1819
- description
1820
- }) {
1821
- if (docsUrl) {
1822
- const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
1823
- if (!description) {
1824
- return section2(docsLink);
1825
- }
1826
- const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
1827
- return section2(`${parsedDescription}${docsLink}`);
1828
- }
1829
- if (description && description.trim().length > 0) {
1830
- return section2(description);
1831
- }
1832
- return "";
1703
+ function sortPlugins(plugins) {
1704
+ return plugins.map((plugin) => ({
1705
+ ...plugin,
1706
+ audits: [...plugin.audits].sort(compareAudits).map(
1707
+ (audit) => audit.details?.issues ? {
1708
+ ...audit,
1709
+ details: {
1710
+ ...audit.details,
1711
+ issues: [...audit.details.issues].sort(compareIssues)
1712
+ }
1713
+ } : audit
1714
+ )
1715
+ }));
1833
1716
  }
1834
1717
 
1835
1718
  // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1836
- var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
1837
1719
  function categoriesOverviewSection(report) {
1838
1720
  const { categories, plugins } = report;
1839
- if (categories.length > 0 && plugins.length > 0) {
1840
- const tableContent = {
1841
- columns: reportOverviewTableHeaders,
1842
- rows: categories.map(({ title, refs, score }) => ({
1843
- // The heading "ID" is inferred from the heading text in Markdown.
1844
- category: link5(`#${slugify(title)}`, title),
1845
- score: `${scoreMarker(score)}${SPACE}${boldMd2(
1846
- formatReportScore(score)
1847
- )}`,
1848
- audits: countCategoryAudits(refs, plugins).toString()
1849
- }))
1850
- };
1851
- return tableSection(tableContent);
1852
- }
1853
- return "";
1721
+ return new MarkdownDocument2().table(
1722
+ [
1723
+ { heading: "\u{1F3F7} Category", alignment: "left" },
1724
+ { heading: "\u2B50 Score", alignment: "center" },
1725
+ { heading: "\u{1F6E1} Audits", alignment: "center" }
1726
+ ],
1727
+ categories.map(({ title, refs, score, isBinary }) => [
1728
+ // @TODO refactor `isBinary: boolean` to `targetScore: number` #713
1729
+ // The heading "ID" is inferred from the heading text in Markdown.
1730
+ md3.link(`#${slugify(title)}`, title),
1731
+ md3`${scoreMarker(score)} ${md3.bold(
1732
+ formatReportScore(score)
1733
+ )}${binaryIconSuffix(score, isBinary)}`,
1734
+ countCategoryAudits(refs, plugins).toString()
1735
+ ])
1736
+ );
1854
1737
  }
1855
1738
  function categoriesDetailsSection(report) {
1856
1739
  const { categories, plugins } = report;
1857
- const categoryDetails = categories.flatMap((category) => {
1858
- const categoryTitle = h32(category.title);
1859
- const categoryScore = `${scoreMarker(
1860
- category.score
1861
- )}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
1862
- const categoryMDItems = category.refs.map((ref) => {
1863
- if (ref.type === "group") {
1864
- const group = getSortableGroupByRef(ref, plugins);
1865
- const groupAudits = group.refs.map(
1866
- (groupRef) => getSortableAuditByRef(
1867
- { ...groupRef, plugin: group.plugin, type: "audit" },
1868
- plugins
1869
- )
1870
- );
1871
- const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1872
- return categoryGroupItem(group, groupAudits, pluginTitle);
1873
- } else {
1874
- const audit = getSortableAuditByRef(ref, plugins);
1875
- const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1876
- return categoryRef(audit, pluginTitle);
1877
- }
1878
- });
1879
- return section3(
1880
- categoryTitle,
1881
- metaDescription(category),
1882
- categoryScore,
1883
- ...categoryMDItems
1884
- );
1885
- });
1886
- return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
1740
+ return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
1741
+ categories,
1742
+ (doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
1743
+ md3`${scoreMarker(category.score)} Score: ${md3.bold(
1744
+ formatReportScore(category.score)
1745
+ )}${binaryIconSuffix(category.score, category.isBinary)}`
1746
+ ).list(
1747
+ category.refs.map((ref) => {
1748
+ if (ref.type === "group") {
1749
+ const group = getSortableGroupByRef(ref, plugins);
1750
+ const groupAudits = group.refs.map(
1751
+ (groupRef) => getSortableAuditByRef(
1752
+ { ...groupRef, plugin: group.plugin, type: "audit" },
1753
+ plugins
1754
+ )
1755
+ );
1756
+ const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1757
+ return categoryGroupItem(group, groupAudits, pluginTitle);
1758
+ } else {
1759
+ const audit = getSortableAuditByRef(ref, plugins);
1760
+ const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1761
+ return categoryRef(audit, pluginTitle);
1762
+ }
1763
+ })
1764
+ )
1765
+ );
1887
1766
  }
1888
1767
  function categoryRef({ title, score, value, displayValue }, pluginTitle) {
1889
- const auditTitleAsLink = link5(
1768
+ const auditTitleAsLink = md3.link(
1890
1769
  `#${slugify(title)}-${slugify(pluginTitle)}`,
1891
1770
  title
1892
1771
  );
1893
1772
  const marker = scoreMarker(score, "square");
1894
- return li2(
1895
- `${marker}${SPACE}${auditTitleAsLink}${SPACE}(_${pluginTitle}_) - ${boldMd2(
1896
- (displayValue || value).toString()
1897
- )}`
1898
- );
1773
+ return md3`${marker} ${auditTitleAsLink} (${md3.italic(
1774
+ pluginTitle
1775
+ )}) - ${md3.bold((displayValue || value).toString())}`;
1899
1776
  }
1900
1777
  function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
1901
- const groupTitle = li2(
1902
- `${scoreMarker(score)}${SPACE}${title}${SPACE}(_${pluginTitle}_)`
1903
- );
1904
- const auditTitles = groupAudits.map(
1905
- ({ title: auditTitle, score: auditScore, value, displayValue }) => {
1906
- const auditTitleLink = link5(
1907
- `#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
1908
- auditTitle
1909
- );
1910
- const marker = scoreMarker(auditScore, "square");
1911
- return indentation2(
1912
- li2(
1913
- `${marker}${SPACE}${auditTitleLink} - ${boldMd2(
1914
- String(displayValue ?? value)
1915
- )}`
1916
- )
1917
- );
1918
- }
1778
+ const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
1779
+ pluginTitle
1780
+ )})`;
1781
+ const auditsList = md3.list(
1782
+ groupAudits.map(
1783
+ ({ title: auditTitle, score: auditScore, value, displayValue }) => {
1784
+ const auditTitleLink = md3.link(
1785
+ `#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
1786
+ auditTitle
1787
+ );
1788
+ const marker = scoreMarker(auditScore, "square");
1789
+ return md3`${marker} ${auditTitleLink} - ${md3.bold(
1790
+ String(displayValue ?? value)
1791
+ )}`;
1792
+ }
1793
+ )
1919
1794
  );
1920
- return lines3(groupTitle, ...auditTitles);
1795
+ return md3`${groupTitle}${auditsList}`;
1796
+ }
1797
+ function binaryIconSuffix(score, isBinary) {
1798
+ return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
1921
1799
  }
1922
1800
 
1923
1801
  // packages/utils/src/lib/reports/generate-md-report.ts
1924
- var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
1925
- var { bold: boldHtml, details: details2 } = html;
1926
1802
  function auditDetailsAuditValue({
1927
1803
  score,
1928
1804
  value,
1929
1805
  displayValue
1930
1806
  }) {
1931
- return `${scoreMarker(score, "square")} ${boldHtml(
1807
+ return md4`${scoreMarker(score, "square")} ${md4.bold(
1932
1808
  String(displayValue ?? value)
1933
1809
  )} (score: ${formatReportScore(score)})`;
1934
1810
  }
1935
1811
  function generateMdReport(report) {
1936
- const printCategories = report.categories.length > 0;
1937
- return lines4(
1938
- h12(reportHeadlineText),
1939
- printCategories ? categoriesOverviewSection(report) : "",
1940
- printCategories ? categoriesDetailsSection(report) : "",
1941
- auditsSection(report),
1942
- aboutSection(report),
1943
- `${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
1944
- );
1812
+ return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
1813
+ report.categories.length > 0,
1814
+ (doc) => doc.$concat(
1815
+ categoriesOverviewSection(report),
1816
+ categoriesDetailsSection(report)
1817
+ )
1818
+ ).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
1945
1819
  }
1946
1820
  function auditDetailsIssues(issues = []) {
1947
1821
  if (issues.length === 0) {
1948
- return "";
1949
- }
1950
- const detailsTableData = {
1951
- title: "Issues",
1952
- columns: issuesTableHeadings,
1953
- rows: issues.map(
1954
- ({ severity: severityVal, message, source: sourceVal }) => {
1955
- const severity = `${severityMarker(severityVal)} <i>${severityVal}</i>`;
1956
- if (!sourceVal) {
1957
- return { severity, message, file: "", line: "" };
1958
- }
1959
- const file = `<code>${sourceVal.file}</code>`;
1960
- if (!sourceVal.position) {
1961
- return { severity, message, file, line: "" };
1962
- }
1963
- const { startLine, endLine } = sourceVal.position;
1964
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1965
- return { severity, message, file, line };
1822
+ return null;
1823
+ }
1824
+ return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
1825
+ [
1826
+ { heading: "Severity", alignment: "center" },
1827
+ { heading: "Message", alignment: "left" },
1828
+ { heading: "Source file", alignment: "left" },
1829
+ { heading: "Line(s)", alignment: "center" }
1830
+ ],
1831
+ issues.map(({ severity: level, message, source }) => {
1832
+ const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
1833
+ if (!source) {
1834
+ return [severity, message];
1966
1835
  }
1967
- )
1968
- };
1969
- return tableSection(detailsTableData);
1836
+ const file = md4.code(source.file);
1837
+ if (!source.position) {
1838
+ return [severity, message, file];
1839
+ }
1840
+ const { startLine, endLine } = source.position;
1841
+ const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1842
+ return [severity, message, file, line];
1843
+ })
1844
+ );
1970
1845
  }
1971
1846
  function auditDetails(audit) {
1972
- const { table: table5, issues = [] } = audit.details ?? {};
1847
+ const { table: table2, issues = [] } = audit.details ?? {};
1973
1848
  const detailsValue = auditDetailsAuditValue(audit);
1974
- if (issues.length === 0 && table5 == null) {
1975
- return section4(detailsValue);
1849
+ if (issues.length === 0 && !table2?.rows.length) {
1850
+ return new MarkdownDocument3().paragraph(detailsValue);
1976
1851
  }
1977
- const tableSectionContent = table5 == null ? "" : tableSection(table5);
1978
- const issuesSectionContent = issues.length > 0 ? auditDetailsIssues(issues) : "";
1979
- return details2(
1852
+ const tableSectionContent = table2 && tableSection(table2);
1853
+ const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
1854
+ return new MarkdownDocument3().details(
1980
1855
  detailsValue,
1981
- lines4(tableSectionContent, issuesSectionContent)
1856
+ new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
1982
1857
  );
1983
1858
  }
1984
1859
  function auditsSection({
1985
1860
  plugins
1986
1861
  }) {
1987
- const content = plugins.flatMap(
1988
- ({ slug, audits }) => audits.flatMap((audit) => {
1989
- const auditTitle = `${audit.title}${SPACE}(${getPluginNameFromSlug(
1990
- slug,
1991
- plugins
1992
- )})`;
1862
+ return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
1863
+ plugins.flatMap(
1864
+ (plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
1865
+ ),
1866
+ (doc, { plugin, ...audit }) => {
1867
+ const auditTitle = `${audit.title} (${plugin.title})`;
1993
1868
  const detailsContent = auditDetails(audit);
1994
1869
  const descriptionContent = metaDescription(audit);
1995
- return [h33(auditTitle), detailsContent, descriptionContent];
1996
- })
1870
+ return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
1871
+ }
1997
1872
  );
1998
- return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
1999
1873
  }
2000
1874
  function aboutSection(report) {
2001
1875
  const { date, plugins } = report;
2002
- const reportMetaTable = reportMetaData(report);
2003
- const pluginMetaTable = reportPluginMeta({ plugins });
2004
- return lines4(
2005
- h23("About"),
2006
- section4(
2007
- `Report was created by [Code PushUp](${README_LINK}) on ${formatDate(
2008
- new Date(date)
2009
- )}.`
2010
- ),
2011
- tableSection(pluginMetaTable),
2012
- tableSection(reportMetaTable)
2013
- );
2014
- }
2015
- function reportPluginMeta({ plugins }) {
2016
- return {
2017
- columns: [
2018
- {
2019
- key: "plugin",
2020
- align: "left"
2021
- },
2022
- {
2023
- key: "audits"
2024
- },
2025
- {
2026
- key: "version"
2027
- },
2028
- {
2029
- key: "duration"
2030
- }
1876
+ return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
1877
+ md4`Report was created by ${md4.link(
1878
+ README_LINK,
1879
+ "Code PushUp"
1880
+ )} on ${formatDate(new Date(date))}.`
1881
+ ).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
1882
+ }
1883
+ function pluginMetaTable({
1884
+ plugins
1885
+ }) {
1886
+ return [
1887
+ [
1888
+ { heading: "Plugin", alignment: "left" },
1889
+ { heading: "Audits", alignment: "center" },
1890
+ { heading: "Version", alignment: "center" },
1891
+ { heading: "Duration", alignment: "right" }
2031
1892
  ],
2032
- rows: plugins.map(
2033
- ({
2034
- title: pluginTitle,
2035
- audits,
2036
- version: pluginVersion,
2037
- duration: pluginDuration
2038
- }) => ({
2039
- plugin: pluginTitle,
2040
- audits: audits.length.toString(),
2041
- version: codeMd(pluginVersion || ""),
2042
- duration: formatDuration(pluginDuration)
2043
- })
2044
- )
2045
- };
1893
+ plugins.map(({ title, audits, version: version2 = "", duration }) => [
1894
+ title,
1895
+ audits.length.toString(),
1896
+ version2 && md4.code(version2),
1897
+ formatDuration(duration)
1898
+ ])
1899
+ ];
2046
1900
  }
2047
- function reportMetaData({
1901
+ function reportMetaTable({
2048
1902
  commit,
2049
1903
  version: version2,
2050
1904
  duration,
2051
1905
  plugins,
2052
1906
  categories
2053
1907
  }) {
2054
- const commitInfo = commit ? `${commit.message}${SPACE}(${commit.hash})` : "N/A";
2055
- return {
2056
- columns: [
2057
- {
2058
- key: "commit",
2059
- align: "left"
2060
- },
2061
- {
2062
- key: "version"
2063
- },
2064
- {
2065
- key: "duration"
2066
- },
2067
- {
2068
- key: "plugins"
2069
- },
2070
- {
2071
- key: "categories"
2072
- },
2073
- {
2074
- key: "audits"
2075
- }
1908
+ return [
1909
+ [
1910
+ { heading: "Commit", alignment: "left" },
1911
+ { heading: "Version", alignment: "center" },
1912
+ { heading: "Duration", alignment: "right" },
1913
+ { heading: "Plugins", alignment: "center" },
1914
+ { heading: "Categories", alignment: "center" },
1915
+ { heading: "Audits", alignment: "center" }
2076
1916
  ],
2077
- rows: [
2078
- {
2079
- commit: commitInfo,
2080
- version: codeMd(version2 || ""),
2081
- duration: formatDuration(duration),
2082
- plugins: plugins.length,
2083
- categories: categories.length,
2084
- audits: plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
2085
- }
1917
+ [
1918
+ [
1919
+ commit ? `${commit.message} (${commit.hash})` : "N/A",
1920
+ md4.code(version2),
1921
+ formatDuration(duration),
1922
+ plugins.length.toString(),
1923
+ categories.length.toString(),
1924
+ plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1925
+ ]
2086
1926
  ]
2087
- };
1927
+ ];
2088
1928
  }
2089
1929
 
2090
1930
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
2091
- var {
2092
- h1: h13,
2093
- h2: h24,
2094
- lines: lines5,
2095
- link: link7,
2096
- bold: boldMd3,
2097
- italic: italicMd,
2098
- table: table4,
2099
- section: section5
2100
- } = md;
2101
- var { details: details3 } = html;
1931
+ import {
1932
+ MarkdownDocument as MarkdownDocument4,
1933
+ md as md5
1934
+ } from "build-md";
2102
1935
  var MAX_ROWS = 100;
2103
- function generateMdReportsDiff(diff) {
2104
- return lines5(
2105
- section5(formatDiffHeaderSection(diff)),
2106
- formatDiffCategoriesSection(diff),
2107
- formatDiffGroupsSection(diff),
2108
- formatDiffAuditsSection(diff)
2109
- );
2110
- }
2111
- function formatDiffHeaderSection(diff) {
1936
+ function generateMdReportsDiff(diff, portalUrl) {
1937
+ return new MarkdownDocument4().$concat(
1938
+ createDiffHeaderSection(diff, portalUrl),
1939
+ createDiffCategoriesSection(diff),
1940
+ createDiffGroupsSection(diff),
1941
+ createDiffAuditsSection(diff)
1942
+ ).toString();
1943
+ }
1944
+ function createDiffHeaderSection(diff, portalUrl) {
2112
1945
  const outcomeTexts = {
2113
- positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
2114
- negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
2115
- mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
1946
+ positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
1947
+ negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
1948
+ mixed: md5`🤨 Code PushUp report has both ${md5.bold(
2116
1949
  "improvements and regressions"
2117
1950
  )}`,
2118
- unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
1951
+ unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
2119
1952
  };
2120
1953
  const outcome = mergeDiffOutcomes(
2121
1954
  changesToDiffOutcomes([
@@ -2125,143 +1958,127 @@ function formatDiffHeaderSection(diff) {
2125
1958
  ])
2126
1959
  );
2127
1960
  const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
2128
- return lines5(
2129
- h13("Code PushUp"),
2130
- diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1961
+ return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
1962
+ diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
1963
+ ).paragraph(
1964
+ portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
2131
1965
  );
2132
1966
  }
2133
- function formatDiffCategoriesSection(diff) {
1967
+ function createDiffCategoriesSection(diff) {
2134
1968
  const { changed, unchanged, added } = diff.categories;
2135
1969
  const categoriesCount = changed.length + unchanged.length + added.length;
2136
1970
  const hasChanges = unchanged.length < categoriesCount;
2137
1971
  if (categoriesCount === 0) {
2138
- return "";
1972
+ return null;
2139
1973
  }
2140
1974
  const columns = [
2141
- { key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
2142
- { key: "before", label: "\u2B50 Previous score" },
2143
- { key: "after", label: hasChanges ? "\u2B50 Current score" : "\u2B50 Score" },
2144
- { key: "change", label: "\u{1F504} Score change" }
1975
+ { heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
1976
+ {
1977
+ heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
1978
+ alignment: "center"
1979
+ },
1980
+ { heading: "\u2B50 Current score", alignment: "center" },
1981
+ { heading: "\u{1F504} Score change", alignment: "center" }
2145
1982
  ];
2146
- return lines5(
2147
- h24("\u{1F3F7}\uFE0F Categories"),
2148
- categoriesCount > 0 && table4({
2149
- columns: hasChanges ? columns : columns.slice(0, 2),
2150
- rows: [
2151
- ...sortChanges(changed).map((category) => ({
2152
- category: formatTitle(category),
2153
- after: formatScoreWithColor(category.scores.after),
2154
- before: formatScoreWithColor(category.scores.before, {
2155
- skipBold: true
2156
- }),
2157
- change: formatScoreChange(category.scores.diff)
2158
- })),
2159
- ...added.map((category) => ({
2160
- category: formatTitle(category),
2161
- after: formatScoreWithColor(category.score),
2162
- before: italicMd("n/a (\\*)"),
2163
- change: italicMd("n/a (\\*)")
2164
- })),
2165
- ...unchanged.map((category) => ({
2166
- category: formatTitle(category),
2167
- after: formatScoreWithColor(category.score),
2168
- before: formatScoreWithColor(category.score, { skipBold: true }),
2169
- change: "\u2013"
2170
- }))
2171
- ].map(
2172
- (row) => hasChanges ? row : { category: row.category, after: row.after }
2173
- )
2174
- }),
2175
- added.length > 0 && section5(italicMd("(\\*) New category."))
2176
- );
1983
+ const rows = [
1984
+ ...sortChanges(changed).map((category) => [
1985
+ formatTitle(category),
1986
+ formatScoreWithColor(category.scores.before, {
1987
+ skipBold: true
1988
+ }),
1989
+ formatScoreWithColor(category.scores.after),
1990
+ formatScoreChange(category.scores.diff)
1991
+ ]),
1992
+ ...added.map((category) => [
1993
+ formatTitle(category),
1994
+ md5.italic("n/a (\\*)"),
1995
+ formatScoreWithColor(category.score),
1996
+ md5.italic("n/a (\\*)")
1997
+ ]),
1998
+ ...unchanged.map((category) => [
1999
+ formatTitle(category),
2000
+ formatScoreWithColor(category.score, { skipBold: true }),
2001
+ formatScoreWithColor(category.score),
2002
+ "\u2013"
2003
+ ])
2004
+ ];
2005
+ return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
2006
+ hasChanges ? columns : columns.slice(0, 2),
2007
+ rows.map((row) => hasChanges ? row : row.slice(0, 2))
2008
+ ).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
2177
2009
  }
2178
- function formatDiffGroupsSection(diff) {
2010
+ function createDiffGroupsSection(diff) {
2179
2011
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
2180
- return "";
2181
- }
2182
- return lines5(
2183
- h24("\u{1F5C3}\uFE0F Groups"),
2184
- formatGroupsOrAuditsDetails("group", diff.groups, {
2185
- columns: [
2186
- { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2187
- { key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
2188
- { key: "before", label: "\u2B50 Previous score" },
2189
- { key: "after", label: "\u2B50 Current score" },
2190
- { key: "change", label: "\u{1F504} Score change" }
2012
+ return null;
2013
+ }
2014
+ return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
2015
+ createGroupsOrAuditsDetails(
2016
+ "group",
2017
+ diff.groups,
2018
+ [
2019
+ { heading: "\u{1F50C} Plugin", alignment: "left" },
2020
+ { heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
2021
+ { heading: "\u2B50 Previous score", alignment: "center" },
2022
+ { heading: "\u2B50 Current score", alignment: "center" },
2023
+ { heading: "\u{1F504} Score change", alignment: "center" }
2191
2024
  ],
2192
- rows: sortChanges(diff.groups.changed).map((group) => ({
2193
- plugin: formatTitle(group.plugin),
2194
- group: formatTitle(group),
2195
- after: formatScoreWithColor(group.scores.after),
2196
- before: formatScoreWithColor(group.scores.before, { skipBold: true }),
2197
- change: formatScoreChange(group.scores.diff)
2198
- }))
2199
- })
2025
+ sortChanges(diff.groups.changed).map((group) => [
2026
+ formatTitle(group.plugin),
2027
+ formatTitle(group),
2028
+ formatScoreWithColor(group.scores.before, { skipBold: true }),
2029
+ formatScoreWithColor(group.scores.after),
2030
+ formatScoreChange(group.scores.diff)
2031
+ ])
2032
+ )
2200
2033
  );
2201
2034
  }
2202
- function formatDiffAuditsSection(diff) {
2203
- return lines5(
2204
- h24("\u{1F6E1}\uFE0F Audits"),
2205
- formatGroupsOrAuditsDetails("audit", diff.audits, {
2206
- columns: [
2207
- { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2208
- { key: "audit", label: "\u{1F6E1}\uFE0F Audit", align: "left" },
2209
- { key: "before", label: "\u{1F4CF} Previous value" },
2210
- { key: "after", label: "\u{1F4CF} Current value" },
2211
- { key: "change", label: "\u{1F504} Value change" }
2035
+ function createDiffAuditsSection(diff) {
2036
+ return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
2037
+ createGroupsOrAuditsDetails(
2038
+ "audit",
2039
+ diff.audits,
2040
+ [
2041
+ { heading: "\u{1F50C} Plugin", alignment: "left" },
2042
+ { heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
2043
+ { heading: "\u{1F4CF} Previous value", alignment: "center" },
2044
+ { heading: "\u{1F4CF} Current value", alignment: "center" },
2045
+ { heading: "\u{1F504} Value change", alignment: "center" }
2212
2046
  ],
2213
- rows: sortChanges(diff.audits.changed).map((audit) => ({
2214
- plugin: formatTitle(audit.plugin),
2215
- audit: formatTitle(audit),
2216
- after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
2047
+ sortChanges(diff.audits.changed).map((audit) => [
2048
+ formatTitle(audit.plugin),
2049
+ formatTitle(audit),
2050
+ `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2051
+ md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
2217
2052
  audit.displayValues.after || audit.values.after.toString()
2218
2053
  )}`,
2219
- before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2220
- change: formatValueChange(audit)
2221
- }))
2222
- })
2054
+ formatValueChange(audit)
2055
+ ])
2056
+ )
2223
2057
  );
2224
2058
  }
2225
- function formatGroupsOrAuditsDetails(token, { changed, unchanged }, tableData) {
2226
- return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details3(
2059
+ function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
2060
+ if (changed.length === 0) {
2061
+ return new MarkdownDocument4().paragraph(
2062
+ summarizeUnchanged(token, { changed, unchanged })
2063
+ );
2064
+ }
2065
+ return new MarkdownDocument4().details(
2227
2066
  summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
2228
- lines5(
2229
- table4({
2230
- ...tableData,
2231
- rows: tableData.rows.slice(0, MAX_ROWS)
2232
- // use never to avoid typing problem
2233
- }),
2234
- changed.length > MAX_ROWS && italicMd(
2067
+ md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
2068
+ md5.italic(
2235
2069
  `Only the ${MAX_ROWS} most affected ${pluralize(
2236
2070
  token
2237
2071
  )} are listed above for brevity.`
2238
- ),
2239
- unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
2240
- )
2072
+ )
2073
+ ) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
2241
2074
  );
2242
2075
  }
2243
- function formatScoreChange(diff) {
2244
- const marker = getDiffMarker(diff);
2245
- const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
2246
- return colorByScoreDiff(`${marker} ${text}`, diff);
2247
- }
2248
- function formatValueChange({
2249
- values,
2250
- scores
2251
- }) {
2252
- const marker = getDiffMarker(values.diff);
2253
- const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
2254
- const text = `${formatDiffNumber(percentage)}\u2009%`;
2255
- return colorByScoreDiff(`${marker} ${text}`, scores.diff);
2256
- }
2257
2076
  function summarizeUnchanged(token, { changed, unchanged }) {
2258
- return section5(
2259
- [
2260
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2261
- unchanged.length === 1 ? "is" : "are",
2262
- "unchanged."
2263
- ].join(" ")
2264
- );
2077
+ return [
2078
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2079
+ unchanged.length === 1 ? "is" : "are",
2080
+ "unchanged."
2081
+ ].join(" ");
2265
2082
  }
2266
2083
  function summarizeDiffOutcomes(outcomes, token) {
2267
2084
  return objectToEntries(countDiffOutcomes(outcomes)).filter(
@@ -2286,7 +2103,7 @@ function formatTitle({
2286
2103
  docsUrl
2287
2104
  }) {
2288
2105
  if (docsUrl) {
2289
- return link7(docsUrl, title);
2106
+ return md5.link(docsUrl, title);
2290
2107
  }
2291
2108
  return title;
2292
2109
  }
@@ -2330,8 +2147,22 @@ function countDiffOutcomes(outcomes) {
2330
2147
  };
2331
2148
  }
2332
2149
 
2150
+ // packages/utils/src/lib/reports/load-report.ts
2151
+ import { join as join2 } from "node:path";
2152
+ async function loadReport(options2) {
2153
+ const { outputDir, filename, format } = options2;
2154
+ await ensureDirectoryExists(outputDir);
2155
+ const filePath = join2(outputDir, `${filename}.${format}`);
2156
+ if (format === "json") {
2157
+ const content = await readJsonFile(filePath);
2158
+ return reportSchema.parse(content);
2159
+ }
2160
+ const text = await readTextFile(filePath);
2161
+ return text;
2162
+ }
2163
+
2333
2164
  // packages/utils/src/lib/reports/log-stdout-summary.ts
2334
- import chalk4 from "chalk";
2165
+ import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
2335
2166
  function log(msg = "") {
2336
2167
  ui().logger.log(msg);
2337
2168
  }
@@ -2348,14 +2179,14 @@ function logStdoutSummary(report) {
2348
2179
  }
2349
2180
  function reportToHeaderSection(report) {
2350
2181
  const { packageName, version: version2 } = report;
2351
- return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
2182
+ return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version2}`;
2352
2183
  }
2353
2184
  function logPlugins(report) {
2354
2185
  const { plugins } = report;
2355
2186
  plugins.forEach((plugin) => {
2356
2187
  const { title, audits } = plugin;
2357
2188
  log();
2358
- log(chalk4.magentaBright.bold(`${title} audits`));
2189
+ log(bold4.magentaBright(`${title} audits`));
2359
2190
  log();
2360
2191
  audits.forEach((audit) => {
2361
2192
  ui().row([
@@ -2370,7 +2201,7 @@ function logPlugins(report) {
2370
2201
  padding: [0, 3, 0, 0]
2371
2202
  },
2372
2203
  {
2373
- text: chalk4.cyanBright(audit.displayValue || `${audit.value}`),
2204
+ text: cyanBright(audit.displayValue || `${audit.value}`),
2374
2205
  width: 10,
2375
2206
  padding: [0, 0, 0, 0]
2376
2207
  }
@@ -2381,42 +2212,38 @@ function logPlugins(report) {
2381
2212
  }
2382
2213
  function logCategories({ categories, plugins }) {
2383
2214
  const hAlign = (idx) => idx === 0 ? "left" : "right";
2384
- const rows = categories.map(({ title, score, refs }) => [
2215
+ const rows = categories.map(({ title, score, refs, isBinary }) => [
2385
2216
  title,
2386
- applyScoreColor({ score }),
2217
+ `${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
2387
2218
  countCategoryAudits(refs, plugins)
2388
2219
  ]);
2389
- const table5 = ui().table();
2390
- table5.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2391
- table5.head(
2392
- reportRawOverviewTableHeaders.map((heading, idx) => ({
2393
- content: chalk4.cyan(heading),
2220
+ const table2 = ui().table();
2221
+ table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2222
+ table2.head(
2223
+ REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
2224
+ content: cyan(heading),
2394
2225
  hAlign: hAlign(idx)
2395
2226
  }))
2396
2227
  );
2397
2228
  rows.forEach(
2398
- (row) => table5.row(
2229
+ (row) => table2.row(
2399
2230
  row.map((content, idx) => ({
2400
2231
  content: content.toString(),
2401
2232
  hAlign: hAlign(idx)
2402
2233
  }))
2403
2234
  )
2404
2235
  );
2405
- log(chalk4.magentaBright.bold("Categories"));
2236
+ log(bold4.magentaBright("Categories"));
2406
2237
  log();
2407
- table5.render();
2238
+ table2.render();
2408
2239
  log();
2409
2240
  }
2410
- function applyScoreColor({ score, text }) {
2411
- const formattedScore = text ?? formatReportScore(score);
2412
- const style = text ? chalk4 : chalk4.bold;
2413
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
2414
- return style.green(formattedScore);
2415
- }
2416
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
2417
- return style.yellow(formattedScore);
2418
- }
2419
- return style.red(formattedScore);
2241
+ function binaryIconPrefix(score, isBinary) {
2242
+ return targetScoreIcon(score, isBinary ? 1 : void 0, {
2243
+ passIcon: bold4(green2("\u2713")),
2244
+ failIcon: bold4(red("\u2717")),
2245
+ postfix: " "
2246
+ });
2420
2247
  }
2421
2248
 
2422
2249
  // packages/utils/src/lib/reports/scoring.ts
@@ -2506,56 +2333,6 @@ function parseScoringParameters(refs, scoreFn) {
2506
2333
  return scoredRefs;
2507
2334
  }
2508
2335
 
2509
- // packages/utils/src/lib/reports/sorting.ts
2510
- function sortReport(report) {
2511
- const { categories, plugins } = report;
2512
- const sortedCategories = categories.map((category) => {
2513
- const { audits, groups: groups2 } = category.refs.reduce(
2514
- (acc, ref) => ({
2515
- ...acc,
2516
- ...ref.type === "group" ? {
2517
- groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
2518
- } : {
2519
- audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
2520
- }
2521
- }),
2522
- { groups: [], audits: [] }
2523
- );
2524
- const sortedAuditsAndGroups = [...audits, ...groups2].sort(
2525
- compareCategoryAuditsAndGroups
2526
- );
2527
- const sortedRefs = [...category.refs].sort((a, b) => {
2528
- const aIndex = sortedAuditsAndGroups.findIndex(
2529
- (ref) => ref.slug === a.slug && ref.plugin === a.plugin
2530
- );
2531
- const bIndex = sortedAuditsAndGroups.findIndex(
2532
- (ref) => ref.slug === b.slug && ref.plugin === b.plugin
2533
- );
2534
- return aIndex - bIndex;
2535
- });
2536
- return { ...category, refs: sortedRefs };
2537
- });
2538
- return {
2539
- ...report,
2540
- categories: sortedCategories,
2541
- plugins: sortPlugins(plugins)
2542
- };
2543
- }
2544
- function sortPlugins(plugins) {
2545
- return plugins.map((plugin) => ({
2546
- ...plugin,
2547
- audits: [...plugin.audits].sort(compareAudits).map(
2548
- (audit) => audit.details?.issues ? {
2549
- ...audit,
2550
- details: {
2551
- ...audit.details,
2552
- issues: [...audit.details.issues].sort(compareIssues)
2553
- }
2554
- } : audit
2555
- )
2556
- }));
2557
- }
2558
-
2559
2336
  // packages/utils/src/lib/verbose-utils.ts
2560
2337
  function getLogVerbose(verbose = false) {
2561
2338
  return (msg) => {
@@ -2578,10 +2355,10 @@ var verboseUtils = (verbose = false) => ({
2578
2355
 
2579
2356
  // packages/core/package.json
2580
2357
  var name = "@code-pushup/core";
2581
- var version = "0.47.0";
2358
+ var version = "0.49.0";
2582
2359
 
2583
2360
  // packages/core/src/lib/implementation/execute-plugin.ts
2584
- import chalk5 from "chalk";
2361
+ import { bold as bold5 } from "ansis";
2585
2362
 
2586
2363
  // packages/core/src/lib/normalize.ts
2587
2364
  function normalizeIssue(issue, gitRoot) {
@@ -2644,7 +2421,7 @@ async function executeRunnerFunction(runner, onProgress) {
2644
2421
  var PluginOutputMissingAuditError = class extends Error {
2645
2422
  constructor(auditSlug) {
2646
2423
  super(
2647
- `Audit metadata not present in plugin config. Missing slug: ${chalk5.bold(
2424
+ `Audit metadata not present in plugin config. Missing slug: ${bold5(
2648
2425
  auditSlug
2649
2426
  )}`
2650
2427
  );
@@ -2686,7 +2463,7 @@ async function executePlugin(pluginConfig, onProgress) {
2686
2463
  };
2687
2464
  }
2688
2465
  var wrapProgress = async (pluginCfg, steps, progressBar) => {
2689
- progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2466
+ progressBar?.updateTitle(`Executing ${bold5(pluginCfg.title)}`);
2690
2467
  try {
2691
2468
  const pluginReport = await executePlugin(pluginCfg);
2692
2469
  progressBar?.incrementInSteps(steps);
@@ -2694,7 +2471,7 @@ var wrapProgress = async (pluginCfg, steps, progressBar) => {
2694
2471
  } catch (error) {
2695
2472
  progressBar?.incrementInSteps(steps);
2696
2473
  throw new Error(
2697
- error instanceof Error ? `- Plugin ${chalk5.bold(pluginCfg.title)} (${chalk5.bold(
2474
+ error instanceof Error ? `- Plugin ${bold5(pluginCfg.title)} (${bold5(
2698
2475
  pluginCfg.slug
2699
2476
  )}) produced the following error:
2700
2477
  - ${error.message}` : String(error)
@@ -2834,6 +2611,10 @@ async function collectAndPersistReports(options2) {
2834
2611
  // packages/core/src/lib/compare.ts
2835
2612
  import { writeFile as writeFile2 } from "node:fs/promises";
2836
2613
  import { join as join5 } from "node:path";
2614
+ import {
2615
+ PortalOperationError,
2616
+ getPortalComparisonLink
2617
+ } from "@code-pushup/portal-client";
2837
2618
 
2838
2619
  // packages/core/src/lib/implementation/compare-scorables.ts
2839
2620
  function compareCategories(reports) {
@@ -2970,7 +2751,7 @@ function selectMeta(meta) {
2970
2751
  }
2971
2752
 
2972
2753
  // packages/core/src/lib/compare.ts
2973
- async function compareReportFiles(inputPaths, persistConfig) {
2754
+ async function compareReportFiles(inputPaths, persistConfig, uploadConfig) {
2974
2755
  const { outputDir, filename, format } = persistConfig;
2975
2756
  const [reportBefore, reportAfter] = await Promise.all([
2976
2757
  readJsonFile(inputPaths.before),
@@ -2981,10 +2762,11 @@ async function compareReportFiles(inputPaths, persistConfig) {
2981
2762
  after: reportSchema.parse(reportAfter)
2982
2763
  };
2983
2764
  const reportsDiff = compareReports(reports);
2765
+ const portalUrl = uploadConfig && reportsDiff.commits && format.includes("md") ? await fetchPortalComparisonLink(uploadConfig, reportsDiff.commits) : void 0;
2984
2766
  return Promise.all(
2985
2767
  format.map(async (fmt) => {
2986
2768
  const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
2987
- const content = reportsDiffToFileContent(reportsDiff, fmt);
2769
+ const content = reportsDiffToFileContent(reportsDiff, fmt, portalUrl);
2988
2770
  await ensureDirectoryExists(outputDir);
2989
2771
  await writeFile2(outputPath, content);
2990
2772
  return outputPath;
@@ -3014,12 +2796,35 @@ function compareReports(reports) {
3014
2796
  duration
3015
2797
  };
3016
2798
  }
3017
- function reportsDiffToFileContent(reportsDiff, format) {
2799
+ function reportsDiffToFileContent(reportsDiff, format, portalUrl) {
3018
2800
  switch (format) {
3019
2801
  case "json":
3020
2802
  return JSON.stringify(reportsDiff, null, 2);
3021
2803
  case "md":
3022
- return generateMdReportsDiff(reportsDiff);
2804
+ return generateMdReportsDiff(reportsDiff, portalUrl ?? void 0);
2805
+ }
2806
+ }
2807
+ async function fetchPortalComparisonLink(uploadConfig, commits) {
2808
+ const { server, apiKey, organization, project } = uploadConfig;
2809
+ try {
2810
+ return await getPortalComparisonLink({
2811
+ server,
2812
+ apiKey,
2813
+ parameters: {
2814
+ organization,
2815
+ project,
2816
+ before: commits.before.hash,
2817
+ after: commits.after.hash
2818
+ }
2819
+ });
2820
+ } catch (error) {
2821
+ if (error instanceof PortalOperationError) {
2822
+ ui().logger.warning(
2823
+ `Failed to fetch portal comparison link - ${error.message}`
2824
+ );
2825
+ return void 0;
2826
+ }
2827
+ throw error;
3023
2828
  }
3024
2829
  }
3025
2830
 
@@ -3077,9 +2882,9 @@ function auditToGQL(audit) {
3077
2882
  score,
3078
2883
  value,
3079
2884
  displayValue: formattedValue,
3080
- details: details4
2885
+ details: details2
3081
2886
  } = audit;
3082
- const { issues, table: table5 } = details4 ?? {};
2887
+ const { issues, table: table2 } = details2 ?? {};
3083
2888
  return {
3084
2889
  slug,
3085
2890
  title,
@@ -3088,10 +2893,10 @@ function auditToGQL(audit) {
3088
2893
  score,
3089
2894
  value,
3090
2895
  formattedValue,
3091
- ...details4 && {
2896
+ ...details2 && {
3092
2897
  details: {
3093
2898
  ...issues && { issues: issues.map(issueToGQL) },
3094
- ...table5 && { tables: [tableToGQL(table5)] }
2899
+ ...table2 && { tables: [tableToGQL(table2)] }
3095
2900
  }
3096
2901
  }
3097
2902
  };
@@ -3110,11 +2915,11 @@ function issueToGQL(issue) {
3110
2915
  }
3111
2916
  };
3112
2917
  }
3113
- function tableToGQL(table5) {
2918
+ function tableToGQL(table2) {
3114
2919
  return {
3115
- ...table5.title && { title: table5.title },
3116
- ...table5.columns?.length && {
3117
- columns: table5.columns.map(
2920
+ ...table2.title && { title: table2.title },
2921
+ ...table2.columns?.length && {
2922
+ columns: table2.columns.map(
3118
2923
  (column) => typeof column === "string" ? { alignment: tableAlignmentToGQL(column) } : {
3119
2924
  key: column.key,
3120
2925
  label: column.label,
@@ -3122,7 +2927,7 @@ function tableToGQL(table5) {
3122
2927
  }
3123
2928
  )
3124
2929
  },
3125
- rows: table5.rows.map(
2930
+ rows: table2.rows.map(
3126
2931
  (row) => Array.isArray(row) ? row.map((content) => ({ content: content?.toString() ?? "" })) : Object.entries(row).map(([key, content]) => ({
3127
2932
  key,
3128
2933
  content: content?.toString() ?? ""
@@ -3274,10 +3079,10 @@ var CLI_NAME = "Code PushUp CLI";
3274
3079
  var CLI_SCRIPT_NAME = "code-pushup";
3275
3080
 
3276
3081
  // packages/cli/src/lib/implementation/logging.ts
3277
- import chalk6 from "chalk";
3082
+ import { bold as bold6, gray as gray3 } from "ansis";
3278
3083
  function renderConfigureCategoriesHint() {
3279
3084
  ui().logger.info(
3280
- chalk6.gray(
3085
+ gray3(
3281
3086
  `\u{1F4A1} Configure categories to see the scores in an overview table. See: ${link(
3282
3087
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md"
3283
3088
  )}`
@@ -3292,8 +3097,8 @@ function collectSuccessfulLog() {
3292
3097
  ui().logger.success("Collecting report successful!");
3293
3098
  }
3294
3099
  function renderIntegratePortalHint() {
3295
- ui().sticker().add(chalk6.bold(chalk6.gray("\u{1F4A1} Integrate the portal"))).add("").add(
3296
- `${chalk6.gray("\u276F")} Upload a report to the server - ${chalk6.gray(
3100
+ ui().sticker().add(bold6.gray("\u{1F4A1} Integrate the portal")).add("").add(
3101
+ `${gray3("\u276F")} Upload a report to the server - ${gray3(
3297
3102
  "npx code-pushup upload"
3298
3103
  )}`
3299
3104
  ).add(
@@ -3301,11 +3106,11 @@ function renderIntegratePortalHint() {
3301
3106
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
3302
3107
  )}`
3303
3108
  ).add(
3304
- `${chalk6.gray("\u276F")} ${chalk6.gray("Portal Integration")} - ${link(
3109
+ `${gray3("\u276F")} ${gray3("Portal Integration")} - ${link(
3305
3110
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
3306
3111
  )}`
3307
3112
  ).add(
3308
- `${chalk6.gray("\u276F")} ${chalk6.gray("Upload Command")} - ${link(
3113
+ `${gray3("\u276F")} ${gray3("Upload Command")} - ${link(
3309
3114
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
3310
3115
  )}`
3311
3116
  ).render();
@@ -3318,8 +3123,8 @@ function yargsAutorunCommandObject() {
3318
3123
  command: command2,
3319
3124
  describe: "Shortcut for running collect followed by upload",
3320
3125
  handler: async (args) => {
3321
- ui().logger.log(chalk7.bold(CLI_NAME));
3322
- ui().logger.info(chalk7.gray(`Run ${command2}...`));
3126
+ ui().logger.log(bold7(CLI_NAME));
3127
+ ui().logger.info(gray4(`Run ${command2}...`));
3323
3128
  const options2 = args;
3324
3129
  const optionsWithFormat = {
3325
3130
  ...options2,
@@ -3347,7 +3152,7 @@ function yargsAutorunCommandObject() {
3347
3152
  }
3348
3153
 
3349
3154
  // packages/cli/src/lib/collect/collect-command.ts
3350
- import chalk8 from "chalk";
3155
+ import { bold as bold8, gray as gray5 } from "ansis";
3351
3156
  function yargsCollectCommandObject() {
3352
3157
  const command2 = "collect";
3353
3158
  return {
@@ -3355,8 +3160,8 @@ function yargsCollectCommandObject() {
3355
3160
  describe: "Run Plugins and collect results",
3356
3161
  handler: async (args) => {
3357
3162
  const options2 = args;
3358
- ui().logger.log(chalk8.bold(CLI_NAME));
3359
- ui().logger.info(chalk8.gray(`Run ${command2}...`));
3163
+ ui().logger.log(bold8(CLI_NAME));
3164
+ ui().logger.info(gray5(`Run ${command2}...`));
3360
3165
  await collectAndPersistReports(options2);
3361
3166
  collectSuccessfulLog();
3362
3167
  if (options2.categories.length === 0) {
@@ -3370,8 +3175,8 @@ function yargsCollectCommandObject() {
3370
3175
  };
3371
3176
  }
3372
3177
  function renderUploadAutorunHint() {
3373
- ui().sticker().add(chalk8.bold(chalk8.gray("\u{1F4A1} Visualize your reports"))).add("").add(
3374
- `${chalk8.gray("\u276F")} npx code-pushup upload - ${chalk8.gray(
3178
+ ui().sticker().add(bold8.gray("\u{1F4A1} Visualize your reports")).add("").add(
3179
+ `${gray5("\u276F")} npx code-pushup upload - ${gray5(
3375
3180
  "Run upload to upload the created report to the server"
3376
3181
  )}`
3377
3182
  ).add(
@@ -3379,9 +3184,7 @@ function renderUploadAutorunHint() {
3379
3184
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
3380
3185
  )}`
3381
3186
  ).add(
3382
- `${chalk8.gray("\u276F")} npx code-pushup autorun - ${chalk8.gray(
3383
- "Run collect & upload"
3384
- )}`
3187
+ `${gray5("\u276F")} npx code-pushup autorun - ${gray5("Run collect & upload")}`
3385
3188
  ).add(
3386
3189
  ` ${link(
3387
3190
  "https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command"
@@ -3390,7 +3193,7 @@ function renderUploadAutorunHint() {
3390
3193
  }
3391
3194
 
3392
3195
  // packages/cli/src/lib/compare/compare-command.ts
3393
- import chalk9 from "chalk";
3196
+ import { bold as bold9, gray as gray6 } from "ansis";
3394
3197
 
3395
3198
  // packages/cli/src/lib/implementation/compare.options.ts
3396
3199
  function yargsCompareOptionsDefinition() {
@@ -3416,20 +3219,24 @@ function yargsCompareCommandObject() {
3416
3219
  describe: "Compare 2 report files and create a diff file",
3417
3220
  builder: yargsCompareOptionsDefinition(),
3418
3221
  handler: async (args) => {
3419
- ui().logger.log(chalk9.bold(CLI_NAME));
3420
- ui().logger.info(chalk9.gray(`Run ${command2}...`));
3222
+ ui().logger.log(bold9(CLI_NAME));
3223
+ ui().logger.info(gray6(`Run ${command2}...`));
3421
3224
  const options2 = args;
3422
- const { before, after, persist } = options2;
3423
- const outputPaths = await compareReportFiles({ before, after }, persist);
3225
+ const { before, after, persist, upload: upload2 } = options2;
3226
+ const outputPaths = await compareReportFiles(
3227
+ { before, after },
3228
+ persist,
3229
+ upload2
3230
+ );
3424
3231
  ui().logger.info(
3425
- `Reports diff written to ${outputPaths.map((path) => chalk9.bold(path)).join(" and ")}`
3232
+ `Reports diff written to ${outputPaths.map((path) => bold9(path)).join(" and ")}`
3426
3233
  );
3427
3234
  }
3428
3235
  };
3429
3236
  }
3430
3237
 
3431
3238
  // packages/cli/src/lib/history/history-command.ts
3432
- import chalk10 from "chalk";
3239
+ import { bold as bold10, gray as gray7 } from "ansis";
3433
3240
 
3434
3241
  // packages/cli/src/lib/implementation/global.utils.ts
3435
3242
  function filterKebabCaseKeys(obj) {
@@ -3469,6 +3276,19 @@ function yargsOnlyPluginsOptionsDefinition() {
3469
3276
  };
3470
3277
  }
3471
3278
 
3279
+ // packages/cli/src/lib/implementation/skip-plugins.options.ts
3280
+ var skipPluginsOption = {
3281
+ describe: "List of plugins to skip. If not set all plugins are run.",
3282
+ type: "array",
3283
+ default: [],
3284
+ coerce: coerceArray
3285
+ };
3286
+ function yargsSkipPluginsOptionsDefinition() {
3287
+ return {
3288
+ skipPlugins: skipPluginsOption
3289
+ };
3290
+ }
3291
+
3472
3292
  // packages/cli/src/lib/history/history.options.ts
3473
3293
  function yargsHistoryOptionsDefinition() {
3474
3294
  return {
@@ -3542,8 +3362,8 @@ async function normalizeHashOptions(processArgs) {
3542
3362
  // packages/cli/src/lib/history/history-command.ts
3543
3363
  var command = "history";
3544
3364
  async function handler(args) {
3545
- ui().logger.info(chalk10.bold(CLI_NAME));
3546
- ui().logger.info(chalk10.gray(`Run ${command}`));
3365
+ ui().logger.info(bold10(CLI_NAME));
3366
+ ui().logger.info(gray7(`Run ${command}`));
3547
3367
  const currentBranch = await getCurrentBranchOrTag();
3548
3368
  const { targetBranch: rawTargetBranch, ...opt } = args;
3549
3369
  const {
@@ -3579,7 +3399,8 @@ function yargsHistoryCommandObject() {
3579
3399
  builder: (yargs2) => {
3580
3400
  yargs2.options({
3581
3401
  ...yargsHistoryOptionsDefinition(),
3582
- ...yargsOnlyPluginsOptionsDefinition()
3402
+ ...yargsOnlyPluginsOptionsDefinition(),
3403
+ ...yargsSkipPluginsOptionsDefinition()
3583
3404
  });
3584
3405
  yargs2.group(
3585
3406
  Object.keys(yargsHistoryOptionsDefinition()),
@@ -3606,15 +3427,15 @@ function yargsConfigCommandObject() {
3606
3427
  }
3607
3428
 
3608
3429
  // packages/cli/src/lib/upload/upload-command.ts
3609
- import chalk11 from "chalk";
3430
+ import { bold as bold11, gray as gray8 } from "ansis";
3610
3431
  function yargsUploadCommandObject() {
3611
3432
  const command2 = "upload";
3612
3433
  return {
3613
3434
  command: command2,
3614
3435
  describe: "Upload report results to the portal",
3615
3436
  handler: async (args) => {
3616
- ui().logger.log(chalk11.bold(CLI_NAME));
3617
- ui().logger.info(chalk11.gray(`Run ${command2}...`));
3437
+ ui().logger.log(bold11(CLI_NAME));
3438
+ ui().logger.info(gray8(`Run ${command2}...`));
3618
3439
  const options2 = args;
3619
3440
  if (options2.upload == null) {
3620
3441
  renderIntegratePortalHint();
@@ -3665,7 +3486,9 @@ async function coreConfigMiddleware(processArgs) {
3665
3486
  persist: {
3666
3487
  outputDir: cliPersist?.outputDir ?? rcPersist?.outputDir ?? DEFAULT_PERSIST_OUTPUT_DIR,
3667
3488
  filename: cliPersist?.filename ?? rcPersist?.filename ?? DEFAULT_PERSIST_FILENAME,
3668
- format: cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT
3489
+ format: normalizeFormats(
3490
+ cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT
3491
+ )
3669
3492
  },
3670
3493
  ...upload2 != null && { upload: upload2 },
3671
3494
  categories: rcCategories ?? [],
@@ -3673,36 +3496,39 @@ async function coreConfigMiddleware(processArgs) {
3673
3496
  ...remainingCliOptions
3674
3497
  };
3675
3498
  }
3499
+ var normalizeFormats = (formats) => (formats ?? []).flatMap((format) => format.split(","));
3676
3500
 
3677
- // packages/cli/src/lib/implementation/only-plugins.utils.ts
3678
- import chalk12 from "chalk";
3679
- function validateOnlyPluginsOption({
3501
+ // packages/cli/src/lib/implementation/validate-plugin-filter-options.utils.ts
3502
+ import { yellow } from "ansis";
3503
+ function validatePluginFilterOption(filterOption, {
3680
3504
  plugins,
3681
3505
  categories
3682
3506
  }, {
3683
- onlyPlugins = [],
3507
+ pluginsToFilter = [],
3684
3508
  verbose = false
3685
3509
  } = {}) {
3686
- const onlyPluginsSet = new Set(onlyPlugins);
3687
- const missingPlugins = onlyPlugins.filter(
3510
+ const pluginsToFilterSet = new Set(pluginsToFilter);
3511
+ const missingPlugins = pluginsToFilter.filter(
3688
3512
  (plugin) => !plugins.some(({ slug }) => slug === plugin)
3689
3513
  );
3514
+ const isSkipOption = filterOption === "skipPlugins";
3515
+ const filterFunction = (plugin) => isSkipOption ? pluginsToFilterSet.has(plugin) : !pluginsToFilterSet.has(plugin);
3690
3516
  if (missingPlugins.length > 0 && verbose) {
3691
3517
  ui().logger.info(
3692
- `${chalk12.yellow(
3518
+ `${yellow(
3693
3519
  "\u26A0"
3694
- )} The --onlyPlugin argument references plugins with "${missingPlugins.join(
3520
+ )} The --${filterOption} argument references plugins with "${missingPlugins.join(
3695
3521
  '", "'
3696
3522
  )}" slugs, but no such plugins are present in the configuration. Expected one of the following plugin slugs: "${plugins.map(({ slug }) => slug).join('", "')}".`
3697
3523
  );
3698
3524
  }
3699
- if (categories.length > 0) {
3700
- const removedCategorieSlugs = filterItemRefsBy(
3525
+ if (categories.length > 0 && verbose) {
3526
+ const removedCategorySlugs = filterItemRefsBy(
3701
3527
  categories,
3702
- ({ plugin }) => !onlyPluginsSet.has(plugin)
3528
+ ({ plugin }) => filterFunction(plugin)
3703
3529
  ).map(({ slug }) => slug);
3704
3530
  ui().logger.info(
3705
- `The --onlyPlugin argument removed categories with "${removedCategorieSlugs.join(
3531
+ `The --${filterOption} argument removed categories with "${removedCategorySlugs.join(
3706
3532
  '", "'
3707
3533
  )}" slugs.
3708
3534
  `
@@ -3715,9 +3541,10 @@ function onlyPluginsMiddleware(originalProcessArgs) {
3715
3541
  const { categories = [], onlyPlugins: originalOnlyPlugins } = originalProcessArgs;
3716
3542
  if (originalOnlyPlugins && originalOnlyPlugins.length > 0) {
3717
3543
  const { verbose, plugins, onlyPlugins = [] } = originalProcessArgs;
3718
- validateOnlyPluginsOption(
3544
+ validatePluginFilterOption(
3545
+ "onlyPlugins",
3719
3546
  { plugins, categories },
3720
- { onlyPlugins, verbose }
3547
+ { pluginsToFilter: onlyPlugins, verbose }
3721
3548
  );
3722
3549
  const validOnlyPlugins = onlyPlugins.filter(
3723
3550
  (oP) => plugins.find((p) => p.slug === oP)
@@ -3739,6 +3566,36 @@ function onlyPluginsMiddleware(originalProcessArgs) {
3739
3566
  };
3740
3567
  }
3741
3568
 
3569
+ // packages/cli/src/lib/implementation/skip-plugins.middleware.ts
3570
+ function skipPluginsMiddleware(originalProcessArgs) {
3571
+ const { categories = [], skipPlugins: originalSkipPlugins } = originalProcessArgs;
3572
+ if (originalSkipPlugins && originalSkipPlugins.length > 0) {
3573
+ const { verbose, plugins, skipPlugins = [] } = originalProcessArgs;
3574
+ validatePluginFilterOption(
3575
+ "skipPlugins",
3576
+ { plugins, categories },
3577
+ { pluginsToFilter: skipPlugins, verbose }
3578
+ );
3579
+ const validSkipPlugins = skipPlugins.filter(
3580
+ (sP) => plugins.find((p) => p.slug === sP)
3581
+ );
3582
+ const skipPluginsSet = new Set(validSkipPlugins);
3583
+ return {
3584
+ ...originalProcessArgs,
3585
+ plugins: skipPluginsSet.size > 0 ? plugins.filter(({ slug }) => !skipPluginsSet.has(slug)) : plugins,
3586
+ categories: skipPluginsSet.size > 0 ? filterItemRefsBy(
3587
+ categories,
3588
+ ({ plugin }) => !skipPluginsSet.has(plugin)
3589
+ ) : categories
3590
+ };
3591
+ }
3592
+ return {
3593
+ ...originalProcessArgs,
3594
+ // if undefined fill categories with empty array
3595
+ categories
3596
+ };
3597
+ }
3598
+
3742
3599
  // packages/cli/src/lib/middlewares.ts
3743
3600
  var middlewares = [
3744
3601
  {
@@ -3748,6 +3605,10 @@ var middlewares = [
3748
3605
  {
3749
3606
  middlewareFunction: onlyPluginsMiddleware,
3750
3607
  applyBeforeValidation: false
3608
+ },
3609
+ {
3610
+ middlewareFunction: skipPluginsMiddleware,
3611
+ applyBeforeValidation: false
3751
3612
  }
3752
3613
  ];
3753
3614
 
@@ -3823,19 +3684,21 @@ function yargsGlobalOptionsDefinition() {
3823
3684
  var options = {
3824
3685
  ...yargsGlobalOptionsDefinition(),
3825
3686
  ...yargsCoreConfigOptionsDefinition(),
3826
- ...yargsOnlyPluginsOptionsDefinition()
3687
+ ...yargsOnlyPluginsOptionsDefinition(),
3688
+ ...yargsSkipPluginsOptionsDefinition()
3827
3689
  };
3828
3690
  var groups = {
3829
3691
  "Global Options:": [
3830
3692
  ...Object.keys(yargsGlobalOptionsDefinition()),
3831
- ...Object.keys(yargsOnlyPluginsOptionsDefinition())
3693
+ ...Object.keys(yargsOnlyPluginsOptionsDefinition()),
3694
+ ...Object.keys(yargsSkipPluginsOptionsDefinition())
3832
3695
  ],
3833
3696
  "Persist Options:": Object.keys(yargsPersistConfigOptionsDefinition()),
3834
3697
  "Upload Options:": Object.keys(yargsUploadConfigOptionsDefinition())
3835
3698
  };
3836
3699
 
3837
3700
  // packages/cli/src/lib/yargs-cli.ts
3838
- import chalk13 from "chalk";
3701
+ import { bold as bold12 } from "ansis";
3839
3702
  import yargs from "yargs";
3840
3703
  function yargsCli(argv, cfg) {
3841
3704
  const { usageMessage, scriptName, noExitProcess } = cfg;
@@ -3855,7 +3718,7 @@ function yargsCli(argv, cfg) {
3855
3718
  (config) => Array.isArray(config) ? config.at(-1) : config
3856
3719
  ).options(options2).wrap(TERMINAL_WIDTH);
3857
3720
  if (usageMessage) {
3858
- cli2.usage(chalk13.bold(usageMessage));
3721
+ cli2.usage(bold12(usageMessage));
3859
3722
  }
3860
3723
  if (scriptName) {
3861
3724
  cli2.scriptName(scriptName);
@@ -3889,7 +3752,7 @@ function yargsCli(argv, cfg) {
3889
3752
  function validatePersistFormat(persist) {
3890
3753
  try {
3891
3754
  if (persist.format != null) {
3892
- persist.format.forEach((format) => formatSchema.parse(format));
3755
+ persist.format.flatMap((format) => format.split(",")).forEach((format) => formatSchema.parse(format));
3893
3756
  }
3894
3757
  return true;
3895
3758
  } catch {
@@ -3920,6 +3783,10 @@ var cli = (args) => yargsCli(args, {
3920
3783
  "code-pushup collect --onlyPlugins=coverage",
3921
3784
  "Run collect with only coverage plugin, other plugins from config file will be skipped."
3922
3785
  ],
3786
+ [
3787
+ "code-pushup collect --skipPlugins=coverage",
3788
+ "Run collect skiping the coverage plugin, other plugins from config file will be included."
3789
+ ],
3923
3790
  [
3924
3791
  "code-pushup upload --persist.outputDir=dist --persist.filename=cp-report --upload.apiKey=$CP_API_KEY",
3925
3792
  "Upload dist/cp-report.json to portal using API key from environment variable"