@code-pushup/core 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 +1054 -1090
- package/package.json +9 -35
- package/src/index.d.ts +7 -6
- package/src/lib/collect-and-persist.d.ts +2 -2
- package/src/lib/compare.d.ts +3 -3
- package/src/lib/history.d.ts +2 -2
- package/src/lib/implementation/collect.d.ts +2 -2
- package/src/lib/implementation/compare-scorables.d.ts +2 -2
- package/src/lib/implementation/execute-plugin.d.ts +1 -1
- package/src/lib/implementation/persist.d.ts +2 -2
- package/src/lib/implementation/read-rc-file.d.ts +1 -1
- package/src/lib/implementation/runner.d.ts +1 -1
- package/src/lib/merge-diffs.d.ts +2 -0
- package/src/lib/normalize.d.ts +1 -1
- package/src/lib/upload.d.ts +2 -2
package/index.js
CHANGED
|
@@ -670,6 +670,8 @@ var auditResultSchema = scorableWithPluginMetaSchema.merge(
|
|
|
670
670
|
);
|
|
671
671
|
var reportsDiffSchema = z15.object({
|
|
672
672
|
commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
|
|
673
|
+
portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
|
|
674
|
+
label: z15.string().optional().describe("Label (e.g. project name)"),
|
|
673
675
|
categories: makeArraysComparisonSchema(
|
|
674
676
|
categoryDiffSchema,
|
|
675
677
|
categoryResultSchema,
|
|
@@ -739,15 +741,286 @@ function comparePairs(pairs, equalsFn) {
|
|
|
739
741
|
);
|
|
740
742
|
}
|
|
741
743
|
|
|
744
|
+
// packages/utils/src/lib/errors.ts
|
|
745
|
+
function stringifyError(error) {
|
|
746
|
+
if (error instanceof Error) {
|
|
747
|
+
if (error.name === "Error" || error.message.startsWith(error.name)) {
|
|
748
|
+
return error.message;
|
|
749
|
+
}
|
|
750
|
+
return `${error.name}: ${error.message}`;
|
|
751
|
+
}
|
|
752
|
+
if (typeof error === "string") {
|
|
753
|
+
return error;
|
|
754
|
+
}
|
|
755
|
+
return JSON.stringify(error);
|
|
756
|
+
}
|
|
757
|
+
|
|
742
758
|
// packages/utils/src/lib/execute-process.ts
|
|
743
|
-
import {
|
|
759
|
+
import {
|
|
760
|
+
spawn
|
|
761
|
+
} from "node:child_process";
|
|
762
|
+
|
|
763
|
+
// packages/utils/src/lib/reports/utils.ts
|
|
764
|
+
import ansis from "ansis";
|
|
765
|
+
import { md } from "build-md";
|
|
766
|
+
|
|
767
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
768
|
+
var TERMINAL_WIDTH = 80;
|
|
769
|
+
var SCORE_COLOR_RANGE = {
|
|
770
|
+
GREEN_MIN: 0.9,
|
|
771
|
+
YELLOW_MIN: 0.5
|
|
772
|
+
};
|
|
773
|
+
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
774
|
+
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
775
|
+
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
776
|
+
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
777
|
+
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
778
|
+
"Category",
|
|
779
|
+
"Score",
|
|
780
|
+
"Audits"
|
|
781
|
+
];
|
|
744
782
|
|
|
745
783
|
// packages/utils/src/lib/reports/utils.ts
|
|
746
|
-
|
|
784
|
+
function formatReportScore(score) {
|
|
785
|
+
const scaledScore = score * 100;
|
|
786
|
+
const roundedScore = Math.round(scaledScore);
|
|
787
|
+
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
788
|
+
}
|
|
789
|
+
function formatScoreWithColor(score, options) {
|
|
790
|
+
const styledNumber = options?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
791
|
+
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
792
|
+
}
|
|
793
|
+
var MARKERS = {
|
|
794
|
+
circle: {
|
|
795
|
+
red: "\u{1F534}",
|
|
796
|
+
yellow: "\u{1F7E1}",
|
|
797
|
+
green: "\u{1F7E2}"
|
|
798
|
+
},
|
|
799
|
+
square: {
|
|
800
|
+
red: "\u{1F7E5}",
|
|
801
|
+
yellow: "\u{1F7E8}",
|
|
802
|
+
green: "\u{1F7E9}"
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
function scoreMarker(score, markerType = "circle") {
|
|
806
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
807
|
+
return MARKERS[markerType].green;
|
|
808
|
+
}
|
|
809
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
810
|
+
return MARKERS[markerType].yellow;
|
|
811
|
+
}
|
|
812
|
+
return MARKERS[markerType].red;
|
|
813
|
+
}
|
|
814
|
+
function getDiffMarker(diff) {
|
|
815
|
+
if (diff > 0) {
|
|
816
|
+
return "\u2191";
|
|
817
|
+
}
|
|
818
|
+
if (diff < 0) {
|
|
819
|
+
return "\u2193";
|
|
820
|
+
}
|
|
821
|
+
return "";
|
|
822
|
+
}
|
|
823
|
+
function colorByScoreDiff(text, diff) {
|
|
824
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
825
|
+
return shieldsBadge(text, color);
|
|
826
|
+
}
|
|
827
|
+
function shieldsBadge(text, color) {
|
|
828
|
+
return md.image(
|
|
829
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
830
|
+
text
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
function formatDiffNumber(diff) {
|
|
834
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
835
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
836
|
+
return `${sign}${number}`;
|
|
837
|
+
}
|
|
838
|
+
function severityMarker(severity) {
|
|
839
|
+
if (severity === "error") {
|
|
840
|
+
return "\u{1F6A8}";
|
|
841
|
+
}
|
|
842
|
+
if (severity === "warning") {
|
|
843
|
+
return "\u26A0\uFE0F";
|
|
844
|
+
}
|
|
845
|
+
return "\u2139\uFE0F";
|
|
846
|
+
}
|
|
847
|
+
function formatScoreChange(diff) {
|
|
848
|
+
const marker = getDiffMarker(diff);
|
|
849
|
+
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
850
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
851
|
+
}
|
|
852
|
+
function formatValueChange({
|
|
853
|
+
values,
|
|
854
|
+
scores
|
|
855
|
+
}) {
|
|
856
|
+
const marker = getDiffMarker(values.diff);
|
|
857
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
858
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
859
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
860
|
+
}
|
|
861
|
+
function calcDuration(start, stop) {
|
|
862
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
863
|
+
}
|
|
864
|
+
function countCategoryAudits(refs, plugins) {
|
|
865
|
+
const groupLookup = plugins.reduce(
|
|
866
|
+
(lookup, plugin) => {
|
|
867
|
+
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
868
|
+
return lookup;
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
...lookup,
|
|
872
|
+
[plugin.slug]: Object.fromEntries(
|
|
873
|
+
plugin.groups.map((group) => [group.slug, group])
|
|
874
|
+
)
|
|
875
|
+
};
|
|
876
|
+
},
|
|
877
|
+
{}
|
|
878
|
+
);
|
|
879
|
+
return refs.reduce((acc, ref) => {
|
|
880
|
+
if (ref.type === "group") {
|
|
881
|
+
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
882
|
+
return acc + (groupRefs?.length ?? 0);
|
|
883
|
+
}
|
|
884
|
+
return acc + 1;
|
|
885
|
+
}, 0);
|
|
886
|
+
}
|
|
887
|
+
function compareCategoryAuditsAndGroups(a, b) {
|
|
888
|
+
if (a.score !== b.score) {
|
|
889
|
+
return a.score - b.score;
|
|
890
|
+
}
|
|
891
|
+
if (a.weight !== b.weight) {
|
|
892
|
+
return b.weight - a.weight;
|
|
893
|
+
}
|
|
894
|
+
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
895
|
+
return b.value - a.value;
|
|
896
|
+
}
|
|
897
|
+
return a.title.localeCompare(b.title);
|
|
898
|
+
}
|
|
899
|
+
function compareAudits(a, b) {
|
|
900
|
+
if (a.score !== b.score) {
|
|
901
|
+
return a.score - b.score;
|
|
902
|
+
}
|
|
903
|
+
if (a.value !== b.value) {
|
|
904
|
+
return b.value - a.value;
|
|
905
|
+
}
|
|
906
|
+
return a.title.localeCompare(b.title);
|
|
907
|
+
}
|
|
908
|
+
function compareIssueSeverity(severity1, severity2) {
|
|
909
|
+
const levels = {
|
|
910
|
+
info: 0,
|
|
911
|
+
warning: 1,
|
|
912
|
+
error: 2
|
|
913
|
+
};
|
|
914
|
+
return levels[severity1] - levels[severity2];
|
|
915
|
+
}
|
|
916
|
+
function throwIsNotPresentError(itemName, presentPlace) {
|
|
917
|
+
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
918
|
+
}
|
|
919
|
+
function getPluginNameFromSlug(slug, plugins) {
|
|
920
|
+
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
921
|
+
}
|
|
922
|
+
function compareIssues(a, b) {
|
|
923
|
+
if (a.severity !== b.severity) {
|
|
924
|
+
return -compareIssueSeverity(a.severity, b.severity);
|
|
925
|
+
}
|
|
926
|
+
if (!a.source && b.source) {
|
|
927
|
+
return -1;
|
|
928
|
+
}
|
|
929
|
+
if (a.source && !b.source) {
|
|
930
|
+
return 1;
|
|
931
|
+
}
|
|
932
|
+
if (a.source?.file !== b.source?.file) {
|
|
933
|
+
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
934
|
+
}
|
|
935
|
+
if (!a.source?.position && b.source?.position) {
|
|
936
|
+
return -1;
|
|
937
|
+
}
|
|
938
|
+
if (a.source?.position && !b.source?.position) {
|
|
939
|
+
return 1;
|
|
940
|
+
}
|
|
941
|
+
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
942
|
+
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
943
|
+
}
|
|
944
|
+
return 0;
|
|
945
|
+
}
|
|
946
|
+
function applyScoreColor({ score, text }, style = ansis) {
|
|
947
|
+
const formattedScore = text ?? formatReportScore(score);
|
|
948
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
949
|
+
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
950
|
+
}
|
|
951
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
952
|
+
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
953
|
+
}
|
|
954
|
+
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
955
|
+
}
|
|
956
|
+
function targetScoreIcon(score, targetScore, options = {}) {
|
|
957
|
+
if (targetScore != null) {
|
|
958
|
+
const {
|
|
959
|
+
passIcon = "\u2705",
|
|
960
|
+
failIcon = "\u274C",
|
|
961
|
+
prefix = "",
|
|
962
|
+
postfix = ""
|
|
963
|
+
} = options;
|
|
964
|
+
if (score >= targetScore) {
|
|
965
|
+
return `${prefix}${passIcon}${postfix}`;
|
|
966
|
+
}
|
|
967
|
+
return `${prefix}${failIcon}${postfix}`;
|
|
968
|
+
}
|
|
969
|
+
return "";
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// packages/utils/src/lib/execute-process.ts
|
|
973
|
+
var ProcessError = class extends Error {
|
|
974
|
+
code;
|
|
975
|
+
stderr;
|
|
976
|
+
stdout;
|
|
977
|
+
constructor(result) {
|
|
978
|
+
super(result.stderr);
|
|
979
|
+
this.code = result.code;
|
|
980
|
+
this.stderr = result.stderr;
|
|
981
|
+
this.stdout = result.stdout;
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
function executeProcess(cfg) {
|
|
985
|
+
const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
|
|
986
|
+
const { onStdout, onStderr, onError, onComplete } = observer ?? {};
|
|
987
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
988
|
+
const start = performance.now();
|
|
989
|
+
return new Promise((resolve, reject) => {
|
|
990
|
+
const spawnedProcess = spawn(command, args ?? [], {
|
|
991
|
+
shell: true,
|
|
992
|
+
...options
|
|
993
|
+
});
|
|
994
|
+
let stdout = "";
|
|
995
|
+
let stderr = "";
|
|
996
|
+
spawnedProcess.stdout.on("data", (data) => {
|
|
997
|
+
stdout += String(data);
|
|
998
|
+
onStdout?.(String(data), spawnedProcess);
|
|
999
|
+
});
|
|
1000
|
+
spawnedProcess.stderr.on("data", (data) => {
|
|
1001
|
+
stderr += String(data);
|
|
1002
|
+
onStderr?.(String(data), spawnedProcess);
|
|
1003
|
+
});
|
|
1004
|
+
spawnedProcess.on("error", (err) => {
|
|
1005
|
+
stderr += err.toString();
|
|
1006
|
+
});
|
|
1007
|
+
spawnedProcess.on("close", (code2) => {
|
|
1008
|
+
const timings = { date, duration: calcDuration(start) };
|
|
1009
|
+
if (code2 === 0 || ignoreExitCode) {
|
|
1010
|
+
onComplete?.();
|
|
1011
|
+
resolve({ code: code2, stdout, stderr, ...timings });
|
|
1012
|
+
} else {
|
|
1013
|
+
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
1014
|
+
onError?.(errorMsg);
|
|
1015
|
+
reject(errorMsg);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
747
1020
|
|
|
748
1021
|
// packages/utils/src/lib/file-system.ts
|
|
1022
|
+
import { bold, gray } from "ansis";
|
|
749
1023
|
import { bundleRequire } from "bundle-require";
|
|
750
|
-
import chalk2 from "chalk";
|
|
751
1024
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
752
1025
|
|
|
753
1026
|
// packages/utils/src/lib/formatting.ts
|
|
@@ -810,55 +1083,7 @@ function isPromiseRejectedResult(result) {
|
|
|
810
1083
|
// packages/utils/src/lib/logging.ts
|
|
811
1084
|
import isaacs_cliui from "@isaacs/cliui";
|
|
812
1085
|
import { cliui } from "@poppinss/cliui";
|
|
813
|
-
import
|
|
814
|
-
|
|
815
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
816
|
-
var TERMINAL_WIDTH = 80;
|
|
817
|
-
var SCORE_COLOR_RANGE = {
|
|
818
|
-
GREEN_MIN: 0.9,
|
|
819
|
-
YELLOW_MIN: 0.5
|
|
820
|
-
};
|
|
821
|
-
var CATEGORIES_TITLE = "\u{1F3F7} Categories";
|
|
822
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
823
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
824
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
825
|
-
var reportHeadlineText = "Code PushUp Report";
|
|
826
|
-
var reportOverviewTableHeaders = [
|
|
827
|
-
{
|
|
828
|
-
key: "category",
|
|
829
|
-
label: "\u{1F3F7} Category",
|
|
830
|
-
align: "left"
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
key: "score",
|
|
834
|
-
label: "\u2B50 Score"
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
key: "audits",
|
|
838
|
-
label: "\u{1F6E1} Audits"
|
|
839
|
-
}
|
|
840
|
-
];
|
|
841
|
-
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
842
|
-
var issuesTableHeadings = [
|
|
843
|
-
{
|
|
844
|
-
key: "severity",
|
|
845
|
-
label: "Severity"
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
key: "message",
|
|
849
|
-
label: "Message"
|
|
850
|
-
},
|
|
851
|
-
{
|
|
852
|
-
key: "file",
|
|
853
|
-
label: "Source file"
|
|
854
|
-
},
|
|
855
|
-
{
|
|
856
|
-
key: "line",
|
|
857
|
-
label: "Line(s)"
|
|
858
|
-
}
|
|
859
|
-
];
|
|
860
|
-
|
|
861
|
-
// packages/utils/src/lib/logging.ts
|
|
1086
|
+
import { underline } from "ansis";
|
|
862
1087
|
var singletonUiInstance;
|
|
863
1088
|
function ui() {
|
|
864
1089
|
if (singletonUiInstance === void 0) {
|
|
@@ -954,10 +1179,10 @@ async function ensureDirectoryExists(baseDir) {
|
|
|
954
1179
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
955
1180
|
const succeededTransform = (result) => {
|
|
956
1181
|
const [fileName, size] = result.value;
|
|
957
|
-
const formattedSize = size ? ` (${
|
|
958
|
-
return `- ${
|
|
1182
|
+
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1183
|
+
return `- ${bold(fileName)}${formattedSize}`;
|
|
959
1184
|
};
|
|
960
|
-
const failedTransform = (result) => `- ${
|
|
1185
|
+
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
961
1186
|
logMultipleResults(
|
|
962
1187
|
fileResults,
|
|
963
1188
|
messagePrefix,
|
|
@@ -973,570 +1198,35 @@ async function importModule(options) {
|
|
|
973
1198
|
return mod;
|
|
974
1199
|
}
|
|
975
1200
|
|
|
976
|
-
// packages/utils/src/lib/
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
var SPACE = " ";
|
|
980
|
-
|
|
981
|
-
// packages/utils/src/lib/text-formats/html/details.ts
|
|
982
|
-
function details(title, content, cfg = { open: false }) {
|
|
983
|
-
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.
|
|
984
|
-
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
985
|
-
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
986
|
-
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
987
|
-
NEW_LINE}`;
|
|
988
|
-
}
|
|
1201
|
+
// packages/utils/src/lib/git/git.ts
|
|
1202
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
1203
|
+
import { simpleGit } from "simple-git";
|
|
989
1204
|
|
|
990
|
-
// packages/utils/src/lib/
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1205
|
+
// packages/utils/src/lib/transform.ts
|
|
1206
|
+
function toArray(val) {
|
|
1207
|
+
return Array.isArray(val) ? val : [val];
|
|
994
1208
|
}
|
|
995
|
-
var italicElement = "i";
|
|
996
|
-
function italic(text) {
|
|
997
|
-
return `<${italicElement}>${text}</${italicElement}>`;
|
|
998
|
-
}
|
|
999
|
-
var codeElement = "code";
|
|
1000
|
-
function code(text) {
|
|
1001
|
-
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// packages/utils/src/lib/text-formats/html/link.ts
|
|
1005
|
-
function link(href, text) {
|
|
1006
|
-
return `<a href="${href}">${text || href}</a>`;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// packages/utils/src/lib/transform.ts
|
|
1010
1209
|
function objectToEntries(obj) {
|
|
1011
1210
|
return Object.entries(obj);
|
|
1012
1211
|
}
|
|
1013
1212
|
function deepClone(obj) {
|
|
1014
1213
|
return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
|
|
1015
1214
|
}
|
|
1016
|
-
function toUnixPath(path) {
|
|
1017
|
-
return path.replace(/\\/g, "/");
|
|
1018
|
-
}
|
|
1019
|
-
function capitalize(text) {
|
|
1020
|
-
return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
|
|
1021
|
-
1
|
|
1022
|
-
)}`;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// packages/utils/src/lib/text-formats/table.ts
|
|
1026
|
-
function rowToStringArray({ rows, columns = [] }) {
|
|
1027
|
-
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1028
|
-
throw new TypeError(
|
|
1029
|
-
"Column can`t be object when rows are primitive values"
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
return rows.map((row) => {
|
|
1033
|
-
if (Array.isArray(row)) {
|
|
1034
|
-
return row.map(String);
|
|
1035
|
-
}
|
|
1036
|
-
const objectRow = row;
|
|
1037
|
-
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1038
|
-
return Object.values(objectRow).map(
|
|
1039
|
-
(value) => value == null ? "" : String(value)
|
|
1040
|
-
);
|
|
1041
|
-
}
|
|
1042
|
-
return columns.map(
|
|
1043
|
-
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1044
|
-
);
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
function columnsToStringArray({
|
|
1048
|
-
rows,
|
|
1049
|
-
columns = []
|
|
1050
|
-
}) {
|
|
1051
|
-
const firstRow = rows.at(0);
|
|
1052
|
-
const primitiveRows = Array.isArray(firstRow);
|
|
1053
|
-
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1054
|
-
throw new Error("invalid union type. Caught by model parsing.");
|
|
1055
|
-
}
|
|
1056
|
-
if (columns.length === 0) {
|
|
1057
|
-
if (Array.isArray(firstRow)) {
|
|
1058
|
-
return firstRow.map((_, idx) => String(idx));
|
|
1059
|
-
}
|
|
1060
|
-
return Object.keys(firstRow);
|
|
1061
|
-
}
|
|
1062
|
-
if (typeof columns.at(0) === "string") {
|
|
1063
|
-
return columns.map(String);
|
|
1064
|
-
}
|
|
1065
|
-
const cols = columns;
|
|
1066
|
-
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1067
|
-
}
|
|
1068
|
-
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1069
|
-
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1070
|
-
if (typeof column === "string") {
|
|
1071
|
-
return column;
|
|
1072
|
-
} else if (typeof column === "object") {
|
|
1073
|
-
return column.align ?? "center";
|
|
1074
|
-
} else {
|
|
1075
|
-
return "center";
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1079
|
-
const column = columns.at(targetIdx);
|
|
1080
|
-
if (column == null) {
|
|
1081
|
-
return "center";
|
|
1082
|
-
} else if (typeof column === "string") {
|
|
1083
|
-
return column;
|
|
1084
|
-
} else if (typeof column === "object") {
|
|
1085
|
-
return column.align ?? "center";
|
|
1086
|
-
} else {
|
|
1087
|
-
return "center";
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
function getColumnAlignments(tableData) {
|
|
1091
|
-
const { rows, columns = [] } = tableData;
|
|
1092
|
-
if (rows.at(0) == null) {
|
|
1093
|
-
throw new Error("first row can`t be undefined.");
|
|
1094
|
-
}
|
|
1095
|
-
if (Array.isArray(rows.at(0))) {
|
|
1096
|
-
const firstPrimitiveRow = rows.at(0);
|
|
1097
|
-
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1098
|
-
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1102
|
-
if (columns.length > 0) {
|
|
1103
|
-
return columns.map(
|
|
1104
|
-
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1105
|
-
column.key,
|
|
1106
|
-
idx,
|
|
1107
|
-
columns
|
|
1108
|
-
)
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// packages/utils/src/lib/text-formats/html/table.ts
|
|
1115
|
-
function wrap(elem, content) {
|
|
1116
|
-
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1117
|
-
}
|
|
1118
|
-
function wrapRow(content) {
|
|
1119
|
-
const elem = "tr";
|
|
1120
|
-
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1121
|
-
}
|
|
1122
|
-
function table(tableData) {
|
|
1123
|
-
if (tableData.rows.length === 0) {
|
|
1124
|
-
throw new Error("Data can't be empty");
|
|
1125
|
-
}
|
|
1126
|
-
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1127
|
-
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1128
|
-
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1129
|
-
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1130
|
-
return wrapRow(columns);
|
|
1131
|
-
}).join("");
|
|
1132
|
-
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// packages/utils/src/lib/text-formats/md/font-style.ts
|
|
1136
|
-
var boldWrap = "**";
|
|
1137
|
-
function bold2(text) {
|
|
1138
|
-
return `${boldWrap}${text}${boldWrap}`;
|
|
1139
|
-
}
|
|
1140
|
-
var italicWrap = "_";
|
|
1141
|
-
function italic2(text) {
|
|
1142
|
-
return `${italicWrap}${text}${italicWrap}`;
|
|
1143
|
-
}
|
|
1144
|
-
var strikeThroughWrap = "~";
|
|
1145
|
-
function strikeThrough(text) {
|
|
1146
|
-
return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
|
|
1147
|
-
}
|
|
1148
|
-
var codeWrap = "`";
|
|
1149
|
-
function code2(text) {
|
|
1150
|
-
return `${codeWrap}${text}${codeWrap}`;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// packages/utils/src/lib/text-formats/md/headline.ts
|
|
1154
|
-
function headline(text, hierarchy = 1) {
|
|
1155
|
-
return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
|
|
1156
|
-
}
|
|
1157
|
-
function h(text, hierarchy = 1) {
|
|
1158
|
-
return headline(text, hierarchy);
|
|
1159
|
-
}
|
|
1160
|
-
function h1(text) {
|
|
1161
|
-
return headline(text, 1);
|
|
1162
|
-
}
|
|
1163
|
-
function h2(text) {
|
|
1164
|
-
return headline(text, 2);
|
|
1165
|
-
}
|
|
1166
|
-
function h3(text) {
|
|
1167
|
-
return headline(text, 3);
|
|
1168
|
-
}
|
|
1169
|
-
function h4(text) {
|
|
1170
|
-
return headline(text, 4);
|
|
1171
|
-
}
|
|
1172
|
-
function h5(text) {
|
|
1173
|
-
return headline(text, 5);
|
|
1174
|
-
}
|
|
1175
|
-
function h6(text) {
|
|
1176
|
-
return headline(text, 6);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// packages/utils/src/lib/text-formats/md/image.ts
|
|
1180
|
-
function image(src, alt) {
|
|
1181
|
-
return ``;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// packages/utils/src/lib/text-formats/md/link.ts
|
|
1185
|
-
function link2(href, text) {
|
|
1186
|
-
return `[${text || href}](${href})`;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
// packages/utils/src/lib/text-formats/md/list.ts
|
|
1190
|
-
function li(text, order = "unordered") {
|
|
1191
|
-
const style = order === "unordered" ? "-" : "- [ ]";
|
|
1192
|
-
return `${style} ${text}`;
|
|
1193
|
-
}
|
|
1194
|
-
function indentation(text, level = 1) {
|
|
1195
|
-
return `${TAB.repeat(level)}${text}`;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// packages/utils/src/lib/text-formats/md/paragraphs.ts
|
|
1199
|
-
function paragraphs(...sections) {
|
|
1200
|
-
return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// packages/utils/src/lib/text-formats/md/section.ts
|
|
1204
|
-
function section(...contents) {
|
|
1205
|
-
return `${lines(...contents)}${NEW_LINE}`;
|
|
1206
|
-
}
|
|
1207
|
-
function lines(...contents) {
|
|
1208
|
-
const filteredContent = contents.filter(
|
|
1209
|
-
(value) => value != null && value !== "" && value !== false
|
|
1210
|
-
);
|
|
1211
|
-
return `${filteredContent.join(NEW_LINE)}`;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// packages/utils/src/lib/text-formats/md/table.ts
|
|
1215
|
-
var alignString = /* @__PURE__ */ new Map([
|
|
1216
|
-
["left", ":--"],
|
|
1217
|
-
["center", ":--:"],
|
|
1218
|
-
["right", "--:"]
|
|
1219
|
-
]);
|
|
1220
|
-
function tableRow(rows) {
|
|
1221
|
-
return `|${rows.join("|")}|`;
|
|
1222
|
-
}
|
|
1223
|
-
function table2(data) {
|
|
1224
|
-
if (data.rows.length === 0) {
|
|
1225
|
-
throw new Error("Data can't be empty");
|
|
1226
|
-
}
|
|
1227
|
-
const alignmentRow = getColumnAlignments(data).map(
|
|
1228
|
-
(s) => alignString.get(s) ?? String(alignString.get("center"))
|
|
1229
|
-
);
|
|
1230
|
-
return section(
|
|
1231
|
-
`${lines(
|
|
1232
|
-
tableRow(columnsToStringArray(data)),
|
|
1233
|
-
tableRow(alignmentRow),
|
|
1234
|
-
...rowToStringArray(data).map(tableRow)
|
|
1235
|
-
)}`
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// packages/utils/src/lib/text-formats/index.ts
|
|
1240
|
-
var md = {
|
|
1241
|
-
bold: bold2,
|
|
1242
|
-
italic: italic2,
|
|
1243
|
-
strikeThrough,
|
|
1244
|
-
code: code2,
|
|
1245
|
-
link: link2,
|
|
1246
|
-
image,
|
|
1247
|
-
headline,
|
|
1248
|
-
h,
|
|
1249
|
-
h1,
|
|
1250
|
-
h2,
|
|
1251
|
-
h3,
|
|
1252
|
-
h4,
|
|
1253
|
-
h5,
|
|
1254
|
-
h6,
|
|
1255
|
-
indentation,
|
|
1256
|
-
lines,
|
|
1257
|
-
li,
|
|
1258
|
-
section,
|
|
1259
|
-
paragraphs,
|
|
1260
|
-
table: table2
|
|
1261
|
-
};
|
|
1262
|
-
var html = {
|
|
1263
|
-
bold,
|
|
1264
|
-
italic,
|
|
1265
|
-
code,
|
|
1266
|
-
link,
|
|
1267
|
-
details,
|
|
1268
|
-
table
|
|
1269
|
-
};
|
|
1270
|
-
|
|
1271
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
1272
|
-
var { image: image2, bold: boldMd } = md;
|
|
1273
|
-
function formatReportScore(score) {
|
|
1274
|
-
const scaledScore = score * 100;
|
|
1275
|
-
const roundedScore = Math.round(scaledScore);
|
|
1276
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
1277
|
-
}
|
|
1278
|
-
function formatScoreWithColor(score, options) {
|
|
1279
|
-
const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
|
|
1280
|
-
return `${scoreMarker(score)} ${styledNumber}`;
|
|
1281
|
-
}
|
|
1282
|
-
var MARKERS = {
|
|
1283
|
-
circle: {
|
|
1284
|
-
red: "\u{1F534}",
|
|
1285
|
-
yellow: "\u{1F7E1}",
|
|
1286
|
-
green: "\u{1F7E2}"
|
|
1287
|
-
},
|
|
1288
|
-
square: {
|
|
1289
|
-
red: "\u{1F7E5}",
|
|
1290
|
-
yellow: "\u{1F7E8}",
|
|
1291
|
-
green: "\u{1F7E9}"
|
|
1292
|
-
}
|
|
1293
|
-
};
|
|
1294
|
-
function scoreMarker(score, markerType = "circle") {
|
|
1295
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
1296
|
-
return MARKERS[markerType].green;
|
|
1297
|
-
}
|
|
1298
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
1299
|
-
return MARKERS[markerType].yellow;
|
|
1300
|
-
}
|
|
1301
|
-
return MARKERS[markerType].red;
|
|
1302
|
-
}
|
|
1303
|
-
function getDiffMarker(diff) {
|
|
1304
|
-
if (diff > 0) {
|
|
1305
|
-
return "\u2191";
|
|
1306
|
-
}
|
|
1307
|
-
if (diff < 0) {
|
|
1308
|
-
return "\u2193";
|
|
1309
|
-
}
|
|
1310
|
-
return "";
|
|
1311
|
-
}
|
|
1312
|
-
function colorByScoreDiff(text, diff) {
|
|
1313
|
-
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
1314
|
-
return shieldsBadge(text, color);
|
|
1315
|
-
}
|
|
1316
|
-
function shieldsBadge(text, color) {
|
|
1317
|
-
return image2(
|
|
1318
|
-
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
1319
|
-
text
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
function formatDiffNumber(diff) {
|
|
1323
|
-
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
1324
|
-
const sign = diff < 0 ? "\u2212" : "+";
|
|
1325
|
-
return `${sign}${number}`;
|
|
1326
|
-
}
|
|
1327
|
-
function severityMarker(severity) {
|
|
1328
|
-
if (severity === "error") {
|
|
1329
|
-
return "\u{1F6A8}";
|
|
1330
|
-
}
|
|
1331
|
-
if (severity === "warning") {
|
|
1332
|
-
return "\u26A0\uFE0F";
|
|
1333
|
-
}
|
|
1334
|
-
return "\u2139\uFE0F";
|
|
1335
|
-
}
|
|
1336
|
-
function calcDuration(start, stop) {
|
|
1337
|
-
return Math.round((stop ?? performance.now()) - start);
|
|
1338
|
-
}
|
|
1339
|
-
function countCategoryAudits(refs, plugins) {
|
|
1340
|
-
const groupLookup = plugins.reduce(
|
|
1341
|
-
(lookup, plugin) => {
|
|
1342
|
-
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
1343
|
-
return lookup;
|
|
1344
|
-
}
|
|
1345
|
-
return {
|
|
1346
|
-
...lookup,
|
|
1347
|
-
[plugin.slug]: Object.fromEntries(
|
|
1348
|
-
plugin.groups.map((group) => [group.slug, group])
|
|
1349
|
-
)
|
|
1350
|
-
};
|
|
1351
|
-
},
|
|
1352
|
-
{}
|
|
1353
|
-
);
|
|
1354
|
-
return refs.reduce((acc, ref) => {
|
|
1355
|
-
if (ref.type === "group") {
|
|
1356
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
1357
|
-
return acc + (groupRefs?.length ?? 0);
|
|
1358
|
-
}
|
|
1359
|
-
return acc + 1;
|
|
1360
|
-
}, 0);
|
|
1361
|
-
}
|
|
1362
|
-
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1363
|
-
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1364
|
-
if (!auditPlugin) {
|
|
1365
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1366
|
-
}
|
|
1367
|
-
const audit = auditPlugin.audits.find(
|
|
1368
|
-
({ slug: auditSlug }) => auditSlug === slug
|
|
1369
|
-
);
|
|
1370
|
-
if (!audit) {
|
|
1371
|
-
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1372
|
-
}
|
|
1373
|
-
return {
|
|
1374
|
-
...audit,
|
|
1375
|
-
weight,
|
|
1376
|
-
plugin
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1380
|
-
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1381
|
-
if (!groupPlugin) {
|
|
1382
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1383
|
-
}
|
|
1384
|
-
const group = groupPlugin.groups?.find(
|
|
1385
|
-
({ slug: groupSlug }) => groupSlug === slug
|
|
1386
|
-
);
|
|
1387
|
-
if (!group) {
|
|
1388
|
-
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1389
|
-
}
|
|
1390
|
-
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1391
|
-
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1392
|
-
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1393
|
-
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1394
|
-
return aIndex - bIndex;
|
|
1395
|
-
});
|
|
1396
|
-
return {
|
|
1397
|
-
...group,
|
|
1398
|
-
refs: sortedAuditRefs,
|
|
1399
|
-
plugin,
|
|
1400
|
-
weight
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1404
|
-
return group.refs.map(
|
|
1405
|
-
(ref) => getSortableAuditByRef(
|
|
1406
|
-
{
|
|
1407
|
-
plugin,
|
|
1408
|
-
slug: ref.slug,
|
|
1409
|
-
weight: ref.weight,
|
|
1410
|
-
type: "audit"
|
|
1411
|
-
},
|
|
1412
|
-
plugins
|
|
1413
|
-
)
|
|
1414
|
-
).sort(compareCategoryAuditsAndGroups);
|
|
1415
|
-
}
|
|
1416
|
-
function compareCategoryAuditsAndGroups(a, b) {
|
|
1417
|
-
if (a.weight !== b.weight) {
|
|
1418
|
-
return b.weight - a.weight;
|
|
1419
|
-
}
|
|
1420
|
-
if (a.score !== b.score) {
|
|
1421
|
-
return a.score - b.score;
|
|
1422
|
-
}
|
|
1423
|
-
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
1424
|
-
return b.value - a.value;
|
|
1425
|
-
}
|
|
1426
|
-
return a.title.localeCompare(b.title);
|
|
1427
|
-
}
|
|
1428
|
-
function compareAudits(a, b) {
|
|
1429
|
-
if (a.score !== b.score) {
|
|
1430
|
-
return a.score - b.score;
|
|
1431
|
-
}
|
|
1432
|
-
if (a.value !== b.value) {
|
|
1433
|
-
return b.value - a.value;
|
|
1434
|
-
}
|
|
1435
|
-
return a.title.localeCompare(b.title);
|
|
1436
|
-
}
|
|
1437
|
-
function compareIssueSeverity(severity1, severity2) {
|
|
1438
|
-
const levels = {
|
|
1439
|
-
info: 0,
|
|
1440
|
-
warning: 1,
|
|
1441
|
-
error: 2
|
|
1442
|
-
};
|
|
1443
|
-
return levels[severity1] - levels[severity2];
|
|
1444
|
-
}
|
|
1445
|
-
async function loadReport(options) {
|
|
1446
|
-
const { outputDir, filename, format } = options;
|
|
1447
|
-
await ensureDirectoryExists(outputDir);
|
|
1448
|
-
const filePath = join(outputDir, `${filename}.${format}`);
|
|
1449
|
-
if (format === "json") {
|
|
1450
|
-
const content = await readJsonFile(filePath);
|
|
1451
|
-
return reportSchema.parse(content);
|
|
1452
|
-
}
|
|
1453
|
-
const text = await readTextFile(filePath);
|
|
1454
|
-
return text;
|
|
1455
|
-
}
|
|
1456
|
-
function throwIsNotPresentError(itemName, presentPlace) {
|
|
1457
|
-
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
1458
|
-
}
|
|
1459
|
-
function getPluginNameFromSlug(slug, plugins) {
|
|
1460
|
-
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
1461
|
-
}
|
|
1462
|
-
function compareIssues(a, b) {
|
|
1463
|
-
if (a.severity !== b.severity) {
|
|
1464
|
-
return -compareIssueSeverity(a.severity, b.severity);
|
|
1465
|
-
}
|
|
1466
|
-
if (!a.source && b.source) {
|
|
1467
|
-
return -1;
|
|
1468
|
-
}
|
|
1469
|
-
if (a.source && !b.source) {
|
|
1470
|
-
return 1;
|
|
1471
|
-
}
|
|
1472
|
-
if (a.source?.file !== b.source?.file) {
|
|
1473
|
-
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
1474
|
-
}
|
|
1475
|
-
if (!a.source?.position && b.source?.position) {
|
|
1476
|
-
return -1;
|
|
1477
|
-
}
|
|
1478
|
-
if (a.source?.position && !b.source?.position) {
|
|
1479
|
-
return 1;
|
|
1480
|
-
}
|
|
1481
|
-
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
1482
|
-
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
1483
|
-
}
|
|
1484
|
-
return 0;
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// packages/utils/src/lib/execute-process.ts
|
|
1488
|
-
var ProcessError = class extends Error {
|
|
1489
|
-
code;
|
|
1490
|
-
stderr;
|
|
1491
|
-
stdout;
|
|
1492
|
-
constructor(result) {
|
|
1493
|
-
super(result.stderr);
|
|
1494
|
-
this.code = result.code;
|
|
1495
|
-
this.stderr = result.stderr;
|
|
1496
|
-
this.stdout = result.stdout;
|
|
1497
|
-
}
|
|
1498
|
-
};
|
|
1499
|
-
function executeProcess(cfg) {
|
|
1500
|
-
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
1501
|
-
const { onStdout, onError, onComplete } = observer ?? {};
|
|
1502
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1503
|
-
const start = performance.now();
|
|
1504
|
-
return new Promise((resolve, reject) => {
|
|
1505
|
-
const process2 = spawn(command, args, { cwd, shell: true });
|
|
1506
|
-
let stdout = "";
|
|
1507
|
-
let stderr = "";
|
|
1508
|
-
process2.stdout.on("data", (data) => {
|
|
1509
|
-
stdout += String(data);
|
|
1510
|
-
onStdout?.(String(data));
|
|
1511
|
-
});
|
|
1512
|
-
process2.stderr.on("data", (data) => {
|
|
1513
|
-
stderr += String(data);
|
|
1514
|
-
});
|
|
1515
|
-
process2.on("error", (err) => {
|
|
1516
|
-
stderr += err.toString();
|
|
1517
|
-
});
|
|
1518
|
-
process2.on("close", (code3) => {
|
|
1519
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1520
|
-
if (code3 === 0 || ignoreExitCode) {
|
|
1521
|
-
onComplete?.();
|
|
1522
|
-
resolve({ code: code3, stdout, stderr, ...timings });
|
|
1523
|
-
} else {
|
|
1524
|
-
const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
|
|
1525
|
-
onError?.(errorMsg);
|
|
1526
|
-
reject(errorMsg);
|
|
1527
|
-
}
|
|
1528
|
-
});
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1215
|
+
function toUnixPath(path) {
|
|
1216
|
+
return path.replace(/\\/g, "/");
|
|
1217
|
+
}
|
|
1218
|
+
function capitalize(text) {
|
|
1219
|
+
return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
|
|
1220
|
+
1
|
|
1221
|
+
)}`;
|
|
1222
|
+
}
|
|
1531
1223
|
|
|
1532
1224
|
// packages/utils/src/lib/git/git.ts
|
|
1533
|
-
import { isAbsolute, join as join2, relative } from "node:path";
|
|
1534
|
-
import { simpleGit } from "simple-git";
|
|
1535
1225
|
function getGitRoot(git = simpleGit()) {
|
|
1536
1226
|
return git.revparse("--show-toplevel");
|
|
1537
1227
|
}
|
|
1538
1228
|
function formatGitPath(path, gitRoot) {
|
|
1539
|
-
const absolutePath = isAbsolute(path) ? path :
|
|
1229
|
+
const absolutePath = isAbsolute(path) ? path : join(process.cwd(), path);
|
|
1540
1230
|
const relativePath = relative(gitRoot, absolutePath);
|
|
1541
1231
|
return toUnixPath(relativePath);
|
|
1542
1232
|
}
|
|
@@ -1618,17 +1308,17 @@ function groupByStatus(results) {
|
|
|
1618
1308
|
}
|
|
1619
1309
|
|
|
1620
1310
|
// packages/utils/src/lib/progress.ts
|
|
1621
|
-
import
|
|
1311
|
+
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1622
1312
|
import { MultiProgressBars } from "multi-progress-bars";
|
|
1623
1313
|
var barStyles = {
|
|
1624
|
-
active: (s) =>
|
|
1625
|
-
done: (s) =>
|
|
1626
|
-
idle: (s) =>
|
|
1314
|
+
active: (s) => green(s),
|
|
1315
|
+
done: (s) => gray2(s),
|
|
1316
|
+
idle: (s) => gray2(s)
|
|
1627
1317
|
};
|
|
1628
1318
|
var messageStyles = {
|
|
1629
|
-
active: (s) =>
|
|
1630
|
-
done: (s) =>
|
|
1631
|
-
idle: (s) =>
|
|
1319
|
+
active: (s) => black(s),
|
|
1320
|
+
done: (s) => bold2.green(s),
|
|
1321
|
+
idle: (s) => gray2(s)
|
|
1632
1322
|
};
|
|
1633
1323
|
var mpb;
|
|
1634
1324
|
function getSingletonProgressBars(options) {
|
|
@@ -1684,467 +1374,483 @@ function listAuditsFromAllPlugins(report) {
|
|
|
1684
1374
|
);
|
|
1685
1375
|
}
|
|
1686
1376
|
|
|
1377
|
+
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1378
|
+
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1379
|
+
|
|
1380
|
+
// packages/utils/src/lib/text-formats/constants.ts
|
|
1381
|
+
var HIERARCHY = {
|
|
1382
|
+
level_1: 1,
|
|
1383
|
+
level_2: 2,
|
|
1384
|
+
level_3: 3,
|
|
1385
|
+
level_4: 4,
|
|
1386
|
+
level_5: 5,
|
|
1387
|
+
level_6: 6
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
// packages/utils/src/lib/text-formats/table.ts
|
|
1391
|
+
function rowToStringArray({ rows, columns = [] }) {
|
|
1392
|
+
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1393
|
+
throw new TypeError(
|
|
1394
|
+
"Column can`t be object when rows are primitive values"
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
return rows.map((row) => {
|
|
1398
|
+
if (Array.isArray(row)) {
|
|
1399
|
+
return row.map(String);
|
|
1400
|
+
}
|
|
1401
|
+
const objectRow = row;
|
|
1402
|
+
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1403
|
+
return Object.values(objectRow).map(
|
|
1404
|
+
(value) => value == null ? "" : String(value)
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
return columns.map(
|
|
1408
|
+
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1409
|
+
);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function columnsToStringArray({
|
|
1413
|
+
rows,
|
|
1414
|
+
columns = []
|
|
1415
|
+
}) {
|
|
1416
|
+
const firstRow = rows.at(0);
|
|
1417
|
+
const primitiveRows = Array.isArray(firstRow);
|
|
1418
|
+
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1419
|
+
throw new Error("invalid union type. Caught by model parsing.");
|
|
1420
|
+
}
|
|
1421
|
+
if (columns.length === 0) {
|
|
1422
|
+
if (Array.isArray(firstRow)) {
|
|
1423
|
+
return firstRow.map((_, idx) => String(idx));
|
|
1424
|
+
}
|
|
1425
|
+
return Object.keys(firstRow);
|
|
1426
|
+
}
|
|
1427
|
+
if (typeof columns.at(0) === "string") {
|
|
1428
|
+
return columns.map(String);
|
|
1429
|
+
}
|
|
1430
|
+
const cols = columns;
|
|
1431
|
+
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1432
|
+
}
|
|
1433
|
+
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1434
|
+
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1435
|
+
if (typeof column === "string") {
|
|
1436
|
+
return column;
|
|
1437
|
+
} else if (typeof column === "object") {
|
|
1438
|
+
return column.align ?? "center";
|
|
1439
|
+
} else {
|
|
1440
|
+
return "center";
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1444
|
+
const column = columns.at(targetIdx);
|
|
1445
|
+
if (column == null) {
|
|
1446
|
+
return "center";
|
|
1447
|
+
} else if (typeof column === "string") {
|
|
1448
|
+
return column;
|
|
1449
|
+
} else if (typeof column === "object") {
|
|
1450
|
+
return column.align ?? "center";
|
|
1451
|
+
} else {
|
|
1452
|
+
return "center";
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function getColumnAlignments(tableData) {
|
|
1456
|
+
const { rows, columns = [] } = tableData;
|
|
1457
|
+
if (rows.at(0) == null) {
|
|
1458
|
+
throw new Error("first row can`t be undefined.");
|
|
1459
|
+
}
|
|
1460
|
+
if (Array.isArray(rows.at(0))) {
|
|
1461
|
+
const firstPrimitiveRow = rows.at(0);
|
|
1462
|
+
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1463
|
+
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1467
|
+
if (columns.length > 0) {
|
|
1468
|
+
return columns.map(
|
|
1469
|
+
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1470
|
+
column.key,
|
|
1471
|
+
idx,
|
|
1472
|
+
columns
|
|
1473
|
+
)
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1687
1479
|
// packages/utils/src/lib/reports/formatting.ts
|
|
1688
|
-
|
|
1480
|
+
import {
|
|
1481
|
+
MarkdownDocument,
|
|
1482
|
+
md as md2
|
|
1483
|
+
} from "build-md";
|
|
1689
1484
|
function tableSection(tableData, options) {
|
|
1690
1485
|
if (tableData.rows.length === 0) {
|
|
1691
|
-
return
|
|
1692
|
-
}
|
|
1693
|
-
const { level =
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
const { level = HIERARCHY.level_4 } = options ?? {};
|
|
1489
|
+
const columns = columnsToStringArray(tableData);
|
|
1490
|
+
const alignments = getColumnAlignments(tableData);
|
|
1491
|
+
const rows = rowToStringArray(tableData);
|
|
1492
|
+
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1493
|
+
columns.map((heading, i) => {
|
|
1494
|
+
const alignment = alignments[i];
|
|
1495
|
+
if (alignment) {
|
|
1496
|
+
return { heading, alignment };
|
|
1497
|
+
}
|
|
1498
|
+
return heading;
|
|
1499
|
+
}),
|
|
1500
|
+
rows
|
|
1698
1501
|
);
|
|
1699
1502
|
}
|
|
1700
|
-
function metaDescription({
|
|
1701
|
-
docsUrl
|
|
1702
|
-
description
|
|
1703
|
-
}) {
|
|
1503
|
+
function metaDescription(audit) {
|
|
1504
|
+
const docsUrl = audit.docsUrl;
|
|
1505
|
+
const description = audit.description?.trim();
|
|
1704
1506
|
if (docsUrl) {
|
|
1705
|
-
const docsLink =
|
|
1507
|
+
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1706
1508
|
if (!description) {
|
|
1707
|
-
return
|
|
1509
|
+
return docsLink;
|
|
1708
1510
|
}
|
|
1709
|
-
const parsedDescription = description.
|
|
1710
|
-
|
|
1511
|
+
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1512
|
+
|
|
1513
|
+
` : `${description} `;
|
|
1514
|
+
return md2`${parsedDescription}${docsLink}`;
|
|
1711
1515
|
}
|
|
1712
1516
|
if (description && description.trim().length > 0) {
|
|
1713
|
-
return
|
|
1517
|
+
return description;
|
|
1714
1518
|
}
|
|
1715
1519
|
return "";
|
|
1716
1520
|
}
|
|
1717
1521
|
|
|
1718
1522
|
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1727
|
-
category: link4(`#${slugify(title)}`, title),
|
|
1728
|
-
score: `${scoreMarker(score)}${SPACE}${boldMd2(
|
|
1729
|
-
formatReportScore(score)
|
|
1730
|
-
)}`,
|
|
1731
|
-
audits: countCategoryAudits(refs, plugins).toString()
|
|
1732
|
-
}))
|
|
1733
|
-
};
|
|
1734
|
-
return tableSection(tableContent);
|
|
1523
|
+
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1524
|
+
|
|
1525
|
+
// packages/utils/src/lib/reports/sorting.ts
|
|
1526
|
+
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1527
|
+
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1528
|
+
if (!auditPlugin) {
|
|
1529
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1735
1530
|
}
|
|
1736
|
-
|
|
1531
|
+
const audit = auditPlugin.audits.find(
|
|
1532
|
+
({ slug: auditSlug }) => auditSlug === slug
|
|
1533
|
+
);
|
|
1534
|
+
if (!audit) {
|
|
1535
|
+
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1536
|
+
}
|
|
1537
|
+
return {
|
|
1538
|
+
...audit,
|
|
1539
|
+
weight,
|
|
1540
|
+
plugin
|
|
1541
|
+
};
|
|
1737
1542
|
}
|
|
1738
|
-
function
|
|
1543
|
+
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1544
|
+
return group.refs.map(
|
|
1545
|
+
(ref) => getSortableAuditByRef(
|
|
1546
|
+
{
|
|
1547
|
+
plugin,
|
|
1548
|
+
slug: ref.slug,
|
|
1549
|
+
weight: ref.weight,
|
|
1550
|
+
type: "audit"
|
|
1551
|
+
},
|
|
1552
|
+
plugins
|
|
1553
|
+
)
|
|
1554
|
+
).sort(compareCategoryAuditsAndGroups);
|
|
1555
|
+
}
|
|
1556
|
+
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1557
|
+
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1558
|
+
if (!groupPlugin) {
|
|
1559
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1560
|
+
}
|
|
1561
|
+
const group = groupPlugin.groups?.find(
|
|
1562
|
+
({ slug: groupSlug }) => groupSlug === slug
|
|
1563
|
+
);
|
|
1564
|
+
if (!group) {
|
|
1565
|
+
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1566
|
+
}
|
|
1567
|
+
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1568
|
+
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1569
|
+
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1570
|
+
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1571
|
+
return aIndex - bIndex;
|
|
1572
|
+
});
|
|
1573
|
+
return {
|
|
1574
|
+
...group,
|
|
1575
|
+
refs: sortedAuditRefs,
|
|
1576
|
+
plugin,
|
|
1577
|
+
weight
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function sortReport(report) {
|
|
1739
1581
|
const { categories, plugins } = report;
|
|
1740
|
-
const
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1755
|
-
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1756
|
-
} else {
|
|
1757
|
-
const audit = getSortableAuditByRef(ref, plugins);
|
|
1758
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1759
|
-
return categoryRef(audit, pluginTitle);
|
|
1760
|
-
}
|
|
1761
|
-
});
|
|
1762
|
-
return section3(
|
|
1763
|
-
categoryTitle,
|
|
1764
|
-
metaDescription(category),
|
|
1765
|
-
categoryScore,
|
|
1766
|
-
...categoryMDItems
|
|
1582
|
+
const sortedCategories = categories.map((category) => {
|
|
1583
|
+
const { audits, groups } = category.refs.reduce(
|
|
1584
|
+
(acc, ref) => ({
|
|
1585
|
+
...acc,
|
|
1586
|
+
...ref.type === "group" ? {
|
|
1587
|
+
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1588
|
+
} : {
|
|
1589
|
+
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1590
|
+
}
|
|
1591
|
+
}),
|
|
1592
|
+
{ groups: [], audits: [] }
|
|
1593
|
+
);
|
|
1594
|
+
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
1595
|
+
compareCategoryAuditsAndGroups
|
|
1767
1596
|
);
|
|
1597
|
+
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
1598
|
+
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
1599
|
+
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
1600
|
+
);
|
|
1601
|
+
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
1602
|
+
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
1603
|
+
);
|
|
1604
|
+
return aIndex - bIndex;
|
|
1605
|
+
});
|
|
1606
|
+
return { ...category, refs: sortedRefs };
|
|
1768
1607
|
});
|
|
1769
|
-
return
|
|
1608
|
+
return {
|
|
1609
|
+
...report,
|
|
1610
|
+
categories: sortedCategories,
|
|
1611
|
+
plugins: sortPlugins(plugins)
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function sortPlugins(plugins) {
|
|
1615
|
+
return plugins.map((plugin) => ({
|
|
1616
|
+
...plugin,
|
|
1617
|
+
audits: [...plugin.audits].sort(compareAudits).map(
|
|
1618
|
+
(audit) => audit.details?.issues ? {
|
|
1619
|
+
...audit,
|
|
1620
|
+
details: {
|
|
1621
|
+
...audit.details,
|
|
1622
|
+
issues: [...audit.details.issues].sort(compareIssues)
|
|
1623
|
+
}
|
|
1624
|
+
} : audit
|
|
1625
|
+
)
|
|
1626
|
+
}));
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1630
|
+
function categoriesOverviewSection(report) {
|
|
1631
|
+
const { categories, plugins } = report;
|
|
1632
|
+
return new MarkdownDocument2().table(
|
|
1633
|
+
[
|
|
1634
|
+
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
1635
|
+
{ heading: "\u2B50 Score", alignment: "center" },
|
|
1636
|
+
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
1637
|
+
],
|
|
1638
|
+
categories.map(({ title, refs, score, isBinary }) => [
|
|
1639
|
+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
1640
|
+
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1641
|
+
md3.link(`#${slugify(title)}`, title),
|
|
1642
|
+
md3`${scoreMarker(score)} ${md3.bold(
|
|
1643
|
+
formatReportScore(score)
|
|
1644
|
+
)}${binaryIconSuffix(score, isBinary)}`,
|
|
1645
|
+
countCategoryAudits(refs, plugins).toString()
|
|
1646
|
+
])
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
function categoriesDetailsSection(report) {
|
|
1650
|
+
const { categories, plugins } = report;
|
|
1651
|
+
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
1652
|
+
categories,
|
|
1653
|
+
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
1654
|
+
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
1655
|
+
formatReportScore(category.score)
|
|
1656
|
+
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
1657
|
+
).list(
|
|
1658
|
+
category.refs.map((ref) => {
|
|
1659
|
+
if (ref.type === "group") {
|
|
1660
|
+
const group = getSortableGroupByRef(ref, plugins);
|
|
1661
|
+
const groupAudits = group.refs.map(
|
|
1662
|
+
(groupRef) => getSortableAuditByRef(
|
|
1663
|
+
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
1664
|
+
plugins
|
|
1665
|
+
)
|
|
1666
|
+
);
|
|
1667
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1668
|
+
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1669
|
+
} else {
|
|
1670
|
+
const audit = getSortableAuditByRef(ref, plugins);
|
|
1671
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1672
|
+
return categoryRef(audit, pluginTitle);
|
|
1673
|
+
}
|
|
1674
|
+
})
|
|
1675
|
+
)
|
|
1676
|
+
);
|
|
1770
1677
|
}
|
|
1771
1678
|
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
1772
|
-
const auditTitleAsLink =
|
|
1679
|
+
const auditTitleAsLink = md3.link(
|
|
1773
1680
|
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
1774
1681
|
title
|
|
1775
1682
|
);
|
|
1776
1683
|
const marker = scoreMarker(score, "square");
|
|
1777
|
-
return
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
)}`
|
|
1781
|
-
);
|
|
1684
|
+
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
1685
|
+
pluginTitle
|
|
1686
|
+
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
1782
1687
|
}
|
|
1783
1688
|
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
1784
|
-
const groupTitle =
|
|
1785
|
-
|
|
1786
|
-
)
|
|
1787
|
-
const
|
|
1788
|
-
(
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1689
|
+
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
1690
|
+
pluginTitle
|
|
1691
|
+
)})`;
|
|
1692
|
+
const auditsList = md3.list(
|
|
1693
|
+
groupAudits.map(
|
|
1694
|
+
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
1695
|
+
const auditTitleLink = md3.link(
|
|
1696
|
+
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
1697
|
+
auditTitle
|
|
1698
|
+
);
|
|
1699
|
+
const marker = scoreMarker(auditScore, "square");
|
|
1700
|
+
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
1701
|
+
String(displayValue ?? value)
|
|
1702
|
+
)}`;
|
|
1703
|
+
}
|
|
1704
|
+
)
|
|
1802
1705
|
);
|
|
1803
|
-
return
|
|
1706
|
+
return md3`${groupTitle}${auditsList}`;
|
|
1707
|
+
}
|
|
1708
|
+
function binaryIconSuffix(score, isBinary) {
|
|
1709
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
1804
1710
|
}
|
|
1805
1711
|
|
|
1806
1712
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1807
|
-
var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link5, section: section4, code: codeMd } = md;
|
|
1808
|
-
var { bold: boldHtml, details: details2 } = html;
|
|
1809
1713
|
function auditDetailsAuditValue({
|
|
1810
1714
|
score,
|
|
1811
1715
|
value,
|
|
1812
1716
|
displayValue
|
|
1813
1717
|
}) {
|
|
1814
|
-
return `${scoreMarker(score, "square")} ${
|
|
1718
|
+
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
1815
1719
|
String(displayValue ?? value)
|
|
1816
1720
|
)} (score: ${formatReportScore(score)})`;
|
|
1817
1721
|
}
|
|
1818
1722
|
function generateMdReport(report) {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
`${FOOTER_PREFIX}${SPACE}${link5(README_LINK, "Code PushUp")}`
|
|
1827
|
-
);
|
|
1723
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
|
|
1724
|
+
report.categories.length > 0,
|
|
1725
|
+
(doc) => doc.$concat(
|
|
1726
|
+
categoriesOverviewSection(report),
|
|
1727
|
+
categoriesDetailsSection(report)
|
|
1728
|
+
)
|
|
1729
|
+
).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
1828
1730
|
}
|
|
1829
1731
|
function auditDetailsIssues(issues = []) {
|
|
1830
1732
|
if (issues.length === 0) {
|
|
1831
|
-
return
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
return { severity, message, file, line: "" };
|
|
1845
|
-
}
|
|
1846
|
-
const { startLine, endLine } = sourceVal.position;
|
|
1847
|
-
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1848
|
-
return { severity, message, file, line };
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
1736
|
+
[
|
|
1737
|
+
{ heading: "Severity", alignment: "center" },
|
|
1738
|
+
{ heading: "Message", alignment: "left" },
|
|
1739
|
+
{ heading: "Source file", alignment: "left" },
|
|
1740
|
+
{ heading: "Line(s)", alignment: "center" }
|
|
1741
|
+
],
|
|
1742
|
+
issues.map(({ severity: level, message, source }) => {
|
|
1743
|
+
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
1744
|
+
if (!source) {
|
|
1745
|
+
return [severity, message];
|
|
1849
1746
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1747
|
+
const file = md4.code(source.file);
|
|
1748
|
+
if (!source.position) {
|
|
1749
|
+
return [severity, message, file];
|
|
1750
|
+
}
|
|
1751
|
+
const { startLine, endLine } = source.position;
|
|
1752
|
+
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1753
|
+
return [severity, message, file, line];
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1853
1756
|
}
|
|
1854
1757
|
function auditDetails(audit) {
|
|
1855
|
-
const { table:
|
|
1758
|
+
const { table: table2, issues = [] } = audit.details ?? {};
|
|
1856
1759
|
const detailsValue = auditDetailsAuditValue(audit);
|
|
1857
|
-
if (issues.length === 0 &&
|
|
1858
|
-
return
|
|
1760
|
+
if (issues.length === 0 && !table2?.rows.length) {
|
|
1761
|
+
return new MarkdownDocument3().paragraph(detailsValue);
|
|
1859
1762
|
}
|
|
1860
|
-
const tableSectionContent =
|
|
1861
|
-
const issuesSectionContent = issues.length > 0
|
|
1862
|
-
return
|
|
1763
|
+
const tableSectionContent = table2 && tableSection(table2);
|
|
1764
|
+
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
|
|
1765
|
+
return new MarkdownDocument3().details(
|
|
1863
1766
|
detailsValue,
|
|
1864
|
-
|
|
1767
|
+
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
1865
1768
|
);
|
|
1866
1769
|
}
|
|
1867
1770
|
function auditsSection({
|
|
1868
1771
|
plugins
|
|
1869
1772
|
}) {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1773
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
1774
|
+
plugins.flatMap(
|
|
1775
|
+
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
1776
|
+
),
|
|
1777
|
+
(doc, { plugin, ...audit }) => {
|
|
1778
|
+
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
1876
1779
|
const detailsContent = auditDetails(audit);
|
|
1877
1780
|
const descriptionContent = metaDescription(audit);
|
|
1878
|
-
return
|
|
1879
|
-
}
|
|
1781
|
+
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
1782
|
+
}
|
|
1880
1783
|
);
|
|
1881
|
-
return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
|
|
1882
1784
|
}
|
|
1883
1785
|
function aboutSection(report) {
|
|
1884
1786
|
const { date, plugins } = report;
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
{
|
|
1902
|
-
key: "plugin",
|
|
1903
|
-
align: "left"
|
|
1904
|
-
},
|
|
1905
|
-
{
|
|
1906
|
-
key: "audits"
|
|
1907
|
-
},
|
|
1908
|
-
{
|
|
1909
|
-
key: "version"
|
|
1910
|
-
},
|
|
1911
|
-
{
|
|
1912
|
-
key: "duration"
|
|
1913
|
-
}
|
|
1787
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
1788
|
+
md4`Report was created by ${md4.link(
|
|
1789
|
+
README_LINK,
|
|
1790
|
+
"Code PushUp"
|
|
1791
|
+
)} on ${formatDate(new Date(date))}.`
|
|
1792
|
+
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
1793
|
+
}
|
|
1794
|
+
function pluginMetaTable({
|
|
1795
|
+
plugins
|
|
1796
|
+
}) {
|
|
1797
|
+
return [
|
|
1798
|
+
[
|
|
1799
|
+
{ heading: "Plugin", alignment: "left" },
|
|
1800
|
+
{ heading: "Audits", alignment: "center" },
|
|
1801
|
+
{ heading: "Version", alignment: "center" },
|
|
1802
|
+
{ heading: "Duration", alignment: "right" }
|
|
1914
1803
|
],
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
plugin: pluginTitle,
|
|
1923
|
-
audits: audits.length.toString(),
|
|
1924
|
-
version: codeMd(pluginVersion || ""),
|
|
1925
|
-
duration: formatDuration(pluginDuration)
|
|
1926
|
-
})
|
|
1927
|
-
)
|
|
1928
|
-
};
|
|
1804
|
+
plugins.map(({ title, audits, version: version2 = "", duration }) => [
|
|
1805
|
+
title,
|
|
1806
|
+
audits.length.toString(),
|
|
1807
|
+
version2 && md4.code(version2),
|
|
1808
|
+
formatDuration(duration)
|
|
1809
|
+
])
|
|
1810
|
+
];
|
|
1929
1811
|
}
|
|
1930
|
-
function
|
|
1812
|
+
function reportMetaTable({
|
|
1931
1813
|
commit,
|
|
1932
1814
|
version: version2,
|
|
1933
1815
|
duration,
|
|
1934
1816
|
plugins,
|
|
1935
1817
|
categories
|
|
1936
1818
|
}) {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
{
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
key: "version"
|
|
1946
|
-
},
|
|
1947
|
-
{
|
|
1948
|
-
key: "duration"
|
|
1949
|
-
},
|
|
1950
|
-
{
|
|
1951
|
-
key: "plugins"
|
|
1952
|
-
},
|
|
1953
|
-
{
|
|
1954
|
-
key: "categories"
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
key: "audits"
|
|
1958
|
-
}
|
|
1819
|
+
return [
|
|
1820
|
+
[
|
|
1821
|
+
{ heading: "Commit", alignment: "left" },
|
|
1822
|
+
{ heading: "Version", alignment: "center" },
|
|
1823
|
+
{ heading: "Duration", alignment: "right" },
|
|
1824
|
+
{ heading: "Plugins", alignment: "center" },
|
|
1825
|
+
{ heading: "Categories", alignment: "center" },
|
|
1826
|
+
{ heading: "Audits", alignment: "center" }
|
|
1959
1827
|
],
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
commit:
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
plugins
|
|
1966
|
-
categories
|
|
1967
|
-
|
|
1968
|
-
|
|
1828
|
+
[
|
|
1829
|
+
[
|
|
1830
|
+
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
1831
|
+
md4.code(version2),
|
|
1832
|
+
formatDuration(duration),
|
|
1833
|
+
plugins.length.toString(),
|
|
1834
|
+
categories.length.toString(),
|
|
1835
|
+
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
1836
|
+
]
|
|
1969
1837
|
]
|
|
1970
|
-
|
|
1838
|
+
];
|
|
1971
1839
|
}
|
|
1972
1840
|
|
|
1973
1841
|
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
table: table4,
|
|
1982
|
-
section: section5
|
|
1983
|
-
} = md;
|
|
1984
|
-
var { details: details3 } = html;
|
|
1842
|
+
import {
|
|
1843
|
+
MarkdownDocument as MarkdownDocument5,
|
|
1844
|
+
md as md6
|
|
1845
|
+
} from "build-md";
|
|
1846
|
+
|
|
1847
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
|
|
1848
|
+
import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
|
|
1985
1849
|
var MAX_ROWS = 100;
|
|
1986
|
-
function generateMdReportsDiff(diff) {
|
|
1987
|
-
return lines5(
|
|
1988
|
-
section5(formatDiffHeaderSection(diff)),
|
|
1989
|
-
formatDiffCategoriesSection(diff),
|
|
1990
|
-
formatDiffGroupsSection(diff),
|
|
1991
|
-
formatDiffAuditsSection(diff)
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
|
-
function formatDiffHeaderSection(diff) {
|
|
1995
|
-
const outcomeTexts = {
|
|
1996
|
-
positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
|
|
1997
|
-
negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
|
|
1998
|
-
mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
|
|
1999
|
-
"improvements and regressions"
|
|
2000
|
-
)}`,
|
|
2001
|
-
unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
|
|
2002
|
-
};
|
|
2003
|
-
const outcome = mergeDiffOutcomes(
|
|
2004
|
-
changesToDiffOutcomes([
|
|
2005
|
-
...diff.categories.changed,
|
|
2006
|
-
...diff.groups.changed,
|
|
2007
|
-
...diff.audits.changed
|
|
2008
|
-
])
|
|
2009
|
-
);
|
|
2010
|
-
const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2011
|
-
return lines5(
|
|
2012
|
-
h13("Code PushUp"),
|
|
2013
|
-
diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
function formatDiffCategoriesSection(diff) {
|
|
2017
|
-
const { changed, unchanged, added } = diff.categories;
|
|
2018
|
-
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2019
|
-
const hasChanges = unchanged.length < categoriesCount;
|
|
2020
|
-
if (categoriesCount === 0) {
|
|
2021
|
-
return "";
|
|
2022
|
-
}
|
|
2023
|
-
const columns = [
|
|
2024
|
-
{ key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
|
|
2025
|
-
{ key: "before", label: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score" },
|
|
2026
|
-
{ key: "after", label: "\u2B50 Current score" },
|
|
2027
|
-
{ key: "change", label: "\u{1F504} Score change" }
|
|
2028
|
-
];
|
|
2029
|
-
return lines5(
|
|
2030
|
-
h24("\u{1F3F7}\uFE0F Categories"),
|
|
2031
|
-
categoriesCount > 0 && table4({
|
|
2032
|
-
columns: hasChanges ? columns : columns.slice(0, 2),
|
|
2033
|
-
rows: [
|
|
2034
|
-
...sortChanges(changed).map((category) => ({
|
|
2035
|
-
category: formatTitle(category),
|
|
2036
|
-
after: formatScoreWithColor(category.scores.after),
|
|
2037
|
-
before: formatScoreWithColor(category.scores.before, {
|
|
2038
|
-
skipBold: true
|
|
2039
|
-
}),
|
|
2040
|
-
change: formatScoreChange(category.scores.diff)
|
|
2041
|
-
})),
|
|
2042
|
-
...added.map((category) => ({
|
|
2043
|
-
category: formatTitle(category),
|
|
2044
|
-
after: formatScoreWithColor(category.score),
|
|
2045
|
-
before: italicMd("n/a (\\*)"),
|
|
2046
|
-
change: italicMd("n/a (\\*)")
|
|
2047
|
-
})),
|
|
2048
|
-
...unchanged.map((category) => ({
|
|
2049
|
-
category: formatTitle(category),
|
|
2050
|
-
after: formatScoreWithColor(category.score),
|
|
2051
|
-
before: formatScoreWithColor(category.score, { skipBold: true }),
|
|
2052
|
-
change: "\u2013"
|
|
2053
|
-
}))
|
|
2054
|
-
].map(
|
|
2055
|
-
(row) => hasChanges ? row : { category: row.category, before: row.before }
|
|
2056
|
-
)
|
|
2057
|
-
}),
|
|
2058
|
-
added.length > 0 && section5(italicMd("(\\*) New category."))
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2061
|
-
function formatDiffGroupsSection(diff) {
|
|
2062
|
-
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2063
|
-
return "";
|
|
2064
|
-
}
|
|
2065
|
-
return lines5(
|
|
2066
|
-
h24("\u{1F5C3}\uFE0F Groups"),
|
|
2067
|
-
formatGroupsOrAuditsDetails("group", diff.groups, {
|
|
2068
|
-
columns: [
|
|
2069
|
-
{ key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
|
|
2070
|
-
{ key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
|
|
2071
|
-
{ key: "before", label: "\u2B50 Previous score" },
|
|
2072
|
-
{ key: "after", label: "\u2B50 Current score" },
|
|
2073
|
-
{ key: "change", label: "\u{1F504} Score change" }
|
|
2074
|
-
],
|
|
2075
|
-
rows: sortChanges(diff.groups.changed).map((group) => ({
|
|
2076
|
-
plugin: formatTitle(group.plugin),
|
|
2077
|
-
group: formatTitle(group),
|
|
2078
|
-
after: formatScoreWithColor(group.scores.after),
|
|
2079
|
-
before: formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2080
|
-
change: formatScoreChange(group.scores.diff)
|
|
2081
|
-
}))
|
|
2082
|
-
})
|
|
2083
|
-
);
|
|
2084
|
-
}
|
|
2085
|
-
function formatDiffAuditsSection(diff) {
|
|
2086
|
-
return lines5(
|
|
2087
|
-
h24("\u{1F6E1}\uFE0F Audits"),
|
|
2088
|
-
formatGroupsOrAuditsDetails("audit", diff.audits, {
|
|
2089
|
-
columns: [
|
|
2090
|
-
{ key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
|
|
2091
|
-
{ key: "audit", label: "\u{1F6E1}\uFE0F Audit", align: "left" },
|
|
2092
|
-
{ key: "before", label: "\u{1F4CF} Previous value" },
|
|
2093
|
-
{ key: "after", label: "\u{1F4CF} Current value" },
|
|
2094
|
-
{ key: "change", label: "\u{1F504} Value change" }
|
|
2095
|
-
],
|
|
2096
|
-
rows: sortChanges(diff.audits.changed).map((audit) => ({
|
|
2097
|
-
plugin: formatTitle(audit.plugin),
|
|
2098
|
-
audit: formatTitle(audit),
|
|
2099
|
-
after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
|
|
2100
|
-
audit.displayValues.after || audit.values.after.toString()
|
|
2101
|
-
)}`,
|
|
2102
|
-
before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2103
|
-
change: formatValueChange(audit)
|
|
2104
|
-
}))
|
|
2105
|
-
})
|
|
2106
|
-
);
|
|
2107
|
-
}
|
|
2108
|
-
function formatGroupsOrAuditsDetails(token, { changed, unchanged }, tableData) {
|
|
2109
|
-
return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details3(
|
|
2110
|
-
summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
|
|
2111
|
-
lines5(
|
|
2112
|
-
table4({
|
|
2113
|
-
...tableData,
|
|
2114
|
-
rows: tableData.rows.slice(0, MAX_ROWS)
|
|
2115
|
-
// use never to avoid typing problem
|
|
2116
|
-
}),
|
|
2117
|
-
changed.length > MAX_ROWS && italicMd(
|
|
2118
|
-
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
2119
|
-
token
|
|
2120
|
-
)} are listed above for brevity.`
|
|
2121
|
-
),
|
|
2122
|
-
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
2123
|
-
)
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
function formatScoreChange(diff) {
|
|
2127
|
-
const marker = getDiffMarker(diff);
|
|
2128
|
-
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
2129
|
-
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
2130
|
-
}
|
|
2131
|
-
function formatValueChange({
|
|
2132
|
-
values,
|
|
2133
|
-
scores
|
|
2134
|
-
}) {
|
|
2135
|
-
const marker = getDiffMarker(values.diff);
|
|
2136
|
-
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
2137
|
-
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
2138
|
-
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
2139
|
-
}
|
|
2140
1850
|
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
unchanged.length === 1 ? "is" : "are",
|
|
2145
|
-
"unchanged."
|
|
2146
|
-
].join(" ")
|
|
2147
|
-
);
|
|
1851
|
+
const pluralizedCount = changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`;
|
|
1852
|
+
const pluralizedVerb = unchanged.length === 1 ? "is" : "are";
|
|
1853
|
+
return `${pluralizedCount} ${pluralizedVerb} unchanged.`;
|
|
2148
1854
|
}
|
|
2149
1855
|
function summarizeDiffOutcomes(outcomes, token) {
|
|
2150
1856
|
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
@@ -2164,20 +1870,46 @@ function summarizeDiffOutcomes(outcomes, token) {
|
|
|
2164
1870
|
}
|
|
2165
1871
|
}).join(", ");
|
|
2166
1872
|
}
|
|
1873
|
+
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
1874
|
+
if (changed.length === 0) {
|
|
1875
|
+
return new MarkdownDocument4().paragraph(
|
|
1876
|
+
summarizeUnchanged(token, { changed, unchanged })
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
return new MarkdownDocument4().table(columns, rows.slice(0, MAX_ROWS)).paragraph(
|
|
1880
|
+
changed.length > MAX_ROWS && md5.italic(
|
|
1881
|
+
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
1882
|
+
token
|
|
1883
|
+
)} are listed above for brevity.`
|
|
1884
|
+
)
|
|
1885
|
+
).paragraph(
|
|
1886
|
+
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
2167
1889
|
function formatTitle({
|
|
2168
1890
|
title,
|
|
2169
1891
|
docsUrl
|
|
2170
1892
|
}) {
|
|
2171
1893
|
if (docsUrl) {
|
|
2172
|
-
return
|
|
1894
|
+
return md5.link(docsUrl, title);
|
|
2173
1895
|
}
|
|
2174
1896
|
return title;
|
|
2175
1897
|
}
|
|
1898
|
+
function formatPortalLink(portalUrl) {
|
|
1899
|
+
return portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}");
|
|
1900
|
+
}
|
|
2176
1901
|
function sortChanges(changes) {
|
|
2177
1902
|
return [...changes].sort(
|
|
2178
1903
|
(a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
|
|
2179
1904
|
);
|
|
2180
1905
|
}
|
|
1906
|
+
function getDiffChanges(diff) {
|
|
1907
|
+
return [
|
|
1908
|
+
...diff.categories.changed,
|
|
1909
|
+
...diff.groups.changed,
|
|
1910
|
+
...diff.audits.changed
|
|
1911
|
+
];
|
|
1912
|
+
}
|
|
2181
1913
|
function changesToDiffOutcomes(changes) {
|
|
2182
1914
|
return changes.map((change) => {
|
|
2183
1915
|
if (change.scores.diff > 0) {
|
|
@@ -2212,9 +1944,218 @@ function countDiffOutcomes(outcomes) {
|
|
|
2212
1944
|
unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
|
|
2213
1945
|
};
|
|
2214
1946
|
}
|
|
1947
|
+
function formatReportOutcome(outcome, commits) {
|
|
1948
|
+
const outcomeTexts = {
|
|
1949
|
+
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
1950
|
+
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
1951
|
+
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
1952
|
+
"improvements and regressions"
|
|
1953
|
+
)}`,
|
|
1954
|
+
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
1955
|
+
};
|
|
1956
|
+
if (commits) {
|
|
1957
|
+
const commitsText = `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
1958
|
+
return md5`${outcomeTexts[outcome]} – ${commitsText}.`;
|
|
1959
|
+
}
|
|
1960
|
+
return md5`${outcomeTexts[outcome]}.`;
|
|
1961
|
+
}
|
|
1962
|
+
function compareDiffsBy(type, a, b) {
|
|
1963
|
+
return sumScoreChanges(b[type].changed) - sumScoreChanges(a[type].changed) || sumConfigChanges(b[type]) - sumConfigChanges(a[type]);
|
|
1964
|
+
}
|
|
1965
|
+
function sumScoreChanges(changes) {
|
|
1966
|
+
return changes.reduce(
|
|
1967
|
+
(acc, { scores }) => acc + Math.abs(scores.diff),
|
|
1968
|
+
0
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
function sumConfigChanges({
|
|
1972
|
+
added,
|
|
1973
|
+
removed
|
|
1974
|
+
}) {
|
|
1975
|
+
return added.length + removed.length;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
1979
|
+
function generateMdReportsDiff(diff) {
|
|
1980
|
+
return new MarkdownDocument5().$concat(
|
|
1981
|
+
createDiffHeaderSection(diff),
|
|
1982
|
+
createDiffCategoriesSection(diff),
|
|
1983
|
+
createDiffDetailsSection(diff)
|
|
1984
|
+
).toString();
|
|
1985
|
+
}
|
|
1986
|
+
function generateMdReportsDiffForMonorepo(diffs) {
|
|
1987
|
+
const diffsWithOutcomes = diffs.map((diff) => ({
|
|
1988
|
+
...diff,
|
|
1989
|
+
outcome: mergeDiffOutcomes(changesToDiffOutcomes(getDiffChanges(diff)))
|
|
1990
|
+
})).sort(
|
|
1991
|
+
(a, b) => compareDiffsBy("categories", a, b) || compareDiffsBy("groups", a, b) || compareDiffsBy("audits", a, b) || a.label.localeCompare(b.label)
|
|
1992
|
+
);
|
|
1993
|
+
const unchanged = diffsWithOutcomes.filter(
|
|
1994
|
+
({ outcome }) => outcome === "unchanged"
|
|
1995
|
+
);
|
|
1996
|
+
const changed = diffsWithOutcomes.filter((diff) => !unchanged.includes(diff));
|
|
1997
|
+
return new MarkdownDocument5().$concat(
|
|
1998
|
+
createDiffHeaderSection(diffs),
|
|
1999
|
+
...changed.map(createDiffProjectSection)
|
|
2000
|
+
).$if(
|
|
2001
|
+
unchanged.length > 0,
|
|
2002
|
+
(doc) => doc.rule().paragraph(summarizeUnchanged("project", { unchanged, changed }))
|
|
2003
|
+
).toString();
|
|
2004
|
+
}
|
|
2005
|
+
function createDiffHeaderSection(diff) {
|
|
2006
|
+
const outcome = mergeDiffOutcomes(
|
|
2007
|
+
changesToDiffOutcomes(toArray(diff).flatMap(getDiffChanges))
|
|
2008
|
+
);
|
|
2009
|
+
const commits = Array.isArray(diff) ? diff[0]?.commits : diff.commits;
|
|
2010
|
+
const portalUrl = Array.isArray(diff) ? void 0 : diff.portalUrl;
|
|
2011
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_1, "Code PushUp").paragraph(formatReportOutcome(outcome, commits)).paragraph(formatPortalLink(portalUrl));
|
|
2012
|
+
}
|
|
2013
|
+
function createDiffProjectSection(diff) {
|
|
2014
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_2, md6`💼 Project ${md6.code(diff.label)}`).paragraph(formatReportOutcome(diff.outcome)).paragraph(formatPortalLink(diff.portalUrl)).$concat(
|
|
2015
|
+
createDiffCategoriesSection(diff, {
|
|
2016
|
+
skipHeading: true,
|
|
2017
|
+
skipUnchanged: true
|
|
2018
|
+
}),
|
|
2019
|
+
createDiffDetailsSection(diff, HIERARCHY.level_3)
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
function createDiffCategoriesSection(diff, options) {
|
|
2023
|
+
const { changed, unchanged, added } = diff.categories;
|
|
2024
|
+
const { skipHeading, skipUnchanged } = options ?? {};
|
|
2025
|
+
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2026
|
+
const hasChanges = unchanged.length < categoriesCount;
|
|
2027
|
+
if (categoriesCount === 0) {
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
const [columns, rows] = createCategoriesTable(diff, {
|
|
2031
|
+
hasChanges,
|
|
2032
|
+
skipUnchanged
|
|
2033
|
+
});
|
|
2034
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_2, !skipHeading && "\u{1F3F7}\uFE0F Categories").table(columns, rows).paragraph(added.length > 0 && md6.italic("(\\*) New category.")).paragraph(
|
|
2035
|
+
skipUnchanged && unchanged.length > 0 && summarizeUnchanged("category", { changed, unchanged })
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
function createCategoriesTable(diff, options) {
|
|
2039
|
+
const { changed, unchanged, added } = diff.categories;
|
|
2040
|
+
const { hasChanges, skipUnchanged } = options;
|
|
2041
|
+
const columns = [
|
|
2042
|
+
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
2043
|
+
{
|
|
2044
|
+
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
2045
|
+
alignment: "center"
|
|
2046
|
+
},
|
|
2047
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2048
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2049
|
+
];
|
|
2050
|
+
const rows = [
|
|
2051
|
+
...sortChanges(changed).map((category) => [
|
|
2052
|
+
formatTitle(category),
|
|
2053
|
+
formatScoreWithColor(category.scores.before, {
|
|
2054
|
+
skipBold: true
|
|
2055
|
+
}),
|
|
2056
|
+
formatScoreWithColor(category.scores.after),
|
|
2057
|
+
formatScoreChange(category.scores.diff)
|
|
2058
|
+
]),
|
|
2059
|
+
...added.map((category) => [
|
|
2060
|
+
formatTitle(category),
|
|
2061
|
+
md6.italic("n/a (\\*)"),
|
|
2062
|
+
formatScoreWithColor(category.score),
|
|
2063
|
+
md6.italic("n/a (\\*)")
|
|
2064
|
+
]),
|
|
2065
|
+
...skipUnchanged ? [] : unchanged.map((category) => [
|
|
2066
|
+
formatTitle(category),
|
|
2067
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
2068
|
+
formatScoreWithColor(category.score),
|
|
2069
|
+
"\u2013"
|
|
2070
|
+
])
|
|
2071
|
+
];
|
|
2072
|
+
return [
|
|
2073
|
+
hasChanges ? columns : columns.slice(0, 2),
|
|
2074
|
+
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
2075
|
+
];
|
|
2076
|
+
}
|
|
2077
|
+
function createDiffDetailsSection(diff, level = HIERARCHY.level_2) {
|
|
2078
|
+
if (diff.groups.changed.length + diff.audits.changed.length === 0) {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
const summary = ["group", "audit"].map(
|
|
2082
|
+
(token) => summarizeDiffOutcomes(
|
|
2083
|
+
changesToDiffOutcomes(diff[`${token}s`].changed),
|
|
2084
|
+
token
|
|
2085
|
+
)
|
|
2086
|
+
).filter(Boolean).join(", ");
|
|
2087
|
+
const details2 = new MarkdownDocument5().$concat(
|
|
2088
|
+
createDiffGroupsSection(diff, level),
|
|
2089
|
+
createDiffAuditsSection(diff, level)
|
|
2090
|
+
);
|
|
2091
|
+
return new MarkdownDocument5().details(summary, details2);
|
|
2092
|
+
}
|
|
2093
|
+
function createDiffGroupsSection(diff, level) {
|
|
2094
|
+
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2095
|
+
return null;
|
|
2096
|
+
}
|
|
2097
|
+
return new MarkdownDocument5().heading(level, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
2098
|
+
createGroupsOrAuditsDetails(
|
|
2099
|
+
"group",
|
|
2100
|
+
diff.groups,
|
|
2101
|
+
[
|
|
2102
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2103
|
+
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
2104
|
+
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
2105
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2106
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2107
|
+
],
|
|
2108
|
+
sortChanges(diff.groups.changed).map((group) => [
|
|
2109
|
+
formatTitle(group.plugin),
|
|
2110
|
+
formatTitle(group),
|
|
2111
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2112
|
+
formatScoreWithColor(group.scores.after),
|
|
2113
|
+
formatScoreChange(group.scores.diff)
|
|
2114
|
+
])
|
|
2115
|
+
)
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
function createDiffAuditsSection(diff, level) {
|
|
2119
|
+
return new MarkdownDocument5().heading(level, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
2120
|
+
createGroupsOrAuditsDetails(
|
|
2121
|
+
"audit",
|
|
2122
|
+
diff.audits,
|
|
2123
|
+
[
|
|
2124
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2125
|
+
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
2126
|
+
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
2127
|
+
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
2128
|
+
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2129
|
+
],
|
|
2130
|
+
sortChanges(diff.audits.changed).map((audit) => [
|
|
2131
|
+
formatTitle(audit.plugin),
|
|
2132
|
+
formatTitle(audit),
|
|
2133
|
+
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2134
|
+
md6`${scoreMarker(audit.scores.after, "square")} ${md6.bold(
|
|
2135
|
+
audit.displayValues.after || audit.values.after.toString()
|
|
2136
|
+
)}`,
|
|
2137
|
+
formatValueChange(audit)
|
|
2138
|
+
])
|
|
2139
|
+
)
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// packages/utils/src/lib/reports/load-report.ts
|
|
2144
|
+
import { join as join2 } from "node:path";
|
|
2145
|
+
async function loadReport(options) {
|
|
2146
|
+
const { outputDir, filename, format } = options;
|
|
2147
|
+
await ensureDirectoryExists(outputDir);
|
|
2148
|
+
const filePath = join2(outputDir, `${filename}.${format}`);
|
|
2149
|
+
if (format === "json") {
|
|
2150
|
+
const content = await readJsonFile(filePath);
|
|
2151
|
+
return reportSchema.parse(content);
|
|
2152
|
+
}
|
|
2153
|
+
const text = await readTextFile(filePath);
|
|
2154
|
+
return text;
|
|
2155
|
+
}
|
|
2215
2156
|
|
|
2216
2157
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2217
|
-
import
|
|
2158
|
+
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2218
2159
|
function log(msg = "") {
|
|
2219
2160
|
ui().logger.log(msg);
|
|
2220
2161
|
}
|
|
@@ -2231,14 +2172,14 @@ function logStdoutSummary(report) {
|
|
|
2231
2172
|
}
|
|
2232
2173
|
function reportToHeaderSection(report) {
|
|
2233
2174
|
const { packageName, version: version2 } = report;
|
|
2234
|
-
return `${
|
|
2175
|
+
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version2}`;
|
|
2235
2176
|
}
|
|
2236
2177
|
function logPlugins(report) {
|
|
2237
2178
|
const { plugins } = report;
|
|
2238
2179
|
plugins.forEach((plugin) => {
|
|
2239
2180
|
const { title, audits } = plugin;
|
|
2240
2181
|
log();
|
|
2241
|
-
log(
|
|
2182
|
+
log(bold4.magentaBright(`${title} audits`));
|
|
2242
2183
|
log();
|
|
2243
2184
|
audits.forEach((audit) => {
|
|
2244
2185
|
ui().row([
|
|
@@ -2253,8 +2194,9 @@ function logPlugins(report) {
|
|
|
2253
2194
|
padding: [0, 3, 0, 0]
|
|
2254
2195
|
},
|
|
2255
2196
|
{
|
|
2256
|
-
text:
|
|
2257
|
-
|
|
2197
|
+
text: cyanBright(audit.displayValue || `${audit.value}`),
|
|
2198
|
+
// eslint-disable-next-line no-magic-numbers
|
|
2199
|
+
width: 20,
|
|
2258
2200
|
padding: [0, 0, 0, 0]
|
|
2259
2201
|
}
|
|
2260
2202
|
]);
|
|
@@ -2264,42 +2206,38 @@ function logPlugins(report) {
|
|
|
2264
2206
|
}
|
|
2265
2207
|
function logCategories({ categories, plugins }) {
|
|
2266
2208
|
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2267
|
-
const rows = categories.map(({ title, score, refs }) => [
|
|
2209
|
+
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2268
2210
|
title,
|
|
2269
|
-
applyScoreColor({ score })
|
|
2211
|
+
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2270
2212
|
countCategoryAudits(refs, plugins)
|
|
2271
2213
|
]);
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
content:
|
|
2214
|
+
const table2 = ui().table();
|
|
2215
|
+
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2216
|
+
table2.head(
|
|
2217
|
+
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2218
|
+
content: cyan(heading),
|
|
2277
2219
|
hAlign: hAlign(idx)
|
|
2278
2220
|
}))
|
|
2279
2221
|
);
|
|
2280
2222
|
rows.forEach(
|
|
2281
|
-
(row) =>
|
|
2223
|
+
(row) => table2.row(
|
|
2282
2224
|
row.map((content, idx) => ({
|
|
2283
2225
|
content: content.toString(),
|
|
2284
2226
|
hAlign: hAlign(idx)
|
|
2285
2227
|
}))
|
|
2286
2228
|
)
|
|
2287
2229
|
);
|
|
2288
|
-
log(
|
|
2230
|
+
log(bold4.magentaBright("Categories"));
|
|
2289
2231
|
log();
|
|
2290
|
-
|
|
2232
|
+
table2.render();
|
|
2291
2233
|
log();
|
|
2292
2234
|
}
|
|
2293
|
-
function
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
}
|
|
2299
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
2300
|
-
return style.yellow(formattedScore);
|
|
2301
|
-
}
|
|
2302
|
-
return style.red(formattedScore);
|
|
2235
|
+
function binaryIconPrefix(score, isBinary) {
|
|
2236
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2237
|
+
passIcon: bold4(green2("\u2713")),
|
|
2238
|
+
failIcon: bold4(red("\u2717")),
|
|
2239
|
+
postfix: " "
|
|
2240
|
+
});
|
|
2303
2241
|
}
|
|
2304
2242
|
|
|
2305
2243
|
// packages/utils/src/lib/reports/scoring.ts
|
|
@@ -2389,56 +2327,6 @@ function parseScoringParameters(refs, scoreFn) {
|
|
|
2389
2327
|
return scoredRefs;
|
|
2390
2328
|
}
|
|
2391
2329
|
|
|
2392
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
2393
|
-
function sortReport(report) {
|
|
2394
|
-
const { categories, plugins } = report;
|
|
2395
|
-
const sortedCategories = categories.map((category) => {
|
|
2396
|
-
const { audits, groups } = category.refs.reduce(
|
|
2397
|
-
(acc, ref) => ({
|
|
2398
|
-
...acc,
|
|
2399
|
-
...ref.type === "group" ? {
|
|
2400
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
2401
|
-
} : {
|
|
2402
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
2403
|
-
}
|
|
2404
|
-
}),
|
|
2405
|
-
{ groups: [], audits: [] }
|
|
2406
|
-
);
|
|
2407
|
-
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
2408
|
-
compareCategoryAuditsAndGroups
|
|
2409
|
-
);
|
|
2410
|
-
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
2411
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2412
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2413
|
-
);
|
|
2414
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2415
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2416
|
-
);
|
|
2417
|
-
return aIndex - bIndex;
|
|
2418
|
-
});
|
|
2419
|
-
return { ...category, refs: sortedRefs };
|
|
2420
|
-
});
|
|
2421
|
-
return {
|
|
2422
|
-
...report,
|
|
2423
|
-
categories: sortedCategories,
|
|
2424
|
-
plugins: sortPlugins(plugins)
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
function sortPlugins(plugins) {
|
|
2428
|
-
return plugins.map((plugin) => ({
|
|
2429
|
-
...plugin,
|
|
2430
|
-
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2431
|
-
(audit) => audit.details?.issues ? {
|
|
2432
|
-
...audit,
|
|
2433
|
-
details: {
|
|
2434
|
-
...audit.details,
|
|
2435
|
-
issues: [...audit.details.issues].sort(compareIssues)
|
|
2436
|
-
}
|
|
2437
|
-
} : audit
|
|
2438
|
-
)
|
|
2439
|
-
}));
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
2330
|
// packages/utils/src/lib/verbose-utils.ts
|
|
2443
2331
|
function getLogVerbose(verbose = false) {
|
|
2444
2332
|
return (msg) => {
|
|
@@ -2461,10 +2349,10 @@ var verboseUtils = (verbose = false) => ({
|
|
|
2461
2349
|
|
|
2462
2350
|
// packages/core/package.json
|
|
2463
2351
|
var name = "@code-pushup/core";
|
|
2464
|
-
var version = "0.
|
|
2352
|
+
var version = "0.50.0";
|
|
2465
2353
|
|
|
2466
2354
|
// packages/core/src/lib/implementation/execute-plugin.ts
|
|
2467
|
-
import
|
|
2355
|
+
import { bold as bold5 } from "ansis";
|
|
2468
2356
|
|
|
2469
2357
|
// packages/core/src/lib/normalize.ts
|
|
2470
2358
|
function normalizeIssue(issue, gitRoot) {
|
|
@@ -2527,7 +2415,7 @@ async function executeRunnerFunction(runner, onProgress) {
|
|
|
2527
2415
|
var PluginOutputMissingAuditError = class extends Error {
|
|
2528
2416
|
constructor(auditSlug) {
|
|
2529
2417
|
super(
|
|
2530
|
-
`Audit metadata not present in plugin config. Missing slug: ${
|
|
2418
|
+
`Audit metadata not present in plugin config. Missing slug: ${bold5(
|
|
2531
2419
|
auditSlug
|
|
2532
2420
|
)}`
|
|
2533
2421
|
);
|
|
@@ -2569,7 +2457,7 @@ async function executePlugin(pluginConfig, onProgress) {
|
|
|
2569
2457
|
};
|
|
2570
2458
|
}
|
|
2571
2459
|
var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
2572
|
-
progressBar?.updateTitle(`Executing ${
|
|
2460
|
+
progressBar?.updateTitle(`Executing ${bold5(pluginCfg.title)}`);
|
|
2573
2461
|
try {
|
|
2574
2462
|
const pluginReport = await executePlugin(pluginCfg);
|
|
2575
2463
|
progressBar?.incrementInSteps(steps);
|
|
@@ -2577,7 +2465,7 @@ var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
|
2577
2465
|
} catch (error) {
|
|
2578
2466
|
progressBar?.incrementInSteps(steps);
|
|
2579
2467
|
throw new Error(
|
|
2580
|
-
error instanceof Error ? `- Plugin ${
|
|
2468
|
+
error instanceof Error ? `- Plugin ${bold5(pluginCfg.title)} (${bold5(
|
|
2581
2469
|
pluginCfg.slug
|
|
2582
2470
|
)}) produced the following error:
|
|
2583
2471
|
- ${error.message}` : String(error)
|
|
@@ -2717,6 +2605,10 @@ async function collectAndPersistReports(options) {
|
|
|
2717
2605
|
// packages/core/src/lib/compare.ts
|
|
2718
2606
|
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
2719
2607
|
import { join as join5 } from "node:path";
|
|
2608
|
+
import {
|
|
2609
|
+
PortalOperationError,
|
|
2610
|
+
getPortalComparisonLink
|
|
2611
|
+
} from "@code-pushup/portal-client";
|
|
2720
2612
|
|
|
2721
2613
|
// packages/core/src/lib/implementation/compare-scorables.ts
|
|
2722
2614
|
function compareCategories(reports) {
|
|
@@ -2853,7 +2745,7 @@ function selectMeta(meta) {
|
|
|
2853
2745
|
}
|
|
2854
2746
|
|
|
2855
2747
|
// packages/core/src/lib/compare.ts
|
|
2856
|
-
async function compareReportFiles(inputPaths, persistConfig) {
|
|
2748
|
+
async function compareReportFiles(inputPaths, persistConfig, uploadConfig, label) {
|
|
2857
2749
|
const { outputDir, filename, format } = persistConfig;
|
|
2858
2750
|
const [reportBefore, reportAfter] = await Promise.all([
|
|
2859
2751
|
readJsonFile(inputPaths.before),
|
|
@@ -2863,11 +2755,20 @@ async function compareReportFiles(inputPaths, persistConfig) {
|
|
|
2863
2755
|
before: reportSchema.parse(reportBefore),
|
|
2864
2756
|
after: reportSchema.parse(reportAfter)
|
|
2865
2757
|
};
|
|
2866
|
-
const
|
|
2758
|
+
const diff = compareReports(reports);
|
|
2759
|
+
if (label) {
|
|
2760
|
+
diff.label = label;
|
|
2761
|
+
}
|
|
2762
|
+
if (uploadConfig && diff.commits) {
|
|
2763
|
+
diff.portalUrl = await fetchPortalComparisonLink(
|
|
2764
|
+
uploadConfig,
|
|
2765
|
+
diff.commits
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2867
2768
|
return Promise.all(
|
|
2868
2769
|
format.map(async (fmt) => {
|
|
2869
2770
|
const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
|
|
2870
|
-
const content = reportsDiffToFileContent(
|
|
2771
|
+
const content = reportsDiffToFileContent(diff, fmt);
|
|
2871
2772
|
await ensureDirectoryExists(outputDir);
|
|
2872
2773
|
await writeFile2(outputPath, content);
|
|
2873
2774
|
return outputPath;
|
|
@@ -2905,6 +2806,29 @@ function reportsDiffToFileContent(reportsDiff, format) {
|
|
|
2905
2806
|
return generateMdReportsDiff(reportsDiff);
|
|
2906
2807
|
}
|
|
2907
2808
|
}
|
|
2809
|
+
async function fetchPortalComparisonLink(uploadConfig, commits) {
|
|
2810
|
+
const { server, apiKey, organization, project } = uploadConfig;
|
|
2811
|
+
try {
|
|
2812
|
+
return await getPortalComparisonLink({
|
|
2813
|
+
server,
|
|
2814
|
+
apiKey,
|
|
2815
|
+
parameters: {
|
|
2816
|
+
organization,
|
|
2817
|
+
project,
|
|
2818
|
+
before: commits.before.hash,
|
|
2819
|
+
after: commits.after.hash
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
} catch (error) {
|
|
2823
|
+
if (error instanceof PortalOperationError) {
|
|
2824
|
+
ui().logger.warning(
|
|
2825
|
+
`Failed to fetch portal comparison link - ${error.message}`
|
|
2826
|
+
);
|
|
2827
|
+
return void 0;
|
|
2828
|
+
}
|
|
2829
|
+
throw error;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2908
2832
|
|
|
2909
2833
|
// packages/core/src/lib/upload.ts
|
|
2910
2834
|
import {
|
|
@@ -2960,9 +2884,9 @@ function auditToGQL(audit) {
|
|
|
2960
2884
|
score,
|
|
2961
2885
|
value,
|
|
2962
2886
|
displayValue: formattedValue,
|
|
2963
|
-
details:
|
|
2887
|
+
details: details2
|
|
2964
2888
|
} = audit;
|
|
2965
|
-
const { issues, table:
|
|
2889
|
+
const { issues, table: table2 } = details2 ?? {};
|
|
2966
2890
|
return {
|
|
2967
2891
|
slug,
|
|
2968
2892
|
title,
|
|
@@ -2971,10 +2895,10 @@ function auditToGQL(audit) {
|
|
|
2971
2895
|
score,
|
|
2972
2896
|
value,
|
|
2973
2897
|
formattedValue,
|
|
2974
|
-
...
|
|
2898
|
+
...details2 && {
|
|
2975
2899
|
details: {
|
|
2976
2900
|
...issues && { issues: issues.map(issueToGQL) },
|
|
2977
|
-
...
|
|
2901
|
+
...table2 && { tables: [tableToGQL(table2)] }
|
|
2978
2902
|
}
|
|
2979
2903
|
}
|
|
2980
2904
|
};
|
|
@@ -2993,11 +2917,11 @@ function issueToGQL(issue) {
|
|
|
2993
2917
|
}
|
|
2994
2918
|
};
|
|
2995
2919
|
}
|
|
2996
|
-
function tableToGQL(
|
|
2920
|
+
function tableToGQL(table2) {
|
|
2997
2921
|
return {
|
|
2998
|
-
...
|
|
2999
|
-
...
|
|
3000
|
-
columns:
|
|
2922
|
+
...table2.title && { title: table2.title },
|
|
2923
|
+
...table2.columns?.length && {
|
|
2924
|
+
columns: table2.columns.map(
|
|
3001
2925
|
(column) => typeof column === "string" ? { alignment: tableAlignmentToGQL(column) } : {
|
|
3002
2926
|
key: column.key,
|
|
3003
2927
|
label: column.label,
|
|
@@ -3005,7 +2929,7 @@ function tableToGQL(table5) {
|
|
|
3005
2929
|
}
|
|
3006
2930
|
)
|
|
3007
2931
|
},
|
|
3008
|
-
rows:
|
|
2932
|
+
rows: table2.rows.map(
|
|
3009
2933
|
(row) => Array.isArray(row) ? row.map((content) => ({ content: content?.toString() ?? "" })) : Object.entries(row).map(([key, content]) => ({
|
|
3010
2934
|
key,
|
|
3011
2935
|
content: content?.toString() ?? ""
|
|
@@ -3151,6 +3075,45 @@ async function autoloadRc(tsconfig) {
|
|
|
3151
3075
|
tsconfig
|
|
3152
3076
|
);
|
|
3153
3077
|
}
|
|
3078
|
+
|
|
3079
|
+
// packages/core/src/lib/merge-diffs.ts
|
|
3080
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
3081
|
+
import { basename, dirname, join as join7 } from "node:path";
|
|
3082
|
+
async function mergeDiffs(files, persistConfig) {
|
|
3083
|
+
const results = await Promise.allSettled(
|
|
3084
|
+
files.map(async (file) => {
|
|
3085
|
+
const json = await readJsonFile(file).catch((error) => {
|
|
3086
|
+
throw new Error(
|
|
3087
|
+
`Failed to read JSON file ${file} - ${stringifyError(error)}`
|
|
3088
|
+
);
|
|
3089
|
+
});
|
|
3090
|
+
const result = await reportsDiffSchema.safeParseAsync(json);
|
|
3091
|
+
if (!result.success) {
|
|
3092
|
+
throw new Error(
|
|
3093
|
+
`Invalid reports diff in ${file} - ${result.error.message}`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
return { ...result.data, file };
|
|
3097
|
+
})
|
|
3098
|
+
);
|
|
3099
|
+
results.filter(isPromiseRejectedResult).forEach(({ reason }) => {
|
|
3100
|
+
ui().logger.warning(
|
|
3101
|
+
`Skipped invalid report diff - ${stringifyError(reason)}`
|
|
3102
|
+
);
|
|
3103
|
+
});
|
|
3104
|
+
const diffs = results.filter(isPromiseFulfilledResult).map(({ value }) => value);
|
|
3105
|
+
const labeledDiffs = diffs.map((diff) => ({
|
|
3106
|
+
...diff,
|
|
3107
|
+
label: diff.label || basename(dirname(diff.file))
|
|
3108
|
+
// fallback is parent folder name
|
|
3109
|
+
}));
|
|
3110
|
+
const markdown = generateMdReportsDiffForMonorepo(labeledDiffs);
|
|
3111
|
+
const { outputDir, filename } = persistConfig;
|
|
3112
|
+
const outputPath = join7(outputDir, `${filename}-diff.md`);
|
|
3113
|
+
await ensureDirectoryExists(outputDir);
|
|
3114
|
+
await writeFile3(outputPath, markdown);
|
|
3115
|
+
return outputPath;
|
|
3116
|
+
}
|
|
3154
3117
|
export {
|
|
3155
3118
|
ConfigPathError,
|
|
3156
3119
|
PersistDirError,
|
|
@@ -3164,6 +3127,7 @@ export {
|
|
|
3164
3127
|
executePlugin,
|
|
3165
3128
|
executePlugins,
|
|
3166
3129
|
history,
|
|
3130
|
+
mergeDiffs,
|
|
3167
3131
|
persistReport,
|
|
3168
3132
|
readRcByPath,
|
|
3169
3133
|
upload
|