@code-pushup/cli 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/README.md +20 -5
- package/index.js +1331 -1276
- package/package.json +7 -33
- package/src/lib/autorun/autorun-command.d.ts +1 -1
- package/src/lib/collect/collect-command.d.ts +1 -1
- package/src/lib/commands.d.ts +1 -1
- package/src/lib/compare/compare-command.d.ts +1 -2
- package/src/lib/history/history.model.d.ts +2 -2
- package/src/lib/history/history.options.d.ts +2 -2
- package/src/lib/history/utils.d.ts +2 -2
- package/src/lib/implementation/compare.model.d.ts +4 -2
- package/src/lib/implementation/compare.options.d.ts +1 -1
- package/src/lib/implementation/core-config.middleware.d.ts +8 -6
- package/src/lib/implementation/core-config.options.d.ts +2 -2
- package/src/lib/implementation/formatting.d.ts +11 -0
- package/src/lib/implementation/global.model.d.ts +2 -2
- package/src/lib/implementation/global.options.d.ts +2 -2
- package/src/lib/implementation/merge-diffs.model.d.ts +3 -0
- package/src/lib/implementation/merge-diffs.options.d.ts +3 -0
- package/src/lib/implementation/only-plugins.middleware.d.ts +1 -1
- package/src/lib/implementation/only-plugins.model.d.ts +2 -2
- package/src/lib/implementation/only-plugins.options.d.ts +3 -2
- package/src/lib/implementation/skip-plugins.middleware.d.ts +1 -1
- package/src/lib/implementation/skip-plugins.model.d.ts +2 -2
- package/src/lib/implementation/skip-plugins.options.d.ts +3 -2
- package/src/lib/merge-diffs/merge-diffs-command.d.ts +6 -0
- package/src/lib/middlewares.d.ts +1 -1
- package/src/lib/upload/upload-command.d.ts +1 -1
- package/src/lib/yargs-cli.d.ts +14 -1
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { hideBin } from "yargs/helpers";
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/lib/autorun/autorun-command.ts
|
|
7
|
-
import
|
|
7
|
+
import { bold as bold7, gray as gray4 } from "ansis";
|
|
8
8
|
|
|
9
9
|
// packages/models/src/lib/implementation/schemas.ts
|
|
10
10
|
import { MATERIAL_ICONS } from "vscode-material-icons";
|
|
@@ -683,6 +683,8 @@ var auditResultSchema = scorableWithPluginMetaSchema.merge(
|
|
|
683
683
|
);
|
|
684
684
|
var reportsDiffSchema = z15.object({
|
|
685
685
|
commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
|
|
686
|
+
portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
|
|
687
|
+
label: z15.string().optional().describe("Label (e.g. project name)"),
|
|
686
688
|
categories: makeArraysComparisonSchema(
|
|
687
689
|
categoryDiffSchema,
|
|
688
690
|
categoryResultSchema,
|
|
@@ -752,15 +754,286 @@ function comparePairs(pairs, equalsFn) {
|
|
|
752
754
|
);
|
|
753
755
|
}
|
|
754
756
|
|
|
757
|
+
// packages/utils/src/lib/errors.ts
|
|
758
|
+
function stringifyError(error) {
|
|
759
|
+
if (error instanceof Error) {
|
|
760
|
+
if (error.name === "Error" || error.message.startsWith(error.name)) {
|
|
761
|
+
return error.message;
|
|
762
|
+
}
|
|
763
|
+
return `${error.name}: ${error.message}`;
|
|
764
|
+
}
|
|
765
|
+
if (typeof error === "string") {
|
|
766
|
+
return error;
|
|
767
|
+
}
|
|
768
|
+
return JSON.stringify(error);
|
|
769
|
+
}
|
|
770
|
+
|
|
755
771
|
// packages/utils/src/lib/execute-process.ts
|
|
756
|
-
import {
|
|
772
|
+
import {
|
|
773
|
+
spawn
|
|
774
|
+
} from "node:child_process";
|
|
775
|
+
|
|
776
|
+
// packages/utils/src/lib/reports/utils.ts
|
|
777
|
+
import ansis from "ansis";
|
|
778
|
+
import { md } from "build-md";
|
|
779
|
+
|
|
780
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
781
|
+
var TERMINAL_WIDTH = 80;
|
|
782
|
+
var SCORE_COLOR_RANGE = {
|
|
783
|
+
GREEN_MIN: 0.9,
|
|
784
|
+
YELLOW_MIN: 0.5
|
|
785
|
+
};
|
|
786
|
+
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
787
|
+
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
788
|
+
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
789
|
+
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
790
|
+
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
791
|
+
"Category",
|
|
792
|
+
"Score",
|
|
793
|
+
"Audits"
|
|
794
|
+
];
|
|
757
795
|
|
|
758
796
|
// packages/utils/src/lib/reports/utils.ts
|
|
759
|
-
|
|
797
|
+
function formatReportScore(score) {
|
|
798
|
+
const scaledScore = score * 100;
|
|
799
|
+
const roundedScore = Math.round(scaledScore);
|
|
800
|
+
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
801
|
+
}
|
|
802
|
+
function formatScoreWithColor(score, options2) {
|
|
803
|
+
const styledNumber = options2?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
804
|
+
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
805
|
+
}
|
|
806
|
+
var MARKERS = {
|
|
807
|
+
circle: {
|
|
808
|
+
red: "\u{1F534}",
|
|
809
|
+
yellow: "\u{1F7E1}",
|
|
810
|
+
green: "\u{1F7E2}"
|
|
811
|
+
},
|
|
812
|
+
square: {
|
|
813
|
+
red: "\u{1F7E5}",
|
|
814
|
+
yellow: "\u{1F7E8}",
|
|
815
|
+
green: "\u{1F7E9}"
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
function scoreMarker(score, markerType = "circle") {
|
|
819
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
820
|
+
return MARKERS[markerType].green;
|
|
821
|
+
}
|
|
822
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
823
|
+
return MARKERS[markerType].yellow;
|
|
824
|
+
}
|
|
825
|
+
return MARKERS[markerType].red;
|
|
826
|
+
}
|
|
827
|
+
function getDiffMarker(diff) {
|
|
828
|
+
if (diff > 0) {
|
|
829
|
+
return "\u2191";
|
|
830
|
+
}
|
|
831
|
+
if (diff < 0) {
|
|
832
|
+
return "\u2193";
|
|
833
|
+
}
|
|
834
|
+
return "";
|
|
835
|
+
}
|
|
836
|
+
function colorByScoreDiff(text, diff) {
|
|
837
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
838
|
+
return shieldsBadge(text, color);
|
|
839
|
+
}
|
|
840
|
+
function shieldsBadge(text, color) {
|
|
841
|
+
return md.image(
|
|
842
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
843
|
+
text
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
function formatDiffNumber(diff) {
|
|
847
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
848
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
849
|
+
return `${sign}${number}`;
|
|
850
|
+
}
|
|
851
|
+
function severityMarker(severity) {
|
|
852
|
+
if (severity === "error") {
|
|
853
|
+
return "\u{1F6A8}";
|
|
854
|
+
}
|
|
855
|
+
if (severity === "warning") {
|
|
856
|
+
return "\u26A0\uFE0F";
|
|
857
|
+
}
|
|
858
|
+
return "\u2139\uFE0F";
|
|
859
|
+
}
|
|
860
|
+
function formatScoreChange(diff) {
|
|
861
|
+
const marker = getDiffMarker(diff);
|
|
862
|
+
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
863
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
864
|
+
}
|
|
865
|
+
function formatValueChange({
|
|
866
|
+
values,
|
|
867
|
+
scores
|
|
868
|
+
}) {
|
|
869
|
+
const marker = getDiffMarker(values.diff);
|
|
870
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
871
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
872
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
873
|
+
}
|
|
874
|
+
function calcDuration(start, stop) {
|
|
875
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
876
|
+
}
|
|
877
|
+
function countCategoryAudits(refs, plugins) {
|
|
878
|
+
const groupLookup = plugins.reduce(
|
|
879
|
+
(lookup, plugin) => {
|
|
880
|
+
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
881
|
+
return lookup;
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
...lookup,
|
|
885
|
+
[plugin.slug]: Object.fromEntries(
|
|
886
|
+
plugin.groups.map((group) => [group.slug, group])
|
|
887
|
+
)
|
|
888
|
+
};
|
|
889
|
+
},
|
|
890
|
+
{}
|
|
891
|
+
);
|
|
892
|
+
return refs.reduce((acc, ref) => {
|
|
893
|
+
if (ref.type === "group") {
|
|
894
|
+
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
895
|
+
return acc + (groupRefs?.length ?? 0);
|
|
896
|
+
}
|
|
897
|
+
return acc + 1;
|
|
898
|
+
}, 0);
|
|
899
|
+
}
|
|
900
|
+
function compareCategoryAuditsAndGroups(a, b) {
|
|
901
|
+
if (a.score !== b.score) {
|
|
902
|
+
return a.score - b.score;
|
|
903
|
+
}
|
|
904
|
+
if (a.weight !== b.weight) {
|
|
905
|
+
return b.weight - a.weight;
|
|
906
|
+
}
|
|
907
|
+
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
908
|
+
return b.value - a.value;
|
|
909
|
+
}
|
|
910
|
+
return a.title.localeCompare(b.title);
|
|
911
|
+
}
|
|
912
|
+
function compareAudits(a, b) {
|
|
913
|
+
if (a.score !== b.score) {
|
|
914
|
+
return a.score - b.score;
|
|
915
|
+
}
|
|
916
|
+
if (a.value !== b.value) {
|
|
917
|
+
return b.value - a.value;
|
|
918
|
+
}
|
|
919
|
+
return a.title.localeCompare(b.title);
|
|
920
|
+
}
|
|
921
|
+
function compareIssueSeverity(severity1, severity2) {
|
|
922
|
+
const levels = {
|
|
923
|
+
info: 0,
|
|
924
|
+
warning: 1,
|
|
925
|
+
error: 2
|
|
926
|
+
};
|
|
927
|
+
return levels[severity1] - levels[severity2];
|
|
928
|
+
}
|
|
929
|
+
function throwIsNotPresentError(itemName, presentPlace) {
|
|
930
|
+
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
931
|
+
}
|
|
932
|
+
function getPluginNameFromSlug(slug, plugins) {
|
|
933
|
+
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
934
|
+
}
|
|
935
|
+
function compareIssues(a, b) {
|
|
936
|
+
if (a.severity !== b.severity) {
|
|
937
|
+
return -compareIssueSeverity(a.severity, b.severity);
|
|
938
|
+
}
|
|
939
|
+
if (!a.source && b.source) {
|
|
940
|
+
return -1;
|
|
941
|
+
}
|
|
942
|
+
if (a.source && !b.source) {
|
|
943
|
+
return 1;
|
|
944
|
+
}
|
|
945
|
+
if (a.source?.file !== b.source?.file) {
|
|
946
|
+
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
947
|
+
}
|
|
948
|
+
if (!a.source?.position && b.source?.position) {
|
|
949
|
+
return -1;
|
|
950
|
+
}
|
|
951
|
+
if (a.source?.position && !b.source?.position) {
|
|
952
|
+
return 1;
|
|
953
|
+
}
|
|
954
|
+
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
955
|
+
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
956
|
+
}
|
|
957
|
+
return 0;
|
|
958
|
+
}
|
|
959
|
+
function applyScoreColor({ score, text }, style = ansis) {
|
|
960
|
+
const formattedScore = text ?? formatReportScore(score);
|
|
961
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
962
|
+
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
963
|
+
}
|
|
964
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
965
|
+
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
966
|
+
}
|
|
967
|
+
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
968
|
+
}
|
|
969
|
+
function targetScoreIcon(score, targetScore, options2 = {}) {
|
|
970
|
+
if (targetScore != null) {
|
|
971
|
+
const {
|
|
972
|
+
passIcon = "\u2705",
|
|
973
|
+
failIcon = "\u274C",
|
|
974
|
+
prefix = "",
|
|
975
|
+
postfix = ""
|
|
976
|
+
} = options2;
|
|
977
|
+
if (score >= targetScore) {
|
|
978
|
+
return `${prefix}${passIcon}${postfix}`;
|
|
979
|
+
}
|
|
980
|
+
return `${prefix}${failIcon}${postfix}`;
|
|
981
|
+
}
|
|
982
|
+
return "";
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// packages/utils/src/lib/execute-process.ts
|
|
986
|
+
var ProcessError = class extends Error {
|
|
987
|
+
code;
|
|
988
|
+
stderr;
|
|
989
|
+
stdout;
|
|
990
|
+
constructor(result) {
|
|
991
|
+
super(result.stderr);
|
|
992
|
+
this.code = result.code;
|
|
993
|
+
this.stderr = result.stderr;
|
|
994
|
+
this.stdout = result.stdout;
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
function executeProcess(cfg) {
|
|
998
|
+
const { command: command2, args, observer, ignoreExitCode = false, ...options2 } = cfg;
|
|
999
|
+
const { onStdout, onStderr, onError, onComplete } = observer ?? {};
|
|
1000
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1001
|
+
const start = performance.now();
|
|
1002
|
+
return new Promise((resolve, reject) => {
|
|
1003
|
+
const spawnedProcess = spawn(command2, args ?? [], {
|
|
1004
|
+
shell: true,
|
|
1005
|
+
...options2
|
|
1006
|
+
});
|
|
1007
|
+
let stdout = "";
|
|
1008
|
+
let stderr = "";
|
|
1009
|
+
spawnedProcess.stdout.on("data", (data) => {
|
|
1010
|
+
stdout += String(data);
|
|
1011
|
+
onStdout?.(String(data), spawnedProcess);
|
|
1012
|
+
});
|
|
1013
|
+
spawnedProcess.stderr.on("data", (data) => {
|
|
1014
|
+
stderr += String(data);
|
|
1015
|
+
onStderr?.(String(data), spawnedProcess);
|
|
1016
|
+
});
|
|
1017
|
+
spawnedProcess.on("error", (err) => {
|
|
1018
|
+
stderr += err.toString();
|
|
1019
|
+
});
|
|
1020
|
+
spawnedProcess.on("close", (code2) => {
|
|
1021
|
+
const timings = { date, duration: calcDuration(start) };
|
|
1022
|
+
if (code2 === 0 || ignoreExitCode) {
|
|
1023
|
+
onComplete?.();
|
|
1024
|
+
resolve({ code: code2, stdout, stderr, ...timings });
|
|
1025
|
+
} else {
|
|
1026
|
+
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
1027
|
+
onError?.(errorMsg);
|
|
1028
|
+
reject(errorMsg);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
760
1033
|
|
|
761
1034
|
// packages/utils/src/lib/file-system.ts
|
|
1035
|
+
import { bold, gray } from "ansis";
|
|
762
1036
|
import { bundleRequire } from "bundle-require";
|
|
763
|
-
import chalk2 from "chalk";
|
|
764
1037
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
765
1038
|
|
|
766
1039
|
// packages/utils/src/lib/formatting.ts
|
|
@@ -823,55 +1096,7 @@ function isPromiseRejectedResult(result) {
|
|
|
823
1096
|
// packages/utils/src/lib/logging.ts
|
|
824
1097
|
import isaacs_cliui from "@isaacs/cliui";
|
|
825
1098
|
import { cliui } from "@poppinss/cliui";
|
|
826
|
-
import
|
|
827
|
-
|
|
828
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
829
|
-
var TERMINAL_WIDTH = 80;
|
|
830
|
-
var SCORE_COLOR_RANGE = {
|
|
831
|
-
GREEN_MIN: 0.9,
|
|
832
|
-
YELLOW_MIN: 0.5
|
|
833
|
-
};
|
|
834
|
-
var CATEGORIES_TITLE = "\u{1F3F7} Categories";
|
|
835
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
836
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
837
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
838
|
-
var reportHeadlineText = "Code PushUp Report";
|
|
839
|
-
var reportOverviewTableHeaders = [
|
|
840
|
-
{
|
|
841
|
-
key: "category",
|
|
842
|
-
label: "\u{1F3F7} Category",
|
|
843
|
-
align: "left"
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
key: "score",
|
|
847
|
-
label: "\u2B50 Score"
|
|
848
|
-
},
|
|
849
|
-
{
|
|
850
|
-
key: "audits",
|
|
851
|
-
label: "\u{1F6E1} Audits"
|
|
852
|
-
}
|
|
853
|
-
];
|
|
854
|
-
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
855
|
-
var issuesTableHeadings = [
|
|
856
|
-
{
|
|
857
|
-
key: "severity",
|
|
858
|
-
label: "Severity"
|
|
859
|
-
},
|
|
860
|
-
{
|
|
861
|
-
key: "message",
|
|
862
|
-
label: "Message"
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
key: "file",
|
|
866
|
-
label: "Source file"
|
|
867
|
-
},
|
|
868
|
-
{
|
|
869
|
-
key: "line",
|
|
870
|
-
label: "Line(s)"
|
|
871
|
-
}
|
|
872
|
-
];
|
|
873
|
-
|
|
874
|
-
// packages/utils/src/lib/logging.ts
|
|
1099
|
+
import { underline } from "ansis";
|
|
875
1100
|
var singletonUiInstance;
|
|
876
1101
|
function ui() {
|
|
877
1102
|
if (singletonUiInstance === void 0) {
|
|
@@ -895,7 +1120,7 @@ function logListItem(args) {
|
|
|
895
1120
|
singletonUiInstance?.logger.log(content);
|
|
896
1121
|
}
|
|
897
1122
|
function link(text) {
|
|
898
|
-
return
|
|
1123
|
+
return underline.blueBright(text);
|
|
899
1124
|
}
|
|
900
1125
|
|
|
901
1126
|
// packages/utils/src/lib/log-results.ts
|
|
@@ -970,10 +1195,10 @@ async function ensureDirectoryExists(baseDir) {
|
|
|
970
1195
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
971
1196
|
const succeededTransform = (result) => {
|
|
972
1197
|
const [fileName, size] = result.value;
|
|
973
|
-
const formattedSize = size ? ` (${
|
|
974
|
-
return `- ${
|
|
1198
|
+
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1199
|
+
return `- ${bold(fileName)}${formattedSize}`;
|
|
975
1200
|
};
|
|
976
|
-
const failedTransform = (result) => `- ${
|
|
1201
|
+
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
977
1202
|
logMultipleResults(
|
|
978
1203
|
fileResults,
|
|
979
1204
|
messagePrefix,
|
|
@@ -989,38 +1214,17 @@ async function importModule(options2) {
|
|
|
989
1214
|
return mod;
|
|
990
1215
|
}
|
|
991
1216
|
|
|
992
|
-
// packages/utils/src/lib/
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
function details(title, content, cfg = { open: false }) {
|
|
999
|
-
return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
|
|
1000
|
-
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
1001
|
-
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
1002
|
-
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
1003
|
-
NEW_LINE}`;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// packages/utils/src/lib/text-formats/html/font-style.ts
|
|
1007
|
-
var boldElement = "b";
|
|
1008
|
-
function bold(text) {
|
|
1009
|
-
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1010
|
-
}
|
|
1011
|
-
var italicElement = "i";
|
|
1012
|
-
function italic(text) {
|
|
1013
|
-
return `<${italicElement}>${text}</${italicElement}>`;
|
|
1014
|
-
}
|
|
1015
|
-
var codeElement = "code";
|
|
1016
|
-
function code(text) {
|
|
1017
|
-
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1217
|
+
// packages/utils/src/lib/filter.ts
|
|
1218
|
+
function filterItemRefsBy(items, refFilterFn) {
|
|
1219
|
+
return items.map((item) => ({
|
|
1220
|
+
...item,
|
|
1221
|
+
refs: item.refs.filter(refFilterFn)
|
|
1222
|
+
})).filter((item) => item.refs.length);
|
|
1018
1223
|
}
|
|
1019
1224
|
|
|
1020
|
-
// packages/utils/src/lib/
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1225
|
+
// packages/utils/src/lib/git/git.ts
|
|
1226
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
1227
|
+
import { simpleGit } from "simple-git";
|
|
1024
1228
|
|
|
1025
1229
|
// packages/utils/src/lib/transform.ts
|
|
1026
1230
|
function toArray(val) {
|
|
@@ -1041,6 +1245,262 @@ function capitalize(text) {
|
|
|
1041
1245
|
)}`;
|
|
1042
1246
|
}
|
|
1043
1247
|
|
|
1248
|
+
// packages/utils/src/lib/git/git.ts
|
|
1249
|
+
function getGitRoot(git = simpleGit()) {
|
|
1250
|
+
return git.revparse("--show-toplevel");
|
|
1251
|
+
}
|
|
1252
|
+
function formatGitPath(path, gitRoot) {
|
|
1253
|
+
const absolutePath = isAbsolute(path) ? path : join(process.cwd(), path);
|
|
1254
|
+
const relativePath = relative(gitRoot, absolutePath);
|
|
1255
|
+
return toUnixPath(relativePath);
|
|
1256
|
+
}
|
|
1257
|
+
var GitStatusError = class _GitStatusError extends Error {
|
|
1258
|
+
static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
|
|
1259
|
+
static getReducedStatus(status) {
|
|
1260
|
+
return Object.fromEntries(
|
|
1261
|
+
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1262
|
+
(entry) => {
|
|
1263
|
+
const value = entry[1];
|
|
1264
|
+
if (value == null) {
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
if (typeof value === "number" && value === 0) {
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
return !(typeof value === "boolean" && !value);
|
|
1274
|
+
}
|
|
1275
|
+
)
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
constructor(status) {
|
|
1279
|
+
super(
|
|
1280
|
+
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1281
|
+
${JSON.stringify(
|
|
1282
|
+
_GitStatusError.getReducedStatus(status),
|
|
1283
|
+
null,
|
|
1284
|
+
2
|
|
1285
|
+
)}`
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1290
|
+
const status = await git.status(["-s"]);
|
|
1291
|
+
if (status.files.length > 0) {
|
|
1292
|
+
throw new GitStatusError(status);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1296
|
+
if (forceCleanStatus) {
|
|
1297
|
+
await git.raw(["reset", "--hard"]);
|
|
1298
|
+
await git.clean(["f", "d"]);
|
|
1299
|
+
ui().logger.info(`git status cleaned`);
|
|
1300
|
+
}
|
|
1301
|
+
await guardAgainstLocalChanges(git);
|
|
1302
|
+
await git.checkout(branchOrHash);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1306
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1307
|
+
|
|
1308
|
+
// packages/utils/src/lib/semver.ts
|
|
1309
|
+
import { rcompare, valid } from "semver";
|
|
1310
|
+
function normalizeSemver(semverString) {
|
|
1311
|
+
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1312
|
+
return semverString.slice(1);
|
|
1313
|
+
}
|
|
1314
|
+
if (semverString.includes("@")) {
|
|
1315
|
+
return semverString.split("@").at(-1) ?? "";
|
|
1316
|
+
}
|
|
1317
|
+
return semverString;
|
|
1318
|
+
}
|
|
1319
|
+
function isSemver(semverString = "") {
|
|
1320
|
+
return valid(normalizeSemver(semverString)) != null;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1324
|
+
async function getLatestCommit(git = simpleGit2()) {
|
|
1325
|
+
const log2 = await git.log({
|
|
1326
|
+
maxCount: 1,
|
|
1327
|
+
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1328
|
+
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1329
|
+
});
|
|
1330
|
+
return commitSchema.parse(log2.latest);
|
|
1331
|
+
}
|
|
1332
|
+
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1333
|
+
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1334
|
+
// @TODO use simple git
|
|
1335
|
+
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1336
|
+
}
|
|
1337
|
+
function validateFilter({ from, to }) {
|
|
1338
|
+
if (to && !from) {
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
`filter needs the "from" option defined to accept the "to" option.
|
|
1341
|
+
`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function filterLogs(allTags, opt) {
|
|
1346
|
+
if (!opt) {
|
|
1347
|
+
return allTags;
|
|
1348
|
+
}
|
|
1349
|
+
validateFilter(opt);
|
|
1350
|
+
const { from, to, maxCount } = opt;
|
|
1351
|
+
const finIndex = (tagName, fallback) => {
|
|
1352
|
+
const idx = allTags.indexOf(tagName ?? "");
|
|
1353
|
+
if (idx > -1) {
|
|
1354
|
+
return idx;
|
|
1355
|
+
}
|
|
1356
|
+
return fallback;
|
|
1357
|
+
};
|
|
1358
|
+
const fromIndex = finIndex(from, 0);
|
|
1359
|
+
const toIndex = finIndex(to, void 0);
|
|
1360
|
+
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1361
|
+
}
|
|
1362
|
+
async function getHashFromTag(tag, git = simpleGit2()) {
|
|
1363
|
+
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1364
|
+
const hash = tagDetails.trim();
|
|
1365
|
+
return {
|
|
1366
|
+
hash: hash.split("\n").at(-1) ?? "",
|
|
1367
|
+
message: tag
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
async function getSemverTags(opt = {}, git = simpleGit2()) {
|
|
1371
|
+
validateFilter(opt);
|
|
1372
|
+
const { targetBranch, ...options2 } = opt;
|
|
1373
|
+
let currentBranch;
|
|
1374
|
+
if (targetBranch) {
|
|
1375
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1376
|
+
await git.checkout(targetBranch);
|
|
1377
|
+
}
|
|
1378
|
+
const tagsRaw = await git.tag([
|
|
1379
|
+
"--merged",
|
|
1380
|
+
targetBranch ?? await getCurrentBranchOrTag(git)
|
|
1381
|
+
]);
|
|
1382
|
+
const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
|
|
1383
|
+
const relevantTags = filterLogs(allTags, options2);
|
|
1384
|
+
const tagsWithHashes = await Promise.all(
|
|
1385
|
+
relevantTags.map((tag) => getHashFromTag(tag, git))
|
|
1386
|
+
);
|
|
1387
|
+
if (currentBranch) {
|
|
1388
|
+
await git.checkout(currentBranch);
|
|
1389
|
+
}
|
|
1390
|
+
return tagsWithHashes;
|
|
1391
|
+
}
|
|
1392
|
+
async function getHashes(options2 = {}, git = simpleGit2()) {
|
|
1393
|
+
const { targetBranch, from, to, maxCount, ...opt } = options2;
|
|
1394
|
+
validateFilter({ from, to });
|
|
1395
|
+
let currentBranch;
|
|
1396
|
+
if (targetBranch) {
|
|
1397
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1398
|
+
await git.checkout(targetBranch);
|
|
1399
|
+
}
|
|
1400
|
+
const logs = await git.log({
|
|
1401
|
+
...opt,
|
|
1402
|
+
format: {
|
|
1403
|
+
hash: "%H",
|
|
1404
|
+
message: "%s"
|
|
1405
|
+
},
|
|
1406
|
+
from,
|
|
1407
|
+
to,
|
|
1408
|
+
maxCount
|
|
1409
|
+
});
|
|
1410
|
+
if (targetBranch) {
|
|
1411
|
+
await git.checkout(currentBranch);
|
|
1412
|
+
}
|
|
1413
|
+
return [...logs.all];
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// packages/utils/src/lib/group-by-status.ts
|
|
1417
|
+
function groupByStatus(results) {
|
|
1418
|
+
return results.reduce(
|
|
1419
|
+
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1420
|
+
{ fulfilled: [], rejected: [] }
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// packages/utils/src/lib/progress.ts
|
|
1425
|
+
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1426
|
+
import { MultiProgressBars } from "multi-progress-bars";
|
|
1427
|
+
var barStyles = {
|
|
1428
|
+
active: (s) => green(s),
|
|
1429
|
+
done: (s) => gray2(s),
|
|
1430
|
+
idle: (s) => gray2(s)
|
|
1431
|
+
};
|
|
1432
|
+
var messageStyles = {
|
|
1433
|
+
active: (s) => black(s),
|
|
1434
|
+
done: (s) => bold2.green(s),
|
|
1435
|
+
idle: (s) => gray2(s)
|
|
1436
|
+
};
|
|
1437
|
+
var mpb;
|
|
1438
|
+
function getSingletonProgressBars(options2) {
|
|
1439
|
+
if (!mpb) {
|
|
1440
|
+
mpb = new MultiProgressBars({
|
|
1441
|
+
progressWidth: TERMINAL_WIDTH,
|
|
1442
|
+
initMessage: "",
|
|
1443
|
+
border: true,
|
|
1444
|
+
...options2
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
return mpb;
|
|
1448
|
+
}
|
|
1449
|
+
function getProgressBar(taskName) {
|
|
1450
|
+
const tasks = getSingletonProgressBars();
|
|
1451
|
+
tasks.addTask(taskName, {
|
|
1452
|
+
type: "percentage",
|
|
1453
|
+
percentage: 0,
|
|
1454
|
+
message: "",
|
|
1455
|
+
barTransformFn: barStyles.idle
|
|
1456
|
+
});
|
|
1457
|
+
return {
|
|
1458
|
+
incrementInSteps: (numPlugins) => {
|
|
1459
|
+
tasks.incrementTask(taskName, {
|
|
1460
|
+
percentage: 1 / numPlugins,
|
|
1461
|
+
barTransformFn: barStyles.active
|
|
1462
|
+
});
|
|
1463
|
+
},
|
|
1464
|
+
updateTitle: (title) => {
|
|
1465
|
+
tasks.updateTask(taskName, {
|
|
1466
|
+
message: title,
|
|
1467
|
+
barTransformFn: barStyles.active
|
|
1468
|
+
});
|
|
1469
|
+
},
|
|
1470
|
+
endProgress: (message = "") => {
|
|
1471
|
+
tasks.done(taskName, {
|
|
1472
|
+
message: messageStyles.done(message),
|
|
1473
|
+
barTransformFn: barStyles.done
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1480
|
+
function listGroupsFromAllPlugins(report) {
|
|
1481
|
+
return report.plugins.flatMap(
|
|
1482
|
+
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
function listAuditsFromAllPlugins(report) {
|
|
1486
|
+
return report.plugins.flatMap(
|
|
1487
|
+
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1492
|
+
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1493
|
+
|
|
1494
|
+
// packages/utils/src/lib/text-formats/constants.ts
|
|
1495
|
+
var HIERARCHY = {
|
|
1496
|
+
level_1: 1,
|
|
1497
|
+
level_2: 2,
|
|
1498
|
+
level_3: 3,
|
|
1499
|
+
level_4: 4,
|
|
1500
|
+
level_5: 5,
|
|
1501
|
+
level_6: 6
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1044
1504
|
// packages/utils/src/lib/text-formats/table.ts
|
|
1045
1505
|
function rowToStringArray({ rows, columns = [] }) {
|
|
1046
1506
|
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
@@ -1130,254 +1590,53 @@ function getColumnAlignments(tableData) {
|
|
|
1130
1590
|
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1131
1591
|
}
|
|
1132
1592
|
|
|
1133
|
-
// packages/utils/src/lib/
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1140
|
-
}
|
|
1141
|
-
function table(tableData) {
|
|
1593
|
+
// packages/utils/src/lib/reports/formatting.ts
|
|
1594
|
+
import {
|
|
1595
|
+
MarkdownDocument,
|
|
1596
|
+
md as md2
|
|
1597
|
+
} from "build-md";
|
|
1598
|
+
function tableSection(tableData, options2) {
|
|
1142
1599
|
if (tableData.rows.length === 0) {
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
const
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1159
|
-
var italicWrap = "_";
|
|
1160
|
-
function italic2(text) {
|
|
1161
|
-
return `${italicWrap}${text}${italicWrap}`;
|
|
1162
|
-
}
|
|
1163
|
-
var strikeThroughWrap = "~";
|
|
1164
|
-
function strikeThrough(text) {
|
|
1165
|
-
return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
|
|
1166
|
-
}
|
|
1167
|
-
var codeWrap = "`";
|
|
1168
|
-
function code2(text) {
|
|
1169
|
-
return `${codeWrap}${text}${codeWrap}`;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// packages/utils/src/lib/text-formats/md/headline.ts
|
|
1173
|
-
function headline(text, hierarchy = 1) {
|
|
1174
|
-
return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
|
|
1175
|
-
}
|
|
1176
|
-
function h(text, hierarchy = 1) {
|
|
1177
|
-
return headline(text, hierarchy);
|
|
1178
|
-
}
|
|
1179
|
-
function h1(text) {
|
|
1180
|
-
return headline(text, 1);
|
|
1181
|
-
}
|
|
1182
|
-
function h2(text) {
|
|
1183
|
-
return headline(text, 2);
|
|
1184
|
-
}
|
|
1185
|
-
function h3(text) {
|
|
1186
|
-
return headline(text, 3);
|
|
1187
|
-
}
|
|
1188
|
-
function h4(text) {
|
|
1189
|
-
return headline(text, 4);
|
|
1190
|
-
}
|
|
1191
|
-
function h5(text) {
|
|
1192
|
-
return headline(text, 5);
|
|
1193
|
-
}
|
|
1194
|
-
function h6(text) {
|
|
1195
|
-
return headline(text, 6);
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// packages/utils/src/lib/text-formats/md/image.ts
|
|
1199
|
-
function image(src, alt) {
|
|
1200
|
-
return ``;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// packages/utils/src/lib/text-formats/md/link.ts
|
|
1204
|
-
function link3(href, text) {
|
|
1205
|
-
return `[${text || href}](${href})`;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// packages/utils/src/lib/text-formats/md/list.ts
|
|
1209
|
-
function li(text, order = "unordered") {
|
|
1210
|
-
const style = order === "unordered" ? "-" : "- [ ]";
|
|
1211
|
-
return `${style} ${text}`;
|
|
1212
|
-
}
|
|
1213
|
-
function indentation(text, level = 1) {
|
|
1214
|
-
return `${TAB.repeat(level)}${text}`;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// packages/utils/src/lib/text-formats/md/paragraphs.ts
|
|
1218
|
-
function paragraphs(...sections) {
|
|
1219
|
-
return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// packages/utils/src/lib/text-formats/md/section.ts
|
|
1223
|
-
function section(...contents) {
|
|
1224
|
-
return `${lines(...contents)}${NEW_LINE}`;
|
|
1225
|
-
}
|
|
1226
|
-
function lines(...contents) {
|
|
1227
|
-
const filteredContent = contents.filter(
|
|
1228
|
-
(value) => value != null && value !== "" && value !== false
|
|
1229
|
-
);
|
|
1230
|
-
return `${filteredContent.join(NEW_LINE)}`;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// packages/utils/src/lib/text-formats/md/table.ts
|
|
1234
|
-
var alignString = /* @__PURE__ */ new Map([
|
|
1235
|
-
["left", ":--"],
|
|
1236
|
-
["center", ":--:"],
|
|
1237
|
-
["right", "--:"]
|
|
1238
|
-
]);
|
|
1239
|
-
function tableRow(rows) {
|
|
1240
|
-
return `|${rows.join("|")}|`;
|
|
1241
|
-
}
|
|
1242
|
-
function table2(data) {
|
|
1243
|
-
if (data.rows.length === 0) {
|
|
1244
|
-
throw new Error("Data can't be empty");
|
|
1245
|
-
}
|
|
1246
|
-
const alignmentRow = getColumnAlignments(data).map(
|
|
1247
|
-
(s) => alignString.get(s) ?? String(alignString.get("center"))
|
|
1248
|
-
);
|
|
1249
|
-
return section(
|
|
1250
|
-
`${lines(
|
|
1251
|
-
tableRow(columnsToStringArray(data)),
|
|
1252
|
-
tableRow(alignmentRow),
|
|
1253
|
-
...rowToStringArray(data).map(tableRow)
|
|
1254
|
-
)}`
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
const { level = HIERARCHY.level_4 } = options2 ?? {};
|
|
1603
|
+
const columns = columnsToStringArray(tableData);
|
|
1604
|
+
const alignments = getColumnAlignments(tableData);
|
|
1605
|
+
const rows = rowToStringArray(tableData);
|
|
1606
|
+
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1607
|
+
columns.map((heading, i) => {
|
|
1608
|
+
const alignment = alignments[i];
|
|
1609
|
+
if (alignment) {
|
|
1610
|
+
return { heading, alignment };
|
|
1611
|
+
}
|
|
1612
|
+
return heading;
|
|
1613
|
+
}),
|
|
1614
|
+
rows
|
|
1255
1615
|
);
|
|
1256
1616
|
}
|
|
1617
|
+
function metaDescription(audit) {
|
|
1618
|
+
const docsUrl = audit.docsUrl;
|
|
1619
|
+
const description = audit.description?.trim();
|
|
1620
|
+
if (docsUrl) {
|
|
1621
|
+
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1622
|
+
if (!description) {
|
|
1623
|
+
return docsLink;
|
|
1624
|
+
}
|
|
1625
|
+
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1257
1626
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
bold: bold2,
|
|
1261
|
-
italic: italic2,
|
|
1262
|
-
strikeThrough,
|
|
1263
|
-
code: code2,
|
|
1264
|
-
link: link3,
|
|
1265
|
-
image,
|
|
1266
|
-
headline,
|
|
1267
|
-
h,
|
|
1268
|
-
h1,
|
|
1269
|
-
h2,
|
|
1270
|
-
h3,
|
|
1271
|
-
h4,
|
|
1272
|
-
h5,
|
|
1273
|
-
h6,
|
|
1274
|
-
indentation,
|
|
1275
|
-
lines,
|
|
1276
|
-
li,
|
|
1277
|
-
section,
|
|
1278
|
-
paragraphs,
|
|
1279
|
-
table: table2
|
|
1280
|
-
};
|
|
1281
|
-
var html = {
|
|
1282
|
-
bold,
|
|
1283
|
-
italic,
|
|
1284
|
-
code,
|
|
1285
|
-
link: link2,
|
|
1286
|
-
details,
|
|
1287
|
-
table
|
|
1288
|
-
};
|
|
1289
|
-
|
|
1290
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
1291
|
-
var { image: image2, bold: boldMd } = md;
|
|
1292
|
-
function formatReportScore(score) {
|
|
1293
|
-
const scaledScore = score * 100;
|
|
1294
|
-
const roundedScore = Math.round(scaledScore);
|
|
1295
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
1296
|
-
}
|
|
1297
|
-
function formatScoreWithColor(score, options2) {
|
|
1298
|
-
const styledNumber = options2?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
|
|
1299
|
-
return `${scoreMarker(score)} ${styledNumber}`;
|
|
1300
|
-
}
|
|
1301
|
-
var MARKERS = {
|
|
1302
|
-
circle: {
|
|
1303
|
-
red: "\u{1F534}",
|
|
1304
|
-
yellow: "\u{1F7E1}",
|
|
1305
|
-
green: "\u{1F7E2}"
|
|
1306
|
-
},
|
|
1307
|
-
square: {
|
|
1308
|
-
red: "\u{1F7E5}",
|
|
1309
|
-
yellow: "\u{1F7E8}",
|
|
1310
|
-
green: "\u{1F7E9}"
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
function scoreMarker(score, markerType = "circle") {
|
|
1314
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
1315
|
-
return MARKERS[markerType].green;
|
|
1316
|
-
}
|
|
1317
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
1318
|
-
return MARKERS[markerType].yellow;
|
|
1319
|
-
}
|
|
1320
|
-
return MARKERS[markerType].red;
|
|
1321
|
-
}
|
|
1322
|
-
function getDiffMarker(diff) {
|
|
1323
|
-
if (diff > 0) {
|
|
1324
|
-
return "\u2191";
|
|
1627
|
+
` : `${description} `;
|
|
1628
|
+
return md2`${parsedDescription}${docsLink}`;
|
|
1325
1629
|
}
|
|
1326
|
-
if (
|
|
1327
|
-
return
|
|
1630
|
+
if (description && description.trim().length > 0) {
|
|
1631
|
+
return description;
|
|
1328
1632
|
}
|
|
1329
1633
|
return "";
|
|
1330
1634
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
return image2(
|
|
1337
|
-
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
1338
|
-
text
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
function formatDiffNumber(diff) {
|
|
1342
|
-
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
1343
|
-
const sign = diff < 0 ? "\u2212" : "+";
|
|
1344
|
-
return `${sign}${number}`;
|
|
1345
|
-
}
|
|
1346
|
-
function severityMarker(severity) {
|
|
1347
|
-
if (severity === "error") {
|
|
1348
|
-
return "\u{1F6A8}";
|
|
1349
|
-
}
|
|
1350
|
-
if (severity === "warning") {
|
|
1351
|
-
return "\u26A0\uFE0F";
|
|
1352
|
-
}
|
|
1353
|
-
return "\u2139\uFE0F";
|
|
1354
|
-
}
|
|
1355
|
-
function calcDuration(start, stop) {
|
|
1356
|
-
return Math.round((stop ?? performance.now()) - start);
|
|
1357
|
-
}
|
|
1358
|
-
function countCategoryAudits(refs, plugins) {
|
|
1359
|
-
const groupLookup = plugins.reduce(
|
|
1360
|
-
(lookup, plugin) => {
|
|
1361
|
-
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
1362
|
-
return lookup;
|
|
1363
|
-
}
|
|
1364
|
-
return {
|
|
1365
|
-
...lookup,
|
|
1366
|
-
[plugin.slug]: Object.fromEntries(
|
|
1367
|
-
plugin.groups.map((group) => [group.slug, group])
|
|
1368
|
-
)
|
|
1369
|
-
};
|
|
1370
|
-
},
|
|
1371
|
-
{}
|
|
1372
|
-
);
|
|
1373
|
-
return refs.reduce((acc, ref) => {
|
|
1374
|
-
if (ref.type === "group") {
|
|
1375
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
1376
|
-
return acc + (groupRefs?.length ?? 0);
|
|
1377
|
-
}
|
|
1378
|
-
return acc + 1;
|
|
1379
|
-
}, 0);
|
|
1380
|
-
}
|
|
1635
|
+
|
|
1636
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1637
|
+
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1638
|
+
|
|
1639
|
+
// packages/utils/src/lib/reports/sorting.ts
|
|
1381
1640
|
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1382
1641
|
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1383
1642
|
if (!auditPlugin) {
|
|
@@ -1395,6 +1654,19 @@ function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
|
1395
1654
|
plugin
|
|
1396
1655
|
};
|
|
1397
1656
|
}
|
|
1657
|
+
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1658
|
+
return group.refs.map(
|
|
1659
|
+
(ref) => getSortableAuditByRef(
|
|
1660
|
+
{
|
|
1661
|
+
plugin,
|
|
1662
|
+
slug: ref.slug,
|
|
1663
|
+
weight: ref.weight,
|
|
1664
|
+
type: "audit"
|
|
1665
|
+
},
|
|
1666
|
+
plugins
|
|
1667
|
+
)
|
|
1668
|
+
).sort(compareCategoryAuditsAndGroups);
|
|
1669
|
+
}
|
|
1398
1670
|
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1399
1671
|
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1400
1672
|
if (!groupPlugin) {
|
|
@@ -1419,849 +1691,280 @@ function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
|
1419
1691
|
weight
|
|
1420
1692
|
};
|
|
1421
1693
|
}
|
|
1422
|
-
function
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
return a.title.localeCompare(b.title);
|
|
1455
|
-
}
|
|
1456
|
-
function compareIssueSeverity(severity1, severity2) {
|
|
1457
|
-
const levels = {
|
|
1458
|
-
info: 0,
|
|
1459
|
-
warning: 1,
|
|
1460
|
-
error: 2
|
|
1694
|
+
function sortReport(report) {
|
|
1695
|
+
const { categories, plugins } = report;
|
|
1696
|
+
const sortedCategories = categories.map((category) => {
|
|
1697
|
+
const { audits, groups: groups2 } = category.refs.reduce(
|
|
1698
|
+
(acc, ref) => ({
|
|
1699
|
+
...acc,
|
|
1700
|
+
...ref.type === "group" ? {
|
|
1701
|
+
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1702
|
+
} : {
|
|
1703
|
+
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1704
|
+
}
|
|
1705
|
+
}),
|
|
1706
|
+
{ groups: [], audits: [] }
|
|
1707
|
+
);
|
|
1708
|
+
const sortedAuditsAndGroups = [...audits, ...groups2].sort(
|
|
1709
|
+
compareCategoryAuditsAndGroups
|
|
1710
|
+
);
|
|
1711
|
+
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
1712
|
+
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
1713
|
+
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
1714
|
+
);
|
|
1715
|
+
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
1716
|
+
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
1717
|
+
);
|
|
1718
|
+
return aIndex - bIndex;
|
|
1719
|
+
});
|
|
1720
|
+
return { ...category, refs: sortedRefs };
|
|
1721
|
+
});
|
|
1722
|
+
return {
|
|
1723
|
+
...report,
|
|
1724
|
+
categories: sortedCategories,
|
|
1725
|
+
plugins: sortPlugins(plugins)
|
|
1461
1726
|
};
|
|
1462
|
-
return levels[severity1] - levels[severity2];
|
|
1463
1727
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1728
|
+
function sortPlugins(plugins) {
|
|
1729
|
+
return plugins.map((plugin) => ({
|
|
1730
|
+
...plugin,
|
|
1731
|
+
audits: [...plugin.audits].sort(compareAudits).map(
|
|
1732
|
+
(audit) => audit.details?.issues ? {
|
|
1733
|
+
...audit,
|
|
1734
|
+
details: {
|
|
1735
|
+
...audit.details,
|
|
1736
|
+
issues: [...audit.details.issues].sort(compareIssues)
|
|
1737
|
+
}
|
|
1738
|
+
} : audit
|
|
1739
|
+
)
|
|
1740
|
+
}));
|
|
1474
1741
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1742
|
+
|
|
1743
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1744
|
+
function categoriesOverviewSection(report) {
|
|
1745
|
+
const { categories, plugins } = report;
|
|
1746
|
+
return new MarkdownDocument2().table(
|
|
1747
|
+
[
|
|
1748
|
+
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
1749
|
+
{ heading: "\u2B50 Score", alignment: "center" },
|
|
1750
|
+
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
1751
|
+
],
|
|
1752
|
+
categories.map(({ title, refs, score, isBinary }) => [
|
|
1753
|
+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
1754
|
+
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1755
|
+
md3.link(`#${slugify(title)}`, title),
|
|
1756
|
+
md3`${scoreMarker(score)} ${md3.bold(
|
|
1757
|
+
formatReportScore(score)
|
|
1758
|
+
)}${binaryIconSuffix(score, isBinary)}`,
|
|
1759
|
+
countCategoryAudits(refs, plugins).toString()
|
|
1760
|
+
])
|
|
1761
|
+
);
|
|
1477
1762
|
}
|
|
1478
|
-
function
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
// packages/utils/src/lib/execute-process.ts
|
|
1507
|
-
var ProcessError = class extends Error {
|
|
1508
|
-
code;
|
|
1509
|
-
stderr;
|
|
1510
|
-
stdout;
|
|
1511
|
-
constructor(result) {
|
|
1512
|
-
super(result.stderr);
|
|
1513
|
-
this.code = result.code;
|
|
1514
|
-
this.stderr = result.stderr;
|
|
1515
|
-
this.stdout = result.stdout;
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
function executeProcess(cfg) {
|
|
1519
|
-
const { observer, cwd, command: command2, args, ignoreExitCode = false } = cfg;
|
|
1520
|
-
const { onStdout, onError, onComplete } = observer ?? {};
|
|
1521
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1522
|
-
const start = performance.now();
|
|
1523
|
-
return new Promise((resolve, reject) => {
|
|
1524
|
-
const process2 = spawn(command2, args, { cwd, shell: true });
|
|
1525
|
-
let stdout = "";
|
|
1526
|
-
let stderr = "";
|
|
1527
|
-
process2.stdout.on("data", (data) => {
|
|
1528
|
-
stdout += String(data);
|
|
1529
|
-
onStdout?.(String(data));
|
|
1530
|
-
});
|
|
1531
|
-
process2.stderr.on("data", (data) => {
|
|
1532
|
-
stderr += String(data);
|
|
1533
|
-
});
|
|
1534
|
-
process2.on("error", (err) => {
|
|
1535
|
-
stderr += err.toString();
|
|
1536
|
-
});
|
|
1537
|
-
process2.on("close", (code3) => {
|
|
1538
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1539
|
-
if (code3 === 0 || ignoreExitCode) {
|
|
1540
|
-
onComplete?.();
|
|
1541
|
-
resolve({ code: code3, stdout, stderr, ...timings });
|
|
1542
|
-
} else {
|
|
1543
|
-
const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
|
|
1544
|
-
onError?.(errorMsg);
|
|
1545
|
-
reject(errorMsg);
|
|
1546
|
-
}
|
|
1547
|
-
});
|
|
1548
|
-
});
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
// packages/utils/src/lib/filter.ts
|
|
1552
|
-
function filterItemRefsBy(items, refFilterFn) {
|
|
1553
|
-
return items.map((item) => ({
|
|
1554
|
-
...item,
|
|
1555
|
-
refs: item.refs.filter(refFilterFn)
|
|
1556
|
-
})).filter((item) => item.refs.length);
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
// packages/utils/src/lib/git/git.ts
|
|
1560
|
-
import { isAbsolute, join as join2, relative } from "node:path";
|
|
1561
|
-
import { simpleGit } from "simple-git";
|
|
1562
|
-
function getGitRoot(git = simpleGit()) {
|
|
1563
|
-
return git.revparse("--show-toplevel");
|
|
1564
|
-
}
|
|
1565
|
-
function formatGitPath(path, gitRoot) {
|
|
1566
|
-
const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
|
|
1567
|
-
const relativePath = relative(gitRoot, absolutePath);
|
|
1568
|
-
return toUnixPath(relativePath);
|
|
1569
|
-
}
|
|
1570
|
-
var GitStatusError = class _GitStatusError extends Error {
|
|
1571
|
-
static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
|
|
1572
|
-
static getReducedStatus(status) {
|
|
1573
|
-
return Object.fromEntries(
|
|
1574
|
-
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1575
|
-
(entry) => {
|
|
1576
|
-
const value = entry[1];
|
|
1577
|
-
if (value == null) {
|
|
1578
|
-
return false;
|
|
1579
|
-
}
|
|
1580
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
1581
|
-
return false;
|
|
1582
|
-
}
|
|
1583
|
-
if (typeof value === "number" && value === 0) {
|
|
1584
|
-
return false;
|
|
1585
|
-
}
|
|
1586
|
-
return !(typeof value === "boolean" && !value);
|
|
1587
|
-
}
|
|
1588
|
-
)
|
|
1589
|
-
);
|
|
1590
|
-
}
|
|
1591
|
-
constructor(status) {
|
|
1592
|
-
super(
|
|
1593
|
-
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1594
|
-
${JSON.stringify(
|
|
1595
|
-
_GitStatusError.getReducedStatus(status),
|
|
1596
|
-
null,
|
|
1597
|
-
2
|
|
1598
|
-
)}`
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
};
|
|
1602
|
-
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1603
|
-
const status = await git.status(["-s"]);
|
|
1604
|
-
if (status.files.length > 0) {
|
|
1605
|
-
throw new GitStatusError(status);
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1609
|
-
if (forceCleanStatus) {
|
|
1610
|
-
await git.raw(["reset", "--hard"]);
|
|
1611
|
-
await git.clean(["f", "d"]);
|
|
1612
|
-
ui().logger.info(`git status cleaned`);
|
|
1613
|
-
}
|
|
1614
|
-
await guardAgainstLocalChanges(git);
|
|
1615
|
-
await git.checkout(branchOrHash);
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1619
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1620
|
-
|
|
1621
|
-
// packages/utils/src/lib/semver.ts
|
|
1622
|
-
import { rcompare, valid } from "semver";
|
|
1623
|
-
function normalizeSemver(semverString) {
|
|
1624
|
-
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1625
|
-
return semverString.slice(1);
|
|
1626
|
-
}
|
|
1627
|
-
if (semverString.includes("@")) {
|
|
1628
|
-
return semverString.split("@").at(-1) ?? "";
|
|
1629
|
-
}
|
|
1630
|
-
return semverString;
|
|
1631
|
-
}
|
|
1632
|
-
function isSemver(semverString = "") {
|
|
1633
|
-
return valid(normalizeSemver(semverString)) != null;
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1637
|
-
async function getLatestCommit(git = simpleGit2()) {
|
|
1638
|
-
const log2 = await git.log({
|
|
1639
|
-
maxCount: 1,
|
|
1640
|
-
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1641
|
-
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1642
|
-
});
|
|
1643
|
-
return commitSchema.parse(log2.latest);
|
|
1644
|
-
}
|
|
1645
|
-
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1646
|
-
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1647
|
-
// @TODO use simple git
|
|
1648
|
-
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1649
|
-
}
|
|
1650
|
-
function validateFilter({ from, to }) {
|
|
1651
|
-
if (to && !from) {
|
|
1652
|
-
throw new Error(
|
|
1653
|
-
`filter needs the "from" option defined to accept the "to" option.
|
|
1654
|
-
`
|
|
1655
|
-
);
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
function filterLogs(allTags, opt) {
|
|
1659
|
-
if (!opt) {
|
|
1660
|
-
return allTags;
|
|
1661
|
-
}
|
|
1662
|
-
validateFilter(opt);
|
|
1663
|
-
const { from, to, maxCount } = opt;
|
|
1664
|
-
const finIndex = (tagName, fallback) => {
|
|
1665
|
-
const idx = allTags.indexOf(tagName ?? "");
|
|
1666
|
-
if (idx > -1) {
|
|
1667
|
-
return idx;
|
|
1668
|
-
}
|
|
1669
|
-
return fallback;
|
|
1670
|
-
};
|
|
1671
|
-
const fromIndex = finIndex(from, 0);
|
|
1672
|
-
const toIndex = finIndex(to, void 0);
|
|
1673
|
-
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1674
|
-
}
|
|
1675
|
-
async function getHashFromTag(tag, git = simpleGit2()) {
|
|
1676
|
-
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1677
|
-
const hash = tagDetails.trim();
|
|
1678
|
-
return {
|
|
1679
|
-
hash: hash.split("\n").at(-1) ?? "",
|
|
1680
|
-
message: tag
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
async function getSemverTags(opt = {}, git = simpleGit2()) {
|
|
1684
|
-
validateFilter(opt);
|
|
1685
|
-
const { targetBranch, ...options2 } = opt;
|
|
1686
|
-
let currentBranch;
|
|
1687
|
-
if (targetBranch) {
|
|
1688
|
-
currentBranch = await getCurrentBranchOrTag(git);
|
|
1689
|
-
await git.checkout(targetBranch);
|
|
1690
|
-
}
|
|
1691
|
-
const tagsRaw = await git.tag([
|
|
1692
|
-
"--merged",
|
|
1693
|
-
targetBranch ?? await getCurrentBranchOrTag(git)
|
|
1694
|
-
]);
|
|
1695
|
-
const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
|
|
1696
|
-
const relevantTags = filterLogs(allTags, options2);
|
|
1697
|
-
const tagsWithHashes = await Promise.all(
|
|
1698
|
-
relevantTags.map((tag) => getHashFromTag(tag, git))
|
|
1699
|
-
);
|
|
1700
|
-
if (currentBranch) {
|
|
1701
|
-
await git.checkout(currentBranch);
|
|
1702
|
-
}
|
|
1703
|
-
return tagsWithHashes;
|
|
1704
|
-
}
|
|
1705
|
-
async function getHashes(options2 = {}, git = simpleGit2()) {
|
|
1706
|
-
const { targetBranch, from, to, maxCount, ...opt } = options2;
|
|
1707
|
-
validateFilter({ from, to });
|
|
1708
|
-
let currentBranch;
|
|
1709
|
-
if (targetBranch) {
|
|
1710
|
-
currentBranch = await getCurrentBranchOrTag(git);
|
|
1711
|
-
await git.checkout(targetBranch);
|
|
1712
|
-
}
|
|
1713
|
-
const logs = await git.log({
|
|
1714
|
-
...opt,
|
|
1715
|
-
format: {
|
|
1716
|
-
hash: "%H",
|
|
1717
|
-
message: "%s"
|
|
1718
|
-
},
|
|
1719
|
-
from,
|
|
1720
|
-
to,
|
|
1721
|
-
maxCount
|
|
1722
|
-
});
|
|
1723
|
-
if (targetBranch) {
|
|
1724
|
-
await git.checkout(currentBranch);
|
|
1725
|
-
}
|
|
1726
|
-
return [...logs.all];
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
// packages/utils/src/lib/group-by-status.ts
|
|
1730
|
-
function groupByStatus(results) {
|
|
1731
|
-
return results.reduce(
|
|
1732
|
-
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1733
|
-
{ fulfilled: [], rejected: [] }
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
// packages/utils/src/lib/progress.ts
|
|
1738
|
-
import chalk3 from "chalk";
|
|
1739
|
-
import { MultiProgressBars } from "multi-progress-bars";
|
|
1740
|
-
var barStyles = {
|
|
1741
|
-
active: (s) => chalk3.green(s),
|
|
1742
|
-
done: (s) => chalk3.gray(s),
|
|
1743
|
-
idle: (s) => chalk3.gray(s)
|
|
1744
|
-
};
|
|
1745
|
-
var messageStyles = {
|
|
1746
|
-
active: (s) => chalk3.black(s),
|
|
1747
|
-
done: (s) => chalk3.green(chalk3.bold(s)),
|
|
1748
|
-
idle: (s) => chalk3.gray(s)
|
|
1749
|
-
};
|
|
1750
|
-
var mpb;
|
|
1751
|
-
function getSingletonProgressBars(options2) {
|
|
1752
|
-
if (!mpb) {
|
|
1753
|
-
mpb = new MultiProgressBars({
|
|
1754
|
-
progressWidth: TERMINAL_WIDTH,
|
|
1755
|
-
initMessage: "",
|
|
1756
|
-
border: true,
|
|
1757
|
-
...options2
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
return mpb;
|
|
1761
|
-
}
|
|
1762
|
-
function getProgressBar(taskName) {
|
|
1763
|
-
const tasks = getSingletonProgressBars();
|
|
1764
|
-
tasks.addTask(taskName, {
|
|
1765
|
-
type: "percentage",
|
|
1766
|
-
percentage: 0,
|
|
1767
|
-
message: "",
|
|
1768
|
-
barTransformFn: barStyles.idle
|
|
1769
|
-
});
|
|
1770
|
-
return {
|
|
1771
|
-
incrementInSteps: (numPlugins) => {
|
|
1772
|
-
tasks.incrementTask(taskName, {
|
|
1773
|
-
percentage: 1 / numPlugins,
|
|
1774
|
-
barTransformFn: barStyles.active
|
|
1775
|
-
});
|
|
1776
|
-
},
|
|
1777
|
-
updateTitle: (title) => {
|
|
1778
|
-
tasks.updateTask(taskName, {
|
|
1779
|
-
message: title,
|
|
1780
|
-
barTransformFn: barStyles.active
|
|
1781
|
-
});
|
|
1782
|
-
},
|
|
1783
|
-
endProgress: (message = "") => {
|
|
1784
|
-
tasks.done(taskName, {
|
|
1785
|
-
message: messageStyles.done(message),
|
|
1786
|
-
barTransformFn: barStyles.done
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
};
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1793
|
-
function listGroupsFromAllPlugins(report) {
|
|
1794
|
-
return report.plugins.flatMap(
|
|
1795
|
-
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1796
|
-
);
|
|
1797
|
-
}
|
|
1798
|
-
function listAuditsFromAllPlugins(report) {
|
|
1799
|
-
return report.plugins.flatMap(
|
|
1800
|
-
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1801
|
-
);
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
// packages/utils/src/lib/reports/formatting.ts
|
|
1805
|
-
var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
|
|
1806
|
-
function tableSection(tableData, options2) {
|
|
1807
|
-
if (tableData.rows.length === 0) {
|
|
1808
|
-
return "";
|
|
1809
|
-
}
|
|
1810
|
-
const { level = 4 } = options2 ?? {};
|
|
1811
|
-
const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
|
|
1812
|
-
return lines2(
|
|
1813
|
-
tableData.title && render(tableData.title, level),
|
|
1814
|
-
table3(tableData)
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
function metaDescription({
|
|
1818
|
-
docsUrl,
|
|
1819
|
-
description
|
|
1820
|
-
}) {
|
|
1821
|
-
if (docsUrl) {
|
|
1822
|
-
const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
|
|
1823
|
-
if (!description) {
|
|
1824
|
-
return section2(docsLink);
|
|
1825
|
-
}
|
|
1826
|
-
const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
|
|
1827
|
-
return section2(`${parsedDescription}${docsLink}`);
|
|
1828
|
-
}
|
|
1829
|
-
if (description && description.trim().length > 0) {
|
|
1830
|
-
return section2(description);
|
|
1831
|
-
}
|
|
1832
|
-
return "";
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1836
|
-
var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
|
|
1837
|
-
function categoriesOverviewSection(report) {
|
|
1838
|
-
const { categories, plugins } = report;
|
|
1839
|
-
if (categories.length > 0 && plugins.length > 0) {
|
|
1840
|
-
const tableContent = {
|
|
1841
|
-
columns: reportOverviewTableHeaders,
|
|
1842
|
-
rows: categories.map(({ title, refs, score }) => ({
|
|
1843
|
-
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1844
|
-
category: link5(`#${slugify(title)}`, title),
|
|
1845
|
-
score: `${scoreMarker(score)}${SPACE}${boldMd2(
|
|
1846
|
-
formatReportScore(score)
|
|
1847
|
-
)}`,
|
|
1848
|
-
audits: countCategoryAudits(refs, plugins).toString()
|
|
1849
|
-
}))
|
|
1850
|
-
};
|
|
1851
|
-
return tableSection(tableContent);
|
|
1852
|
-
}
|
|
1853
|
-
return "";
|
|
1854
|
-
}
|
|
1855
|
-
function categoriesDetailsSection(report) {
|
|
1856
|
-
const { categories, plugins } = report;
|
|
1857
|
-
const categoryDetails = categories.flatMap((category) => {
|
|
1858
|
-
const categoryTitle = h32(category.title);
|
|
1859
|
-
const categoryScore = `${scoreMarker(
|
|
1860
|
-
category.score
|
|
1861
|
-
)}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
|
|
1862
|
-
const categoryMDItems = category.refs.map((ref) => {
|
|
1863
|
-
if (ref.type === "group") {
|
|
1864
|
-
const group = getSortableGroupByRef(ref, plugins);
|
|
1865
|
-
const groupAudits = group.refs.map(
|
|
1866
|
-
(groupRef) => getSortableAuditByRef(
|
|
1867
|
-
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
1868
|
-
plugins
|
|
1869
|
-
)
|
|
1870
|
-
);
|
|
1871
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1872
|
-
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1873
|
-
} else {
|
|
1874
|
-
const audit = getSortableAuditByRef(ref, plugins);
|
|
1875
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1876
|
-
return categoryRef(audit, pluginTitle);
|
|
1877
|
-
}
|
|
1878
|
-
});
|
|
1879
|
-
return section3(
|
|
1880
|
-
categoryTitle,
|
|
1881
|
-
metaDescription(category),
|
|
1882
|
-
categoryScore,
|
|
1883
|
-
...categoryMDItems
|
|
1884
|
-
);
|
|
1885
|
-
});
|
|
1886
|
-
return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
|
|
1763
|
+
function categoriesDetailsSection(report) {
|
|
1764
|
+
const { categories, plugins } = report;
|
|
1765
|
+
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
1766
|
+
categories,
|
|
1767
|
+
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
1768
|
+
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
1769
|
+
formatReportScore(category.score)
|
|
1770
|
+
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
1771
|
+
).list(
|
|
1772
|
+
category.refs.map((ref) => {
|
|
1773
|
+
if (ref.type === "group") {
|
|
1774
|
+
const group = getSortableGroupByRef(ref, plugins);
|
|
1775
|
+
const groupAudits = group.refs.map(
|
|
1776
|
+
(groupRef) => getSortableAuditByRef(
|
|
1777
|
+
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
1778
|
+
plugins
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1782
|
+
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1783
|
+
} else {
|
|
1784
|
+
const audit = getSortableAuditByRef(ref, plugins);
|
|
1785
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1786
|
+
return categoryRef(audit, pluginTitle);
|
|
1787
|
+
}
|
|
1788
|
+
})
|
|
1789
|
+
)
|
|
1790
|
+
);
|
|
1887
1791
|
}
|
|
1888
1792
|
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
1889
|
-
const auditTitleAsLink =
|
|
1793
|
+
const auditTitleAsLink = md3.link(
|
|
1890
1794
|
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
1891
1795
|
title
|
|
1892
1796
|
);
|
|
1893
1797
|
const marker = scoreMarker(score, "square");
|
|
1894
|
-
return
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
)}`
|
|
1898
|
-
);
|
|
1798
|
+
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
1799
|
+
pluginTitle
|
|
1800
|
+
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
1899
1801
|
}
|
|
1900
1802
|
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
1901
|
-
const groupTitle =
|
|
1902
|
-
|
|
1903
|
-
)
|
|
1904
|
-
const
|
|
1905
|
-
(
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1803
|
+
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
1804
|
+
pluginTitle
|
|
1805
|
+
)})`;
|
|
1806
|
+
const auditsList = md3.list(
|
|
1807
|
+
groupAudits.map(
|
|
1808
|
+
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
1809
|
+
const auditTitleLink = md3.link(
|
|
1810
|
+
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
1811
|
+
auditTitle
|
|
1812
|
+
);
|
|
1813
|
+
const marker = scoreMarker(auditScore, "square");
|
|
1814
|
+
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
1815
|
+
String(displayValue ?? value)
|
|
1816
|
+
)}`;
|
|
1817
|
+
}
|
|
1818
|
+
)
|
|
1919
1819
|
);
|
|
1920
|
-
return
|
|
1820
|
+
return md3`${groupTitle}${auditsList}`;
|
|
1821
|
+
}
|
|
1822
|
+
function binaryIconSuffix(score, isBinary) {
|
|
1823
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
1921
1824
|
}
|
|
1922
1825
|
|
|
1923
1826
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1924
|
-
var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
|
|
1925
|
-
var { bold: boldHtml, details: details2 } = html;
|
|
1926
1827
|
function auditDetailsAuditValue({
|
|
1927
1828
|
score,
|
|
1928
1829
|
value,
|
|
1929
1830
|
displayValue
|
|
1930
1831
|
}) {
|
|
1931
|
-
return `${scoreMarker(score, "square")} ${
|
|
1832
|
+
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
1932
1833
|
String(displayValue ?? value)
|
|
1933
1834
|
)} (score: ${formatReportScore(score)})`;
|
|
1934
1835
|
}
|
|
1935
1836
|
function generateMdReport(report) {
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
auditsSection(report),
|
|
1942
|
-
aboutSection(report),
|
|
1943
|
-
`${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
|
|
1944
|
-
);
|
|
1945
|
-
}
|
|
1946
|
-
function auditDetailsIssues(issues = []) {
|
|
1947
|
-
if (issues.length === 0) {
|
|
1948
|
-
return "";
|
|
1949
|
-
}
|
|
1950
|
-
const detailsTableData = {
|
|
1951
|
-
title: "Issues",
|
|
1952
|
-
columns: issuesTableHeadings,
|
|
1953
|
-
rows: issues.map(
|
|
1954
|
-
({ severity: severityVal, message, source: sourceVal }) => {
|
|
1955
|
-
const severity = `${severityMarker(severityVal)} <i>${severityVal}</i>`;
|
|
1956
|
-
if (!sourceVal) {
|
|
1957
|
-
return { severity, message, file: "", line: "" };
|
|
1958
|
-
}
|
|
1959
|
-
const file = `<code>${sourceVal.file}</code>`;
|
|
1960
|
-
if (!sourceVal.position) {
|
|
1961
|
-
return { severity, message, file, line: "" };
|
|
1962
|
-
}
|
|
1963
|
-
const { startLine, endLine } = sourceVal.position;
|
|
1964
|
-
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1965
|
-
return { severity, message, file, line };
|
|
1966
|
-
}
|
|
1967
|
-
)
|
|
1968
|
-
};
|
|
1969
|
-
return tableSection(detailsTableData);
|
|
1970
|
-
}
|
|
1971
|
-
function auditDetails(audit) {
|
|
1972
|
-
const { table: table5, issues = [] } = audit.details ?? {};
|
|
1973
|
-
const detailsValue = auditDetailsAuditValue(audit);
|
|
1974
|
-
if (issues.length === 0 && table5 == null) {
|
|
1975
|
-
return section4(detailsValue);
|
|
1976
|
-
}
|
|
1977
|
-
const tableSectionContent = table5 == null ? "" : tableSection(table5);
|
|
1978
|
-
const issuesSectionContent = issues.length > 0 ? auditDetailsIssues(issues) : "";
|
|
1979
|
-
return details2(
|
|
1980
|
-
detailsValue,
|
|
1981
|
-
lines4(tableSectionContent, issuesSectionContent)
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
function auditsSection({
|
|
1985
|
-
plugins
|
|
1986
|
-
}) {
|
|
1987
|
-
const content = plugins.flatMap(
|
|
1988
|
-
({ slug, audits }) => audits.flatMap((audit) => {
|
|
1989
|
-
const auditTitle = `${audit.title}${SPACE}(${getPluginNameFromSlug(
|
|
1990
|
-
slug,
|
|
1991
|
-
plugins
|
|
1992
|
-
)})`;
|
|
1993
|
-
const detailsContent = auditDetails(audit);
|
|
1994
|
-
const descriptionContent = metaDescription(audit);
|
|
1995
|
-
return [h33(auditTitle), detailsContent, descriptionContent];
|
|
1996
|
-
})
|
|
1997
|
-
);
|
|
1998
|
-
return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
|
|
1999
|
-
}
|
|
2000
|
-
function aboutSection(report) {
|
|
2001
|
-
const { date, plugins } = report;
|
|
2002
|
-
const reportMetaTable = reportMetaData(report);
|
|
2003
|
-
const pluginMetaTable = reportPluginMeta({ plugins });
|
|
2004
|
-
return lines4(
|
|
2005
|
-
h23("About"),
|
|
2006
|
-
section4(
|
|
2007
|
-
`Report was created by [Code PushUp](${README_LINK}) on ${formatDate(
|
|
2008
|
-
new Date(date)
|
|
2009
|
-
)}.`
|
|
2010
|
-
),
|
|
2011
|
-
tableSection(pluginMetaTable),
|
|
2012
|
-
tableSection(reportMetaTable)
|
|
2013
|
-
);
|
|
2014
|
-
}
|
|
2015
|
-
function reportPluginMeta({ plugins }) {
|
|
2016
|
-
return {
|
|
2017
|
-
columns: [
|
|
2018
|
-
{
|
|
2019
|
-
key: "plugin",
|
|
2020
|
-
align: "left"
|
|
2021
|
-
},
|
|
2022
|
-
{
|
|
2023
|
-
key: "audits"
|
|
2024
|
-
},
|
|
2025
|
-
{
|
|
2026
|
-
key: "version"
|
|
2027
|
-
},
|
|
2028
|
-
{
|
|
2029
|
-
key: "duration"
|
|
2030
|
-
}
|
|
2031
|
-
],
|
|
2032
|
-
rows: plugins.map(
|
|
2033
|
-
({
|
|
2034
|
-
title: pluginTitle,
|
|
2035
|
-
audits,
|
|
2036
|
-
version: pluginVersion,
|
|
2037
|
-
duration: pluginDuration
|
|
2038
|
-
}) => ({
|
|
2039
|
-
plugin: pluginTitle,
|
|
2040
|
-
audits: audits.length.toString(),
|
|
2041
|
-
version: codeMd(pluginVersion || ""),
|
|
2042
|
-
duration: formatDuration(pluginDuration)
|
|
2043
|
-
})
|
|
1837
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
|
|
1838
|
+
report.categories.length > 0,
|
|
1839
|
+
(doc) => doc.$concat(
|
|
1840
|
+
categoriesOverviewSection(report),
|
|
1841
|
+
categoriesDetailsSection(report)
|
|
2044
1842
|
)
|
|
2045
|
-
};
|
|
2046
|
-
}
|
|
2047
|
-
function
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
{
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
{
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
{
|
|
2071
|
-
key: "categories"
|
|
2072
|
-
},
|
|
2073
|
-
{
|
|
2074
|
-
key: "audits"
|
|
2075
|
-
}
|
|
2076
|
-
],
|
|
2077
|
-
rows: [
|
|
2078
|
-
{
|
|
2079
|
-
commit: commitInfo,
|
|
2080
|
-
version: codeMd(version2 || ""),
|
|
2081
|
-
duration: formatDuration(duration),
|
|
2082
|
-
plugins: plugins.length,
|
|
2083
|
-
categories: categories.length,
|
|
2084
|
-
audits: plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
2085
|
-
}
|
|
2086
|
-
]
|
|
2087
|
-
};
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2091
|
-
var {
|
|
2092
|
-
h1: h13,
|
|
2093
|
-
h2: h24,
|
|
2094
|
-
lines: lines5,
|
|
2095
|
-
link: link7,
|
|
2096
|
-
bold: boldMd3,
|
|
2097
|
-
italic: italicMd,
|
|
2098
|
-
table: table4,
|
|
2099
|
-
section: section5
|
|
2100
|
-
} = md;
|
|
2101
|
-
var { details: details3 } = html;
|
|
2102
|
-
var MAX_ROWS = 100;
|
|
2103
|
-
function generateMdReportsDiff(diff) {
|
|
2104
|
-
return lines5(
|
|
2105
|
-
section5(formatDiffHeaderSection(diff)),
|
|
2106
|
-
formatDiffCategoriesSection(diff),
|
|
2107
|
-
formatDiffGroupsSection(diff),
|
|
2108
|
-
formatDiffAuditsSection(diff)
|
|
2109
|
-
);
|
|
2110
|
-
}
|
|
2111
|
-
function formatDiffHeaderSection(diff) {
|
|
2112
|
-
const outcomeTexts = {
|
|
2113
|
-
positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
|
|
2114
|
-
negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
|
|
2115
|
-
mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
|
|
2116
|
-
"improvements and regressions"
|
|
2117
|
-
)}`,
|
|
2118
|
-
unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
|
|
2119
|
-
};
|
|
2120
|
-
const outcome = mergeDiffOutcomes(
|
|
2121
|
-
changesToDiffOutcomes([
|
|
2122
|
-
...diff.categories.changed,
|
|
2123
|
-
...diff.groups.changed,
|
|
2124
|
-
...diff.audits.changed
|
|
2125
|
-
])
|
|
2126
|
-
);
|
|
2127
|
-
const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2128
|
-
return lines5(
|
|
2129
|
-
h13("Code PushUp"),
|
|
2130
|
-
diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
|
|
2131
|
-
);
|
|
2132
|
-
}
|
|
2133
|
-
function formatDiffCategoriesSection(diff) {
|
|
2134
|
-
const { changed, unchanged, added } = diff.categories;
|
|
2135
|
-
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2136
|
-
const hasChanges = unchanged.length < categoriesCount;
|
|
2137
|
-
if (categoriesCount === 0) {
|
|
2138
|
-
return "";
|
|
2139
|
-
}
|
|
2140
|
-
const columns = [
|
|
2141
|
-
{ key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
|
|
2142
|
-
{ key: "before", label: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score" },
|
|
2143
|
-
{ key: "after", label: "\u2B50 Current score" },
|
|
2144
|
-
{ key: "change", label: "\u{1F504} Score change" }
|
|
2145
|
-
];
|
|
2146
|
-
return lines5(
|
|
2147
|
-
h24("\u{1F3F7}\uFE0F Categories"),
|
|
2148
|
-
categoriesCount > 0 && table4({
|
|
2149
|
-
columns: hasChanges ? columns : columns.slice(0, 2),
|
|
2150
|
-
rows: [
|
|
2151
|
-
...sortChanges(changed).map((category) => ({
|
|
2152
|
-
category: formatTitle(category),
|
|
2153
|
-
after: formatScoreWithColor(category.scores.after),
|
|
2154
|
-
before: formatScoreWithColor(category.scores.before, {
|
|
2155
|
-
skipBold: true
|
|
2156
|
-
}),
|
|
2157
|
-
change: formatScoreChange(category.scores.diff)
|
|
2158
|
-
})),
|
|
2159
|
-
...added.map((category) => ({
|
|
2160
|
-
category: formatTitle(category),
|
|
2161
|
-
after: formatScoreWithColor(category.score),
|
|
2162
|
-
before: italicMd("n/a (\\*)"),
|
|
2163
|
-
change: italicMd("n/a (\\*)")
|
|
2164
|
-
})),
|
|
2165
|
-
...unchanged.map((category) => ({
|
|
2166
|
-
category: formatTitle(category),
|
|
2167
|
-
after: formatScoreWithColor(category.score),
|
|
2168
|
-
before: formatScoreWithColor(category.score, { skipBold: true }),
|
|
2169
|
-
change: "\u2013"
|
|
2170
|
-
}))
|
|
2171
|
-
].map(
|
|
2172
|
-
(row) => hasChanges ? row : { category: row.category, before: row.before }
|
|
2173
|
-
)
|
|
2174
|
-
}),
|
|
2175
|
-
added.length > 0 && section5(italicMd("(\\*) New category."))
|
|
2176
|
-
);
|
|
2177
|
-
}
|
|
2178
|
-
function formatDiffGroupsSection(diff) {
|
|
2179
|
-
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2180
|
-
return "";
|
|
2181
|
-
}
|
|
2182
|
-
return lines5(
|
|
2183
|
-
h24("\u{1F5C3}\uFE0F Groups"),
|
|
2184
|
-
formatGroupsOrAuditsDetails("group", diff.groups, {
|
|
2185
|
-
columns: [
|
|
2186
|
-
{ key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
|
|
2187
|
-
{ key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
|
|
2188
|
-
{ key: "before", label: "\u2B50 Previous score" },
|
|
2189
|
-
{ key: "after", label: "\u2B50 Current score" },
|
|
2190
|
-
{ key: "change", label: "\u{1F504} Score change" }
|
|
2191
|
-
],
|
|
2192
|
-
rows: sortChanges(diff.groups.changed).map((group) => ({
|
|
2193
|
-
plugin: formatTitle(group.plugin),
|
|
2194
|
-
group: formatTitle(group),
|
|
2195
|
-
after: formatScoreWithColor(group.scores.after),
|
|
2196
|
-
before: formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2197
|
-
change: formatScoreChange(group.scores.diff)
|
|
2198
|
-
}))
|
|
1843
|
+
).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
1844
|
+
}
|
|
1845
|
+
function auditDetailsIssues(issues = []) {
|
|
1846
|
+
if (issues.length === 0) {
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1849
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
1850
|
+
[
|
|
1851
|
+
{ heading: "Severity", alignment: "center" },
|
|
1852
|
+
{ heading: "Message", alignment: "left" },
|
|
1853
|
+
{ heading: "Source file", alignment: "left" },
|
|
1854
|
+
{ heading: "Line(s)", alignment: "center" }
|
|
1855
|
+
],
|
|
1856
|
+
issues.map(({ severity: level, message, source }) => {
|
|
1857
|
+
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
1858
|
+
if (!source) {
|
|
1859
|
+
return [severity, message];
|
|
1860
|
+
}
|
|
1861
|
+
const file = md4.code(source.file);
|
|
1862
|
+
if (!source.position) {
|
|
1863
|
+
return [severity, message, file];
|
|
1864
|
+
}
|
|
1865
|
+
const { startLine, endLine } = source.position;
|
|
1866
|
+
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1867
|
+
return [severity, message, file, line];
|
|
2199
1868
|
})
|
|
2200
1869
|
);
|
|
2201
1870
|
}
|
|
2202
|
-
function
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
rows: sortChanges(diff.audits.changed).map((audit) => ({
|
|
2214
|
-
plugin: formatTitle(audit.plugin),
|
|
2215
|
-
audit: formatTitle(audit),
|
|
2216
|
-
after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
|
|
2217
|
-
audit.displayValues.after || audit.values.after.toString()
|
|
2218
|
-
)}`,
|
|
2219
|
-
before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2220
|
-
change: formatValueChange(audit)
|
|
2221
|
-
}))
|
|
2222
|
-
})
|
|
1871
|
+
function auditDetails(audit) {
|
|
1872
|
+
const { table: table2, issues = [] } = audit.details ?? {};
|
|
1873
|
+
const detailsValue = auditDetailsAuditValue(audit);
|
|
1874
|
+
if (issues.length === 0 && !table2?.rows.length) {
|
|
1875
|
+
return new MarkdownDocument3().paragraph(detailsValue);
|
|
1876
|
+
}
|
|
1877
|
+
const tableSectionContent = table2 && tableSection(table2);
|
|
1878
|
+
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
|
|
1879
|
+
return new MarkdownDocument3().details(
|
|
1880
|
+
detailsValue,
|
|
1881
|
+
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
2223
1882
|
);
|
|
2224
1883
|
}
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
})
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
),
|
|
2239
|
-
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
2240
|
-
)
|
|
1884
|
+
function auditsSection({
|
|
1885
|
+
plugins
|
|
1886
|
+
}) {
|
|
1887
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
1888
|
+
plugins.flatMap(
|
|
1889
|
+
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
1890
|
+
),
|
|
1891
|
+
(doc, { plugin, ...audit }) => {
|
|
1892
|
+
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
1893
|
+
const detailsContent = auditDetails(audit);
|
|
1894
|
+
const descriptionContent = metaDescription(audit);
|
|
1895
|
+
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
1896
|
+
}
|
|
2241
1897
|
);
|
|
2242
1898
|
}
|
|
2243
|
-
function
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
1899
|
+
function aboutSection(report) {
|
|
1900
|
+
const { date, plugins } = report;
|
|
1901
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
1902
|
+
md4`Report was created by ${md4.link(
|
|
1903
|
+
README_LINK,
|
|
1904
|
+
"Code PushUp"
|
|
1905
|
+
)} on ${formatDate(new Date(date))}.`
|
|
1906
|
+
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
1907
|
+
}
|
|
1908
|
+
function pluginMetaTable({
|
|
1909
|
+
plugins
|
|
1910
|
+
}) {
|
|
1911
|
+
return [
|
|
1912
|
+
[
|
|
1913
|
+
{ heading: "Plugin", alignment: "left" },
|
|
1914
|
+
{ heading: "Audits", alignment: "center" },
|
|
1915
|
+
{ heading: "Version", alignment: "center" },
|
|
1916
|
+
{ heading: "Duration", alignment: "right" }
|
|
1917
|
+
],
|
|
1918
|
+
plugins.map(({ title, audits, version: version3 = "", duration }) => [
|
|
1919
|
+
title,
|
|
1920
|
+
audits.length.toString(),
|
|
1921
|
+
version3 && md4.code(version3),
|
|
1922
|
+
formatDuration(duration)
|
|
1923
|
+
])
|
|
1924
|
+
];
|
|
2247
1925
|
}
|
|
2248
|
-
function
|
|
2249
|
-
|
|
2250
|
-
|
|
1926
|
+
function reportMetaTable({
|
|
1927
|
+
commit,
|
|
1928
|
+
version: version3,
|
|
1929
|
+
duration,
|
|
1930
|
+
plugins,
|
|
1931
|
+
categories
|
|
2251
1932
|
}) {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
1933
|
+
return [
|
|
1934
|
+
[
|
|
1935
|
+
{ heading: "Commit", alignment: "left" },
|
|
1936
|
+
{ heading: "Version", alignment: "center" },
|
|
1937
|
+
{ heading: "Duration", alignment: "right" },
|
|
1938
|
+
{ heading: "Plugins", alignment: "center" },
|
|
1939
|
+
{ heading: "Categories", alignment: "center" },
|
|
1940
|
+
{ heading: "Audits", alignment: "center" }
|
|
1941
|
+
],
|
|
1942
|
+
[
|
|
1943
|
+
[
|
|
1944
|
+
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
1945
|
+
md4.code(version3),
|
|
1946
|
+
formatDuration(duration),
|
|
1947
|
+
plugins.length.toString(),
|
|
1948
|
+
categories.length.toString(),
|
|
1949
|
+
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
1950
|
+
]
|
|
1951
|
+
]
|
|
1952
|
+
];
|
|
2256
1953
|
}
|
|
1954
|
+
|
|
1955
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
1956
|
+
import {
|
|
1957
|
+
MarkdownDocument as MarkdownDocument5,
|
|
1958
|
+
md as md6
|
|
1959
|
+
} from "build-md";
|
|
1960
|
+
|
|
1961
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
|
|
1962
|
+
import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
|
|
1963
|
+
var MAX_ROWS = 100;
|
|
2257
1964
|
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
unchanged.length === 1 ? "is" : "are",
|
|
2262
|
-
"unchanged."
|
|
2263
|
-
].join(" ")
|
|
2264
|
-
);
|
|
1965
|
+
const pluralizedCount = changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`;
|
|
1966
|
+
const pluralizedVerb = unchanged.length === 1 ? "is" : "are";
|
|
1967
|
+
return `${pluralizedCount} ${pluralizedVerb} unchanged.`;
|
|
2265
1968
|
}
|
|
2266
1969
|
function summarizeDiffOutcomes(outcomes, token) {
|
|
2267
1970
|
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
@@ -2281,20 +1984,46 @@ function summarizeDiffOutcomes(outcomes, token) {
|
|
|
2281
1984
|
}
|
|
2282
1985
|
}).join(", ");
|
|
2283
1986
|
}
|
|
1987
|
+
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
1988
|
+
if (changed.length === 0) {
|
|
1989
|
+
return new MarkdownDocument4().paragraph(
|
|
1990
|
+
summarizeUnchanged(token, { changed, unchanged })
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
return new MarkdownDocument4().table(columns, rows.slice(0, MAX_ROWS)).paragraph(
|
|
1994
|
+
changed.length > MAX_ROWS && md5.italic(
|
|
1995
|
+
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
1996
|
+
token
|
|
1997
|
+
)} are listed above for brevity.`
|
|
1998
|
+
)
|
|
1999
|
+
).paragraph(
|
|
2000
|
+
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2284
2003
|
function formatTitle({
|
|
2285
2004
|
title,
|
|
2286
2005
|
docsUrl
|
|
2287
2006
|
}) {
|
|
2288
2007
|
if (docsUrl) {
|
|
2289
|
-
return
|
|
2008
|
+
return md5.link(docsUrl, title);
|
|
2290
2009
|
}
|
|
2291
2010
|
return title;
|
|
2292
2011
|
}
|
|
2012
|
+
function formatPortalLink(portalUrl) {
|
|
2013
|
+
return portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}");
|
|
2014
|
+
}
|
|
2293
2015
|
function sortChanges(changes) {
|
|
2294
2016
|
return [...changes].sort(
|
|
2295
2017
|
(a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
|
|
2296
2018
|
);
|
|
2297
2019
|
}
|
|
2020
|
+
function getDiffChanges(diff) {
|
|
2021
|
+
return [
|
|
2022
|
+
...diff.categories.changed,
|
|
2023
|
+
...diff.groups.changed,
|
|
2024
|
+
...diff.audits.changed
|
|
2025
|
+
];
|
|
2026
|
+
}
|
|
2298
2027
|
function changesToDiffOutcomes(changes) {
|
|
2299
2028
|
return changes.map((change) => {
|
|
2300
2029
|
if (change.scores.diff > 0) {
|
|
@@ -2329,9 +2058,218 @@ function countDiffOutcomes(outcomes) {
|
|
|
2329
2058
|
unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
|
|
2330
2059
|
};
|
|
2331
2060
|
}
|
|
2061
|
+
function formatReportOutcome(outcome, commits) {
|
|
2062
|
+
const outcomeTexts = {
|
|
2063
|
+
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
2064
|
+
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
2065
|
+
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
2066
|
+
"improvements and regressions"
|
|
2067
|
+
)}`,
|
|
2068
|
+
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
2069
|
+
};
|
|
2070
|
+
if (commits) {
|
|
2071
|
+
const commitsText = `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2072
|
+
return md5`${outcomeTexts[outcome]} – ${commitsText}.`;
|
|
2073
|
+
}
|
|
2074
|
+
return md5`${outcomeTexts[outcome]}.`;
|
|
2075
|
+
}
|
|
2076
|
+
function compareDiffsBy(type, a, b) {
|
|
2077
|
+
return sumScoreChanges(b[type].changed) - sumScoreChanges(a[type].changed) || sumConfigChanges(b[type]) - sumConfigChanges(a[type]);
|
|
2078
|
+
}
|
|
2079
|
+
function sumScoreChanges(changes) {
|
|
2080
|
+
return changes.reduce(
|
|
2081
|
+
(acc, { scores }) => acc + Math.abs(scores.diff),
|
|
2082
|
+
0
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
function sumConfigChanges({
|
|
2086
|
+
added,
|
|
2087
|
+
removed
|
|
2088
|
+
}) {
|
|
2089
|
+
return added.length + removed.length;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2093
|
+
function generateMdReportsDiff(diff) {
|
|
2094
|
+
return new MarkdownDocument5().$concat(
|
|
2095
|
+
createDiffHeaderSection(diff),
|
|
2096
|
+
createDiffCategoriesSection(diff),
|
|
2097
|
+
createDiffDetailsSection(diff)
|
|
2098
|
+
).toString();
|
|
2099
|
+
}
|
|
2100
|
+
function generateMdReportsDiffForMonorepo(diffs) {
|
|
2101
|
+
const diffsWithOutcomes = diffs.map((diff) => ({
|
|
2102
|
+
...diff,
|
|
2103
|
+
outcome: mergeDiffOutcomes(changesToDiffOutcomes(getDiffChanges(diff)))
|
|
2104
|
+
})).sort(
|
|
2105
|
+
(a, b) => compareDiffsBy("categories", a, b) || compareDiffsBy("groups", a, b) || compareDiffsBy("audits", a, b) || a.label.localeCompare(b.label)
|
|
2106
|
+
);
|
|
2107
|
+
const unchanged = diffsWithOutcomes.filter(
|
|
2108
|
+
({ outcome }) => outcome === "unchanged"
|
|
2109
|
+
);
|
|
2110
|
+
const changed = diffsWithOutcomes.filter((diff) => !unchanged.includes(diff));
|
|
2111
|
+
return new MarkdownDocument5().$concat(
|
|
2112
|
+
createDiffHeaderSection(diffs),
|
|
2113
|
+
...changed.map(createDiffProjectSection)
|
|
2114
|
+
).$if(
|
|
2115
|
+
unchanged.length > 0,
|
|
2116
|
+
(doc) => doc.rule().paragraph(summarizeUnchanged("project", { unchanged, changed }))
|
|
2117
|
+
).toString();
|
|
2118
|
+
}
|
|
2119
|
+
function createDiffHeaderSection(diff) {
|
|
2120
|
+
const outcome = mergeDiffOutcomes(
|
|
2121
|
+
changesToDiffOutcomes(toArray(diff).flatMap(getDiffChanges))
|
|
2122
|
+
);
|
|
2123
|
+
const commits = Array.isArray(diff) ? diff[0]?.commits : diff.commits;
|
|
2124
|
+
const portalUrl = Array.isArray(diff) ? void 0 : diff.portalUrl;
|
|
2125
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_1, "Code PushUp").paragraph(formatReportOutcome(outcome, commits)).paragraph(formatPortalLink(portalUrl));
|
|
2126
|
+
}
|
|
2127
|
+
function createDiffProjectSection(diff) {
|
|
2128
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_2, md6`💼 Project ${md6.code(diff.label)}`).paragraph(formatReportOutcome(diff.outcome)).paragraph(formatPortalLink(diff.portalUrl)).$concat(
|
|
2129
|
+
createDiffCategoriesSection(diff, {
|
|
2130
|
+
skipHeading: true,
|
|
2131
|
+
skipUnchanged: true
|
|
2132
|
+
}),
|
|
2133
|
+
createDiffDetailsSection(diff, HIERARCHY.level_3)
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
function createDiffCategoriesSection(diff, options2) {
|
|
2137
|
+
const { changed, unchanged, added } = diff.categories;
|
|
2138
|
+
const { skipHeading, skipUnchanged } = options2 ?? {};
|
|
2139
|
+
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2140
|
+
const hasChanges = unchanged.length < categoriesCount;
|
|
2141
|
+
if (categoriesCount === 0) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
const [columns, rows] = createCategoriesTable(diff, {
|
|
2145
|
+
hasChanges,
|
|
2146
|
+
skipUnchanged
|
|
2147
|
+
});
|
|
2148
|
+
return new MarkdownDocument5().heading(HIERARCHY.level_2, !skipHeading && "\u{1F3F7}\uFE0F Categories").table(columns, rows).paragraph(added.length > 0 && md6.italic("(\\*) New category.")).paragraph(
|
|
2149
|
+
skipUnchanged && unchanged.length > 0 && summarizeUnchanged("category", { changed, unchanged })
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
function createCategoriesTable(diff, options2) {
|
|
2153
|
+
const { changed, unchanged, added } = diff.categories;
|
|
2154
|
+
const { hasChanges, skipUnchanged } = options2;
|
|
2155
|
+
const columns = [
|
|
2156
|
+
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
2157
|
+
{
|
|
2158
|
+
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
2159
|
+
alignment: "center"
|
|
2160
|
+
},
|
|
2161
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2162
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2163
|
+
];
|
|
2164
|
+
const rows = [
|
|
2165
|
+
...sortChanges(changed).map((category) => [
|
|
2166
|
+
formatTitle(category),
|
|
2167
|
+
formatScoreWithColor(category.scores.before, {
|
|
2168
|
+
skipBold: true
|
|
2169
|
+
}),
|
|
2170
|
+
formatScoreWithColor(category.scores.after),
|
|
2171
|
+
formatScoreChange(category.scores.diff)
|
|
2172
|
+
]),
|
|
2173
|
+
...added.map((category) => [
|
|
2174
|
+
formatTitle(category),
|
|
2175
|
+
md6.italic("n/a (\\*)"),
|
|
2176
|
+
formatScoreWithColor(category.score),
|
|
2177
|
+
md6.italic("n/a (\\*)")
|
|
2178
|
+
]),
|
|
2179
|
+
...skipUnchanged ? [] : unchanged.map((category) => [
|
|
2180
|
+
formatTitle(category),
|
|
2181
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
2182
|
+
formatScoreWithColor(category.score),
|
|
2183
|
+
"\u2013"
|
|
2184
|
+
])
|
|
2185
|
+
];
|
|
2186
|
+
return [
|
|
2187
|
+
hasChanges ? columns : columns.slice(0, 2),
|
|
2188
|
+
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
2189
|
+
];
|
|
2190
|
+
}
|
|
2191
|
+
function createDiffDetailsSection(diff, level = HIERARCHY.level_2) {
|
|
2192
|
+
if (diff.groups.changed.length + diff.audits.changed.length === 0) {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
const summary = ["group", "audit"].map(
|
|
2196
|
+
(token) => summarizeDiffOutcomes(
|
|
2197
|
+
changesToDiffOutcomes(diff[`${token}s`].changed),
|
|
2198
|
+
token
|
|
2199
|
+
)
|
|
2200
|
+
).filter(Boolean).join(", ");
|
|
2201
|
+
const details2 = new MarkdownDocument5().$concat(
|
|
2202
|
+
createDiffGroupsSection(diff, level),
|
|
2203
|
+
createDiffAuditsSection(diff, level)
|
|
2204
|
+
);
|
|
2205
|
+
return new MarkdownDocument5().details(summary, details2);
|
|
2206
|
+
}
|
|
2207
|
+
function createDiffGroupsSection(diff, level) {
|
|
2208
|
+
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
return new MarkdownDocument5().heading(level, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
2212
|
+
createGroupsOrAuditsDetails(
|
|
2213
|
+
"group",
|
|
2214
|
+
diff.groups,
|
|
2215
|
+
[
|
|
2216
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2217
|
+
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
2218
|
+
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
2219
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2220
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2221
|
+
],
|
|
2222
|
+
sortChanges(diff.groups.changed).map((group) => [
|
|
2223
|
+
formatTitle(group.plugin),
|
|
2224
|
+
formatTitle(group),
|
|
2225
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2226
|
+
formatScoreWithColor(group.scores.after),
|
|
2227
|
+
formatScoreChange(group.scores.diff)
|
|
2228
|
+
])
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
function createDiffAuditsSection(diff, level) {
|
|
2233
|
+
return new MarkdownDocument5().heading(level, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
2234
|
+
createGroupsOrAuditsDetails(
|
|
2235
|
+
"audit",
|
|
2236
|
+
diff.audits,
|
|
2237
|
+
[
|
|
2238
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2239
|
+
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
2240
|
+
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
2241
|
+
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
2242
|
+
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2243
|
+
],
|
|
2244
|
+
sortChanges(diff.audits.changed).map((audit) => [
|
|
2245
|
+
formatTitle(audit.plugin),
|
|
2246
|
+
formatTitle(audit),
|
|
2247
|
+
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2248
|
+
md6`${scoreMarker(audit.scores.after, "square")} ${md6.bold(
|
|
2249
|
+
audit.displayValues.after || audit.values.after.toString()
|
|
2250
|
+
)}`,
|
|
2251
|
+
formatValueChange(audit)
|
|
2252
|
+
])
|
|
2253
|
+
)
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// packages/utils/src/lib/reports/load-report.ts
|
|
2258
|
+
import { join as join2 } from "node:path";
|
|
2259
|
+
async function loadReport(options2) {
|
|
2260
|
+
const { outputDir, filename, format } = options2;
|
|
2261
|
+
await ensureDirectoryExists(outputDir);
|
|
2262
|
+
const filePath = join2(outputDir, `${filename}.${format}`);
|
|
2263
|
+
if (format === "json") {
|
|
2264
|
+
const content = await readJsonFile(filePath);
|
|
2265
|
+
return reportSchema.parse(content);
|
|
2266
|
+
}
|
|
2267
|
+
const text = await readTextFile(filePath);
|
|
2268
|
+
return text;
|
|
2269
|
+
}
|
|
2332
2270
|
|
|
2333
2271
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2334
|
-
import
|
|
2272
|
+
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2335
2273
|
function log(msg = "") {
|
|
2336
2274
|
ui().logger.log(msg);
|
|
2337
2275
|
}
|
|
@@ -2347,15 +2285,15 @@ function logStdoutSummary(report) {
|
|
|
2347
2285
|
log();
|
|
2348
2286
|
}
|
|
2349
2287
|
function reportToHeaderSection(report) {
|
|
2350
|
-
const { packageName, version:
|
|
2351
|
-
return `${
|
|
2288
|
+
const { packageName, version: version3 } = report;
|
|
2289
|
+
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version3}`;
|
|
2352
2290
|
}
|
|
2353
2291
|
function logPlugins(report) {
|
|
2354
2292
|
const { plugins } = report;
|
|
2355
2293
|
plugins.forEach((plugin) => {
|
|
2356
2294
|
const { title, audits } = plugin;
|
|
2357
2295
|
log();
|
|
2358
|
-
log(
|
|
2296
|
+
log(bold4.magentaBright(`${title} audits`));
|
|
2359
2297
|
log();
|
|
2360
2298
|
audits.forEach((audit) => {
|
|
2361
2299
|
ui().row([
|
|
@@ -2370,8 +2308,9 @@ function logPlugins(report) {
|
|
|
2370
2308
|
padding: [0, 3, 0, 0]
|
|
2371
2309
|
},
|
|
2372
2310
|
{
|
|
2373
|
-
text:
|
|
2374
|
-
|
|
2311
|
+
text: cyanBright(audit.displayValue || `${audit.value}`),
|
|
2312
|
+
// eslint-disable-next-line no-magic-numbers
|
|
2313
|
+
width: 20,
|
|
2375
2314
|
padding: [0, 0, 0, 0]
|
|
2376
2315
|
}
|
|
2377
2316
|
]);
|
|
@@ -2381,42 +2320,38 @@ function logPlugins(report) {
|
|
|
2381
2320
|
}
|
|
2382
2321
|
function logCategories({ categories, plugins }) {
|
|
2383
2322
|
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2384
|
-
const rows = categories.map(({ title, score, refs }) => [
|
|
2323
|
+
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2385
2324
|
title,
|
|
2386
|
-
applyScoreColor({ score })
|
|
2325
|
+
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2387
2326
|
countCategoryAudits(refs, plugins)
|
|
2388
2327
|
]);
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
content:
|
|
2328
|
+
const table2 = ui().table();
|
|
2329
|
+
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2330
|
+
table2.head(
|
|
2331
|
+
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2332
|
+
content: cyan(heading),
|
|
2394
2333
|
hAlign: hAlign(idx)
|
|
2395
2334
|
}))
|
|
2396
2335
|
);
|
|
2397
2336
|
rows.forEach(
|
|
2398
|
-
(row) =>
|
|
2337
|
+
(row) => table2.row(
|
|
2399
2338
|
row.map((content, idx) => ({
|
|
2400
2339
|
content: content.toString(),
|
|
2401
2340
|
hAlign: hAlign(idx)
|
|
2402
2341
|
}))
|
|
2403
2342
|
)
|
|
2404
2343
|
);
|
|
2405
|
-
log(
|
|
2344
|
+
log(bold4.magentaBright("Categories"));
|
|
2406
2345
|
log();
|
|
2407
|
-
|
|
2346
|
+
table2.render();
|
|
2408
2347
|
log();
|
|
2409
2348
|
}
|
|
2410
|
-
function
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
}
|
|
2416
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
2417
|
-
return style.yellow(formattedScore);
|
|
2418
|
-
}
|
|
2419
|
-
return style.red(formattedScore);
|
|
2349
|
+
function binaryIconPrefix(score, isBinary) {
|
|
2350
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2351
|
+
passIcon: bold4(green2("\u2713")),
|
|
2352
|
+
failIcon: bold4(red("\u2717")),
|
|
2353
|
+
postfix: " "
|
|
2354
|
+
});
|
|
2420
2355
|
}
|
|
2421
2356
|
|
|
2422
2357
|
// packages/utils/src/lib/reports/scoring.ts
|
|
@@ -2506,56 +2441,6 @@ function parseScoringParameters(refs, scoreFn) {
|
|
|
2506
2441
|
return scoredRefs;
|
|
2507
2442
|
}
|
|
2508
2443
|
|
|
2509
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
2510
|
-
function sortReport(report) {
|
|
2511
|
-
const { categories, plugins } = report;
|
|
2512
|
-
const sortedCategories = categories.map((category) => {
|
|
2513
|
-
const { audits, groups: groups2 } = category.refs.reduce(
|
|
2514
|
-
(acc, ref) => ({
|
|
2515
|
-
...acc,
|
|
2516
|
-
...ref.type === "group" ? {
|
|
2517
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
2518
|
-
} : {
|
|
2519
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
2520
|
-
}
|
|
2521
|
-
}),
|
|
2522
|
-
{ groups: [], audits: [] }
|
|
2523
|
-
);
|
|
2524
|
-
const sortedAuditsAndGroups = [...audits, ...groups2].sort(
|
|
2525
|
-
compareCategoryAuditsAndGroups
|
|
2526
|
-
);
|
|
2527
|
-
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
2528
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2529
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2530
|
-
);
|
|
2531
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2532
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2533
|
-
);
|
|
2534
|
-
return aIndex - bIndex;
|
|
2535
|
-
});
|
|
2536
|
-
return { ...category, refs: sortedRefs };
|
|
2537
|
-
});
|
|
2538
|
-
return {
|
|
2539
|
-
...report,
|
|
2540
|
-
categories: sortedCategories,
|
|
2541
|
-
plugins: sortPlugins(plugins)
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
function sortPlugins(plugins) {
|
|
2545
|
-
return plugins.map((plugin) => ({
|
|
2546
|
-
...plugin,
|
|
2547
|
-
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2548
|
-
(audit) => audit.details?.issues ? {
|
|
2549
|
-
...audit,
|
|
2550
|
-
details: {
|
|
2551
|
-
...audit.details,
|
|
2552
|
-
issues: [...audit.details.issues].sort(compareIssues)
|
|
2553
|
-
}
|
|
2554
|
-
} : audit
|
|
2555
|
-
)
|
|
2556
|
-
}));
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
2444
|
// packages/utils/src/lib/verbose-utils.ts
|
|
2560
2445
|
function getLogVerbose(verbose = false) {
|
|
2561
2446
|
return (msg) => {
|
|
@@ -2578,10 +2463,10 @@ var verboseUtils = (verbose = false) => ({
|
|
|
2578
2463
|
|
|
2579
2464
|
// packages/core/package.json
|
|
2580
2465
|
var name = "@code-pushup/core";
|
|
2581
|
-
var version = "0.
|
|
2466
|
+
var version = "0.50.0";
|
|
2582
2467
|
|
|
2583
2468
|
// packages/core/src/lib/implementation/execute-plugin.ts
|
|
2584
|
-
import
|
|
2469
|
+
import { bold as bold5 } from "ansis";
|
|
2585
2470
|
|
|
2586
2471
|
// packages/core/src/lib/normalize.ts
|
|
2587
2472
|
function normalizeIssue(issue, gitRoot) {
|
|
@@ -2644,7 +2529,7 @@ async function executeRunnerFunction(runner, onProgress) {
|
|
|
2644
2529
|
var PluginOutputMissingAuditError = class extends Error {
|
|
2645
2530
|
constructor(auditSlug) {
|
|
2646
2531
|
super(
|
|
2647
|
-
`Audit metadata not present in plugin config. Missing slug: ${
|
|
2532
|
+
`Audit metadata not present in plugin config. Missing slug: ${bold5(
|
|
2648
2533
|
auditSlug
|
|
2649
2534
|
)}`
|
|
2650
2535
|
);
|
|
@@ -2686,7 +2571,7 @@ async function executePlugin(pluginConfig, onProgress) {
|
|
|
2686
2571
|
};
|
|
2687
2572
|
}
|
|
2688
2573
|
var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
2689
|
-
progressBar?.updateTitle(`Executing ${
|
|
2574
|
+
progressBar?.updateTitle(`Executing ${bold5(pluginCfg.title)}`);
|
|
2690
2575
|
try {
|
|
2691
2576
|
const pluginReport = await executePlugin(pluginCfg);
|
|
2692
2577
|
progressBar?.incrementInSteps(steps);
|
|
@@ -2694,7 +2579,7 @@ var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
|
2694
2579
|
} catch (error) {
|
|
2695
2580
|
progressBar?.incrementInSteps(steps);
|
|
2696
2581
|
throw new Error(
|
|
2697
|
-
error instanceof Error ? `- Plugin ${
|
|
2582
|
+
error instanceof Error ? `- Plugin ${bold5(pluginCfg.title)} (${bold5(
|
|
2698
2583
|
pluginCfg.slug
|
|
2699
2584
|
)}) produced the following error:
|
|
2700
2585
|
- ${error.message}` : String(error)
|
|
@@ -2834,6 +2719,10 @@ async function collectAndPersistReports(options2) {
|
|
|
2834
2719
|
// packages/core/src/lib/compare.ts
|
|
2835
2720
|
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
2836
2721
|
import { join as join5 } from "node:path";
|
|
2722
|
+
import {
|
|
2723
|
+
PortalOperationError,
|
|
2724
|
+
getPortalComparisonLink
|
|
2725
|
+
} from "@code-pushup/portal-client";
|
|
2837
2726
|
|
|
2838
2727
|
// packages/core/src/lib/implementation/compare-scorables.ts
|
|
2839
2728
|
function compareCategories(reports) {
|
|
@@ -2970,7 +2859,7 @@ function selectMeta(meta) {
|
|
|
2970
2859
|
}
|
|
2971
2860
|
|
|
2972
2861
|
// packages/core/src/lib/compare.ts
|
|
2973
|
-
async function compareReportFiles(inputPaths, persistConfig) {
|
|
2862
|
+
async function compareReportFiles(inputPaths, persistConfig, uploadConfig, label) {
|
|
2974
2863
|
const { outputDir, filename, format } = persistConfig;
|
|
2975
2864
|
const [reportBefore, reportAfter] = await Promise.all([
|
|
2976
2865
|
readJsonFile(inputPaths.before),
|
|
@@ -2980,11 +2869,20 @@ async function compareReportFiles(inputPaths, persistConfig) {
|
|
|
2980
2869
|
before: reportSchema.parse(reportBefore),
|
|
2981
2870
|
after: reportSchema.parse(reportAfter)
|
|
2982
2871
|
};
|
|
2983
|
-
const
|
|
2872
|
+
const diff = compareReports(reports);
|
|
2873
|
+
if (label) {
|
|
2874
|
+
diff.label = label;
|
|
2875
|
+
}
|
|
2876
|
+
if (uploadConfig && diff.commits) {
|
|
2877
|
+
diff.portalUrl = await fetchPortalComparisonLink(
|
|
2878
|
+
uploadConfig,
|
|
2879
|
+
diff.commits
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2984
2882
|
return Promise.all(
|
|
2985
2883
|
format.map(async (fmt) => {
|
|
2986
2884
|
const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
|
|
2987
|
-
const content = reportsDiffToFileContent(
|
|
2885
|
+
const content = reportsDiffToFileContent(diff, fmt);
|
|
2988
2886
|
await ensureDirectoryExists(outputDir);
|
|
2989
2887
|
await writeFile2(outputPath, content);
|
|
2990
2888
|
return outputPath;
|
|
@@ -3022,6 +2920,29 @@ function reportsDiffToFileContent(reportsDiff, format) {
|
|
|
3022
2920
|
return generateMdReportsDiff(reportsDiff);
|
|
3023
2921
|
}
|
|
3024
2922
|
}
|
|
2923
|
+
async function fetchPortalComparisonLink(uploadConfig, commits) {
|
|
2924
|
+
const { server, apiKey, organization, project } = uploadConfig;
|
|
2925
|
+
try {
|
|
2926
|
+
return await getPortalComparisonLink({
|
|
2927
|
+
server,
|
|
2928
|
+
apiKey,
|
|
2929
|
+
parameters: {
|
|
2930
|
+
organization,
|
|
2931
|
+
project,
|
|
2932
|
+
before: commits.before.hash,
|
|
2933
|
+
after: commits.after.hash
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
if (error instanceof PortalOperationError) {
|
|
2938
|
+
ui().logger.warning(
|
|
2939
|
+
`Failed to fetch portal comparison link - ${error.message}`
|
|
2940
|
+
);
|
|
2941
|
+
return void 0;
|
|
2942
|
+
}
|
|
2943
|
+
throw error;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
3025
2946
|
|
|
3026
2947
|
// packages/core/src/lib/upload.ts
|
|
3027
2948
|
import {
|
|
@@ -3077,9 +2998,9 @@ function auditToGQL(audit) {
|
|
|
3077
2998
|
score,
|
|
3078
2999
|
value,
|
|
3079
3000
|
displayValue: formattedValue,
|
|
3080
|
-
details:
|
|
3001
|
+
details: details2
|
|
3081
3002
|
} = audit;
|
|
3082
|
-
const { issues, table:
|
|
3003
|
+
const { issues, table: table2 } = details2 ?? {};
|
|
3083
3004
|
return {
|
|
3084
3005
|
slug,
|
|
3085
3006
|
title,
|
|
@@ -3088,10 +3009,10 @@ function auditToGQL(audit) {
|
|
|
3088
3009
|
score,
|
|
3089
3010
|
value,
|
|
3090
3011
|
formattedValue,
|
|
3091
|
-
...
|
|
3012
|
+
...details2 && {
|
|
3092
3013
|
details: {
|
|
3093
3014
|
...issues && { issues: issues.map(issueToGQL) },
|
|
3094
|
-
...
|
|
3015
|
+
...table2 && { tables: [tableToGQL(table2)] }
|
|
3095
3016
|
}
|
|
3096
3017
|
}
|
|
3097
3018
|
};
|
|
@@ -3110,11 +3031,11 @@ function issueToGQL(issue) {
|
|
|
3110
3031
|
}
|
|
3111
3032
|
};
|
|
3112
3033
|
}
|
|
3113
|
-
function tableToGQL(
|
|
3034
|
+
function tableToGQL(table2) {
|
|
3114
3035
|
return {
|
|
3115
|
-
...
|
|
3116
|
-
...
|
|
3117
|
-
columns:
|
|
3036
|
+
...table2.title && { title: table2.title },
|
|
3037
|
+
...table2.columns?.length && {
|
|
3038
|
+
columns: table2.columns.map(
|
|
3118
3039
|
(column) => typeof column === "string" ? { alignment: tableAlignmentToGQL(column) } : {
|
|
3119
3040
|
key: column.key,
|
|
3120
3041
|
label: column.label,
|
|
@@ -3122,7 +3043,7 @@ function tableToGQL(table5) {
|
|
|
3122
3043
|
}
|
|
3123
3044
|
)
|
|
3124
3045
|
},
|
|
3125
|
-
rows:
|
|
3046
|
+
rows: table2.rows.map(
|
|
3126
3047
|
(row) => Array.isArray(row) ? row.map((content) => ({ content: content?.toString() ?? "" })) : Object.entries(row).map(([key, content]) => ({
|
|
3127
3048
|
key,
|
|
3128
3049
|
content: content?.toString() ?? ""
|
|
@@ -3269,15 +3190,54 @@ async function autoloadRc(tsconfig) {
|
|
|
3269
3190
|
);
|
|
3270
3191
|
}
|
|
3271
3192
|
|
|
3193
|
+
// packages/core/src/lib/merge-diffs.ts
|
|
3194
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
3195
|
+
import { basename, dirname, join as join7 } from "node:path";
|
|
3196
|
+
async function mergeDiffs(files, persistConfig) {
|
|
3197
|
+
const results = await Promise.allSettled(
|
|
3198
|
+
files.map(async (file) => {
|
|
3199
|
+
const json = await readJsonFile(file).catch((error) => {
|
|
3200
|
+
throw new Error(
|
|
3201
|
+
`Failed to read JSON file ${file} - ${stringifyError(error)}`
|
|
3202
|
+
);
|
|
3203
|
+
});
|
|
3204
|
+
const result = await reportsDiffSchema.safeParseAsync(json);
|
|
3205
|
+
if (!result.success) {
|
|
3206
|
+
throw new Error(
|
|
3207
|
+
`Invalid reports diff in ${file} - ${result.error.message}`
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
return { ...result.data, file };
|
|
3211
|
+
})
|
|
3212
|
+
);
|
|
3213
|
+
results.filter(isPromiseRejectedResult).forEach(({ reason }) => {
|
|
3214
|
+
ui().logger.warning(
|
|
3215
|
+
`Skipped invalid report diff - ${stringifyError(reason)}`
|
|
3216
|
+
);
|
|
3217
|
+
});
|
|
3218
|
+
const diffs = results.filter(isPromiseFulfilledResult).map(({ value }) => value);
|
|
3219
|
+
const labeledDiffs = diffs.map((diff) => ({
|
|
3220
|
+
...diff,
|
|
3221
|
+
label: diff.label || basename(dirname(diff.file))
|
|
3222
|
+
// fallback is parent folder name
|
|
3223
|
+
}));
|
|
3224
|
+
const markdown = generateMdReportsDiffForMonorepo(labeledDiffs);
|
|
3225
|
+
const { outputDir, filename } = persistConfig;
|
|
3226
|
+
const outputPath = join7(outputDir, `${filename}-diff.md`);
|
|
3227
|
+
await ensureDirectoryExists(outputDir);
|
|
3228
|
+
await writeFile3(outputPath, markdown);
|
|
3229
|
+
return outputPath;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3272
3232
|
// packages/cli/src/lib/constants.ts
|
|
3273
3233
|
var CLI_NAME = "Code PushUp CLI";
|
|
3274
3234
|
var CLI_SCRIPT_NAME = "code-pushup";
|
|
3275
3235
|
|
|
3276
3236
|
// packages/cli/src/lib/implementation/logging.ts
|
|
3277
|
-
import
|
|
3237
|
+
import { bold as bold6, gray as gray3 } from "ansis";
|
|
3278
3238
|
function renderConfigureCategoriesHint() {
|
|
3279
3239
|
ui().logger.info(
|
|
3280
|
-
|
|
3240
|
+
gray3(
|
|
3281
3241
|
`\u{1F4A1} Configure categories to see the scores in an overview table. See: ${link(
|
|
3282
3242
|
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md"
|
|
3283
3243
|
)}`
|
|
@@ -3292,8 +3252,8 @@ function collectSuccessfulLog() {
|
|
|
3292
3252
|
ui().logger.success("Collecting report successful!");
|
|
3293
3253
|
}
|
|
3294
3254
|
function renderIntegratePortalHint() {
|
|
3295
|
-
ui().sticker().add(
|
|
3296
|
-
`${
|
|
3255
|
+
ui().sticker().add(bold6.gray("\u{1F4A1} Integrate the portal")).add("").add(
|
|
3256
|
+
`${gray3("\u276F")} Upload a report to the server - ${gray3(
|
|
3297
3257
|
"npx code-pushup upload"
|
|
3298
3258
|
)}`
|
|
3299
3259
|
).add(
|
|
@@ -3301,11 +3261,11 @@ function renderIntegratePortalHint() {
|
|
|
3301
3261
|
"https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
|
|
3302
3262
|
)}`
|
|
3303
3263
|
).add(
|
|
3304
|
-
`${
|
|
3264
|
+
`${gray3("\u276F")} ${gray3("Portal Integration")} - ${link(
|
|
3305
3265
|
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
|
|
3306
3266
|
)}`
|
|
3307
3267
|
).add(
|
|
3308
|
-
`${
|
|
3268
|
+
`${gray3("\u276F")} ${gray3("Upload Command")} - ${link(
|
|
3309
3269
|
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
|
|
3310
3270
|
)}`
|
|
3311
3271
|
).render();
|
|
@@ -3318,8 +3278,8 @@ function yargsAutorunCommandObject() {
|
|
|
3318
3278
|
command: command2,
|
|
3319
3279
|
describe: "Shortcut for running collect followed by upload",
|
|
3320
3280
|
handler: async (args) => {
|
|
3321
|
-
ui().logger.log(
|
|
3322
|
-
ui().logger.info(
|
|
3281
|
+
ui().logger.log(bold7(CLI_NAME));
|
|
3282
|
+
ui().logger.info(gray4(`Run ${command2}...`));
|
|
3323
3283
|
const options2 = args;
|
|
3324
3284
|
const optionsWithFormat = {
|
|
3325
3285
|
...options2,
|
|
@@ -3347,7 +3307,7 @@ function yargsAutorunCommandObject() {
|
|
|
3347
3307
|
}
|
|
3348
3308
|
|
|
3349
3309
|
// packages/cli/src/lib/collect/collect-command.ts
|
|
3350
|
-
import
|
|
3310
|
+
import { bold as bold8, gray as gray5 } from "ansis";
|
|
3351
3311
|
function yargsCollectCommandObject() {
|
|
3352
3312
|
const command2 = "collect";
|
|
3353
3313
|
return {
|
|
@@ -3355,8 +3315,8 @@ function yargsCollectCommandObject() {
|
|
|
3355
3315
|
describe: "Run Plugins and collect results",
|
|
3356
3316
|
handler: async (args) => {
|
|
3357
3317
|
const options2 = args;
|
|
3358
|
-
ui().logger.log(
|
|
3359
|
-
ui().logger.info(
|
|
3318
|
+
ui().logger.log(bold8(CLI_NAME));
|
|
3319
|
+
ui().logger.info(gray5(`Run ${command2}...`));
|
|
3360
3320
|
await collectAndPersistReports(options2);
|
|
3361
3321
|
collectSuccessfulLog();
|
|
3362
3322
|
if (options2.categories.length === 0) {
|
|
@@ -3370,8 +3330,8 @@ function yargsCollectCommandObject() {
|
|
|
3370
3330
|
};
|
|
3371
3331
|
}
|
|
3372
3332
|
function renderUploadAutorunHint() {
|
|
3373
|
-
ui().sticker().add(
|
|
3374
|
-
`${
|
|
3333
|
+
ui().sticker().add(bold8.gray("\u{1F4A1} Visualize your reports")).add("").add(
|
|
3334
|
+
`${gray5("\u276F")} npx code-pushup upload - ${gray5(
|
|
3375
3335
|
"Run upload to upload the created report to the server"
|
|
3376
3336
|
)}`
|
|
3377
3337
|
).add(
|
|
@@ -3379,9 +3339,7 @@ function renderUploadAutorunHint() {
|
|
|
3379
3339
|
"https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
|
|
3380
3340
|
)}`
|
|
3381
3341
|
).add(
|
|
3382
|
-
`${
|
|
3383
|
-
"Run collect & upload"
|
|
3384
|
-
)}`
|
|
3342
|
+
`${gray5("\u276F")} npx code-pushup autorun - ${gray5("Run collect & upload")}`
|
|
3385
3343
|
).add(
|
|
3386
3344
|
` ${link(
|
|
3387
3345
|
"https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command"
|
|
@@ -3390,7 +3348,7 @@ function renderUploadAutorunHint() {
|
|
|
3390
3348
|
}
|
|
3391
3349
|
|
|
3392
3350
|
// packages/cli/src/lib/compare/compare-command.ts
|
|
3393
|
-
import
|
|
3351
|
+
import { bold as bold9, gray as gray6 } from "ansis";
|
|
3394
3352
|
|
|
3395
3353
|
// packages/cli/src/lib/implementation/compare.options.ts
|
|
3396
3354
|
function yargsCompareOptionsDefinition() {
|
|
@@ -3404,6 +3362,10 @@ function yargsCompareOptionsDefinition() {
|
|
|
3404
3362
|
describe: "Path to target report.json",
|
|
3405
3363
|
type: "string",
|
|
3406
3364
|
demandOption: true
|
|
3365
|
+
},
|
|
3366
|
+
label: {
|
|
3367
|
+
describe: "Label for diff (e.g. project name)",
|
|
3368
|
+
type: "string"
|
|
3407
3369
|
}
|
|
3408
3370
|
};
|
|
3409
3371
|
}
|
|
@@ -3416,20 +3378,25 @@ function yargsCompareCommandObject() {
|
|
|
3416
3378
|
describe: "Compare 2 report files and create a diff file",
|
|
3417
3379
|
builder: yargsCompareOptionsDefinition(),
|
|
3418
3380
|
handler: async (args) => {
|
|
3419
|
-
ui().logger.log(
|
|
3420
|
-
ui().logger.info(
|
|
3381
|
+
ui().logger.log(bold9(CLI_NAME));
|
|
3382
|
+
ui().logger.info(gray6(`Run ${command2}...`));
|
|
3421
3383
|
const options2 = args;
|
|
3422
|
-
const { before, after, persist } = options2;
|
|
3423
|
-
const outputPaths = await compareReportFiles(
|
|
3384
|
+
const { before, after, label, persist, upload: upload2 } = options2;
|
|
3385
|
+
const outputPaths = await compareReportFiles(
|
|
3386
|
+
{ before, after },
|
|
3387
|
+
persist,
|
|
3388
|
+
upload2,
|
|
3389
|
+
label
|
|
3390
|
+
);
|
|
3424
3391
|
ui().logger.info(
|
|
3425
|
-
`Reports diff written to ${outputPaths.map((path) =>
|
|
3392
|
+
`Reports diff written to ${outputPaths.map((path) => bold9(path)).join(" and ")}`
|
|
3426
3393
|
);
|
|
3427
3394
|
}
|
|
3428
3395
|
};
|
|
3429
3396
|
}
|
|
3430
3397
|
|
|
3431
3398
|
// packages/cli/src/lib/history/history-command.ts
|
|
3432
|
-
import
|
|
3399
|
+
import { bold as bold10, gray as gray7 } from "ansis";
|
|
3433
3400
|
|
|
3434
3401
|
// packages/cli/src/lib/implementation/global.utils.ts
|
|
3435
3402
|
function filterKebabCaseKeys(obj) {
|
|
@@ -3555,8 +3522,8 @@ async function normalizeHashOptions(processArgs) {
|
|
|
3555
3522
|
// packages/cli/src/lib/history/history-command.ts
|
|
3556
3523
|
var command = "history";
|
|
3557
3524
|
async function handler(args) {
|
|
3558
|
-
ui().logger.info(
|
|
3559
|
-
ui().logger.info(
|
|
3525
|
+
ui().logger.info(bold10(CLI_NAME));
|
|
3526
|
+
ui().logger.info(gray7(`Run ${command}`));
|
|
3560
3527
|
const currentBranch = await getCurrentBranchOrTag();
|
|
3561
3528
|
const { targetBranch: rawTargetBranch, ...opt } = args;
|
|
3562
3529
|
const {
|
|
@@ -3605,6 +3572,38 @@ function yargsHistoryCommandObject() {
|
|
|
3605
3572
|
};
|
|
3606
3573
|
}
|
|
3607
3574
|
|
|
3575
|
+
// packages/cli/src/lib/merge-diffs/merge-diffs-command.ts
|
|
3576
|
+
import { bold as bold11, gray as gray8 } from "ansis";
|
|
3577
|
+
|
|
3578
|
+
// packages/cli/src/lib/implementation/merge-diffs.options.ts
|
|
3579
|
+
function yargsMergeDiffsOptionsDefinition() {
|
|
3580
|
+
return {
|
|
3581
|
+
files: {
|
|
3582
|
+
describe: "List of report-diff.json paths",
|
|
3583
|
+
type: "array",
|
|
3584
|
+
demandOption: true
|
|
3585
|
+
}
|
|
3586
|
+
};
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
// packages/cli/src/lib/merge-diffs/merge-diffs-command.ts
|
|
3590
|
+
function yargsMergeDiffsCommandObject() {
|
|
3591
|
+
const command2 = "merge-diffs";
|
|
3592
|
+
return {
|
|
3593
|
+
command: command2,
|
|
3594
|
+
describe: "Combine many report diffs into a single diff file",
|
|
3595
|
+
builder: yargsMergeDiffsOptionsDefinition(),
|
|
3596
|
+
handler: async (args) => {
|
|
3597
|
+
ui().logger.log(bold11(CLI_NAME));
|
|
3598
|
+
ui().logger.info(gray8(`Run ${command2}...`));
|
|
3599
|
+
const options2 = args;
|
|
3600
|
+
const { files, persist } = options2;
|
|
3601
|
+
const outputPath = await mergeDiffs(files, persist);
|
|
3602
|
+
ui().logger.info(`Reports diff written to ${bold11(outputPath)}`);
|
|
3603
|
+
}
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3608
3607
|
// packages/cli/src/lib/print-config/print-config-command.ts
|
|
3609
3608
|
function yargsConfigCommandObject() {
|
|
3610
3609
|
const command2 = "print-config";
|
|
@@ -3620,15 +3619,15 @@ function yargsConfigCommandObject() {
|
|
|
3620
3619
|
}
|
|
3621
3620
|
|
|
3622
3621
|
// packages/cli/src/lib/upload/upload-command.ts
|
|
3623
|
-
import
|
|
3622
|
+
import { bold as bold12, gray as gray9 } from "ansis";
|
|
3624
3623
|
function yargsUploadCommandObject() {
|
|
3625
3624
|
const command2 = "upload";
|
|
3626
3625
|
return {
|
|
3627
3626
|
command: command2,
|
|
3628
3627
|
describe: "Upload report results to the portal",
|
|
3629
3628
|
handler: async (args) => {
|
|
3630
|
-
ui().logger.log(
|
|
3631
|
-
ui().logger.info(
|
|
3629
|
+
ui().logger.log(bold12(CLI_NAME));
|
|
3630
|
+
ui().logger.info(gray9(`Run ${command2}...`));
|
|
3632
3631
|
const options2 = args;
|
|
3633
3632
|
if (options2.upload == null) {
|
|
3634
3633
|
renderIntegratePortalHint();
|
|
@@ -3651,7 +3650,8 @@ var commands = [
|
|
|
3651
3650
|
yargsUploadCommandObject(),
|
|
3652
3651
|
yargsHistoryCommandObject(),
|
|
3653
3652
|
yargsCompareCommandObject(),
|
|
3654
|
-
yargsConfigCommandObject()
|
|
3653
|
+
yargsConfigCommandObject(),
|
|
3654
|
+
yargsMergeDiffsCommandObject()
|
|
3655
3655
|
];
|
|
3656
3656
|
|
|
3657
3657
|
// packages/cli/src/lib/implementation/core-config.middleware.ts
|
|
@@ -3679,7 +3679,9 @@ async function coreConfigMiddleware(processArgs) {
|
|
|
3679
3679
|
persist: {
|
|
3680
3680
|
outputDir: cliPersist?.outputDir ?? rcPersist?.outputDir ?? DEFAULT_PERSIST_OUTPUT_DIR,
|
|
3681
3681
|
filename: cliPersist?.filename ?? rcPersist?.filename ?? DEFAULT_PERSIST_FILENAME,
|
|
3682
|
-
format:
|
|
3682
|
+
format: normalizeFormats(
|
|
3683
|
+
cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT
|
|
3684
|
+
)
|
|
3683
3685
|
},
|
|
3684
3686
|
...upload2 != null && { upload: upload2 },
|
|
3685
3687
|
categories: rcCategories ?? [],
|
|
@@ -3687,9 +3689,10 @@ async function coreConfigMiddleware(processArgs) {
|
|
|
3687
3689
|
...remainingCliOptions
|
|
3688
3690
|
};
|
|
3689
3691
|
}
|
|
3692
|
+
var normalizeFormats = (formats) => (formats ?? []).flatMap((format) => format.split(","));
|
|
3690
3693
|
|
|
3691
3694
|
// packages/cli/src/lib/implementation/validate-plugin-filter-options.utils.ts
|
|
3692
|
-
import
|
|
3695
|
+
import { yellow } from "ansis";
|
|
3693
3696
|
function validatePluginFilterOption(filterOption, {
|
|
3694
3697
|
plugins,
|
|
3695
3698
|
categories
|
|
@@ -3705,7 +3708,7 @@ function validatePluginFilterOption(filterOption, {
|
|
|
3705
3708
|
const filterFunction = (plugin) => isSkipOption ? pluginsToFilterSet.has(plugin) : !pluginsToFilterSet.has(plugin);
|
|
3706
3709
|
if (missingPlugins.length > 0 && verbose) {
|
|
3707
3710
|
ui().logger.info(
|
|
3708
|
-
`${
|
|
3711
|
+
`${yellow(
|
|
3709
3712
|
"\u26A0"
|
|
3710
3713
|
)} The --${filterOption} argument references plugins with "${missingPlugins.join(
|
|
3711
3714
|
'", "'
|
|
@@ -3860,7 +3863,7 @@ function yargsGlobalOptionsDefinition() {
|
|
|
3860
3863
|
default: false
|
|
3861
3864
|
},
|
|
3862
3865
|
config: {
|
|
3863
|
-
describe: "Path to config file
|
|
3866
|
+
describe: "Path to config file. By default it loads code-pushup.config.(ts|mjs|js).",
|
|
3864
3867
|
type: "string"
|
|
3865
3868
|
},
|
|
3866
3869
|
tsconfig: {
|
|
@@ -3888,8 +3891,55 @@ var groups = {
|
|
|
3888
3891
|
};
|
|
3889
3892
|
|
|
3890
3893
|
// packages/cli/src/lib/yargs-cli.ts
|
|
3891
|
-
import
|
|
3894
|
+
import { blue, dim as dim2, green as green4 } from "ansis";
|
|
3892
3895
|
import yargs from "yargs";
|
|
3896
|
+
|
|
3897
|
+
// packages/cli/package.json
|
|
3898
|
+
var version2 = "0.50.0";
|
|
3899
|
+
|
|
3900
|
+
// packages/cli/src/lib/implementation/formatting.ts
|
|
3901
|
+
import { bold as bold13, dim, green as green3 } from "ansis";
|
|
3902
|
+
function titleStyle(title) {
|
|
3903
|
+
return `${bold13(title)}`;
|
|
3904
|
+
}
|
|
3905
|
+
function headerStyle(title) {
|
|
3906
|
+
return `${green3(title)}`;
|
|
3907
|
+
}
|
|
3908
|
+
function descriptionStyle(title) {
|
|
3909
|
+
return `${dim(title)}`;
|
|
3910
|
+
}
|
|
3911
|
+
function formatObjectValue(opts, propName) {
|
|
3912
|
+
const description = opts[propName];
|
|
3913
|
+
return {
|
|
3914
|
+
...opts,
|
|
3915
|
+
...typeof description === "string" && {
|
|
3916
|
+
[propName]: descriptionStyle(description)
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
function formatNestedValues(options2, propName) {
|
|
3921
|
+
return Object.fromEntries(
|
|
3922
|
+
Object.entries(options2).map(([key, opts]) => [
|
|
3923
|
+
key,
|
|
3924
|
+
formatObjectValue(opts, propName)
|
|
3925
|
+
])
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
// packages/cli/src/lib/yargs-cli.ts
|
|
3930
|
+
var yargsDecorator = {
|
|
3931
|
+
"Commands:": `${green4("Commands")}:`,
|
|
3932
|
+
"Options:": `${green4("Options")}:`,
|
|
3933
|
+
"Examples:": `${green4("Examples")}:`,
|
|
3934
|
+
boolean: blue("boolean"),
|
|
3935
|
+
count: blue("count"),
|
|
3936
|
+
string: blue("string"),
|
|
3937
|
+
array: blue("array"),
|
|
3938
|
+
required: blue("required"),
|
|
3939
|
+
"default:": `${blue("default")}:`,
|
|
3940
|
+
"choices:": `${blue("choices")}:`,
|
|
3941
|
+
"aliases:": `${blue("aliases")}:`
|
|
3942
|
+
};
|
|
3893
3943
|
function yargsCli(argv, cfg) {
|
|
3894
3944
|
const { usageMessage, scriptName, noExitProcess } = cfg;
|
|
3895
3945
|
const commands2 = cfg.commands ?? [];
|
|
@@ -3898,7 +3948,7 @@ function yargsCli(argv, cfg) {
|
|
|
3898
3948
|
const groups2 = cfg.groups ?? {};
|
|
3899
3949
|
const examples = cfg.examples ?? [];
|
|
3900
3950
|
const cli2 = yargs(argv);
|
|
3901
|
-
cli2.
|
|
3951
|
+
cli2.updateLocale(yargsDecorator).wrap(Math.max(TERMINAL_WIDTH, cli2.terminalWidth())).help("help", descriptionStyle("Show help")).alias("h", "help").showHelpOnFail(false).version("version", dim2`Show version`, version2).check((args) => {
|
|
3902
3952
|
const persist = args["persist"];
|
|
3903
3953
|
return persist == null || validatePersistFormat(persist);
|
|
3904
3954
|
}).parserConfiguration({
|
|
@@ -3906,18 +3956,18 @@ function yargsCli(argv, cfg) {
|
|
|
3906
3956
|
}).coerce(
|
|
3907
3957
|
"config",
|
|
3908
3958
|
(config) => Array.isArray(config) ? config.at(-1) : config
|
|
3909
|
-
).options(options2)
|
|
3959
|
+
).options(formatNestedValues(options2, "describe"));
|
|
3910
3960
|
if (usageMessage) {
|
|
3911
|
-
cli2.usage(
|
|
3961
|
+
cli2.usage(titleStyle(usageMessage));
|
|
3912
3962
|
}
|
|
3913
3963
|
if (scriptName) {
|
|
3914
3964
|
cli2.scriptName(scriptName);
|
|
3915
3965
|
}
|
|
3916
3966
|
examples.forEach(
|
|
3917
|
-
([exampleName, description]) => cli2.example(exampleName, description)
|
|
3967
|
+
([exampleName, description]) => cli2.example(exampleName, descriptionStyle(description))
|
|
3918
3968
|
);
|
|
3919
3969
|
Object.entries(groups2).forEach(
|
|
3920
|
-
([groupName, optionNames]) => cli2.group(optionNames, groupName)
|
|
3970
|
+
([groupName, optionNames]) => cli2.group(optionNames, headerStyle(groupName))
|
|
3921
3971
|
);
|
|
3922
3972
|
middlewares2.forEach(({ middlewareFunction, applyBeforeValidation }) => {
|
|
3923
3973
|
cli2.middleware(
|
|
@@ -3926,13 +3976,18 @@ function yargsCli(argv, cfg) {
|
|
|
3926
3976
|
);
|
|
3927
3977
|
});
|
|
3928
3978
|
commands2.forEach((commandObj) => {
|
|
3929
|
-
cli2.command(
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3979
|
+
cli2.command(
|
|
3980
|
+
formatObjectValue(
|
|
3981
|
+
{
|
|
3982
|
+
...commandObj,
|
|
3983
|
+
handler: logErrorBeforeThrow(commandObj.handler),
|
|
3984
|
+
...typeof commandObj.builder === "function" && {
|
|
3985
|
+
builder: logErrorBeforeThrow(commandObj.builder)
|
|
3986
|
+
}
|
|
3987
|
+
},
|
|
3988
|
+
"describe"
|
|
3989
|
+
)
|
|
3990
|
+
);
|
|
3936
3991
|
});
|
|
3937
3992
|
if (noExitProcess) {
|
|
3938
3993
|
cli2.exitProcess(false);
|
|
@@ -3942,7 +3997,7 @@ function yargsCli(argv, cfg) {
|
|
|
3942
3997
|
function validatePersistFormat(persist) {
|
|
3943
3998
|
try {
|
|
3944
3999
|
if (persist.format != null) {
|
|
3945
|
-
persist.format.forEach((format) => formatSchema.parse(format));
|
|
4000
|
+
persist.format.flatMap((format) => format.split(",")).forEach((format) => formatSchema.parse(format));
|
|
3946
4001
|
}
|
|
3947
4002
|
return true;
|
|
3948
4003
|
} catch {
|
|
@@ -3978,8 +4033,8 @@ var cli = (args) => yargsCli(args, {
|
|
|
3978
4033
|
"Run collect skiping the coverage plugin, other plugins from config file will be included."
|
|
3979
4034
|
],
|
|
3980
4035
|
[
|
|
3981
|
-
"code-pushup upload --persist.outputDir=dist --
|
|
3982
|
-
"Upload dist/
|
|
4036
|
+
"code-pushup upload --persist.outputDir=dist --upload.apiKey=$CP_API_KEY",
|
|
4037
|
+
"Upload dist/report.json to portal using API key from environment variable"
|
|
3983
4038
|
],
|
|
3984
4039
|
[
|
|
3985
4040
|
"code-pushup print-config --config code-pushup.config.test.js",
|