@code-pushup/utils 0.26.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +683 -189
- package/package.json +2 -2
- package/src/index.d.ts +7 -4
- package/src/lib/diff.d.ts +15 -0
- package/src/lib/execute-process.d.ts +1 -0
- package/src/lib/formatting.d.ts +2 -2
- package/src/lib/git.d.ts +1 -3
- package/src/lib/log-results.d.ts +2 -2
- package/src/lib/logging.d.ts +20 -0
- package/src/lib/reports/flatten-plugins.d.ts +9 -0
- package/src/lib/reports/generate-md-reports-diff.d.ts +2 -0
- package/src/lib/reports/log-stdout-summary.d.ts +2 -0
- package/src/lib/reports/md/headline.d.ts +1 -0
- package/src/lib/reports/md/image.d.ts +1 -0
- package/src/lib/reports/md/index.d.ts +2 -0
- package/src/lib/reports/md/paragraphs.d.ts +1 -0
- package/src/lib/reports/types.d.ts +1 -0
- package/src/lib/reports/utils.d.ts +7 -0
- package/src/lib/verbose-utils.d.ts +1 -1
- package/src/lib/reports/generate-stdout-summary.d.ts +0 -2
package/index.js
CHANGED
|
@@ -84,6 +84,9 @@ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(
|
|
|
84
84
|
var urlSchema = z.string().url();
|
|
85
85
|
var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
|
|
86
86
|
var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
|
|
87
|
+
var scoreSchema = z.number({
|
|
88
|
+
description: "Value between 0 and 1"
|
|
89
|
+
}).min(0).max(1);
|
|
87
90
|
function metaSchema(options) {
|
|
88
91
|
const {
|
|
89
92
|
descriptionDescription,
|
|
@@ -215,6 +218,8 @@ var issueSchema = z3.object(
|
|
|
215
218
|
);
|
|
216
219
|
|
|
217
220
|
// packages/models/src/lib/audit-output.ts
|
|
221
|
+
var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
|
|
222
|
+
var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
|
|
218
223
|
var auditDetailsSchema = z4.object(
|
|
219
224
|
{
|
|
220
225
|
issues: z4.array(issueSchema, { description: "List of findings" })
|
|
@@ -224,11 +229,9 @@ var auditDetailsSchema = z4.object(
|
|
|
224
229
|
var auditOutputSchema = z4.object(
|
|
225
230
|
{
|
|
226
231
|
slug: slugSchema.describe("Reference to audit"),
|
|
227
|
-
displayValue:
|
|
228
|
-
value:
|
|
229
|
-
score:
|
|
230
|
-
description: "Value between 0 and 1"
|
|
231
|
-
}).min(0).max(1),
|
|
232
|
+
displayValue: auditDisplayValueSchema,
|
|
233
|
+
value: auditValueSchema,
|
|
234
|
+
score: scoreSchema,
|
|
232
235
|
details: auditDetailsSchema.optional()
|
|
233
236
|
},
|
|
234
237
|
{ description: "Audit information" }
|
|
@@ -551,6 +554,138 @@ var reportSchema = packageVersionSchema({
|
|
|
551
554
|
})
|
|
552
555
|
);
|
|
553
556
|
|
|
557
|
+
// packages/models/src/lib/reports-diff.ts
|
|
558
|
+
import { z as z14 } from "zod";
|
|
559
|
+
function makeComparisonSchema(schema) {
|
|
560
|
+
const sharedDescription = schema.description || "Result";
|
|
561
|
+
return z14.object({
|
|
562
|
+
before: schema.describe(`${sharedDescription} (source commit)`),
|
|
563
|
+
after: schema.describe(`${sharedDescription} (target commit)`)
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
|
|
567
|
+
return z14.object(
|
|
568
|
+
{
|
|
569
|
+
changed: z14.array(diffSchema),
|
|
570
|
+
unchanged: z14.array(resultSchema),
|
|
571
|
+
added: z14.array(resultSchema),
|
|
572
|
+
removed: z14.array(resultSchema)
|
|
573
|
+
},
|
|
574
|
+
{ description }
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
var scorableMetaSchema = z14.object({ slug: slugSchema, title: titleSchema });
|
|
578
|
+
var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
|
|
579
|
+
z14.object({
|
|
580
|
+
plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
|
|
581
|
+
})
|
|
582
|
+
);
|
|
583
|
+
var scorableDiffSchema = scorableMetaSchema.merge(
|
|
584
|
+
z14.object({
|
|
585
|
+
scores: makeComparisonSchema(scoreSchema).merge(
|
|
586
|
+
z14.object({
|
|
587
|
+
diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
|
|
588
|
+
})
|
|
589
|
+
).describe("Score comparison")
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
|
|
593
|
+
scorableWithPluginMetaSchema
|
|
594
|
+
);
|
|
595
|
+
var categoryDiffSchema = scorableDiffSchema;
|
|
596
|
+
var groupDiffSchema = scorableWithPluginDiffSchema;
|
|
597
|
+
var auditDiffSchema = scorableWithPluginDiffSchema.merge(
|
|
598
|
+
z14.object({
|
|
599
|
+
values: makeComparisonSchema(auditValueSchema).merge(
|
|
600
|
+
z14.object({
|
|
601
|
+
diff: z14.number().int().describe("Value change (`values.after - values.before`)")
|
|
602
|
+
})
|
|
603
|
+
).describe("Audit `value` comparison"),
|
|
604
|
+
displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
|
|
605
|
+
"Audit `displayValue` comparison"
|
|
606
|
+
)
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
var categoryResultSchema = scorableMetaSchema.merge(
|
|
610
|
+
z14.object({ score: scoreSchema })
|
|
611
|
+
);
|
|
612
|
+
var groupResultSchema = scorableWithPluginMetaSchema.merge(
|
|
613
|
+
z14.object({ score: scoreSchema })
|
|
614
|
+
);
|
|
615
|
+
var auditResultSchema = scorableWithPluginMetaSchema.merge(
|
|
616
|
+
auditOutputSchema.pick({ score: true, value: true, displayValue: true })
|
|
617
|
+
);
|
|
618
|
+
var reportsDiffSchema = z14.object({
|
|
619
|
+
commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
|
|
620
|
+
categories: makeArraysComparisonSchema(
|
|
621
|
+
categoryDiffSchema,
|
|
622
|
+
categoryResultSchema,
|
|
623
|
+
"Changes affecting categories"
|
|
624
|
+
),
|
|
625
|
+
groups: makeArraysComparisonSchema(
|
|
626
|
+
groupDiffSchema,
|
|
627
|
+
groupResultSchema,
|
|
628
|
+
"Changes affecting groups"
|
|
629
|
+
),
|
|
630
|
+
audits: makeArraysComparisonSchema(
|
|
631
|
+
auditDiffSchema,
|
|
632
|
+
auditResultSchema,
|
|
633
|
+
"Changes affecting audits"
|
|
634
|
+
)
|
|
635
|
+
}).merge(
|
|
636
|
+
packageVersionSchema({
|
|
637
|
+
versionDescription: "NPM version of the CLI (when `compare` was run)",
|
|
638
|
+
required: true
|
|
639
|
+
})
|
|
640
|
+
).merge(
|
|
641
|
+
executionMetaSchema({
|
|
642
|
+
descriptionDate: "Start date and time of the compare run",
|
|
643
|
+
descriptionDuration: "Duration of the compare run in ms"
|
|
644
|
+
})
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
// packages/utils/src/lib/diff.ts
|
|
648
|
+
function matchArrayItemsByKey({
|
|
649
|
+
before,
|
|
650
|
+
after,
|
|
651
|
+
key
|
|
652
|
+
}) {
|
|
653
|
+
const pairs = [];
|
|
654
|
+
const added = [];
|
|
655
|
+
const afterKeys = /* @__PURE__ */ new Set();
|
|
656
|
+
const keyFn = typeof key === "function" ? key : (item) => item[key];
|
|
657
|
+
for (const afterItem of after) {
|
|
658
|
+
const afterKey = keyFn(afterItem);
|
|
659
|
+
afterKeys.add(afterKey);
|
|
660
|
+
const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
|
|
661
|
+
if (match) {
|
|
662
|
+
pairs.push({ before: match, after: afterItem });
|
|
663
|
+
} else {
|
|
664
|
+
added.push(afterItem);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const removed = before.filter(
|
|
668
|
+
(beforeItem) => !afterKeys.has(keyFn(beforeItem))
|
|
669
|
+
);
|
|
670
|
+
return {
|
|
671
|
+
pairs,
|
|
672
|
+
added,
|
|
673
|
+
removed
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function comparePairs(pairs, equalsFn) {
|
|
677
|
+
return pairs.reduce(
|
|
678
|
+
(acc, pair) => ({
|
|
679
|
+
...acc,
|
|
680
|
+
...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
|
|
681
|
+
}),
|
|
682
|
+
{
|
|
683
|
+
changed: [],
|
|
684
|
+
unchanged: []
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
554
689
|
// packages/utils/src/lib/execute-process.ts
|
|
555
690
|
import { spawn } from "node:child_process";
|
|
556
691
|
|
|
@@ -559,7 +694,7 @@ import { join as join2 } from "node:path";
|
|
|
559
694
|
|
|
560
695
|
// packages/utils/src/lib/file-system.ts
|
|
561
696
|
import { bundleRequire } from "bundle-require";
|
|
562
|
-
import
|
|
697
|
+
import chalk2 from "chalk";
|
|
563
698
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
564
699
|
import { join } from "node:path";
|
|
565
700
|
|
|
@@ -567,7 +702,10 @@ import { join } from "node:path";
|
|
|
567
702
|
function slugify(text) {
|
|
568
703
|
return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
|
|
569
704
|
}
|
|
570
|
-
function pluralize(text) {
|
|
705
|
+
function pluralize(text, amount) {
|
|
706
|
+
if (amount != null && Math.abs(amount) === 1) {
|
|
707
|
+
return text;
|
|
708
|
+
}
|
|
571
709
|
if (text.endsWith("y")) {
|
|
572
710
|
return `${text.slice(0, -1)}ies`;
|
|
573
711
|
}
|
|
@@ -587,7 +725,7 @@ function formatBytes(bytes, decimals = 2) {
|
|
|
587
725
|
const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
|
|
588
726
|
return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
589
727
|
}
|
|
590
|
-
function pluralizeToken(token, times
|
|
728
|
+
function pluralizeToken(token, times) {
|
|
591
729
|
return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
|
|
592
730
|
}
|
|
593
731
|
function formatDuration(duration) {
|
|
@@ -633,33 +771,106 @@ function isPromiseRejectedResult(result) {
|
|
|
633
771
|
return result.status === "rejected";
|
|
634
772
|
}
|
|
635
773
|
|
|
774
|
+
// packages/utils/src/lib/logging.ts
|
|
775
|
+
import isaacs_cliui from "@isaacs/cliui";
|
|
776
|
+
import { cliui } from "@poppinss/cliui";
|
|
777
|
+
import chalk from "chalk";
|
|
778
|
+
|
|
779
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
780
|
+
var TERMINAL_WIDTH = 80;
|
|
781
|
+
var NEW_LINE = "\n";
|
|
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/flowup/quality-metrics-cli#readme";
|
|
789
|
+
var reportHeadlineText = "Code PushUp Report";
|
|
790
|
+
var reportOverviewTableHeaders = [
|
|
791
|
+
"\u{1F3F7} Category",
|
|
792
|
+
"\u2B50 Score",
|
|
793
|
+
"\u{1F6E1} Audits"
|
|
794
|
+
];
|
|
795
|
+
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
796
|
+
var reportMetaTableHeaders = [
|
|
797
|
+
"Commit",
|
|
798
|
+
"Version",
|
|
799
|
+
"Duration",
|
|
800
|
+
"Plugins",
|
|
801
|
+
"Categories",
|
|
802
|
+
"Audits"
|
|
803
|
+
];
|
|
804
|
+
var pluginMetaTableHeaders = [
|
|
805
|
+
"Plugin",
|
|
806
|
+
"Audits",
|
|
807
|
+
"Version",
|
|
808
|
+
"Duration"
|
|
809
|
+
];
|
|
810
|
+
var detailsTableHeaders = [
|
|
811
|
+
"Severity",
|
|
812
|
+
"Message",
|
|
813
|
+
"Source file",
|
|
814
|
+
"Line(s)"
|
|
815
|
+
];
|
|
816
|
+
|
|
817
|
+
// packages/utils/src/lib/logging.ts
|
|
818
|
+
var singletonUiInstance;
|
|
819
|
+
function ui() {
|
|
820
|
+
if (singletonUiInstance === void 0) {
|
|
821
|
+
singletonUiInstance = cliui();
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
...singletonUiInstance,
|
|
825
|
+
row: (args) => {
|
|
826
|
+
logListItem(args);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
var singletonisaacUi;
|
|
831
|
+
function logListItem(args) {
|
|
832
|
+
if (singletonisaacUi === void 0) {
|
|
833
|
+
singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
|
|
834
|
+
}
|
|
835
|
+
singletonisaacUi.div(...args);
|
|
836
|
+
const content = singletonisaacUi.toString();
|
|
837
|
+
singletonisaacUi.rows = [];
|
|
838
|
+
singletonUiInstance?.logger.log(content);
|
|
839
|
+
}
|
|
840
|
+
function link(text) {
|
|
841
|
+
return chalk.underline(chalk.blueBright(text));
|
|
842
|
+
}
|
|
843
|
+
|
|
636
844
|
// packages/utils/src/lib/log-results.ts
|
|
637
|
-
function logMultipleResults(results, messagePrefix,
|
|
638
|
-
if (
|
|
845
|
+
function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
|
|
846
|
+
if (succeededTransform) {
|
|
639
847
|
const succeededResults = results.filter(isPromiseFulfilledResult);
|
|
640
848
|
logPromiseResults(
|
|
641
849
|
succeededResults,
|
|
642
850
|
`${messagePrefix} successfully: `,
|
|
643
|
-
|
|
851
|
+
succeededTransform
|
|
644
852
|
);
|
|
645
853
|
}
|
|
646
|
-
if (
|
|
854
|
+
if (failedTransform) {
|
|
647
855
|
const failedResults = results.filter(isPromiseRejectedResult);
|
|
648
856
|
logPromiseResults(
|
|
649
857
|
failedResults,
|
|
650
858
|
`${messagePrefix} failed: `,
|
|
651
|
-
|
|
859
|
+
failedTransform
|
|
652
860
|
);
|
|
653
861
|
}
|
|
654
862
|
}
|
|
655
|
-
function logPromiseResults(results, logMessage,
|
|
863
|
+
function logPromiseResults(results, logMessage, getMsg) {
|
|
656
864
|
if (results.length > 0) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
|
|
865
|
+
const log2 = results[0]?.status === "fulfilled" ? (m) => {
|
|
866
|
+
ui().logger.success(m);
|
|
867
|
+
} : (m) => {
|
|
868
|
+
ui().logger.warning(m);
|
|
869
|
+
};
|
|
870
|
+
log2(logMessage);
|
|
871
|
+
results.forEach((result) => {
|
|
872
|
+
log2(getMsg(result));
|
|
873
|
+
});
|
|
663
874
|
}
|
|
664
875
|
}
|
|
665
876
|
|
|
@@ -693,7 +904,7 @@ async function ensureDirectoryExists(baseDir) {
|
|
|
693
904
|
await mkdir(baseDir, { recursive: true });
|
|
694
905
|
return;
|
|
695
906
|
} catch (error) {
|
|
696
|
-
|
|
907
|
+
ui().logger.error(error.message);
|
|
697
908
|
if (error.code !== "EEXIST") {
|
|
698
909
|
throw error;
|
|
699
910
|
}
|
|
@@ -705,19 +916,17 @@ async function removeDirectoryIfExists(dir) {
|
|
|
705
916
|
}
|
|
706
917
|
}
|
|
707
918
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
708
|
-
const
|
|
919
|
+
const succeededTransform = (result) => {
|
|
709
920
|
const [fileName, size] = result.value;
|
|
710
|
-
const formattedSize = size ? ` (${
|
|
711
|
-
|
|
712
|
-
};
|
|
713
|
-
const failedCallback = (result) => {
|
|
714
|
-
console.warn(`- ${chalk.bold(result.reason)}`);
|
|
921
|
+
const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
|
|
922
|
+
return `- ${chalk2.bold(fileName)}${formattedSize}`;
|
|
715
923
|
};
|
|
924
|
+
const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
|
|
716
925
|
logMultipleResults(
|
|
717
926
|
fileResults,
|
|
718
927
|
messagePrefix,
|
|
719
|
-
|
|
720
|
-
|
|
928
|
+
succeededTransform,
|
|
929
|
+
failedTransform
|
|
721
930
|
);
|
|
722
931
|
}
|
|
723
932
|
var NoExportError = class extends Error {
|
|
@@ -765,48 +974,105 @@ function findLineNumberInText(content, pattern) {
|
|
|
765
974
|
return lineNumber === 0 ? null : lineNumber;
|
|
766
975
|
}
|
|
767
976
|
|
|
768
|
-
// packages/utils/src/lib/reports/
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
977
|
+
// packages/utils/src/lib/reports/md/details.ts
|
|
978
|
+
function details(title, content, cfg = { open: false }) {
|
|
979
|
+
return `<details${cfg.open ? " open" : ""}>
|
|
980
|
+
<summary>${title}</summary>
|
|
981
|
+
|
|
982
|
+
${content}
|
|
983
|
+
|
|
984
|
+
</details>
|
|
985
|
+
`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// packages/utils/src/lib/reports/md/font-style.ts
|
|
989
|
+
var stylesMap = {
|
|
990
|
+
i: "_",
|
|
991
|
+
// italic
|
|
992
|
+
b: "**",
|
|
993
|
+
// bold
|
|
994
|
+
s: "~",
|
|
995
|
+
// strike through
|
|
996
|
+
c: "`"
|
|
997
|
+
// code
|
|
774
998
|
};
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
"
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
999
|
+
function style(text, styles = ["b"]) {
|
|
1000
|
+
return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// packages/utils/src/lib/reports/md/headline.ts
|
|
1004
|
+
function headline(text, hierarchy = 1) {
|
|
1005
|
+
return `${"#".repeat(hierarchy)} ${text}`;
|
|
1006
|
+
}
|
|
1007
|
+
function h1(text) {
|
|
1008
|
+
return headline(text, 1);
|
|
1009
|
+
}
|
|
1010
|
+
function h2(text) {
|
|
1011
|
+
return headline(text, 2);
|
|
1012
|
+
}
|
|
1013
|
+
function h3(text) {
|
|
1014
|
+
return headline(text, 3);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// packages/utils/src/lib/reports/md/image.ts
|
|
1018
|
+
function image(src, alt) {
|
|
1019
|
+
return ``;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// packages/utils/src/lib/reports/md/link.ts
|
|
1023
|
+
function link2(href, text) {
|
|
1024
|
+
return `[${text || href}](${href})`;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// packages/utils/src/lib/reports/md/list.ts
|
|
1028
|
+
function li(text, order = "unordered") {
|
|
1029
|
+
const style2 = order === "unordered" ? "-" : "- [ ]";
|
|
1030
|
+
return `${style2} ${text}`;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// packages/utils/src/lib/reports/md/paragraphs.ts
|
|
1034
|
+
function paragraphs(...sections) {
|
|
1035
|
+
return sections.filter(Boolean).join("\n\n");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// packages/utils/src/lib/reports/md/table.ts
|
|
1039
|
+
var alignString = /* @__PURE__ */ new Map([
|
|
1040
|
+
["l", ":--"],
|
|
1041
|
+
["c", ":--:"],
|
|
1042
|
+
["r", "--:"]
|
|
1043
|
+
]);
|
|
1044
|
+
function tableMd(data, align) {
|
|
1045
|
+
if (data.length === 0) {
|
|
1046
|
+
throw new Error("Data can't be empty");
|
|
1047
|
+
}
|
|
1048
|
+
const alignmentSetting = align ?? data[0]?.map(() => "c");
|
|
1049
|
+
const tableContent = data.map((arr) => `|${arr.join("|")}|`);
|
|
1050
|
+
const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
|
|
1051
|
+
return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
|
|
1052
|
+
}
|
|
1053
|
+
function tableHtml(data) {
|
|
1054
|
+
if (data.length === 0) {
|
|
1055
|
+
throw new Error("Data can't be empty");
|
|
1056
|
+
}
|
|
1057
|
+
const tableContent = data.map((arr, index) => {
|
|
1058
|
+
if (index === 0) {
|
|
1059
|
+
const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
|
|
1060
|
+
return `<tr>${headerRow}</tr>`;
|
|
1061
|
+
}
|
|
1062
|
+
const row = arr.map((s) => `<td>${s}</td>`).join("");
|
|
1063
|
+
return `<tr>${row}</tr>`;
|
|
1064
|
+
});
|
|
1065
|
+
return `<table>${tableContent.join("")}</table>`;
|
|
1066
|
+
}
|
|
805
1067
|
|
|
806
1068
|
// packages/utils/src/lib/reports/utils.ts
|
|
807
1069
|
function formatReportScore(score) {
|
|
808
1070
|
return Math.round(score * 100).toString();
|
|
809
1071
|
}
|
|
1072
|
+
function formatScoreWithColor(score, options) {
|
|
1073
|
+
const styledNumber = options?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
|
|
1074
|
+
return `${getRoundScoreMarker(score)} ${styledNumber}`;
|
|
1075
|
+
}
|
|
810
1076
|
function getRoundScoreMarker(score) {
|
|
811
1077
|
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
812
1078
|
return "\u{1F7E2}";
|
|
@@ -825,6 +1091,30 @@ function getSquaredScoreMarker(score) {
|
|
|
825
1091
|
}
|
|
826
1092
|
return "\u{1F7E5}";
|
|
827
1093
|
}
|
|
1094
|
+
function getDiffMarker(diff) {
|
|
1095
|
+
if (diff > 0) {
|
|
1096
|
+
return "\u2191";
|
|
1097
|
+
}
|
|
1098
|
+
if (diff < 0) {
|
|
1099
|
+
return "\u2193";
|
|
1100
|
+
}
|
|
1101
|
+
return "";
|
|
1102
|
+
}
|
|
1103
|
+
function colorByScoreDiff(text, diff) {
|
|
1104
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
1105
|
+
return shieldsBadge(text, color);
|
|
1106
|
+
}
|
|
1107
|
+
function shieldsBadge(text, color) {
|
|
1108
|
+
return image(
|
|
1109
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
1110
|
+
text
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
function formatDiffNumber(diff) {
|
|
1114
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
1115
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
1116
|
+
return `${sign}${number}`;
|
|
1117
|
+
}
|
|
828
1118
|
function getSeverityIcon(severity) {
|
|
829
1119
|
if (severity === "error") {
|
|
830
1120
|
return "\u{1F6A8}";
|
|
@@ -835,7 +1125,7 @@ function getSeverityIcon(severity) {
|
|
|
835
1125
|
return "\u2139\uFE0F";
|
|
836
1126
|
}
|
|
837
1127
|
function calcDuration(start, stop) {
|
|
838
|
-
return Math.
|
|
1128
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
839
1129
|
}
|
|
840
1130
|
function countCategoryAudits(refs, plugins) {
|
|
841
1131
|
const groupLookup = plugins.reduce(
|
|
@@ -998,7 +1288,7 @@ var ProcessError = class extends Error {
|
|
|
998
1288
|
}
|
|
999
1289
|
};
|
|
1000
1290
|
function executeProcess(cfg) {
|
|
1001
|
-
const { observer, cwd, command, args } = cfg;
|
|
1291
|
+
const { observer, cwd, command, args, alwaysResolve = false } = cfg;
|
|
1002
1292
|
const { onStdout, onError, onComplete } = observer ?? {};
|
|
1003
1293
|
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
1294
|
const start = performance.now();
|
|
@@ -1018,7 +1308,7 @@ function executeProcess(cfg) {
|
|
|
1018
1308
|
});
|
|
1019
1309
|
process2.on("close", (code) => {
|
|
1020
1310
|
const timings = { date, duration: calcDuration(start) };
|
|
1021
|
-
if (code === 0) {
|
|
1311
|
+
if (code === 0 || alwaysResolve) {
|
|
1022
1312
|
onComplete?.();
|
|
1023
1313
|
resolve({ code, stdout, stderr, ...timings });
|
|
1024
1314
|
} else {
|
|
@@ -1133,14 +1423,14 @@ function toOrdinal(value) {
|
|
|
1133
1423
|
|
|
1134
1424
|
// packages/utils/src/lib/git.ts
|
|
1135
1425
|
async function getLatestCommit(git = simpleGit()) {
|
|
1136
|
-
const
|
|
1426
|
+
const log2 = await git.log({
|
|
1137
1427
|
maxCount: 1,
|
|
1138
1428
|
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1139
1429
|
});
|
|
1140
|
-
if (!
|
|
1430
|
+
if (!log2.latest) {
|
|
1141
1431
|
return null;
|
|
1142
1432
|
}
|
|
1143
|
-
return commitSchema.parse(
|
|
1433
|
+
return commitSchema.parse(log2.latest);
|
|
1144
1434
|
}
|
|
1145
1435
|
function getGitRoot(git = simpleGit()) {
|
|
1146
1436
|
return git.revparse("--show-toplevel");
|
|
@@ -1154,6 +1444,35 @@ async function toGitPath(path, git = simpleGit()) {
|
|
|
1154
1444
|
const gitRoot = await getGitRoot(git);
|
|
1155
1445
|
return formatGitPath(path, gitRoot);
|
|
1156
1446
|
}
|
|
1447
|
+
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1448
|
+
const isClean = await git.status(["-s"]).then((r) => r.files.length === 0);
|
|
1449
|
+
if (!isClean) {
|
|
1450
|
+
throw new Error(
|
|
1451
|
+
"Working directory needs to be clean before we you can proceed. Commit your local changes or stash them."
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async function getCurrentBranchOrTag(git = simpleGit()) {
|
|
1456
|
+
try {
|
|
1457
|
+
const branch = await git.branch().then((r) => r.current);
|
|
1458
|
+
if (branch) {
|
|
1459
|
+
return branch;
|
|
1460
|
+
} else {
|
|
1461
|
+
return await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1462
|
+
}
|
|
1463
|
+
} catch {
|
|
1464
|
+
throw new Error("Could not get current tag or branch.");
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1468
|
+
if (forceCleanStatus) {
|
|
1469
|
+
await git.raw(["reset", "--hard"]);
|
|
1470
|
+
await git.clean(["f", "d"]);
|
|
1471
|
+
ui().logger.info(`git status cleaned`);
|
|
1472
|
+
}
|
|
1473
|
+
await guardAgainstLocalChanges(git);
|
|
1474
|
+
await git.checkout(branchOrHash);
|
|
1475
|
+
}
|
|
1157
1476
|
|
|
1158
1477
|
// packages/utils/src/lib/group-by-status.ts
|
|
1159
1478
|
function groupByStatus(results) {
|
|
@@ -1163,12 +1482,6 @@ function groupByStatus(results) {
|
|
|
1163
1482
|
);
|
|
1164
1483
|
}
|
|
1165
1484
|
|
|
1166
|
-
// packages/utils/src/lib/logging.ts
|
|
1167
|
-
import chalk2 from "chalk";
|
|
1168
|
-
function link(text) {
|
|
1169
|
-
return chalk2.underline(chalk2.blueBright(text));
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1485
|
// packages/utils/src/lib/progress.ts
|
|
1173
1486
|
import chalk3 from "chalk";
|
|
1174
1487
|
import { MultiProgressBars } from "multi-progress-bars";
|
|
@@ -1224,80 +1537,16 @@ function getProgressBar(taskName) {
|
|
|
1224
1537
|
};
|
|
1225
1538
|
}
|
|
1226
1539
|
|
|
1227
|
-
// packages/utils/src/lib/reports/
|
|
1228
|
-
function
|
|
1229
|
-
return
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
</details>
|
|
1233
|
-
`;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// packages/utils/src/lib/reports/md/font-style.ts
|
|
1237
|
-
var stylesMap = {
|
|
1238
|
-
i: "_",
|
|
1239
|
-
// italic
|
|
1240
|
-
b: "**",
|
|
1241
|
-
// bold
|
|
1242
|
-
s: "~",
|
|
1243
|
-
// strike through
|
|
1244
|
-
c: "`"
|
|
1245
|
-
// code
|
|
1246
|
-
};
|
|
1247
|
-
function style(text, styles = ["b"]) {
|
|
1248
|
-
return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// packages/utils/src/lib/reports/md/headline.ts
|
|
1252
|
-
function headline(text, hierarchy = 1) {
|
|
1253
|
-
return `${"#".repeat(hierarchy)} ${text}`;
|
|
1254
|
-
}
|
|
1255
|
-
function h2(text) {
|
|
1256
|
-
return headline(text, 2);
|
|
1257
|
-
}
|
|
1258
|
-
function h3(text) {
|
|
1259
|
-
return headline(text, 3);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
// packages/utils/src/lib/reports/md/link.ts
|
|
1263
|
-
function link2(href, text) {
|
|
1264
|
-
return `[${text || href}](${href})`;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// packages/utils/src/lib/reports/md/list.ts
|
|
1268
|
-
function li(text, order = "unordered") {
|
|
1269
|
-
const style2 = order === "unordered" ? "-" : "- [ ]";
|
|
1270
|
-
return `${style2} ${text}`;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// packages/utils/src/lib/reports/md/table.ts
|
|
1274
|
-
var alignString = /* @__PURE__ */ new Map([
|
|
1275
|
-
["l", ":--"],
|
|
1276
|
-
["c", ":--:"],
|
|
1277
|
-
["r", "--:"]
|
|
1278
|
-
]);
|
|
1279
|
-
function tableMd(data, align) {
|
|
1280
|
-
if (data.length === 0) {
|
|
1281
|
-
throw new Error("Data can't be empty");
|
|
1282
|
-
}
|
|
1283
|
-
const alignmentSetting = align ?? data[0]?.map(() => "c");
|
|
1284
|
-
const tableContent = data.map((arr) => `|${arr.join("|")}|`);
|
|
1285
|
-
const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
|
|
1286
|
-
return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
|
|
1540
|
+
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1541
|
+
function listGroupsFromAllPlugins(report) {
|
|
1542
|
+
return report.plugins.flatMap(
|
|
1543
|
+
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1544
|
+
);
|
|
1287
1545
|
}
|
|
1288
|
-
function
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const tableContent = data.map((arr, index) => {
|
|
1293
|
-
if (index === 0) {
|
|
1294
|
-
const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
|
|
1295
|
-
return `<tr>${headerRow}</tr>`;
|
|
1296
|
-
}
|
|
1297
|
-
const row = arr.map((s) => `<td>${s}</td>`).join("");
|
|
1298
|
-
return `<tr>${row}</tr>`;
|
|
1299
|
-
});
|
|
1300
|
-
return `<table>${tableContent.join("")}</table>`;
|
|
1546
|
+
function listAuditsFromAllPlugins(report) {
|
|
1547
|
+
return report.plugins.flatMap(
|
|
1548
|
+
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1549
|
+
);
|
|
1301
1550
|
}
|
|
1302
1551
|
|
|
1303
1552
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
@@ -1487,30 +1736,261 @@ function getAuditResult(audit, isHtml = false) {
|
|
|
1487
1736
|
return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
|
|
1488
1737
|
}
|
|
1489
1738
|
|
|
1490
|
-
// packages/utils/src/lib/reports/generate-
|
|
1491
|
-
|
|
1739
|
+
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
1740
|
+
var MAX_ROWS = 100;
|
|
1741
|
+
function generateMdReportsDiff(diff) {
|
|
1742
|
+
return paragraphs(
|
|
1743
|
+
formatDiffHeaderSection(diff),
|
|
1744
|
+
formatDiffCategoriesSection(diff),
|
|
1745
|
+
formatDiffGroupsSection(diff),
|
|
1746
|
+
formatDiffAuditsSection(diff)
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
function formatDiffHeaderSection(diff) {
|
|
1750
|
+
const outcomeTexts = {
|
|
1751
|
+
positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
|
|
1752
|
+
negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
|
|
1753
|
+
mixed: `\u{1F928} Code PushUp report has both ${style(
|
|
1754
|
+
"improvements and regressions"
|
|
1755
|
+
)}`,
|
|
1756
|
+
unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
|
|
1757
|
+
};
|
|
1758
|
+
const outcome = mergeDiffOutcomes(
|
|
1759
|
+
changesToDiffOutcomes([
|
|
1760
|
+
...diff.categories.changed,
|
|
1761
|
+
...diff.groups.changed,
|
|
1762
|
+
...diff.audits.changed
|
|
1763
|
+
])
|
|
1764
|
+
);
|
|
1765
|
+
const styleCommit = (commit) => style(commit.hash.slice(0, 7), ["c"]);
|
|
1766
|
+
const styleCommits = (commits) => {
|
|
1767
|
+
const src = styleCommit(commits.before);
|
|
1768
|
+
const tgt = styleCommit(commits.after);
|
|
1769
|
+
return `compared target commit ${tgt} with source commit ${src}`;
|
|
1770
|
+
};
|
|
1771
|
+
return paragraphs(
|
|
1772
|
+
h1("Code PushUp"),
|
|
1773
|
+
diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
function formatDiffCategoriesSection(diff) {
|
|
1777
|
+
const { changed, unchanged, added } = diff.categories;
|
|
1778
|
+
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
1779
|
+
const hasChanges = unchanged.length < categoriesCount;
|
|
1780
|
+
if (categoriesCount === 0) {
|
|
1781
|
+
return "";
|
|
1782
|
+
}
|
|
1783
|
+
return paragraphs(
|
|
1784
|
+
h2("\u{1F3F7}\uFE0F Categories"),
|
|
1785
|
+
categoriesCount > 0 && tableMd(
|
|
1786
|
+
[
|
|
1787
|
+
[
|
|
1788
|
+
"\u{1F3F7}\uFE0F Category",
|
|
1789
|
+
hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
|
|
1790
|
+
"\u2B50 Previous score",
|
|
1791
|
+
"\u{1F504} Score change"
|
|
1792
|
+
],
|
|
1793
|
+
...sortChanges(changed).map((category) => [
|
|
1794
|
+
category.title,
|
|
1795
|
+
formatScoreWithColor(category.scores.after),
|
|
1796
|
+
formatScoreWithColor(category.scores.before, { skipBold: true }),
|
|
1797
|
+
formatScoreChange(category.scores.diff)
|
|
1798
|
+
]),
|
|
1799
|
+
...added.map((category) => [
|
|
1800
|
+
category.title,
|
|
1801
|
+
formatScoreWithColor(category.score),
|
|
1802
|
+
style("n/a (\\*)", ["i"]),
|
|
1803
|
+
style("n/a (\\*)", ["i"])
|
|
1804
|
+
]),
|
|
1805
|
+
...unchanged.map((category) => [
|
|
1806
|
+
category.title,
|
|
1807
|
+
formatScoreWithColor(category.score),
|
|
1808
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
1809
|
+
"\u2013"
|
|
1810
|
+
])
|
|
1811
|
+
].map((row) => hasChanges ? row : row.slice(0, 2)),
|
|
1812
|
+
hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
|
|
1813
|
+
),
|
|
1814
|
+
added.length > 0 && style("(\\*) New category.", ["i"])
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
function formatDiffGroupsSection(diff) {
|
|
1818
|
+
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
1819
|
+
return "";
|
|
1820
|
+
}
|
|
1821
|
+
return paragraphs(
|
|
1822
|
+
h2("\u{1F397}\uFE0F Groups"),
|
|
1823
|
+
formatGroupsOrAuditsDetails("group", diff.groups, {
|
|
1824
|
+
headings: [
|
|
1825
|
+
"\u{1F50C} Plugin",
|
|
1826
|
+
"\u{1F5C3}\uFE0F Group",
|
|
1827
|
+
"\u2B50 Current score",
|
|
1828
|
+
"\u2B50 Previous score",
|
|
1829
|
+
"\u{1F504} Score change"
|
|
1830
|
+
],
|
|
1831
|
+
rows: sortChanges(diff.groups.changed).map((group) => [
|
|
1832
|
+
group.plugin.title,
|
|
1833
|
+
group.title,
|
|
1834
|
+
formatScoreWithColor(group.scores.after),
|
|
1835
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
1836
|
+
formatScoreChange(group.scores.diff)
|
|
1837
|
+
]),
|
|
1838
|
+
align: ["l", "l", "c", "c", "c"]
|
|
1839
|
+
})
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
function formatDiffAuditsSection(diff) {
|
|
1843
|
+
return paragraphs(
|
|
1844
|
+
h2("\u{1F6E1}\uFE0F Audits"),
|
|
1845
|
+
formatGroupsOrAuditsDetails("audit", diff.audits, {
|
|
1846
|
+
headings: [
|
|
1847
|
+
"\u{1F50C} Plugin",
|
|
1848
|
+
"\u{1F6E1}\uFE0F Audit",
|
|
1849
|
+
"\u{1F4CF} Current value",
|
|
1850
|
+
"\u{1F4CF} Previous value",
|
|
1851
|
+
"\u{1F504} Value change"
|
|
1852
|
+
],
|
|
1853
|
+
rows: sortChanges(diff.audits.changed).map((audit) => [
|
|
1854
|
+
audit.plugin.title,
|
|
1855
|
+
audit.title,
|
|
1856
|
+
`${getSquaredScoreMarker(audit.scores.after)} ${style(
|
|
1857
|
+
audit.displayValues.after || audit.values.after.toString()
|
|
1858
|
+
)}`,
|
|
1859
|
+
`${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
1860
|
+
formatValueChange(audit)
|
|
1861
|
+
]),
|
|
1862
|
+
align: ["l", "l", "c", "c", "c"]
|
|
1863
|
+
})
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
|
|
1867
|
+
return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
|
|
1868
|
+
summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
|
|
1869
|
+
paragraphs(
|
|
1870
|
+
tableMd(
|
|
1871
|
+
[table.headings, ...table.rows.slice(0, MAX_ROWS)],
|
|
1872
|
+
table.align
|
|
1873
|
+
),
|
|
1874
|
+
changed.length > MAX_ROWS && style(
|
|
1875
|
+
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
1876
|
+
token
|
|
1877
|
+
)} are listed above for brevity.`,
|
|
1878
|
+
["i"]
|
|
1879
|
+
),
|
|
1880
|
+
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
1881
|
+
)
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
function formatScoreChange(diff) {
|
|
1885
|
+
const marker = getDiffMarker(diff);
|
|
1886
|
+
const text = formatDiffNumber(Math.round(diff * 100));
|
|
1887
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
1888
|
+
}
|
|
1889
|
+
function formatValueChange({
|
|
1890
|
+
values,
|
|
1891
|
+
scores
|
|
1892
|
+
}) {
|
|
1893
|
+
const marker = getDiffMarker(values.diff);
|
|
1894
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
1895
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
1896
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
1897
|
+
}
|
|
1898
|
+
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
1899
|
+
return [
|
|
1900
|
+
changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
|
|
1901
|
+
unchanged.length === 1 ? "is" : "are",
|
|
1902
|
+
"unchanged."
|
|
1903
|
+
].join(" ");
|
|
1904
|
+
}
|
|
1905
|
+
function summarizeDiffOutcomes(outcomes, token) {
|
|
1906
|
+
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
1907
|
+
(entry) => entry[0] !== "unchanged" && entry[1] > 0
|
|
1908
|
+
).map(([outcome, count]) => {
|
|
1909
|
+
const formattedCount = `<strong>${count}</strong> ${pluralize(
|
|
1910
|
+
token,
|
|
1911
|
+
count
|
|
1912
|
+
)}`;
|
|
1913
|
+
switch (outcome) {
|
|
1914
|
+
case "positive":
|
|
1915
|
+
return `\u{1F44D} ${formattedCount} improved`;
|
|
1916
|
+
case "negative":
|
|
1917
|
+
return `\u{1F44E} ${formattedCount} regressed`;
|
|
1918
|
+
case "mixed":
|
|
1919
|
+
return `${formattedCount} changed without impacting score`;
|
|
1920
|
+
}
|
|
1921
|
+
}).join(", ");
|
|
1922
|
+
}
|
|
1923
|
+
function sortChanges(changes) {
|
|
1924
|
+
return [...changes].sort(
|
|
1925
|
+
(a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
function changesToDiffOutcomes(changes) {
|
|
1929
|
+
return changes.map((change) => {
|
|
1930
|
+
if (change.scores.diff > 0) {
|
|
1931
|
+
return "positive";
|
|
1932
|
+
}
|
|
1933
|
+
if (change.scores.diff < 0) {
|
|
1934
|
+
return "negative";
|
|
1935
|
+
}
|
|
1936
|
+
if (change.values != null && change.values.diff !== 0) {
|
|
1937
|
+
return "mixed";
|
|
1938
|
+
}
|
|
1939
|
+
return "unchanged";
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
function mergeDiffOutcomes(outcomes) {
|
|
1943
|
+
if (outcomes.every((outcome) => outcome === "unchanged")) {
|
|
1944
|
+
return "unchanged";
|
|
1945
|
+
}
|
|
1946
|
+
if (outcomes.includes("positive") && !outcomes.includes("negative")) {
|
|
1947
|
+
return "positive";
|
|
1948
|
+
}
|
|
1949
|
+
if (outcomes.includes("negative") && !outcomes.includes("positive")) {
|
|
1950
|
+
return "negative";
|
|
1951
|
+
}
|
|
1952
|
+
return "mixed";
|
|
1953
|
+
}
|
|
1954
|
+
function countDiffOutcomes(outcomes) {
|
|
1955
|
+
return {
|
|
1956
|
+
positive: outcomes.filter((outcome) => outcome === "positive").length,
|
|
1957
|
+
negative: outcomes.filter((outcome) => outcome === "negative").length,
|
|
1958
|
+
mixed: outcomes.filter((outcome) => outcome === "mixed").length,
|
|
1959
|
+
unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
1492
1964
|
import chalk4 from "chalk";
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
return line + NEW_LINE;
|
|
1965
|
+
function log(msg = "") {
|
|
1966
|
+
ui().logger.log(msg);
|
|
1496
1967
|
}
|
|
1497
|
-
function
|
|
1968
|
+
function logStdoutSummary(report) {
|
|
1498
1969
|
const printCategories = report.categories.length > 0;
|
|
1499
|
-
|
|
1970
|
+
log(reportToHeaderSection2(report));
|
|
1971
|
+
log();
|
|
1972
|
+
logPlugins(report);
|
|
1973
|
+
if (printCategories) {
|
|
1974
|
+
logCategories(report);
|
|
1975
|
+
}
|
|
1976
|
+
log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
|
|
1977
|
+
log();
|
|
1500
1978
|
}
|
|
1501
1979
|
function reportToHeaderSection2(report) {
|
|
1502
1980
|
const { packageName, version } = report;
|
|
1503
1981
|
return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version}`;
|
|
1504
1982
|
}
|
|
1505
|
-
function
|
|
1983
|
+
function logPlugins(report) {
|
|
1506
1984
|
const { plugins } = report;
|
|
1507
|
-
|
|
1985
|
+
plugins.forEach((plugin) => {
|
|
1508
1986
|
const { title, audits } = plugin;
|
|
1509
|
-
|
|
1987
|
+
log();
|
|
1988
|
+
log(chalk4.magentaBright.bold(`${title} audits`));
|
|
1989
|
+
log();
|
|
1510
1990
|
audits.forEach((audit) => {
|
|
1511
|
-
ui.
|
|
1991
|
+
ui().row([
|
|
1512
1992
|
{
|
|
1513
|
-
text:
|
|
1993
|
+
text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
|
|
1514
1994
|
width: 2,
|
|
1515
1995
|
padding: [0, 1, 0, 0]
|
|
1516
1996
|
},
|
|
@@ -1524,34 +2004,40 @@ function reportToDetailSection(report) {
|
|
|
1524
2004
|
width: 10,
|
|
1525
2005
|
padding: [0, 0, 0, 0]
|
|
1526
2006
|
}
|
|
1527
|
-
);
|
|
2007
|
+
]);
|
|
1528
2008
|
});
|
|
1529
|
-
|
|
1530
|
-
}, "");
|
|
1531
|
-
}
|
|
1532
|
-
function reportToOverviewSection2({
|
|
1533
|
-
categories,
|
|
1534
|
-
plugins
|
|
1535
|
-
}) {
|
|
1536
|
-
const table = new CliTable3({
|
|
1537
|
-
// eslint-disable-next-line no-magic-numbers
|
|
1538
|
-
colWidths: [TERMINAL_WIDTH - 7 - 8 - 4, 7, 8],
|
|
1539
|
-
head: reportRawOverviewTableHeaders,
|
|
1540
|
-
colAligns: ["left", "right", "right"],
|
|
1541
|
-
style: {
|
|
1542
|
-
head: ["cyan"]
|
|
1543
|
-
}
|
|
2009
|
+
log();
|
|
1544
2010
|
});
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
2011
|
+
}
|
|
2012
|
+
function logCategories({ categories, plugins }) {
|
|
2013
|
+
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2014
|
+
const rows = categories.map(({ title, score, refs }) => [
|
|
2015
|
+
title,
|
|
2016
|
+
applyScoreColor({ score }),
|
|
2017
|
+
countCategoryAudits(refs, plugins)
|
|
2018
|
+
]);
|
|
2019
|
+
const table = ui().table();
|
|
2020
|
+
table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2021
|
+
table.head(
|
|
2022
|
+
reportRawOverviewTableHeaders.map((heading, idx) => ({
|
|
2023
|
+
content: chalk4.cyan(heading),
|
|
2024
|
+
hAlign: hAlign(idx)
|
|
2025
|
+
}))
|
|
2026
|
+
);
|
|
2027
|
+
rows.forEach(
|
|
2028
|
+
(row) => table.row(
|
|
2029
|
+
row.map((content, idx) => ({
|
|
2030
|
+
content: content.toString(),
|
|
2031
|
+
hAlign: hAlign(idx)
|
|
2032
|
+
}))
|
|
2033
|
+
)
|
|
1551
2034
|
);
|
|
1552
|
-
|
|
2035
|
+
log(chalk4.magentaBright.bold("Categories"));
|
|
2036
|
+
log();
|
|
2037
|
+
table.render();
|
|
2038
|
+
log();
|
|
1553
2039
|
}
|
|
1554
|
-
function
|
|
2040
|
+
function applyScoreColor({ score, text }) {
|
|
1555
2041
|
const formattedScore = text ?? formatReportScore(score);
|
|
1556
2042
|
const style2 = text ? chalk4 : chalk4.bold;
|
|
1557
2043
|
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
@@ -1702,9 +2188,9 @@ function sortPlugins(plugins) {
|
|
|
1702
2188
|
|
|
1703
2189
|
// packages/utils/src/lib/verbose-utils.ts
|
|
1704
2190
|
function getLogVerbose(verbose = false) {
|
|
1705
|
-
return (
|
|
2191
|
+
return (msg) => {
|
|
1706
2192
|
if (verbose) {
|
|
1707
|
-
|
|
2193
|
+
ui().logger.info(msg);
|
|
1708
2194
|
}
|
|
1709
2195
|
};
|
|
1710
2196
|
}
|
|
@@ -1728,6 +2214,7 @@ export {
|
|
|
1728
2214
|
calcDuration,
|
|
1729
2215
|
capitalize,
|
|
1730
2216
|
compareIssueSeverity,
|
|
2217
|
+
comparePairs,
|
|
1731
2218
|
countOccurrences,
|
|
1732
2219
|
crawlFileSystem,
|
|
1733
2220
|
directoryExists,
|
|
@@ -1743,7 +2230,8 @@ export {
|
|
|
1743
2230
|
formatDuration,
|
|
1744
2231
|
formatGitPath,
|
|
1745
2232
|
generateMdReport,
|
|
1746
|
-
|
|
2233
|
+
generateMdReportsDiff,
|
|
2234
|
+
getCurrentBranchOrTag,
|
|
1747
2235
|
getGitRoot,
|
|
1748
2236
|
getLatestCommit,
|
|
1749
2237
|
getProgressBar,
|
|
@@ -1752,9 +2240,13 @@ export {
|
|
|
1752
2240
|
isPromiseFulfilledResult,
|
|
1753
2241
|
isPromiseRejectedResult,
|
|
1754
2242
|
link,
|
|
2243
|
+
listAuditsFromAllPlugins,
|
|
2244
|
+
listGroupsFromAllPlugins,
|
|
1755
2245
|
loadReport,
|
|
1756
2246
|
logMultipleFileResults,
|
|
1757
2247
|
logMultipleResults,
|
|
2248
|
+
logStdoutSummary,
|
|
2249
|
+
matchArrayItemsByKey,
|
|
1758
2250
|
objectToCliArgs,
|
|
1759
2251
|
objectToEntries,
|
|
1760
2252
|
objectToKeys,
|
|
@@ -1764,6 +2256,7 @@ export {
|
|
|
1764
2256
|
readJsonFile,
|
|
1765
2257
|
readTextFile,
|
|
1766
2258
|
removeDirectoryIfExists,
|
|
2259
|
+
safeCheckout,
|
|
1767
2260
|
scoreReport,
|
|
1768
2261
|
slugify,
|
|
1769
2262
|
sortReport,
|
|
@@ -1777,5 +2270,6 @@ export {
|
|
|
1777
2270
|
truncateIssueMessage,
|
|
1778
2271
|
truncateText,
|
|
1779
2272
|
truncateTitle,
|
|
2273
|
+
ui,
|
|
1780
2274
|
verboseUtils
|
|
1781
2275
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-pushup/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@code-pushup/models": "*",
|
|
6
6
|
"bundle-require": "^4.0.1",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"@isaacs/cliui": "^8.0.2",
|
|
9
9
|
"simple-git": "^3.20.0",
|
|
10
10
|
"multi-progress-bars": "^5.0.3",
|
|
11
|
-
"
|
|
11
|
+
"@poppinss/cliui": "^6.4.0"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"homepage": "https://github.com/code-pushup/cli#readme",
|
package/src/index.d.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
export { exists } from '@code-pushup/models';
|
|
2
|
+
export { Diff, comparePairs, matchArrayItemsByKey } from './lib/diff';
|
|
2
3
|
export { ProcessConfig, ProcessError, ProcessObserver, ProcessResult, executeProcess, } from './lib/execute-process';
|
|
3
4
|
export { CrawlFileSystemOptions, FileResult, MultipleFileResults, crawlFileSystem, directoryExists, ensureDirectoryExists, fileExists, findLineNumberInText, importEsmModule, logMultipleFileResults, pluginWorkDir, readJsonFile, readTextFile, removeDirectoryIfExists, } from './lib/file-system';
|
|
4
5
|
export { filterItemRefsBy } from './lib/filter';
|
|
5
6
|
export { formatBytes, formatDuration, pluralize, pluralizeToken, slugify, truncateDescription, truncateIssueMessage, truncateText, truncateTitle, } from './lib/formatting';
|
|
6
|
-
export { formatGitPath, getGitRoot, getLatestCommit, toGitPath, } from './lib/git';
|
|
7
|
+
export { formatGitPath, getCurrentBranchOrTag, getGitRoot, getLatestCommit, safeCheckout, toGitPath, } from './lib/git';
|
|
7
8
|
export { groupByStatus } from './lib/group-by-status';
|
|
8
9
|
export { isPromiseFulfilledResult, isPromiseRejectedResult, } from './lib/guards';
|
|
9
10
|
export { logMultipleResults } from './lib/log-results';
|
|
10
|
-
export { link } from './lib/logging';
|
|
11
|
+
export { CliUi, Column, link, ui } from './lib/logging';
|
|
11
12
|
export { ProgressBar, getProgressBar } from './lib/progress';
|
|
12
13
|
export { CODE_PUSHUP_DOMAIN, FOOTER_PREFIX, README_LINK, TERMINAL_WIDTH, } from './lib/reports/constants';
|
|
14
|
+
export { listAuditsFromAllPlugins, listGroupsFromAllPlugins, } from './lib/reports/flatten-plugins';
|
|
13
15
|
export { generateMdReport } from './lib/reports/generate-md-report';
|
|
14
|
-
export {
|
|
16
|
+
export { generateMdReportsDiff } from './lib/reports/generate-md-reports-diff';
|
|
17
|
+
export { logStdoutSummary } from './lib/reports/log-stdout-summary';
|
|
15
18
|
export { scoreReport } from './lib/reports/scoring';
|
|
16
19
|
export { sortReport } from './lib/reports/sorting';
|
|
17
|
-
export { ScoredReport } from './lib/reports/types';
|
|
20
|
+
export { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/reports/types';
|
|
18
21
|
export { calcDuration, compareIssueSeverity, loadReport, } from './lib/reports/utils';
|
|
19
22
|
export { CliArgsObject, capitalize, countOccurrences, distinct, factorOf, objectToCliArgs, objectToEntries, objectToKeys, toArray, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
|
|
20
23
|
export { verboseUtils } from './lib/verbose-utils';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Diff<T> = {
|
|
2
|
+
before: T;
|
|
3
|
+
after: T;
|
|
4
|
+
};
|
|
5
|
+
export declare function matchArrayItemsByKey<T>({ before, after, key, }: Diff<T[]> & {
|
|
6
|
+
key: keyof T | ((item: T) => unknown);
|
|
7
|
+
}): {
|
|
8
|
+
pairs: Diff<T>[];
|
|
9
|
+
added: T[];
|
|
10
|
+
removed: T[];
|
|
11
|
+
};
|
|
12
|
+
export declare function comparePairs<T>(pairs: Diff<T>[], equalsFn: (pair: Diff<T>) => boolean): {
|
|
13
|
+
changed: Diff<T>[];
|
|
14
|
+
unchanged: T[];
|
|
15
|
+
};
|
package/src/lib/formatting.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare function slugify(text: string): string;
|
|
2
|
-
export declare function pluralize(text: string): string;
|
|
2
|
+
export declare function pluralize(text: string, amount?: number): string;
|
|
3
3
|
export declare function formatBytes(bytes: number, decimals?: number): string;
|
|
4
|
-
export declare function pluralizeToken(token: string, times
|
|
4
|
+
export declare function pluralizeToken(token: string, times: number): string;
|
|
5
5
|
export declare function formatDuration(duration: number): string;
|
|
6
6
|
export declare function formatDate(date: Date): string;
|
|
7
7
|
export declare function truncateText(text: string, maxChars: number): string;
|
package/src/lib/git.d.ts
CHANGED
|
@@ -5,6 +5,4 @@ export declare function formatGitPath(path: string, gitRoot: string): string;
|
|
|
5
5
|
export declare function toGitPath(path: string, git?: import("simple-git").SimpleGit): Promise<string>;
|
|
6
6
|
export declare function guardAgainstLocalChanges(git?: import("simple-git").SimpleGit): Promise<void>;
|
|
7
7
|
export declare function getCurrentBranchOrTag(git?: import("simple-git").SimpleGit): Promise<string>;
|
|
8
|
-
export declare function safeCheckout(branchOrHash: string,
|
|
9
|
-
forceCleanStatus?: true;
|
|
10
|
-
}, git?: import("simple-git").SimpleGit): Promise<void>;
|
|
8
|
+
export declare function safeCheckout(branchOrHash: string, forceCleanStatus?: boolean, git?: import("simple-git").SimpleGit): Promise<void>;
|
package/src/lib/log-results.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function logMultipleResults<T>(results: PromiseSettledResult<T>[], messagePrefix: string,
|
|
2
|
-
export declare function logPromiseResults<T extends PromiseFulfilledResult<unknown> | PromiseRejectedResult>(results: T[], logMessage: string,
|
|
1
|
+
export declare function logMultipleResults<T>(results: PromiseSettledResult<T>[], messagePrefix: string, succeededTransform?: (result: PromiseFulfilledResult<T>) => string, failedTransform?: (result: PromiseRejectedResult) => string): void;
|
|
2
|
+
export declare function logPromiseResults<T extends PromiseFulfilledResult<unknown> | PromiseRejectedResult>(results: T[], logMessage: string, getMsg: (result: T) => string): void;
|
package/src/lib/logging.d.ts
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
+
import isaacs_cliui from '@isaacs/cliui';
|
|
2
|
+
import { cliui } from '@poppinss/cliui';
|
|
3
|
+
type ArgumentsType<T> = T extends (...args: infer U) => any ? U : never;
|
|
4
|
+
export type CliUiBase = ReturnType<typeof cliui>;
|
|
5
|
+
type UI = ReturnType<typeof isaacs_cliui>;
|
|
6
|
+
type CliExtension = {
|
|
7
|
+
row: (r: ArgumentsType<UI['div']>) => void;
|
|
8
|
+
};
|
|
9
|
+
export type Column = {
|
|
10
|
+
text: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
align?: 'right' | 'left' | 'center';
|
|
13
|
+
padding: number[];
|
|
14
|
+
border?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type CliUi = CliUiBase & CliExtension;
|
|
17
|
+
export declare let singletonUiInstance: CliUiBase | undefined;
|
|
18
|
+
export declare function ui(): CliUi;
|
|
19
|
+
export declare function logListItem(args: ArgumentsType<UI['div']>): void;
|
|
1
20
|
export declare function link(text: string): string;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Report } from '@code-pushup/models';
|
|
2
|
+
export declare function listGroupsFromAllPlugins<T extends Report>(report: T): {
|
|
3
|
+
plugin: T['plugins'][number];
|
|
4
|
+
group: NonNullable<T['plugins'][number]['groups']>[number];
|
|
5
|
+
}[];
|
|
6
|
+
export declare function listAuditsFromAllPlugins<T extends Report>(report: T): {
|
|
7
|
+
plugin: T['plugins'][number];
|
|
8
|
+
audit: T['plugins'][number]['audits'][number];
|
|
9
|
+
}[];
|
|
@@ -6,6 +6,7 @@ export type Hierarchy = 1 | 2 | 3 | 4 | 5 | 6;
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function headline(text: string, hierarchy?: Hierarchy): string;
|
|
8
8
|
export declare function h(text: string, hierarchy?: Hierarchy): string;
|
|
9
|
+
export declare function h1(text: string): string;
|
|
9
10
|
export declare function h2(text: string): string;
|
|
10
11
|
export declare function h3(text: string): string;
|
|
11
12
|
export declare function h4(text: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function image(src: string, alt: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function paragraphs(...sections: (string | undefined | boolean)[]): string;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { AuditReport, CategoryRef, IssueSeverity as CliIssueSeverity, Format, Group, Issue, PersistConfig, Report } from '@code-pushup/models';
|
|
2
2
|
import { ScoredReport, SortableAuditReport, SortableGroup } from './types';
|
|
3
3
|
export declare function formatReportScore(score: number): string;
|
|
4
|
+
export declare function formatScoreWithColor(score: number, options?: {
|
|
5
|
+
skipBold?: boolean;
|
|
6
|
+
}): string;
|
|
4
7
|
export declare function getRoundScoreMarker(score: number): string;
|
|
5
8
|
export declare function getSquaredScoreMarker(score: number): string;
|
|
9
|
+
export declare function getDiffMarker(diff: number): string;
|
|
10
|
+
export declare function colorByScoreDiff(text: string, diff: number): string;
|
|
11
|
+
export declare function shieldsBadge(text: string, color: string): string;
|
|
12
|
+
export declare function formatDiffNumber(diff: number): string;
|
|
6
13
|
export declare function getSeverityIcon(severity: 'info' | 'warning' | 'error'): string;
|
|
7
14
|
export declare function calcDuration(start: number, stop?: number): number;
|
|
8
15
|
export declare function countWeightedRefs(refs: CategoryRef[]): number;
|