@code-pushup/utils 0.48.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 +1147 -1308
- package/package.json +8 -7
- package/src/index.d.ts +3 -2
- package/src/lib/reports/constants.d.ts +2 -27
- package/src/lib/reports/formatting.d.ts +4 -4
- package/src/lib/reports/generate-md-report-categoy-section.d.ts +6 -4
- package/src/lib/reports/generate-md-report.d.ts +9 -8
- package/src/lib/reports/generate-md-reports-diff.d.ts +1 -1
- package/src/lib/reports/load-report.d.ts +6 -0
- package/src/lib/reports/log-stdout-summary.d.ts +2 -0
- package/src/lib/reports/sorting.d.ts +5 -1
- package/src/lib/reports/utils.d.ts +18 -12
- package/src/lib/text-formats/constants.d.ts +8 -0
- package/src/lib/text-formats/index.d.ts +9 -40
- package/src/lib/transform.d.ts +0 -1
- package/src/lib/text-formats/md/font-style.d.ts +0 -4
- package/src/lib/text-formats/md/headline.d.ts +0 -14
- package/src/lib/text-formats/md/image.d.ts +0 -1
- package/src/lib/text-formats/md/link.d.ts +0 -1
- package/src/lib/text-formats/md/list.d.ts +0 -7
- package/src/lib/text-formats/md/paragraphs.d.ts +0 -1
- package/src/lib/text-formats/md/section.d.ts +0 -2
- package/src/lib/text-formats/md/table.d.ts +0 -9
- package/src/lib/text-formats/types.d.ts +0 -1
package/index.js
CHANGED
|
@@ -739,11 +739,262 @@ function comparePairs(pairs, equalsFn) {
|
|
|
739
739
|
import { spawn } from "node:child_process";
|
|
740
740
|
|
|
741
741
|
// packages/utils/src/lib/reports/utils.ts
|
|
742
|
-
import
|
|
742
|
+
import ansis from "ansis";
|
|
743
|
+
import { md } from "build-md";
|
|
744
|
+
|
|
745
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
746
|
+
var TERMINAL_WIDTH = 80;
|
|
747
|
+
var SCORE_COLOR_RANGE = {
|
|
748
|
+
GREEN_MIN: 0.9,
|
|
749
|
+
YELLOW_MIN: 0.5
|
|
750
|
+
};
|
|
751
|
+
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
752
|
+
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
753
|
+
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
754
|
+
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
755
|
+
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
756
|
+
"Category",
|
|
757
|
+
"Score",
|
|
758
|
+
"Audits"
|
|
759
|
+
];
|
|
760
|
+
|
|
761
|
+
// packages/utils/src/lib/reports/utils.ts
|
|
762
|
+
function formatReportScore(score) {
|
|
763
|
+
const scaledScore = score * 100;
|
|
764
|
+
const roundedScore = Math.round(scaledScore);
|
|
765
|
+
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
766
|
+
}
|
|
767
|
+
function formatScoreWithColor(score, options) {
|
|
768
|
+
const styledNumber = options?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
769
|
+
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
770
|
+
}
|
|
771
|
+
var MARKERS = {
|
|
772
|
+
circle: {
|
|
773
|
+
red: "\u{1F534}",
|
|
774
|
+
yellow: "\u{1F7E1}",
|
|
775
|
+
green: "\u{1F7E2}"
|
|
776
|
+
},
|
|
777
|
+
square: {
|
|
778
|
+
red: "\u{1F7E5}",
|
|
779
|
+
yellow: "\u{1F7E8}",
|
|
780
|
+
green: "\u{1F7E9}"
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
function scoreMarker(score, markerType = "circle") {
|
|
784
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
785
|
+
return MARKERS[markerType].green;
|
|
786
|
+
}
|
|
787
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
788
|
+
return MARKERS[markerType].yellow;
|
|
789
|
+
}
|
|
790
|
+
return MARKERS[markerType].red;
|
|
791
|
+
}
|
|
792
|
+
function getDiffMarker(diff) {
|
|
793
|
+
if (diff > 0) {
|
|
794
|
+
return "\u2191";
|
|
795
|
+
}
|
|
796
|
+
if (diff < 0) {
|
|
797
|
+
return "\u2193";
|
|
798
|
+
}
|
|
799
|
+
return "";
|
|
800
|
+
}
|
|
801
|
+
function colorByScoreDiff(text, diff) {
|
|
802
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
803
|
+
return shieldsBadge(text, color);
|
|
804
|
+
}
|
|
805
|
+
function shieldsBadge(text, color) {
|
|
806
|
+
return md.image(
|
|
807
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
808
|
+
text
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
function formatDiffNumber(diff) {
|
|
812
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
813
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
814
|
+
return `${sign}${number}`;
|
|
815
|
+
}
|
|
816
|
+
function severityMarker(severity) {
|
|
817
|
+
if (severity === "error") {
|
|
818
|
+
return "\u{1F6A8}";
|
|
819
|
+
}
|
|
820
|
+
if (severity === "warning") {
|
|
821
|
+
return "\u26A0\uFE0F";
|
|
822
|
+
}
|
|
823
|
+
return "\u2139\uFE0F";
|
|
824
|
+
}
|
|
825
|
+
function formatScoreChange(diff) {
|
|
826
|
+
const marker = getDiffMarker(diff);
|
|
827
|
+
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
828
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
829
|
+
}
|
|
830
|
+
function formatValueChange({
|
|
831
|
+
values,
|
|
832
|
+
scores
|
|
833
|
+
}) {
|
|
834
|
+
const marker = getDiffMarker(values.diff);
|
|
835
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
836
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
837
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
838
|
+
}
|
|
839
|
+
function calcDuration(start, stop) {
|
|
840
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
841
|
+
}
|
|
842
|
+
function countCategoryAudits(refs, plugins) {
|
|
843
|
+
const groupLookup = plugins.reduce(
|
|
844
|
+
(lookup, plugin) => {
|
|
845
|
+
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
846
|
+
return lookup;
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
...lookup,
|
|
850
|
+
[plugin.slug]: Object.fromEntries(
|
|
851
|
+
plugin.groups.map((group) => [group.slug, group])
|
|
852
|
+
)
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
{}
|
|
856
|
+
);
|
|
857
|
+
return refs.reduce((acc, ref) => {
|
|
858
|
+
if (ref.type === "group") {
|
|
859
|
+
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
860
|
+
return acc + (groupRefs?.length ?? 0);
|
|
861
|
+
}
|
|
862
|
+
return acc + 1;
|
|
863
|
+
}, 0);
|
|
864
|
+
}
|
|
865
|
+
function compareCategoryAuditsAndGroups(a, b) {
|
|
866
|
+
if (a.weight !== b.weight) {
|
|
867
|
+
return b.weight - a.weight;
|
|
868
|
+
}
|
|
869
|
+
if (a.score !== b.score) {
|
|
870
|
+
return a.score - b.score;
|
|
871
|
+
}
|
|
872
|
+
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
873
|
+
return b.value - a.value;
|
|
874
|
+
}
|
|
875
|
+
return a.title.localeCompare(b.title);
|
|
876
|
+
}
|
|
877
|
+
function compareAudits(a, b) {
|
|
878
|
+
if (a.score !== b.score) {
|
|
879
|
+
return a.score - b.score;
|
|
880
|
+
}
|
|
881
|
+
if (a.value !== b.value) {
|
|
882
|
+
return b.value - a.value;
|
|
883
|
+
}
|
|
884
|
+
return a.title.localeCompare(b.title);
|
|
885
|
+
}
|
|
886
|
+
function compareIssueSeverity(severity1, severity2) {
|
|
887
|
+
const levels = {
|
|
888
|
+
info: 0,
|
|
889
|
+
warning: 1,
|
|
890
|
+
error: 2
|
|
891
|
+
};
|
|
892
|
+
return levels[severity1] - levels[severity2];
|
|
893
|
+
}
|
|
894
|
+
function throwIsNotPresentError(itemName, presentPlace) {
|
|
895
|
+
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
896
|
+
}
|
|
897
|
+
function getPluginNameFromSlug(slug, plugins) {
|
|
898
|
+
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
899
|
+
}
|
|
900
|
+
function compareIssues(a, b) {
|
|
901
|
+
if (a.severity !== b.severity) {
|
|
902
|
+
return -compareIssueSeverity(a.severity, b.severity);
|
|
903
|
+
}
|
|
904
|
+
if (!a.source && b.source) {
|
|
905
|
+
return -1;
|
|
906
|
+
}
|
|
907
|
+
if (a.source && !b.source) {
|
|
908
|
+
return 1;
|
|
909
|
+
}
|
|
910
|
+
if (a.source?.file !== b.source?.file) {
|
|
911
|
+
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
912
|
+
}
|
|
913
|
+
if (!a.source?.position && b.source?.position) {
|
|
914
|
+
return -1;
|
|
915
|
+
}
|
|
916
|
+
if (a.source?.position && !b.source?.position) {
|
|
917
|
+
return 1;
|
|
918
|
+
}
|
|
919
|
+
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
920
|
+
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
921
|
+
}
|
|
922
|
+
return 0;
|
|
923
|
+
}
|
|
924
|
+
function applyScoreColor({ score, text }, style = ansis) {
|
|
925
|
+
const formattedScore = text ?? formatReportScore(score);
|
|
926
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
927
|
+
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
928
|
+
}
|
|
929
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
930
|
+
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
931
|
+
}
|
|
932
|
+
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
933
|
+
}
|
|
934
|
+
function targetScoreIcon(score, targetScore, options = {}) {
|
|
935
|
+
if (targetScore != null) {
|
|
936
|
+
const {
|
|
937
|
+
passIcon = "\u2705",
|
|
938
|
+
failIcon = "\u274C",
|
|
939
|
+
prefix = "",
|
|
940
|
+
postfix = ""
|
|
941
|
+
} = options;
|
|
942
|
+
if (score >= targetScore) {
|
|
943
|
+
return `${prefix}${passIcon}${postfix}`;
|
|
944
|
+
}
|
|
945
|
+
return `${prefix}${failIcon}${postfix}`;
|
|
946
|
+
}
|
|
947
|
+
return "";
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// packages/utils/src/lib/execute-process.ts
|
|
951
|
+
var ProcessError = class extends Error {
|
|
952
|
+
code;
|
|
953
|
+
stderr;
|
|
954
|
+
stdout;
|
|
955
|
+
constructor(result) {
|
|
956
|
+
super(result.stderr);
|
|
957
|
+
this.code = result.code;
|
|
958
|
+
this.stderr = result.stderr;
|
|
959
|
+
this.stdout = result.stdout;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
function executeProcess(cfg) {
|
|
963
|
+
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
964
|
+
const { onStdout, onError, onComplete } = observer ?? {};
|
|
965
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
966
|
+
const start = performance.now();
|
|
967
|
+
return new Promise((resolve, reject) => {
|
|
968
|
+
const process2 = spawn(command, args, { cwd, shell: true });
|
|
969
|
+
let stdout = "";
|
|
970
|
+
let stderr = "";
|
|
971
|
+
process2.stdout.on("data", (data) => {
|
|
972
|
+
stdout += String(data);
|
|
973
|
+
onStdout?.(String(data));
|
|
974
|
+
});
|
|
975
|
+
process2.stderr.on("data", (data) => {
|
|
976
|
+
stderr += String(data);
|
|
977
|
+
});
|
|
978
|
+
process2.on("error", (err) => {
|
|
979
|
+
stderr += err.toString();
|
|
980
|
+
});
|
|
981
|
+
process2.on("close", (code2) => {
|
|
982
|
+
const timings = { date, duration: calcDuration(start) };
|
|
983
|
+
if (code2 === 0 || ignoreExitCode) {
|
|
984
|
+
onComplete?.();
|
|
985
|
+
resolve({ code: code2, stdout, stderr, ...timings });
|
|
986
|
+
} else {
|
|
987
|
+
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
988
|
+
onError?.(errorMsg);
|
|
989
|
+
reject(errorMsg);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
}
|
|
743
994
|
|
|
744
995
|
// packages/utils/src/lib/file-system.ts
|
|
996
|
+
import { bold, gray } from "ansis";
|
|
745
997
|
import { bundleRequire } from "bundle-require";
|
|
746
|
-
import chalk2 from "chalk";
|
|
747
998
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
748
999
|
import { join } from "node:path";
|
|
749
1000
|
|
|
@@ -836,55 +1087,7 @@ function isPromiseRejectedResult(result) {
|
|
|
836
1087
|
// packages/utils/src/lib/logging.ts
|
|
837
1088
|
import isaacs_cliui from "@isaacs/cliui";
|
|
838
1089
|
import { cliui } from "@poppinss/cliui";
|
|
839
|
-
import
|
|
840
|
-
|
|
841
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
842
|
-
var TERMINAL_WIDTH = 80;
|
|
843
|
-
var SCORE_COLOR_RANGE = {
|
|
844
|
-
GREEN_MIN: 0.9,
|
|
845
|
-
YELLOW_MIN: 0.5
|
|
846
|
-
};
|
|
847
|
-
var CATEGORIES_TITLE = "\u{1F3F7} Categories";
|
|
848
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
849
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
850
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
851
|
-
var reportHeadlineText = "Code PushUp Report";
|
|
852
|
-
var reportOverviewTableHeaders = [
|
|
853
|
-
{
|
|
854
|
-
key: "category",
|
|
855
|
-
label: "\u{1F3F7} Category",
|
|
856
|
-
align: "left"
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
key: "score",
|
|
860
|
-
label: "\u2B50 Score"
|
|
861
|
-
},
|
|
862
|
-
{
|
|
863
|
-
key: "audits",
|
|
864
|
-
label: "\u{1F6E1} Audits"
|
|
865
|
-
}
|
|
866
|
-
];
|
|
867
|
-
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
868
|
-
var issuesTableHeadings = [
|
|
869
|
-
{
|
|
870
|
-
key: "severity",
|
|
871
|
-
label: "Severity"
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
key: "message",
|
|
875
|
-
label: "Message"
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
key: "file",
|
|
879
|
-
label: "Source file"
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
key: "line",
|
|
883
|
-
label: "Line(s)"
|
|
884
|
-
}
|
|
885
|
-
];
|
|
886
|
-
|
|
887
|
-
// packages/utils/src/lib/logging.ts
|
|
1090
|
+
import { underline } from "ansis";
|
|
888
1091
|
var singletonUiInstance;
|
|
889
1092
|
function ui() {
|
|
890
1093
|
if (singletonUiInstance === void 0) {
|
|
@@ -908,7 +1111,7 @@ function logListItem(args) {
|
|
|
908
1111
|
singletonUiInstance?.logger.log(content);
|
|
909
1112
|
}
|
|
910
1113
|
function link(text) {
|
|
911
|
-
return
|
|
1114
|
+
return underline.blueBright(text);
|
|
912
1115
|
}
|
|
913
1116
|
|
|
914
1117
|
// packages/utils/src/lib/log-results.ts
|
|
@@ -988,10 +1191,10 @@ async function removeDirectoryIfExists(dir) {
|
|
|
988
1191
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
989
1192
|
const succeededTransform = (result) => {
|
|
990
1193
|
const [fileName, size] = result.value;
|
|
991
|
-
const formattedSize = size ? ` (${
|
|
992
|
-
return `- ${
|
|
1194
|
+
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1195
|
+
return `- ${bold(fileName)}${formattedSize}`;
|
|
993
1196
|
};
|
|
994
|
-
const failedTransform = (result) => `- ${
|
|
1197
|
+
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
995
1198
|
logMultipleResults(
|
|
996
1199
|
fileResults,
|
|
997
1200
|
messagePrefix,
|
|
@@ -1031,46 +1234,25 @@ async function crawlFileSystem(options) {
|
|
|
1031
1234
|
return resultsNestedArray.flat();
|
|
1032
1235
|
}
|
|
1033
1236
|
function findLineNumberInText(content, pattern) {
|
|
1034
|
-
const
|
|
1035
|
-
const lineNumber =
|
|
1237
|
+
const lines = content.split(/\r?\n/);
|
|
1238
|
+
const lineNumber = lines.findIndex((line) => line.includes(pattern)) + 1;
|
|
1036
1239
|
return lineNumber === 0 ? null : lineNumber;
|
|
1037
1240
|
}
|
|
1038
1241
|
function filePathToCliArg(path) {
|
|
1039
1242
|
return `"${path}"`;
|
|
1040
1243
|
}
|
|
1041
1244
|
|
|
1042
|
-
// packages/utils/src/lib/
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
function details(title, content, cfg = { open: false }) {
|
|
1049
|
-
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.
|
|
1050
|
-
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
1051
|
-
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
1052
|
-
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
1053
|
-
NEW_LINE}`;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// packages/utils/src/lib/text-formats/html/font-style.ts
|
|
1057
|
-
var boldElement = "b";
|
|
1058
|
-
function bold(text) {
|
|
1059
|
-
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1060
|
-
}
|
|
1061
|
-
var italicElement = "i";
|
|
1062
|
-
function italic(text) {
|
|
1063
|
-
return `<${italicElement}>${text}</${italicElement}>`;
|
|
1064
|
-
}
|
|
1065
|
-
var codeElement = "code";
|
|
1066
|
-
function code(text) {
|
|
1067
|
-
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1245
|
+
// packages/utils/src/lib/filter.ts
|
|
1246
|
+
function filterItemRefsBy(items, refFilterFn) {
|
|
1247
|
+
return items.map((item) => ({
|
|
1248
|
+
...item,
|
|
1249
|
+
refs: item.refs.filter(refFilterFn)
|
|
1250
|
+
})).filter((item) => item.refs.length);
|
|
1068
1251
|
}
|
|
1069
1252
|
|
|
1070
|
-
// packages/utils/src/lib/
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1253
|
+
// packages/utils/src/lib/git/git.ts
|
|
1254
|
+
import { isAbsolute, join as join2, relative } from "node:path";
|
|
1255
|
+
import { simpleGit } from "simple-git";
|
|
1074
1256
|
|
|
1075
1257
|
// packages/utils/src/lib/transform.ts
|
|
1076
1258
|
import { platform } from "node:os";
|
|
@@ -1121,6 +1303,12 @@ function objectToCliArgs(params) {
|
|
|
1121
1303
|
if (Array.isArray(value)) {
|
|
1122
1304
|
return value.map((v) => `${prefix}${key}="${v}"`);
|
|
1123
1305
|
}
|
|
1306
|
+
if (typeof value === "object") {
|
|
1307
|
+
return Object.entries(value).flatMap(
|
|
1308
|
+
// transform nested objects to the dot notation `key.subkey`
|
|
1309
|
+
([k, v]) => objectToCliArgs({ [`${key}.${k}`]: v })
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1124
1312
|
if (typeof value === "string") {
|
|
1125
1313
|
return [`${prefix}${key}="${value}"`];
|
|
1126
1314
|
}
|
|
@@ -1151,11 +1339,6 @@ function capitalize(text) {
|
|
|
1151
1339
|
1
|
|
1152
1340
|
)}`;
|
|
1153
1341
|
}
|
|
1154
|
-
function apostrophize(text, upperCase) {
|
|
1155
|
-
const lastCharMatch = text.match(/(\w)\W*$/);
|
|
1156
|
-
const lastChar = lastCharMatch?.[1] ?? "";
|
|
1157
|
-
return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
|
|
1158
|
-
}
|
|
1159
1342
|
function toNumberPrecision(value, decimalPlaces) {
|
|
1160
1343
|
return Number(
|
|
1161
1344
|
`${Math.round(
|
|
@@ -1176,1195 +1359,908 @@ function toOrdinal(value) {
|
|
|
1176
1359
|
return `${value}th`;
|
|
1177
1360
|
}
|
|
1178
1361
|
|
|
1179
|
-
// packages/utils/src/lib/
|
|
1180
|
-
function
|
|
1181
|
-
|
|
1182
|
-
throw new TypeError(
|
|
1183
|
-
"Column can`t be object when rows are primitive values"
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
return rows.map((row) => {
|
|
1187
|
-
if (Array.isArray(row)) {
|
|
1188
|
-
return row.map(String);
|
|
1189
|
-
}
|
|
1190
|
-
const objectRow = row;
|
|
1191
|
-
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1192
|
-
return Object.values(objectRow).map(
|
|
1193
|
-
(value) => value == null ? "" : String(value)
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
return columns.map(
|
|
1197
|
-
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1198
|
-
);
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
function columnsToStringArray({
|
|
1202
|
-
rows,
|
|
1203
|
-
columns = []
|
|
1204
|
-
}) {
|
|
1205
|
-
const firstRow = rows.at(0);
|
|
1206
|
-
const primitiveRows = Array.isArray(firstRow);
|
|
1207
|
-
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1208
|
-
throw new Error("invalid union type. Caught by model parsing.");
|
|
1209
|
-
}
|
|
1210
|
-
if (columns.length === 0) {
|
|
1211
|
-
if (Array.isArray(firstRow)) {
|
|
1212
|
-
return firstRow.map((_, idx) => String(idx));
|
|
1213
|
-
}
|
|
1214
|
-
return Object.keys(firstRow);
|
|
1215
|
-
}
|
|
1216
|
-
if (typeof columns.at(0) === "string") {
|
|
1217
|
-
return columns.map(String);
|
|
1218
|
-
}
|
|
1219
|
-
const cols = columns;
|
|
1220
|
-
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1362
|
+
// packages/utils/src/lib/git/git.ts
|
|
1363
|
+
function getGitRoot(git = simpleGit()) {
|
|
1364
|
+
return git.revparse("--show-toplevel");
|
|
1221
1365
|
}
|
|
1222
|
-
function
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
} else if (typeof column === "object") {
|
|
1227
|
-
return column.align ?? "center";
|
|
1228
|
-
} else {
|
|
1229
|
-
return "center";
|
|
1230
|
-
}
|
|
1366
|
+
function formatGitPath(path, gitRoot) {
|
|
1367
|
+
const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
|
|
1368
|
+
const relativePath = relative(gitRoot, absolutePath);
|
|
1369
|
+
return toUnixPath(relativePath);
|
|
1231
1370
|
}
|
|
1232
|
-
function
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
return "center";
|
|
1236
|
-
} else if (typeof column === "string") {
|
|
1237
|
-
return column;
|
|
1238
|
-
} else if (typeof column === "object") {
|
|
1239
|
-
return column.align ?? "center";
|
|
1240
|
-
} else {
|
|
1241
|
-
return "center";
|
|
1242
|
-
}
|
|
1371
|
+
async function toGitPath(path, git = simpleGit()) {
|
|
1372
|
+
const gitRoot = await getGitRoot(git);
|
|
1373
|
+
return formatGitPath(path, gitRoot);
|
|
1243
1374
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1375
|
+
var GitStatusError = class _GitStatusError extends Error {
|
|
1376
|
+
static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
|
|
1377
|
+
static getReducedStatus(status) {
|
|
1378
|
+
return Object.fromEntries(
|
|
1379
|
+
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1380
|
+
(entry) => {
|
|
1381
|
+
const value = entry[1];
|
|
1382
|
+
if (value == null) {
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1386
|
+
return false;
|
|
1387
|
+
}
|
|
1388
|
+
if (typeof value === "number" && value === 0) {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
return !(typeof value === "boolean" && !value);
|
|
1392
|
+
}
|
|
1393
|
+
)
|
|
1253
1394
|
);
|
|
1254
1395
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
)
|
|
1396
|
+
constructor(status) {
|
|
1397
|
+
super(
|
|
1398
|
+
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1399
|
+
${JSON.stringify(
|
|
1400
|
+
_GitStatusError.getReducedStatus(status),
|
|
1401
|
+
null,
|
|
1402
|
+
2
|
|
1403
|
+
)}`
|
|
1263
1404
|
);
|
|
1264
1405
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1271
|
-
}
|
|
1272
|
-
function wrapRow(content) {
|
|
1273
|
-
const elem = "tr";
|
|
1274
|
-
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1275
|
-
}
|
|
1276
|
-
function table(tableData) {
|
|
1277
|
-
if (tableData.rows.length === 0) {
|
|
1278
|
-
throw new Error("Data can't be empty");
|
|
1406
|
+
};
|
|
1407
|
+
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1408
|
+
const status = await git.status(["-s"]);
|
|
1409
|
+
if (status.files.length > 0) {
|
|
1410
|
+
throw new GitStatusError(status);
|
|
1279
1411
|
}
|
|
1280
|
-
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1281
|
-
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1282
|
-
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1283
|
-
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1284
|
-
return wrapRow(columns);
|
|
1285
|
-
}).join("");
|
|
1286
|
-
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// packages/utils/src/lib/text-formats/md/font-style.ts
|
|
1290
|
-
var boldWrap = "**";
|
|
1291
|
-
function bold2(text) {
|
|
1292
|
-
return `${boldWrap}${text}${boldWrap}`;
|
|
1293
1412
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
var codeWrap = "`";
|
|
1303
|
-
function code2(text) {
|
|
1304
|
-
return `${codeWrap}${text}${codeWrap}`;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// packages/utils/src/lib/text-formats/md/headline.ts
|
|
1308
|
-
function headline(text, hierarchy = 1) {
|
|
1309
|
-
return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
|
|
1310
|
-
}
|
|
1311
|
-
function h(text, hierarchy = 1) {
|
|
1312
|
-
return headline(text, hierarchy);
|
|
1313
|
-
}
|
|
1314
|
-
function h1(text) {
|
|
1315
|
-
return headline(text, 1);
|
|
1316
|
-
}
|
|
1317
|
-
function h2(text) {
|
|
1318
|
-
return headline(text, 2);
|
|
1319
|
-
}
|
|
1320
|
-
function h3(text) {
|
|
1321
|
-
return headline(text, 3);
|
|
1322
|
-
}
|
|
1323
|
-
function h4(text) {
|
|
1324
|
-
return headline(text, 4);
|
|
1325
|
-
}
|
|
1326
|
-
function h5(text) {
|
|
1327
|
-
return headline(text, 5);
|
|
1328
|
-
}
|
|
1329
|
-
function h6(text) {
|
|
1330
|
-
return headline(text, 6);
|
|
1413
|
+
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1414
|
+
if (forceCleanStatus) {
|
|
1415
|
+
await git.raw(["reset", "--hard"]);
|
|
1416
|
+
await git.clean(["f", "d"]);
|
|
1417
|
+
ui().logger.info(`git status cleaned`);
|
|
1418
|
+
}
|
|
1419
|
+
await guardAgainstLocalChanges(git);
|
|
1420
|
+
await git.checkout(branchOrHash);
|
|
1331
1421
|
}
|
|
1332
1422
|
|
|
1333
|
-
// packages/utils/src/lib/
|
|
1334
|
-
|
|
1335
|
-
return ``;
|
|
1336
|
-
}
|
|
1423
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1424
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1337
1425
|
|
|
1338
|
-
// packages/utils/src/lib/
|
|
1339
|
-
|
|
1340
|
-
|
|
1426
|
+
// packages/utils/src/lib/semver.ts
|
|
1427
|
+
import { rcompare, valid } from "semver";
|
|
1428
|
+
function normalizeSemver(semverString) {
|
|
1429
|
+
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1430
|
+
return semverString.slice(1);
|
|
1431
|
+
}
|
|
1432
|
+
if (semverString.includes("@")) {
|
|
1433
|
+
return semverString.split("@").at(-1) ?? "";
|
|
1434
|
+
}
|
|
1435
|
+
return semverString;
|
|
1341
1436
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
function li(text, order = "unordered") {
|
|
1345
|
-
const style = order === "unordered" ? "-" : "- [ ]";
|
|
1346
|
-
return `${style} ${text}`;
|
|
1437
|
+
function isSemver(semverString = "") {
|
|
1438
|
+
return valid(normalizeSemver(semverString)) != null;
|
|
1347
1439
|
}
|
|
1348
|
-
function
|
|
1349
|
-
return
|
|
1440
|
+
function sortSemvers(semverStrings) {
|
|
1441
|
+
return semverStrings.map(normalizeSemver).filter(isSemver).sort(rcompare);
|
|
1350
1442
|
}
|
|
1351
1443
|
|
|
1352
|
-
// packages/utils/src/lib/
|
|
1353
|
-
function
|
|
1354
|
-
|
|
1444
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1445
|
+
async function getLatestCommit(git = simpleGit2()) {
|
|
1446
|
+
const log2 = await git.log({
|
|
1447
|
+
maxCount: 1,
|
|
1448
|
+
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1449
|
+
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1450
|
+
});
|
|
1451
|
+
return commitSchema.parse(log2.latest);
|
|
1355
1452
|
}
|
|
1356
|
-
|
|
1357
|
-
//
|
|
1358
|
-
|
|
1359
|
-
|
|
1453
|
+
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1454
|
+
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1455
|
+
// @TODO use simple git
|
|
1456
|
+
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1360
1457
|
}
|
|
1361
|
-
function
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1458
|
+
function validateFilter({ from, to }) {
|
|
1459
|
+
if (to && !from) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
`filter needs the "from" option defined to accept the "to" option.
|
|
1462
|
+
`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1366
1465
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
["left", ":--"],
|
|
1371
|
-
["center", ":--:"],
|
|
1372
|
-
["right", "--:"]
|
|
1373
|
-
]);
|
|
1374
|
-
function tableRow(rows) {
|
|
1375
|
-
return `|${rows.join("|")}|`;
|
|
1376
|
-
}
|
|
1377
|
-
function table2(data) {
|
|
1378
|
-
if (data.rows.length === 0) {
|
|
1379
|
-
throw new Error("Data can't be empty");
|
|
1466
|
+
function filterLogs(allTags, opt) {
|
|
1467
|
+
if (!opt) {
|
|
1468
|
+
return allTags;
|
|
1380
1469
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
)
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
);
|
|
1470
|
+
validateFilter(opt);
|
|
1471
|
+
const { from, to, maxCount } = opt;
|
|
1472
|
+
const finIndex = (tagName, fallback) => {
|
|
1473
|
+
const idx = allTags.indexOf(tagName ?? "");
|
|
1474
|
+
if (idx > -1) {
|
|
1475
|
+
return idx;
|
|
1476
|
+
}
|
|
1477
|
+
return fallback;
|
|
1478
|
+
};
|
|
1479
|
+
const fromIndex = finIndex(from, 0);
|
|
1480
|
+
const toIndex = finIndex(to, void 0);
|
|
1481
|
+
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1391
1482
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
link: link3,
|
|
1400
|
-
image,
|
|
1401
|
-
headline,
|
|
1402
|
-
h,
|
|
1403
|
-
h1,
|
|
1404
|
-
h2,
|
|
1405
|
-
h3,
|
|
1406
|
-
h4,
|
|
1407
|
-
h5,
|
|
1408
|
-
h6,
|
|
1409
|
-
indentation,
|
|
1410
|
-
lines,
|
|
1411
|
-
li,
|
|
1412
|
-
section,
|
|
1413
|
-
paragraphs,
|
|
1414
|
-
table: table2
|
|
1415
|
-
};
|
|
1416
|
-
var html = {
|
|
1417
|
-
bold,
|
|
1418
|
-
italic,
|
|
1419
|
-
code,
|
|
1420
|
-
link: link2,
|
|
1421
|
-
details,
|
|
1422
|
-
table
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1425
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
1426
|
-
var { image: image2, bold: boldMd } = md;
|
|
1427
|
-
function formatReportScore(score) {
|
|
1428
|
-
const scaledScore = score * 100;
|
|
1429
|
-
const roundedScore = Math.round(scaledScore);
|
|
1430
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
1431
|
-
}
|
|
1432
|
-
function formatScoreWithColor(score, options) {
|
|
1433
|
-
const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
|
|
1434
|
-
return `${scoreMarker(score)} ${styledNumber}`;
|
|
1483
|
+
async function getHashFromTag(tag, git = simpleGit2()) {
|
|
1484
|
+
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1485
|
+
const hash = tagDetails.trim();
|
|
1486
|
+
return {
|
|
1487
|
+
hash: hash.split("\n").at(-1) ?? "",
|
|
1488
|
+
message: tag
|
|
1489
|
+
};
|
|
1435
1490
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
red: "\u{1F7E5}",
|
|
1444
|
-
yellow: "\u{1F7E8}",
|
|
1445
|
-
green: "\u{1F7E9}"
|
|
1446
|
-
}
|
|
1447
|
-
};
|
|
1448
|
-
function scoreMarker(score, markerType = "circle") {
|
|
1449
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
1450
|
-
return MARKERS[markerType].green;
|
|
1491
|
+
async function getSemverTags(opt = {}, git = simpleGit2()) {
|
|
1492
|
+
validateFilter(opt);
|
|
1493
|
+
const { targetBranch, ...options } = opt;
|
|
1494
|
+
let currentBranch;
|
|
1495
|
+
if (targetBranch) {
|
|
1496
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1497
|
+
await git.checkout(targetBranch);
|
|
1451
1498
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1499
|
+
const tagsRaw = await git.tag([
|
|
1500
|
+
"--merged",
|
|
1501
|
+
targetBranch ?? await getCurrentBranchOrTag(git)
|
|
1502
|
+
]);
|
|
1503
|
+
const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
|
|
1504
|
+
const relevantTags = filterLogs(allTags, options);
|
|
1505
|
+
const tagsWithHashes = await Promise.all(
|
|
1506
|
+
relevantTags.map((tag) => getHashFromTag(tag, git))
|
|
1507
|
+
);
|
|
1508
|
+
if (currentBranch) {
|
|
1509
|
+
await git.checkout(currentBranch);
|
|
1454
1510
|
}
|
|
1455
|
-
return
|
|
1511
|
+
return tagsWithHashes;
|
|
1456
1512
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
|
|
1513
|
+
async function getHashes(options = {}, git = simpleGit2()) {
|
|
1514
|
+
const { targetBranch, from, to, maxCount, ...opt } = options;
|
|
1515
|
+
validateFilter({ from, to });
|
|
1516
|
+
let currentBranch;
|
|
1517
|
+
if (targetBranch) {
|
|
1518
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1519
|
+
await git.checkout(targetBranch);
|
|
1460
1520
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1521
|
+
const logs = await git.log({
|
|
1522
|
+
...opt,
|
|
1523
|
+
format: {
|
|
1524
|
+
hash: "%H",
|
|
1525
|
+
message: "%s"
|
|
1526
|
+
},
|
|
1527
|
+
from,
|
|
1528
|
+
to,
|
|
1529
|
+
maxCount
|
|
1530
|
+
});
|
|
1531
|
+
if (targetBranch) {
|
|
1532
|
+
await git.checkout(currentBranch);
|
|
1463
1533
|
}
|
|
1464
|
-
return
|
|
1465
|
-
}
|
|
1466
|
-
function colorByScoreDiff(text, diff) {
|
|
1467
|
-
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
1468
|
-
return shieldsBadge(text, color);
|
|
1534
|
+
return [...logs.all];
|
|
1469
1535
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1536
|
+
|
|
1537
|
+
// packages/utils/src/lib/group-by-status.ts
|
|
1538
|
+
function groupByStatus(results) {
|
|
1539
|
+
return results.reduce(
|
|
1540
|
+
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1541
|
+
{ fulfilled: [], rejected: [] }
|
|
1474
1542
|
);
|
|
1475
1543
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
return
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
return "\u2139\uFE0F";
|
|
1489
|
-
}
|
|
1490
|
-
function calcDuration(start, stop) {
|
|
1491
|
-
return Math.round((stop ?? performance.now()) - start);
|
|
1492
|
-
}
|
|
1493
|
-
function countCategoryAudits(refs, plugins) {
|
|
1494
|
-
const groupLookup = plugins.reduce(
|
|
1495
|
-
(lookup, plugin) => {
|
|
1496
|
-
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
1497
|
-
return lookup;
|
|
1498
|
-
}
|
|
1499
|
-
return {
|
|
1500
|
-
...lookup,
|
|
1501
|
-
[plugin.slug]: Object.fromEntries(
|
|
1502
|
-
plugin.groups.map((group) => [group.slug, group])
|
|
1503
|
-
)
|
|
1504
|
-
};
|
|
1505
|
-
},
|
|
1506
|
-
{}
|
|
1544
|
+
|
|
1545
|
+
// packages/utils/src/lib/merge-configs.ts
|
|
1546
|
+
function mergeConfigs(config, ...configs) {
|
|
1547
|
+
return configs.reduce(
|
|
1548
|
+
(acc, obj) => ({
|
|
1549
|
+
...acc,
|
|
1550
|
+
...mergeCategories(acc.categories, obj.categories),
|
|
1551
|
+
...mergePlugins(acc.plugins, obj.plugins),
|
|
1552
|
+
...mergePersist(acc.persist, obj.persist),
|
|
1553
|
+
...mergeUpload(acc.upload, obj.upload)
|
|
1554
|
+
}),
|
|
1555
|
+
config
|
|
1507
1556
|
);
|
|
1508
|
-
return refs.reduce((acc, ref) => {
|
|
1509
|
-
if (ref.type === "group") {
|
|
1510
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
1511
|
-
return acc + (groupRefs?.length ?? 0);
|
|
1512
|
-
}
|
|
1513
|
-
return acc + 1;
|
|
1514
|
-
}, 0);
|
|
1515
1557
|
}
|
|
1516
|
-
function
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1520
|
-
}
|
|
1521
|
-
const audit = auditPlugin.audits.find(
|
|
1522
|
-
({ slug: auditSlug }) => auditSlug === slug
|
|
1523
|
-
);
|
|
1524
|
-
if (!audit) {
|
|
1525
|
-
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1558
|
+
function mergeCategories(a, b) {
|
|
1559
|
+
if (!a && !b) {
|
|
1560
|
+
return {};
|
|
1526
1561
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1562
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
1563
|
+
const addToMap = (categories) => {
|
|
1564
|
+
categories.forEach((newObject) => {
|
|
1565
|
+
if (mergedMap.has(newObject.slug)) {
|
|
1566
|
+
const existingObject = mergedMap.get(
|
|
1567
|
+
newObject.slug
|
|
1568
|
+
);
|
|
1569
|
+
mergedMap.set(newObject.slug, {
|
|
1570
|
+
...existingObject,
|
|
1571
|
+
...newObject,
|
|
1572
|
+
refs: mergeByUniqueCategoryRefCombination(
|
|
1573
|
+
existingObject?.refs,
|
|
1574
|
+
newObject.refs
|
|
1575
|
+
)
|
|
1576
|
+
});
|
|
1577
|
+
} else {
|
|
1578
|
+
mergedMap.set(newObject.slug, newObject);
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1531
1581
|
};
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1535
|
-
if (!groupPlugin) {
|
|
1536
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1582
|
+
if (a) {
|
|
1583
|
+
addToMap(a);
|
|
1537
1584
|
}
|
|
1538
|
-
|
|
1539
|
-
(
|
|
1540
|
-
);
|
|
1541
|
-
if (!group) {
|
|
1542
|
-
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1585
|
+
if (b) {
|
|
1586
|
+
addToMap(b);
|
|
1543
1587
|
}
|
|
1544
|
-
|
|
1545
|
-
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1546
|
-
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1547
|
-
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1548
|
-
return aIndex - bIndex;
|
|
1549
|
-
});
|
|
1550
|
-
return {
|
|
1551
|
-
...group,
|
|
1552
|
-
refs: sortedAuditRefs,
|
|
1553
|
-
plugin,
|
|
1554
|
-
weight
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1558
|
-
return group.refs.map(
|
|
1559
|
-
(ref) => getSortableAuditByRef(
|
|
1560
|
-
{
|
|
1561
|
-
plugin,
|
|
1562
|
-
slug: ref.slug,
|
|
1563
|
-
weight: ref.weight,
|
|
1564
|
-
type: "audit"
|
|
1565
|
-
},
|
|
1566
|
-
plugins
|
|
1567
|
-
)
|
|
1568
|
-
).sort(compareCategoryAuditsAndGroups);
|
|
1588
|
+
return { categories: [...mergedMap.values()] };
|
|
1569
1589
|
}
|
|
1570
|
-
function
|
|
1571
|
-
if (a
|
|
1572
|
-
return
|
|
1590
|
+
function mergePlugins(a, b) {
|
|
1591
|
+
if (!a && !b) {
|
|
1592
|
+
return { plugins: [] };
|
|
1573
1593
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1594
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
1595
|
+
const addToMap = (plugins) => {
|
|
1596
|
+
plugins.forEach((newObject) => {
|
|
1597
|
+
mergedMap.set(newObject.slug, newObject);
|
|
1598
|
+
});
|
|
1599
|
+
};
|
|
1600
|
+
if (a) {
|
|
1601
|
+
addToMap(a);
|
|
1576
1602
|
}
|
|
1577
|
-
if (
|
|
1578
|
-
|
|
1603
|
+
if (b) {
|
|
1604
|
+
addToMap(b);
|
|
1579
1605
|
}
|
|
1580
|
-
return
|
|
1606
|
+
return { plugins: [...mergedMap.values()] };
|
|
1581
1607
|
}
|
|
1582
|
-
function
|
|
1583
|
-
if (a
|
|
1584
|
-
return
|
|
1585
|
-
}
|
|
1586
|
-
if (a.value !== b.value) {
|
|
1587
|
-
return b.value - a.value;
|
|
1608
|
+
function mergePersist(a, b) {
|
|
1609
|
+
if (!a && !b) {
|
|
1610
|
+
return {};
|
|
1588
1611
|
}
|
|
1589
|
-
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
info: 0,
|
|
1594
|
-
warning: 1,
|
|
1595
|
-
error: 2
|
|
1596
|
-
};
|
|
1597
|
-
return levels[severity1] - levels[severity2];
|
|
1598
|
-
}
|
|
1599
|
-
async function loadReport(options) {
|
|
1600
|
-
const { outputDir, filename, format } = options;
|
|
1601
|
-
await ensureDirectoryExists(outputDir);
|
|
1602
|
-
const filePath = join2(outputDir, `${filename}.${format}`);
|
|
1603
|
-
if (format === "json") {
|
|
1604
|
-
const content = await readJsonFile(filePath);
|
|
1605
|
-
return reportSchema.parse(content);
|
|
1612
|
+
if (a) {
|
|
1613
|
+
return b ? { persist: { ...a, ...b } } : {};
|
|
1614
|
+
} else {
|
|
1615
|
+
return { persist: b };
|
|
1606
1616
|
}
|
|
1607
|
-
const text = await readTextFile(filePath);
|
|
1608
|
-
return text;
|
|
1609
|
-
}
|
|
1610
|
-
function throwIsNotPresentError(itemName, presentPlace) {
|
|
1611
|
-
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
1612
1617
|
}
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1618
|
+
function mergeByUniqueCategoryRefCombination(a, b) {
|
|
1619
|
+
const map = /* @__PURE__ */ new Map();
|
|
1620
|
+
const addToMap = (refs) => {
|
|
1621
|
+
refs.forEach((ref) => {
|
|
1622
|
+
const uniqueIdentification = `${ref.type}:${ref.plugin}:${ref.slug}`;
|
|
1623
|
+
if (map.has(uniqueIdentification)) {
|
|
1624
|
+
map.set(uniqueIdentification, {
|
|
1625
|
+
...map.get(uniqueIdentification),
|
|
1626
|
+
...ref
|
|
1627
|
+
});
|
|
1628
|
+
} else {
|
|
1629
|
+
map.set(uniqueIdentification, ref);
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
if (a) {
|
|
1634
|
+
addToMap(a);
|
|
1628
1635
|
}
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1636
|
+
if (b) {
|
|
1637
|
+
addToMap(b);
|
|
1631
1638
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1639
|
+
return [...map.values()];
|
|
1640
|
+
}
|
|
1641
|
+
function mergeUpload(a, b) {
|
|
1642
|
+
if (!a && !b) {
|
|
1643
|
+
return {};
|
|
1634
1644
|
}
|
|
1635
|
-
if (a
|
|
1636
|
-
return
|
|
1645
|
+
if (a) {
|
|
1646
|
+
return b ? { upload: { ...a, ...b } } : {};
|
|
1647
|
+
} else {
|
|
1648
|
+
return { upload: b };
|
|
1637
1649
|
}
|
|
1638
|
-
return 0;
|
|
1639
1650
|
}
|
|
1640
1651
|
|
|
1641
|
-
// packages/utils/src/lib/
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
this.code = result.code;
|
|
1649
|
-
this.stderr = result.stderr;
|
|
1650
|
-
this.stdout = result.stdout;
|
|
1651
|
-
}
|
|
1652
|
+
// packages/utils/src/lib/progress.ts
|
|
1653
|
+
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1654
|
+
import { MultiProgressBars } from "multi-progress-bars";
|
|
1655
|
+
var barStyles = {
|
|
1656
|
+
active: (s) => green(s),
|
|
1657
|
+
done: (s) => gray2(s),
|
|
1658
|
+
idle: (s) => gray2(s)
|
|
1652
1659
|
};
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
process2.stderr.on("data", (data) => {
|
|
1667
|
-
stderr += String(data);
|
|
1668
|
-
});
|
|
1669
|
-
process2.on("error", (err) => {
|
|
1670
|
-
stderr += err.toString();
|
|
1671
|
-
});
|
|
1672
|
-
process2.on("close", (code3) => {
|
|
1673
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1674
|
-
if (code3 === 0 || ignoreExitCode) {
|
|
1675
|
-
onComplete?.();
|
|
1676
|
-
resolve({ code: code3, stdout, stderr, ...timings });
|
|
1677
|
-
} else {
|
|
1678
|
-
const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
|
|
1679
|
-
onError?.(errorMsg);
|
|
1680
|
-
reject(errorMsg);
|
|
1681
|
-
}
|
|
1660
|
+
var messageStyles = {
|
|
1661
|
+
active: (s) => black(s),
|
|
1662
|
+
done: (s) => bold2.green(s),
|
|
1663
|
+
idle: (s) => gray2(s)
|
|
1664
|
+
};
|
|
1665
|
+
var mpb;
|
|
1666
|
+
function getSingletonProgressBars(options) {
|
|
1667
|
+
if (!mpb) {
|
|
1668
|
+
mpb = new MultiProgressBars({
|
|
1669
|
+
progressWidth: TERMINAL_WIDTH,
|
|
1670
|
+
initMessage: "",
|
|
1671
|
+
border: true,
|
|
1672
|
+
...options
|
|
1682
1673
|
});
|
|
1674
|
+
}
|
|
1675
|
+
return mpb;
|
|
1676
|
+
}
|
|
1677
|
+
function getProgressBar(taskName) {
|
|
1678
|
+
const tasks = getSingletonProgressBars();
|
|
1679
|
+
tasks.addTask(taskName, {
|
|
1680
|
+
type: "percentage",
|
|
1681
|
+
percentage: 0,
|
|
1682
|
+
message: "",
|
|
1683
|
+
barTransformFn: barStyles.idle
|
|
1683
1684
|
});
|
|
1685
|
+
return {
|
|
1686
|
+
incrementInSteps: (numPlugins) => {
|
|
1687
|
+
tasks.incrementTask(taskName, {
|
|
1688
|
+
percentage: 1 / numPlugins,
|
|
1689
|
+
barTransformFn: barStyles.active
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
updateTitle: (title) => {
|
|
1693
|
+
tasks.updateTask(taskName, {
|
|
1694
|
+
message: title,
|
|
1695
|
+
barTransformFn: barStyles.active
|
|
1696
|
+
});
|
|
1697
|
+
},
|
|
1698
|
+
endProgress: (message = "") => {
|
|
1699
|
+
tasks.done(taskName, {
|
|
1700
|
+
message: messageStyles.done(message),
|
|
1701
|
+
barTransformFn: barStyles.done
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1684
1705
|
}
|
|
1685
1706
|
|
|
1686
|
-
// packages/utils/src/lib/
|
|
1687
|
-
function
|
|
1688
|
-
return
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
})).filter((item) => item.refs.length);
|
|
1707
|
+
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1708
|
+
function listGroupsFromAllPlugins(report) {
|
|
1709
|
+
return report.plugins.flatMap(
|
|
1710
|
+
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1711
|
+
);
|
|
1692
1712
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
function getGitRoot(git = simpleGit()) {
|
|
1698
|
-
return git.revparse("--show-toplevel");
|
|
1713
|
+
function listAuditsFromAllPlugins(report) {
|
|
1714
|
+
return report.plugins.flatMap(
|
|
1715
|
+
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1716
|
+
);
|
|
1699
1717
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1718
|
+
|
|
1719
|
+
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1720
|
+
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1721
|
+
|
|
1722
|
+
// packages/utils/src/lib/text-formats/constants.ts
|
|
1723
|
+
var NEW_LINE = "\n";
|
|
1724
|
+
var TAB = " ";
|
|
1725
|
+
var SPACE = " ";
|
|
1726
|
+
var HIERARCHY = {
|
|
1727
|
+
level_1: 1,
|
|
1728
|
+
level_2: 2,
|
|
1729
|
+
level_3: 3,
|
|
1730
|
+
level_4: 4,
|
|
1731
|
+
level_5: 5,
|
|
1732
|
+
level_6: 6
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
// packages/utils/src/lib/text-formats/html/details.ts
|
|
1736
|
+
function details(title, content, cfg = { open: false }) {
|
|
1737
|
+
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.
|
|
1738
|
+
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
1739
|
+
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
1740
|
+
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
1741
|
+
NEW_LINE}`;
|
|
1704
1742
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1743
|
+
|
|
1744
|
+
// packages/utils/src/lib/text-formats/html/font-style.ts
|
|
1745
|
+
var boldElement = "b";
|
|
1746
|
+
function bold3(text) {
|
|
1747
|
+
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1708
1748
|
}
|
|
1709
|
-
var
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
return Object.fromEntries(
|
|
1713
|
-
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1714
|
-
(entry) => {
|
|
1715
|
-
const value = entry[1];
|
|
1716
|
-
if (value == null) {
|
|
1717
|
-
return false;
|
|
1718
|
-
}
|
|
1719
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
1720
|
-
return false;
|
|
1721
|
-
}
|
|
1722
|
-
if (typeof value === "number" && value === 0) {
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
return !(typeof value === "boolean" && !value);
|
|
1726
|
-
}
|
|
1727
|
-
)
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
constructor(status) {
|
|
1731
|
-
super(
|
|
1732
|
-
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1733
|
-
${JSON.stringify(
|
|
1734
|
-
_GitStatusError.getReducedStatus(status),
|
|
1735
|
-
null,
|
|
1736
|
-
2
|
|
1737
|
-
)}`
|
|
1738
|
-
);
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1742
|
-
const status = await git.status(["-s"]);
|
|
1743
|
-
if (status.files.length > 0) {
|
|
1744
|
-
throw new GitStatusError(status);
|
|
1745
|
-
}
|
|
1749
|
+
var italicElement = "i";
|
|
1750
|
+
function italic(text) {
|
|
1751
|
+
return `<${italicElement}>${text}</${italicElement}>`;
|
|
1746
1752
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
await git.clean(["f", "d"]);
|
|
1751
|
-
ui().logger.info(`git status cleaned`);
|
|
1752
|
-
}
|
|
1753
|
-
await guardAgainstLocalChanges(git);
|
|
1754
|
-
await git.checkout(branchOrHash);
|
|
1753
|
+
var codeElement = "code";
|
|
1754
|
+
function code(text) {
|
|
1755
|
+
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1755
1756
|
}
|
|
1756
1757
|
|
|
1757
|
-
// packages/utils/src/lib/
|
|
1758
|
-
|
|
1758
|
+
// packages/utils/src/lib/text-formats/html/link.ts
|
|
1759
|
+
function link2(href, text) {
|
|
1760
|
+
return `<a href="${href}">${text || href}</a>`;
|
|
1761
|
+
}
|
|
1759
1762
|
|
|
1760
|
-
// packages/utils/src/lib/
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1766
|
-
if (semverString.includes("@")) {
|
|
1767
|
-
return semverString.split("@").at(-1) ?? "";
|
|
1768
|
-
}
|
|
1769
|
-
return semverString;
|
|
1770
|
-
}
|
|
1771
|
-
function isSemver(semverString = "") {
|
|
1772
|
-
return valid(normalizeSemver(semverString)) != null;
|
|
1773
|
-
}
|
|
1774
|
-
function sortSemvers(semverStrings) {
|
|
1775
|
-
return semverStrings.map(normalizeSemver).filter(isSemver).sort(rcompare);
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1779
|
-
async function getLatestCommit(git = simpleGit2()) {
|
|
1780
|
-
const log2 = await git.log({
|
|
1781
|
-
maxCount: 1,
|
|
1782
|
-
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1783
|
-
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1784
|
-
});
|
|
1785
|
-
return commitSchema.parse(log2.latest);
|
|
1786
|
-
}
|
|
1787
|
-
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1788
|
-
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1789
|
-
// @TODO use simple git
|
|
1790
|
-
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1791
|
-
}
|
|
1792
|
-
function validateFilter({ from, to }) {
|
|
1793
|
-
if (to && !from) {
|
|
1794
|
-
throw new Error(
|
|
1795
|
-
`filter needs the "from" option defined to accept the "to" option.
|
|
1796
|
-
`
|
|
1763
|
+
// packages/utils/src/lib/text-formats/table.ts
|
|
1764
|
+
function rowToStringArray({ rows, columns = [] }) {
|
|
1765
|
+
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1766
|
+
throw new TypeError(
|
|
1767
|
+
"Column can`t be object when rows are primitive values"
|
|
1797
1768
|
);
|
|
1798
1769
|
}
|
|
1770
|
+
return rows.map((row) => {
|
|
1771
|
+
if (Array.isArray(row)) {
|
|
1772
|
+
return row.map(String);
|
|
1773
|
+
}
|
|
1774
|
+
const objectRow = row;
|
|
1775
|
+
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1776
|
+
return Object.values(objectRow).map(
|
|
1777
|
+
(value) => value == null ? "" : String(value)
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
return columns.map(
|
|
1781
|
+
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1782
|
+
);
|
|
1783
|
+
});
|
|
1799
1784
|
}
|
|
1800
|
-
function
|
|
1801
|
-
|
|
1802
|
-
|
|
1785
|
+
function columnsToStringArray({
|
|
1786
|
+
rows,
|
|
1787
|
+
columns = []
|
|
1788
|
+
}) {
|
|
1789
|
+
const firstRow = rows.at(0);
|
|
1790
|
+
const primitiveRows = Array.isArray(firstRow);
|
|
1791
|
+
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1792
|
+
throw new Error("invalid union type. Caught by model parsing.");
|
|
1803
1793
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
const idx = allTags.indexOf(tagName ?? "");
|
|
1808
|
-
if (idx > -1) {
|
|
1809
|
-
return idx;
|
|
1794
|
+
if (columns.length === 0) {
|
|
1795
|
+
if (Array.isArray(firstRow)) {
|
|
1796
|
+
return firstRow.map((_, idx) => String(idx));
|
|
1810
1797
|
}
|
|
1811
|
-
return
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1819
|
-
const hash = tagDetails.trim();
|
|
1820
|
-
return {
|
|
1821
|
-
hash: hash.split("\n").at(-1) ?? "",
|
|
1822
|
-
message: tag
|
|
1823
|
-
};
|
|
1798
|
+
return Object.keys(firstRow);
|
|
1799
|
+
}
|
|
1800
|
+
if (typeof columns.at(0) === "string") {
|
|
1801
|
+
return columns.map(String);
|
|
1802
|
+
}
|
|
1803
|
+
const cols = columns;
|
|
1804
|
+
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1824
1805
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
if (
|
|
1830
|
-
|
|
1831
|
-
|
|
1806
|
+
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1807
|
+
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1808
|
+
if (typeof column === "string") {
|
|
1809
|
+
return column;
|
|
1810
|
+
} else if (typeof column === "object") {
|
|
1811
|
+
return column.align ?? "center";
|
|
1812
|
+
} else {
|
|
1813
|
+
return "center";
|
|
1832
1814
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1815
|
+
}
|
|
1816
|
+
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1817
|
+
const column = columns.at(targetIdx);
|
|
1818
|
+
if (column == null) {
|
|
1819
|
+
return "center";
|
|
1820
|
+
} else if (typeof column === "string") {
|
|
1821
|
+
return column;
|
|
1822
|
+
} else if (typeof column === "object") {
|
|
1823
|
+
return column.align ?? "center";
|
|
1824
|
+
} else {
|
|
1825
|
+
return "center";
|
|
1844
1826
|
}
|
|
1845
|
-
return tagsWithHashes;
|
|
1846
1827
|
}
|
|
1847
|
-
|
|
1848
|
-
const {
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
if (targetBranch) {
|
|
1852
|
-
currentBranch = await getCurrentBranchOrTag(git);
|
|
1853
|
-
await git.checkout(targetBranch);
|
|
1828
|
+
function getColumnAlignments(tableData) {
|
|
1829
|
+
const { rows, columns = [] } = tableData;
|
|
1830
|
+
if (rows.at(0) == null) {
|
|
1831
|
+
throw new Error("first row can`t be undefined.");
|
|
1854
1832
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
},
|
|
1861
|
-
from,
|
|
1862
|
-
to,
|
|
1863
|
-
maxCount
|
|
1864
|
-
});
|
|
1865
|
-
if (targetBranch) {
|
|
1866
|
-
await git.checkout(currentBranch);
|
|
1833
|
+
if (Array.isArray(rows.at(0))) {
|
|
1834
|
+
const firstPrimitiveRow = rows.at(0);
|
|
1835
|
+
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1836
|
+
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1837
|
+
);
|
|
1867
1838
|
}
|
|
1868
|
-
|
|
1839
|
+
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1840
|
+
if (columns.length > 0) {
|
|
1841
|
+
return columns.map(
|
|
1842
|
+
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1843
|
+
column.key,
|
|
1844
|
+
idx,
|
|
1845
|
+
columns
|
|
1846
|
+
)
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1869
1850
|
}
|
|
1870
1851
|
|
|
1871
|
-
// packages/utils/src/lib/
|
|
1872
|
-
function
|
|
1873
|
-
return
|
|
1874
|
-
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1875
|
-
{ fulfilled: [], rejected: [] }
|
|
1876
|
-
);
|
|
1852
|
+
// packages/utils/src/lib/text-formats/html/table.ts
|
|
1853
|
+
function wrap(elem, content) {
|
|
1854
|
+
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1877
1855
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
return configs.reduce(
|
|
1882
|
-
(acc, obj) => ({
|
|
1883
|
-
...acc,
|
|
1884
|
-
...mergeCategories(acc.categories, obj.categories),
|
|
1885
|
-
...mergePlugins(acc.plugins, obj.plugins),
|
|
1886
|
-
...mergePersist(acc.persist, obj.persist),
|
|
1887
|
-
...mergeUpload(acc.upload, obj.upload)
|
|
1888
|
-
}),
|
|
1889
|
-
config
|
|
1890
|
-
);
|
|
1856
|
+
function wrapRow(content) {
|
|
1857
|
+
const elem = "tr";
|
|
1858
|
+
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1891
1859
|
}
|
|
1892
|
-
function
|
|
1893
|
-
if (
|
|
1894
|
-
|
|
1860
|
+
function table(tableData) {
|
|
1861
|
+
if (tableData.rows.length === 0) {
|
|
1862
|
+
throw new Error("Data can't be empty");
|
|
1895
1863
|
}
|
|
1896
|
-
const
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1864
|
+
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1865
|
+
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1866
|
+
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1867
|
+
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1868
|
+
return wrapRow(columns);
|
|
1869
|
+
}).join("");
|
|
1870
|
+
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// packages/utils/src/lib/text-formats/index.ts
|
|
1874
|
+
var html = {
|
|
1875
|
+
bold: bold3,
|
|
1876
|
+
italic,
|
|
1877
|
+
code,
|
|
1878
|
+
link: link2,
|
|
1879
|
+
details,
|
|
1880
|
+
table
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
// packages/utils/src/lib/reports/formatting.ts
|
|
1884
|
+
import { MarkdownDocument, md as md2 } from "build-md";
|
|
1885
|
+
function tableSection(tableData, options) {
|
|
1886
|
+
if (tableData.rows.length === 0) {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
const { level = HIERARCHY.level_4 } = options ?? {};
|
|
1890
|
+
const columns = columnsToStringArray(tableData);
|
|
1891
|
+
const alignments = getColumnAlignments(tableData);
|
|
1892
|
+
const rows = rowToStringArray(tableData);
|
|
1893
|
+
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1894
|
+
columns.map((heading, i) => {
|
|
1895
|
+
const alignment = alignments[i];
|
|
1896
|
+
if (alignment) {
|
|
1897
|
+
return { heading, alignment };
|
|
1913
1898
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
}
|
|
1919
|
-
if (b) {
|
|
1920
|
-
addToMap(b);
|
|
1921
|
-
}
|
|
1922
|
-
return { categories: [...mergedMap.values()] };
|
|
1899
|
+
return heading;
|
|
1900
|
+
}),
|
|
1901
|
+
rows
|
|
1902
|
+
);
|
|
1923
1903
|
}
|
|
1924
|
-
function
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1904
|
+
function metaDescription(audit) {
|
|
1905
|
+
const docsUrl = audit.docsUrl;
|
|
1906
|
+
const description = audit.description?.trim();
|
|
1907
|
+
if (docsUrl) {
|
|
1908
|
+
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1909
|
+
if (!description) {
|
|
1910
|
+
return docsLink;
|
|
1911
|
+
}
|
|
1912
|
+
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1913
|
+
|
|
1914
|
+
` : `${description} `;
|
|
1915
|
+
return md2`${parsedDescription}${docsLink}`;
|
|
1936
1916
|
}
|
|
1937
|
-
if (
|
|
1938
|
-
|
|
1917
|
+
if (description && description.trim().length > 0) {
|
|
1918
|
+
return description;
|
|
1939
1919
|
}
|
|
1940
|
-
return
|
|
1920
|
+
return "";
|
|
1941
1921
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1922
|
+
|
|
1923
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1924
|
+
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1925
|
+
|
|
1926
|
+
// packages/utils/src/lib/reports/sorting.ts
|
|
1927
|
+
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1928
|
+
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1929
|
+
if (!auditPlugin) {
|
|
1930
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1945
1931
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1932
|
+
const audit = auditPlugin.audits.find(
|
|
1933
|
+
({ slug: auditSlug }) => auditSlug === slug
|
|
1934
|
+
);
|
|
1935
|
+
if (!audit) {
|
|
1936
|
+
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1950
1937
|
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
refs.forEach((ref) => {
|
|
1956
|
-
const uniqueIdentification = `${ref.type}:${ref.plugin}:${ref.slug}`;
|
|
1957
|
-
if (map.has(uniqueIdentification)) {
|
|
1958
|
-
map.set(uniqueIdentification, {
|
|
1959
|
-
...map.get(uniqueIdentification),
|
|
1960
|
-
...ref
|
|
1961
|
-
});
|
|
1962
|
-
} else {
|
|
1963
|
-
map.set(uniqueIdentification, ref);
|
|
1964
|
-
}
|
|
1965
|
-
});
|
|
1938
|
+
return {
|
|
1939
|
+
...audit,
|
|
1940
|
+
weight,
|
|
1941
|
+
plugin
|
|
1966
1942
|
};
|
|
1967
|
-
if (a) {
|
|
1968
|
-
addToMap(a);
|
|
1969
|
-
}
|
|
1970
|
-
if (b) {
|
|
1971
|
-
addToMap(b);
|
|
1972
|
-
}
|
|
1973
|
-
return [...map.values()];
|
|
1974
1943
|
}
|
|
1975
|
-
function
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1944
|
+
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1945
|
+
return group.refs.map(
|
|
1946
|
+
(ref) => getSortableAuditByRef(
|
|
1947
|
+
{
|
|
1948
|
+
plugin,
|
|
1949
|
+
slug: ref.slug,
|
|
1950
|
+
weight: ref.weight,
|
|
1951
|
+
type: "audit"
|
|
1952
|
+
},
|
|
1953
|
+
plugins
|
|
1954
|
+
)
|
|
1955
|
+
).sort(compareCategoryAuditsAndGroups);
|
|
1984
1956
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
var barStyles = {
|
|
1990
|
-
active: (s) => chalk3.green(s),
|
|
1991
|
-
done: (s) => chalk3.gray(s),
|
|
1992
|
-
idle: (s) => chalk3.gray(s)
|
|
1993
|
-
};
|
|
1994
|
-
var messageStyles = {
|
|
1995
|
-
active: (s) => chalk3.black(s),
|
|
1996
|
-
done: (s) => chalk3.green(chalk3.bold(s)),
|
|
1997
|
-
idle: (s) => chalk3.gray(s)
|
|
1998
|
-
};
|
|
1999
|
-
var mpb;
|
|
2000
|
-
function getSingletonProgressBars(options) {
|
|
2001
|
-
if (!mpb) {
|
|
2002
|
-
mpb = new MultiProgressBars({
|
|
2003
|
-
progressWidth: TERMINAL_WIDTH,
|
|
2004
|
-
initMessage: "",
|
|
2005
|
-
border: true,
|
|
2006
|
-
...options
|
|
2007
|
-
});
|
|
1957
|
+
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1958
|
+
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1959
|
+
if (!groupPlugin) {
|
|
1960
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
2008
1961
|
}
|
|
2009
|
-
|
|
1962
|
+
const group = groupPlugin.groups?.find(
|
|
1963
|
+
({ slug: groupSlug }) => groupSlug === slug
|
|
1964
|
+
);
|
|
1965
|
+
if (!group) {
|
|
1966
|
+
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1967
|
+
}
|
|
1968
|
+
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1969
|
+
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1970
|
+
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1971
|
+
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1972
|
+
return aIndex - bIndex;
|
|
1973
|
+
});
|
|
1974
|
+
return {
|
|
1975
|
+
...group,
|
|
1976
|
+
refs: sortedAuditRefs,
|
|
1977
|
+
plugin,
|
|
1978
|
+
weight
|
|
1979
|
+
};
|
|
2010
1980
|
}
|
|
2011
|
-
function
|
|
2012
|
-
const
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1981
|
+
function sortReport(report) {
|
|
1982
|
+
const { categories, plugins } = report;
|
|
1983
|
+
const sortedCategories = categories.map((category) => {
|
|
1984
|
+
const { audits, groups } = category.refs.reduce(
|
|
1985
|
+
(acc, ref) => ({
|
|
1986
|
+
...acc,
|
|
1987
|
+
...ref.type === "group" ? {
|
|
1988
|
+
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1989
|
+
} : {
|
|
1990
|
+
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1991
|
+
}
|
|
1992
|
+
}),
|
|
1993
|
+
{ groups: [], audits: [] }
|
|
1994
|
+
);
|
|
1995
|
+
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
1996
|
+
compareCategoryAuditsAndGroups
|
|
1997
|
+
);
|
|
1998
|
+
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
1999
|
+
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2000
|
+
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2001
|
+
);
|
|
2002
|
+
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2003
|
+
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2004
|
+
);
|
|
2005
|
+
return aIndex - bIndex;
|
|
2006
|
+
});
|
|
2007
|
+
return { ...category, refs: sortedRefs };
|
|
2018
2008
|
});
|
|
2019
2009
|
return {
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
barTransformFn: barStyles.active
|
|
2024
|
-
});
|
|
2025
|
-
},
|
|
2026
|
-
updateTitle: (title) => {
|
|
2027
|
-
tasks.updateTask(taskName, {
|
|
2028
|
-
message: title,
|
|
2029
|
-
barTransformFn: barStyles.active
|
|
2030
|
-
});
|
|
2031
|
-
},
|
|
2032
|
-
endProgress: (message = "") => {
|
|
2033
|
-
tasks.done(taskName, {
|
|
2034
|
-
message: messageStyles.done(message),
|
|
2035
|
-
barTransformFn: barStyles.done
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2010
|
+
...report,
|
|
2011
|
+
categories: sortedCategories,
|
|
2012
|
+
plugins: sortPlugins(plugins)
|
|
2038
2013
|
};
|
|
2039
2014
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
// packages/utils/src/lib/reports/formatting.ts
|
|
2054
|
-
var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
|
|
2055
|
-
function tableSection(tableData, options) {
|
|
2056
|
-
if (tableData.rows.length === 0) {
|
|
2057
|
-
return "";
|
|
2058
|
-
}
|
|
2059
|
-
const { level = 4 } = options ?? {};
|
|
2060
|
-
const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
|
|
2061
|
-
return lines2(
|
|
2062
|
-
tableData.title && render(tableData.title, level),
|
|
2063
|
-
table3(tableData)
|
|
2064
|
-
);
|
|
2065
|
-
}
|
|
2066
|
-
function metaDescription({
|
|
2067
|
-
docsUrl,
|
|
2068
|
-
description
|
|
2069
|
-
}) {
|
|
2070
|
-
if (docsUrl) {
|
|
2071
|
-
const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
|
|
2072
|
-
if (!description) {
|
|
2073
|
-
return section2(docsLink);
|
|
2074
|
-
}
|
|
2075
|
-
const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
|
|
2076
|
-
return section2(`${parsedDescription}${docsLink}`);
|
|
2077
|
-
}
|
|
2078
|
-
if (description && description.trim().length > 0) {
|
|
2079
|
-
return section2(description);
|
|
2080
|
-
}
|
|
2081
|
-
return "";
|
|
2015
|
+
function sortPlugins(plugins) {
|
|
2016
|
+
return plugins.map((plugin) => ({
|
|
2017
|
+
...plugin,
|
|
2018
|
+
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2019
|
+
(audit) => audit.details?.issues ? {
|
|
2020
|
+
...audit,
|
|
2021
|
+
details: {
|
|
2022
|
+
...audit.details,
|
|
2023
|
+
issues: [...audit.details.issues].sort(compareIssues)
|
|
2024
|
+
}
|
|
2025
|
+
} : audit
|
|
2026
|
+
)
|
|
2027
|
+
}));
|
|
2082
2028
|
}
|
|
2083
2029
|
|
|
2084
2030
|
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
2085
|
-
var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
|
|
2086
2031
|
function categoriesOverviewSection(report) {
|
|
2087
2032
|
const { categories, plugins } = report;
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2033
|
+
return new MarkdownDocument2().table(
|
|
2034
|
+
[
|
|
2035
|
+
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
2036
|
+
{ heading: "\u2B50 Score", alignment: "center" },
|
|
2037
|
+
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
2038
|
+
],
|
|
2039
|
+
categories.map(({ title, refs, score, isBinary }) => [
|
|
2040
|
+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
2041
|
+
// The heading "ID" is inferred from the heading text in Markdown.
|
|
2042
|
+
md3.link(`#${slugify(title)}`, title),
|
|
2043
|
+
md3`${scoreMarker(score)} ${md3.bold(
|
|
2044
|
+
formatReportScore(score)
|
|
2045
|
+
)}${binaryIconSuffix(score, isBinary)}`,
|
|
2046
|
+
countCategoryAudits(refs, plugins).toString()
|
|
2047
|
+
])
|
|
2048
|
+
);
|
|
2103
2049
|
}
|
|
2104
2050
|
function categoriesDetailsSection(report) {
|
|
2105
2051
|
const { categories, plugins } = report;
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
category.score
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
...categoryMDItems
|
|
2133
|
-
);
|
|
2134
|
-
});
|
|
2135
|
-
return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
|
|
2052
|
+
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
2053
|
+
categories,
|
|
2054
|
+
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
2055
|
+
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
2056
|
+
formatReportScore(category.score)
|
|
2057
|
+
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
2058
|
+
).list(
|
|
2059
|
+
category.refs.map((ref) => {
|
|
2060
|
+
if (ref.type === "group") {
|
|
2061
|
+
const group = getSortableGroupByRef(ref, plugins);
|
|
2062
|
+
const groupAudits = group.refs.map(
|
|
2063
|
+
(groupRef) => getSortableAuditByRef(
|
|
2064
|
+
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
2065
|
+
plugins
|
|
2066
|
+
)
|
|
2067
|
+
);
|
|
2068
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
2069
|
+
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
2070
|
+
} else {
|
|
2071
|
+
const audit = getSortableAuditByRef(ref, plugins);
|
|
2072
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
2073
|
+
return categoryRef(audit, pluginTitle);
|
|
2074
|
+
}
|
|
2075
|
+
})
|
|
2076
|
+
)
|
|
2077
|
+
);
|
|
2136
2078
|
}
|
|
2137
2079
|
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
2138
|
-
const auditTitleAsLink =
|
|
2080
|
+
const auditTitleAsLink = md3.link(
|
|
2139
2081
|
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
2140
2082
|
title
|
|
2141
2083
|
);
|
|
2142
2084
|
const marker = scoreMarker(score, "square");
|
|
2143
|
-
return
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
)}`
|
|
2147
|
-
);
|
|
2085
|
+
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
2086
|
+
pluginTitle
|
|
2087
|
+
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
2148
2088
|
}
|
|
2149
2089
|
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
2150
|
-
const groupTitle =
|
|
2151
|
-
|
|
2152
|
-
)
|
|
2153
|
-
const
|
|
2154
|
-
(
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
);
|
|
2167
|
-
}
|
|
2090
|
+
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
2091
|
+
pluginTitle
|
|
2092
|
+
)})`;
|
|
2093
|
+
const auditsList = md3.list(
|
|
2094
|
+
groupAudits.map(
|
|
2095
|
+
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
2096
|
+
const auditTitleLink = md3.link(
|
|
2097
|
+
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
2098
|
+
auditTitle
|
|
2099
|
+
);
|
|
2100
|
+
const marker = scoreMarker(auditScore, "square");
|
|
2101
|
+
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
2102
|
+
String(displayValue ?? value)
|
|
2103
|
+
)}`;
|
|
2104
|
+
}
|
|
2105
|
+
)
|
|
2168
2106
|
);
|
|
2169
|
-
return
|
|
2107
|
+
return md3`${groupTitle}${auditsList}`;
|
|
2108
|
+
}
|
|
2109
|
+
function binaryIconSuffix(score, isBinary) {
|
|
2110
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
2170
2111
|
}
|
|
2171
2112
|
|
|
2172
2113
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
2173
|
-
var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
|
|
2174
|
-
var { bold: boldHtml, details: details2 } = html;
|
|
2175
2114
|
function auditDetailsAuditValue({
|
|
2176
2115
|
score,
|
|
2177
2116
|
value,
|
|
2178
2117
|
displayValue
|
|
2179
2118
|
}) {
|
|
2180
|
-
return `${scoreMarker(score, "square")} ${
|
|
2119
|
+
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
2181
2120
|
String(displayValue ?? value)
|
|
2182
2121
|
)} (score: ${formatReportScore(score)})`;
|
|
2183
2122
|
}
|
|
2184
2123
|
function generateMdReport(report) {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
`${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
|
|
2193
|
-
);
|
|
2124
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
|
|
2125
|
+
report.categories.length > 0,
|
|
2126
|
+
(doc) => doc.$concat(
|
|
2127
|
+
categoriesOverviewSection(report),
|
|
2128
|
+
categoriesDetailsSection(report)
|
|
2129
|
+
)
|
|
2130
|
+
).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
2194
2131
|
}
|
|
2195
2132
|
function auditDetailsIssues(issues = []) {
|
|
2196
2133
|
if (issues.length === 0) {
|
|
2197
|
-
return
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
return { severity, message, file, line: "" };
|
|
2211
|
-
}
|
|
2212
|
-
const { startLine, endLine } = sourceVal.position;
|
|
2213
|
-
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
2214
|
-
return { severity, message, file, line };
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
2137
|
+
[
|
|
2138
|
+
{ heading: "Severity", alignment: "center" },
|
|
2139
|
+
{ heading: "Message", alignment: "left" },
|
|
2140
|
+
{ heading: "Source file", alignment: "left" },
|
|
2141
|
+
{ heading: "Line(s)", alignment: "center" }
|
|
2142
|
+
],
|
|
2143
|
+
issues.map(({ severity: level, message, source }) => {
|
|
2144
|
+
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
2145
|
+
if (!source) {
|
|
2146
|
+
return [severity, message];
|
|
2215
2147
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2148
|
+
const file = md4.code(source.file);
|
|
2149
|
+
if (!source.position) {
|
|
2150
|
+
return [severity, message, file];
|
|
2151
|
+
}
|
|
2152
|
+
const { startLine, endLine } = source.position;
|
|
2153
|
+
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
2154
|
+
return [severity, message, file, line];
|
|
2155
|
+
})
|
|
2156
|
+
);
|
|
2219
2157
|
}
|
|
2220
2158
|
function auditDetails(audit) {
|
|
2221
|
-
const { table:
|
|
2159
|
+
const { table: table2, issues = [] } = audit.details ?? {};
|
|
2222
2160
|
const detailsValue = auditDetailsAuditValue(audit);
|
|
2223
|
-
if (issues.length === 0 &&
|
|
2224
|
-
return
|
|
2161
|
+
if (issues.length === 0 && !table2?.rows.length) {
|
|
2162
|
+
return new MarkdownDocument3().paragraph(detailsValue);
|
|
2225
2163
|
}
|
|
2226
|
-
const tableSectionContent =
|
|
2227
|
-
const issuesSectionContent = issues.length > 0
|
|
2228
|
-
return
|
|
2164
|
+
const tableSectionContent = table2 && tableSection(table2);
|
|
2165
|
+
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
|
|
2166
|
+
return new MarkdownDocument3().details(
|
|
2229
2167
|
detailsValue,
|
|
2230
|
-
|
|
2168
|
+
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
2231
2169
|
);
|
|
2232
2170
|
}
|
|
2233
2171
|
function auditsSection({
|
|
2234
2172
|
plugins
|
|
2235
2173
|
}) {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2174
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
2175
|
+
plugins.flatMap(
|
|
2176
|
+
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
2177
|
+
),
|
|
2178
|
+
(doc, { plugin, ...audit }) => {
|
|
2179
|
+
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
2242
2180
|
const detailsContent = auditDetails(audit);
|
|
2243
2181
|
const descriptionContent = metaDescription(audit);
|
|
2244
|
-
return
|
|
2245
|
-
}
|
|
2182
|
+
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
2183
|
+
}
|
|
2246
2184
|
);
|
|
2247
|
-
return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
|
|
2248
2185
|
}
|
|
2249
2186
|
function aboutSection(report) {
|
|
2250
2187
|
const { date, plugins } = report;
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
{
|
|
2268
|
-
key: "plugin",
|
|
2269
|
-
align: "left"
|
|
2270
|
-
},
|
|
2271
|
-
{
|
|
2272
|
-
key: "audits"
|
|
2273
|
-
},
|
|
2274
|
-
{
|
|
2275
|
-
key: "version"
|
|
2276
|
-
},
|
|
2277
|
-
{
|
|
2278
|
-
key: "duration"
|
|
2279
|
-
}
|
|
2188
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
2189
|
+
md4`Report was created by ${md4.link(
|
|
2190
|
+
README_LINK,
|
|
2191
|
+
"Code PushUp"
|
|
2192
|
+
)} on ${formatDate(new Date(date))}.`
|
|
2193
|
+
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
2194
|
+
}
|
|
2195
|
+
function pluginMetaTable({
|
|
2196
|
+
plugins
|
|
2197
|
+
}) {
|
|
2198
|
+
return [
|
|
2199
|
+
[
|
|
2200
|
+
{ heading: "Plugin", alignment: "left" },
|
|
2201
|
+
{ heading: "Audits", alignment: "center" },
|
|
2202
|
+
{ heading: "Version", alignment: "center" },
|
|
2203
|
+
{ heading: "Duration", alignment: "right" }
|
|
2280
2204
|
],
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
plugin: pluginTitle,
|
|
2289
|
-
audits: audits.length.toString(),
|
|
2290
|
-
version: codeMd(pluginVersion || ""),
|
|
2291
|
-
duration: formatDuration(pluginDuration)
|
|
2292
|
-
})
|
|
2293
|
-
)
|
|
2294
|
-
};
|
|
2205
|
+
plugins.map(({ title, audits, version = "", duration }) => [
|
|
2206
|
+
title,
|
|
2207
|
+
audits.length.toString(),
|
|
2208
|
+
version && md4.code(version),
|
|
2209
|
+
formatDuration(duration)
|
|
2210
|
+
])
|
|
2211
|
+
];
|
|
2295
2212
|
}
|
|
2296
|
-
function
|
|
2213
|
+
function reportMetaTable({
|
|
2297
2214
|
commit,
|
|
2298
2215
|
version,
|
|
2299
2216
|
duration,
|
|
2300
2217
|
plugins,
|
|
2301
2218
|
categories
|
|
2302
2219
|
}) {
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
{
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
},
|
|
2310
|
-
{
|
|
2311
|
-
key: "version"
|
|
2312
|
-
},
|
|
2313
|
-
{
|
|
2314
|
-
key: "duration"
|
|
2315
|
-
},
|
|
2316
|
-
{
|
|
2317
|
-
key: "plugins"
|
|
2318
|
-
},
|
|
2319
|
-
{
|
|
2320
|
-
key: "categories"
|
|
2321
|
-
},
|
|
2322
|
-
{
|
|
2323
|
-
key: "audits"
|
|
2324
|
-
}
|
|
2220
|
+
return [
|
|
2221
|
+
[
|
|
2222
|
+
{ heading: "Commit", alignment: "left" },
|
|
2223
|
+
{ heading: "Version", alignment: "center" },
|
|
2224
|
+
{ heading: "Duration", alignment: "right" },
|
|
2225
|
+
{ heading: "Plugins", alignment: "center" },
|
|
2226
|
+
{ heading: "Categories", alignment: "center" },
|
|
2227
|
+
{ heading: "Audits", alignment: "center" }
|
|
2325
2228
|
],
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
commit:
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
plugins
|
|
2332
|
-
categories
|
|
2333
|
-
|
|
2334
|
-
|
|
2229
|
+
[
|
|
2230
|
+
[
|
|
2231
|
+
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
2232
|
+
md4.code(version),
|
|
2233
|
+
formatDuration(duration),
|
|
2234
|
+
plugins.length.toString(),
|
|
2235
|
+
categories.length.toString(),
|
|
2236
|
+
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
2237
|
+
]
|
|
2335
2238
|
]
|
|
2336
|
-
|
|
2239
|
+
];
|
|
2337
2240
|
}
|
|
2338
2241
|
|
|
2339
2242
|
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
link: link7,
|
|
2345
|
-
bold: boldMd3,
|
|
2346
|
-
italic: italicMd,
|
|
2347
|
-
table: table4,
|
|
2348
|
-
section: section5
|
|
2349
|
-
} = md;
|
|
2350
|
-
var { details: details3 } = html;
|
|
2243
|
+
import {
|
|
2244
|
+
MarkdownDocument as MarkdownDocument4,
|
|
2245
|
+
md as md5
|
|
2246
|
+
} from "build-md";
|
|
2351
2247
|
var MAX_ROWS = 100;
|
|
2352
|
-
function generateMdReportsDiff(diff) {
|
|
2353
|
-
return
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
function
|
|
2248
|
+
function generateMdReportsDiff(diff, portalUrl) {
|
|
2249
|
+
return new MarkdownDocument4().$concat(
|
|
2250
|
+
createDiffHeaderSection(diff, portalUrl),
|
|
2251
|
+
createDiffCategoriesSection(diff),
|
|
2252
|
+
createDiffGroupsSection(diff),
|
|
2253
|
+
createDiffAuditsSection(diff)
|
|
2254
|
+
).toString();
|
|
2255
|
+
}
|
|
2256
|
+
function createDiffHeaderSection(diff, portalUrl) {
|
|
2361
2257
|
const outcomeTexts = {
|
|
2362
|
-
positive:
|
|
2363
|
-
negative:
|
|
2364
|
-
mixed:
|
|
2258
|
+
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
2259
|
+
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
2260
|
+
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
2365
2261
|
"improvements and regressions"
|
|
2366
2262
|
)}`,
|
|
2367
|
-
unchanged:
|
|
2263
|
+
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
2368
2264
|
};
|
|
2369
2265
|
const outcome = mergeDiffOutcomes(
|
|
2370
2266
|
changesToDiffOutcomes([
|
|
@@ -2374,143 +2270,127 @@ function formatDiffHeaderSection(diff) {
|
|
|
2374
2270
|
])
|
|
2375
2271
|
);
|
|
2376
2272
|
const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2377
|
-
return
|
|
2378
|
-
|
|
2379
|
-
|
|
2273
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
|
|
2274
|
+
diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
|
|
2275
|
+
).paragraph(
|
|
2276
|
+
portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
|
|
2380
2277
|
);
|
|
2381
2278
|
}
|
|
2382
|
-
function
|
|
2279
|
+
function createDiffCategoriesSection(diff) {
|
|
2383
2280
|
const { changed, unchanged, added } = diff.categories;
|
|
2384
2281
|
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2385
2282
|
const hasChanges = unchanged.length < categoriesCount;
|
|
2386
2283
|
if (categoriesCount === 0) {
|
|
2387
|
-
return
|
|
2284
|
+
return null;
|
|
2388
2285
|
}
|
|
2389
2286
|
const columns = [
|
|
2390
|
-
{
|
|
2391
|
-
{
|
|
2392
|
-
|
|
2393
|
-
|
|
2287
|
+
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
2288
|
+
{
|
|
2289
|
+
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
2290
|
+
alignment: "center"
|
|
2291
|
+
},
|
|
2292
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2293
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2394
2294
|
];
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
(row) => hasChanges ? row : { category: row.category, before: row.before }
|
|
2422
|
-
)
|
|
2423
|
-
}),
|
|
2424
|
-
added.length > 0 && section5(italicMd("(\\*) New category."))
|
|
2425
|
-
);
|
|
2295
|
+
const rows = [
|
|
2296
|
+
...sortChanges(changed).map((category) => [
|
|
2297
|
+
formatTitle(category),
|
|
2298
|
+
formatScoreWithColor(category.scores.before, {
|
|
2299
|
+
skipBold: true
|
|
2300
|
+
}),
|
|
2301
|
+
formatScoreWithColor(category.scores.after),
|
|
2302
|
+
formatScoreChange(category.scores.diff)
|
|
2303
|
+
]),
|
|
2304
|
+
...added.map((category) => [
|
|
2305
|
+
formatTitle(category),
|
|
2306
|
+
md5.italic("n/a (\\*)"),
|
|
2307
|
+
formatScoreWithColor(category.score),
|
|
2308
|
+
md5.italic("n/a (\\*)")
|
|
2309
|
+
]),
|
|
2310
|
+
...unchanged.map((category) => [
|
|
2311
|
+
formatTitle(category),
|
|
2312
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
2313
|
+
formatScoreWithColor(category.score),
|
|
2314
|
+
"\u2013"
|
|
2315
|
+
])
|
|
2316
|
+
];
|
|
2317
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
|
|
2318
|
+
hasChanges ? columns : columns.slice(0, 2),
|
|
2319
|
+
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
2320
|
+
).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
|
|
2426
2321
|
}
|
|
2427
|
-
function
|
|
2322
|
+
function createDiffGroupsSection(diff) {
|
|
2428
2323
|
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2429
|
-
return
|
|
2430
|
-
}
|
|
2431
|
-
return
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
{
|
|
2437
|
-
{
|
|
2438
|
-
{
|
|
2439
|
-
{
|
|
2324
|
+
return null;
|
|
2325
|
+
}
|
|
2326
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
2327
|
+
createGroupsOrAuditsDetails(
|
|
2328
|
+
"group",
|
|
2329
|
+
diff.groups,
|
|
2330
|
+
[
|
|
2331
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2332
|
+
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
2333
|
+
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
2334
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2335
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2440
2336
|
],
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2337
|
+
sortChanges(diff.groups.changed).map((group) => [
|
|
2338
|
+
formatTitle(group.plugin),
|
|
2339
|
+
formatTitle(group),
|
|
2340
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2341
|
+
formatScoreWithColor(group.scores.after),
|
|
2342
|
+
formatScoreChange(group.scores.diff)
|
|
2343
|
+
])
|
|
2344
|
+
)
|
|
2449
2345
|
);
|
|
2450
2346
|
}
|
|
2451
|
-
function
|
|
2452
|
-
return
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
{
|
|
2458
|
-
{
|
|
2459
|
-
{
|
|
2460
|
-
{
|
|
2347
|
+
function createDiffAuditsSection(diff) {
|
|
2348
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
2349
|
+
createGroupsOrAuditsDetails(
|
|
2350
|
+
"audit",
|
|
2351
|
+
diff.audits,
|
|
2352
|
+
[
|
|
2353
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2354
|
+
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
2355
|
+
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
2356
|
+
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
2357
|
+
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2461
2358
|
],
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2359
|
+
sortChanges(diff.audits.changed).map((audit) => [
|
|
2360
|
+
formatTitle(audit.plugin),
|
|
2361
|
+
formatTitle(audit),
|
|
2362
|
+
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2363
|
+
md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
|
|
2466
2364
|
audit.displayValues.after || audit.values.after.toString()
|
|
2467
2365
|
)}`,
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
})
|
|
2366
|
+
formatValueChange(audit)
|
|
2367
|
+
])
|
|
2368
|
+
)
|
|
2472
2369
|
);
|
|
2473
2370
|
}
|
|
2474
|
-
function
|
|
2475
|
-
|
|
2371
|
+
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
2372
|
+
if (changed.length === 0) {
|
|
2373
|
+
return new MarkdownDocument4().paragraph(
|
|
2374
|
+
summarizeUnchanged(token, { changed, unchanged })
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return new MarkdownDocument4().details(
|
|
2476
2378
|
summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
...tableData,
|
|
2480
|
-
rows: tableData.rows.slice(0, MAX_ROWS)
|
|
2481
|
-
// use never to avoid typing problem
|
|
2482
|
-
}),
|
|
2483
|
-
changed.length > MAX_ROWS && italicMd(
|
|
2379
|
+
md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
|
|
2380
|
+
md5.italic(
|
|
2484
2381
|
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
2485
2382
|
token
|
|
2486
2383
|
)} are listed above for brevity.`
|
|
2487
|
-
)
|
|
2488
|
-
|
|
2489
|
-
)
|
|
2384
|
+
)
|
|
2385
|
+
) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
|
|
2490
2386
|
);
|
|
2491
2387
|
}
|
|
2492
|
-
function formatScoreChange(diff) {
|
|
2493
|
-
const marker = getDiffMarker(diff);
|
|
2494
|
-
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
2495
|
-
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
2496
|
-
}
|
|
2497
|
-
function formatValueChange({
|
|
2498
|
-
values,
|
|
2499
|
-
scores
|
|
2500
|
-
}) {
|
|
2501
|
-
const marker = getDiffMarker(values.diff);
|
|
2502
|
-
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
2503
|
-
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
2504
|
-
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
2505
|
-
}
|
|
2506
2388
|
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2507
|
-
return
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
].join(" ")
|
|
2513
|
-
);
|
|
2389
|
+
return [
|
|
2390
|
+
changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
|
|
2391
|
+
unchanged.length === 1 ? "is" : "are",
|
|
2392
|
+
"unchanged."
|
|
2393
|
+
].join(" ");
|
|
2514
2394
|
}
|
|
2515
2395
|
function summarizeDiffOutcomes(outcomes, token) {
|
|
2516
2396
|
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
@@ -2535,7 +2415,7 @@ function formatTitle({
|
|
|
2535
2415
|
docsUrl
|
|
2536
2416
|
}) {
|
|
2537
2417
|
if (docsUrl) {
|
|
2538
|
-
return
|
|
2418
|
+
return md5.link(docsUrl, title);
|
|
2539
2419
|
}
|
|
2540
2420
|
return title;
|
|
2541
2421
|
}
|
|
@@ -2579,8 +2459,22 @@ function countDiffOutcomes(outcomes) {
|
|
|
2579
2459
|
};
|
|
2580
2460
|
}
|
|
2581
2461
|
|
|
2462
|
+
// packages/utils/src/lib/reports/load-report.ts
|
|
2463
|
+
import { join as join3 } from "node:path";
|
|
2464
|
+
async function loadReport(options) {
|
|
2465
|
+
const { outputDir, filename, format } = options;
|
|
2466
|
+
await ensureDirectoryExists(outputDir);
|
|
2467
|
+
const filePath = join3(outputDir, `${filename}.${format}`);
|
|
2468
|
+
if (format === "json") {
|
|
2469
|
+
const content = await readJsonFile(filePath);
|
|
2470
|
+
return reportSchema.parse(content);
|
|
2471
|
+
}
|
|
2472
|
+
const text = await readTextFile(filePath);
|
|
2473
|
+
return text;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2582
2476
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2583
|
-
import
|
|
2477
|
+
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2584
2478
|
function log(msg = "") {
|
|
2585
2479
|
ui().logger.log(msg);
|
|
2586
2480
|
}
|
|
@@ -2597,14 +2491,14 @@ function logStdoutSummary(report) {
|
|
|
2597
2491
|
}
|
|
2598
2492
|
function reportToHeaderSection(report) {
|
|
2599
2493
|
const { packageName, version } = report;
|
|
2600
|
-
return `${
|
|
2494
|
+
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version}`;
|
|
2601
2495
|
}
|
|
2602
2496
|
function logPlugins(report) {
|
|
2603
2497
|
const { plugins } = report;
|
|
2604
2498
|
plugins.forEach((plugin) => {
|
|
2605
2499
|
const { title, audits } = plugin;
|
|
2606
2500
|
log();
|
|
2607
|
-
log(
|
|
2501
|
+
log(bold4.magentaBright(`${title} audits`));
|
|
2608
2502
|
log();
|
|
2609
2503
|
audits.forEach((audit) => {
|
|
2610
2504
|
ui().row([
|
|
@@ -2619,7 +2513,7 @@ function logPlugins(report) {
|
|
|
2619
2513
|
padding: [0, 3, 0, 0]
|
|
2620
2514
|
},
|
|
2621
2515
|
{
|
|
2622
|
-
text:
|
|
2516
|
+
text: cyanBright(audit.displayValue || `${audit.value}`),
|
|
2623
2517
|
width: 10,
|
|
2624
2518
|
padding: [0, 0, 0, 0]
|
|
2625
2519
|
}
|
|
@@ -2630,42 +2524,38 @@ function logPlugins(report) {
|
|
|
2630
2524
|
}
|
|
2631
2525
|
function logCategories({ categories, plugins }) {
|
|
2632
2526
|
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2633
|
-
const rows = categories.map(({ title, score, refs }) => [
|
|
2527
|
+
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2634
2528
|
title,
|
|
2635
|
-
applyScoreColor({ score })
|
|
2529
|
+
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2636
2530
|
countCategoryAudits(refs, plugins)
|
|
2637
2531
|
]);
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
content:
|
|
2532
|
+
const table2 = ui().table();
|
|
2533
|
+
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2534
|
+
table2.head(
|
|
2535
|
+
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2536
|
+
content: cyan(heading),
|
|
2643
2537
|
hAlign: hAlign(idx)
|
|
2644
2538
|
}))
|
|
2645
2539
|
);
|
|
2646
2540
|
rows.forEach(
|
|
2647
|
-
(row) =>
|
|
2541
|
+
(row) => table2.row(
|
|
2648
2542
|
row.map((content, idx) => ({
|
|
2649
2543
|
content: content.toString(),
|
|
2650
2544
|
hAlign: hAlign(idx)
|
|
2651
2545
|
}))
|
|
2652
2546
|
)
|
|
2653
2547
|
);
|
|
2654
|
-
log(
|
|
2548
|
+
log(bold4.magentaBright("Categories"));
|
|
2655
2549
|
log();
|
|
2656
|
-
|
|
2550
|
+
table2.render();
|
|
2657
2551
|
log();
|
|
2658
2552
|
}
|
|
2659
|
-
function
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
}
|
|
2665
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
2666
|
-
return style.yellow(formattedScore);
|
|
2667
|
-
}
|
|
2668
|
-
return style.red(formattedScore);
|
|
2553
|
+
function binaryIconPrefix(score, isBinary) {
|
|
2554
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2555
|
+
passIcon: bold4(green2("\u2713")),
|
|
2556
|
+
failIcon: bold4(red("\u2717")),
|
|
2557
|
+
postfix: " "
|
|
2558
|
+
});
|
|
2669
2559
|
}
|
|
2670
2560
|
|
|
2671
2561
|
// packages/utils/src/lib/reports/scoring.ts
|
|
@@ -2755,56 +2645,6 @@ function parseScoringParameters(refs, scoreFn) {
|
|
|
2755
2645
|
return scoredRefs;
|
|
2756
2646
|
}
|
|
2757
2647
|
|
|
2758
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
2759
|
-
function sortReport(report) {
|
|
2760
|
-
const { categories, plugins } = report;
|
|
2761
|
-
const sortedCategories = categories.map((category) => {
|
|
2762
|
-
const { audits, groups } = category.refs.reduce(
|
|
2763
|
-
(acc, ref) => ({
|
|
2764
|
-
...acc,
|
|
2765
|
-
...ref.type === "group" ? {
|
|
2766
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
2767
|
-
} : {
|
|
2768
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
2769
|
-
}
|
|
2770
|
-
}),
|
|
2771
|
-
{ groups: [], audits: [] }
|
|
2772
|
-
);
|
|
2773
|
-
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
2774
|
-
compareCategoryAuditsAndGroups
|
|
2775
|
-
);
|
|
2776
|
-
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
2777
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2778
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2779
|
-
);
|
|
2780
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2781
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2782
|
-
);
|
|
2783
|
-
return aIndex - bIndex;
|
|
2784
|
-
});
|
|
2785
|
-
return { ...category, refs: sortedRefs };
|
|
2786
|
-
});
|
|
2787
|
-
return {
|
|
2788
|
-
...report,
|
|
2789
|
-
categories: sortedCategories,
|
|
2790
|
-
plugins: sortPlugins(plugins)
|
|
2791
|
-
};
|
|
2792
|
-
}
|
|
2793
|
-
function sortPlugins(plugins) {
|
|
2794
|
-
return plugins.map((plugin) => ({
|
|
2795
|
-
...plugin,
|
|
2796
|
-
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2797
|
-
(audit) => audit.details?.issues ? {
|
|
2798
|
-
...audit,
|
|
2799
|
-
details: {
|
|
2800
|
-
...audit.details,
|
|
2801
|
-
issues: [...audit.details.issues].sort(compareIssues)
|
|
2802
|
-
}
|
|
2803
|
-
} : audit
|
|
2804
|
-
)
|
|
2805
|
-
}));
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
2648
|
// packages/utils/src/lib/verbose-utils.ts
|
|
2809
2649
|
function getLogVerbose(verbose = false) {
|
|
2810
2650
|
return (msg) => {
|
|
@@ -2827,13 +2667,13 @@ var verboseUtils = (verbose = false) => ({
|
|
|
2827
2667
|
export {
|
|
2828
2668
|
CODE_PUSHUP_DOMAIN,
|
|
2829
2669
|
FOOTER_PREFIX,
|
|
2670
|
+
HIERARCHY,
|
|
2830
2671
|
NEW_LINE,
|
|
2831
2672
|
ProcessError,
|
|
2832
2673
|
README_LINK,
|
|
2833
2674
|
SPACE,
|
|
2834
2675
|
TAB,
|
|
2835
2676
|
TERMINAL_WIDTH,
|
|
2836
|
-
apostrophize,
|
|
2837
2677
|
calcDuration,
|
|
2838
2678
|
capitalize,
|
|
2839
2679
|
compareIssueSeverity,
|
|
@@ -2878,7 +2718,6 @@ export {
|
|
|
2878
2718
|
logMultipleResults,
|
|
2879
2719
|
logStdoutSummary,
|
|
2880
2720
|
matchArrayItemsByKey,
|
|
2881
|
-
md,
|
|
2882
2721
|
mergeConfigs,
|
|
2883
2722
|
normalizeSemver,
|
|
2884
2723
|
objectFromEntries,
|