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