@code-pushup/utils 0.35.0 → 0.42.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.
Files changed (33) hide show
  1. package/index.js +1087 -597
  2. package/package.json +6 -4
  3. package/src/index.d.ts +5 -1
  4. package/src/lib/git/git.commits-and-tags.d.ts +57 -0
  5. package/src/lib/{git.d.ts → git/git.d.ts} +8 -3
  6. package/src/lib/reports/constants.d.ts +26 -6
  7. package/src/lib/reports/formatting.d.ts +6 -0
  8. package/src/lib/reports/generate-md-report-categoy-section.d.ts +6 -0
  9. package/src/lib/reports/generate-md-report.d.ts +8 -0
  10. package/src/lib/reports/utils.d.ts +5 -3
  11. package/src/lib/semver.d.ts +3 -0
  12. package/src/lib/table.d.ts +6 -0
  13. package/src/lib/text-formats/constants.d.ts +3 -0
  14. package/src/lib/{reports/md → text-formats/html}/details.d.ts +2 -0
  15. package/src/lib/text-formats/html/font-style.d.ts +3 -0
  16. package/src/lib/text-formats/html/link.d.ts +1 -0
  17. package/src/lib/text-formats/html/table.d.ts +2 -0
  18. package/src/lib/text-formats/index.d.ts +44 -0
  19. package/src/lib/text-formats/md/font-style.d.ts +4 -0
  20. package/src/lib/{reports → text-formats}/md/headline.d.ts +1 -1
  21. package/src/lib/text-formats/md/link.d.ts +1 -0
  22. package/src/lib/{reports → text-formats}/md/list.d.ts +2 -0
  23. package/src/lib/text-formats/md/section.d.ts +2 -0
  24. package/src/lib/text-formats/md/table.d.ts +9 -0
  25. package/src/lib/text-formats/table.d.ts +6 -0
  26. package/src/lib/text-formats/types.d.ts +1 -0
  27. package/src/lib/types.d.ts +3 -0
  28. package/src/lib/reports/md/font-style.d.ts +0 -16
  29. package/src/lib/reports/md/index.d.ts +0 -8
  30. package/src/lib/reports/md/link.d.ts +0 -6
  31. package/src/lib/reports/md/table.d.ts +0 -10
  32. /package/src/lib/{reports → text-formats}/md/image.d.ts +0 -0
  33. /package/src/lib/{reports → text-formats}/md/paragraphs.d.ts +0 -0
package/index.js CHANGED
@@ -1,5 +1,372 @@
1
- // packages/models/src/lib/audit.ts
2
- import { z as z2 } from "zod";
1
+ // packages/utils/src/lib/text-formats/constants.ts
2
+ var NEW_LINE = "\n";
3
+ var TAB = " ";
4
+ var SPACE = " ";
5
+
6
+ // packages/utils/src/lib/text-formats/html/details.ts
7
+ function details(title, content, cfg = { open: false }) {
8
+ 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.
9
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
10
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
11
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
12
+ NEW_LINE}`;
13
+ }
14
+
15
+ // packages/utils/src/lib/text-formats/html/font-style.ts
16
+ var boldElement = "b";
17
+ function bold(text) {
18
+ return `<${boldElement}>${text}</${boldElement}>`;
19
+ }
20
+ var italicElement = "i";
21
+ function italic(text) {
22
+ return `<${italicElement}>${text}</${italicElement}>`;
23
+ }
24
+ var codeElement = "code";
25
+ function code(text) {
26
+ return `<${codeElement}>${text}</${codeElement}>`;
27
+ }
28
+
29
+ // packages/utils/src/lib/text-formats/html/link.ts
30
+ function link(href, text) {
31
+ return `<a href="${href}">${text || href}"</a>`;
32
+ }
33
+
34
+ // packages/utils/src/lib/transform.ts
35
+ import { platform } from "node:os";
36
+ function toArray(val) {
37
+ return Array.isArray(val) ? val : [val];
38
+ }
39
+ function objectToKeys(obj) {
40
+ return Object.keys(obj);
41
+ }
42
+ function objectToEntries(obj) {
43
+ return Object.entries(obj);
44
+ }
45
+ function objectFromEntries(entries) {
46
+ return Object.fromEntries(entries);
47
+ }
48
+ function countOccurrences(values) {
49
+ return values.reduce(
50
+ (acc, value) => ({ ...acc, [value]: (acc[value] ?? 0) + 1 }),
51
+ {}
52
+ );
53
+ }
54
+ function distinct(array) {
55
+ return [...new Set(array)];
56
+ }
57
+ function deepClone(obj) {
58
+ return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
59
+ }
60
+ function factorOf(items, filterFn) {
61
+ const itemCount = items.length;
62
+ if (!itemCount) {
63
+ return 1;
64
+ }
65
+ const filterCount = items.filter(filterFn).length;
66
+ return filterCount === 0 ? 1 : (itemCount - filterCount) / itemCount;
67
+ }
68
+ function objectToCliArgs(params) {
69
+ if (!params) {
70
+ return [];
71
+ }
72
+ return Object.entries(params).flatMap(([key, value]) => {
73
+ if (key === "_") {
74
+ return Array.isArray(value) ? value : [`${value}`];
75
+ }
76
+ const prefix = key.length === 1 ? "-" : "--";
77
+ if (Array.isArray(value)) {
78
+ return value.map((v) => `${prefix}${key}="${v}"`);
79
+ }
80
+ if (Array.isArray(value)) {
81
+ return value.map((v) => `${prefix}${key}="${v}"`);
82
+ }
83
+ if (typeof value === "string") {
84
+ return [`${prefix}${key}="${value}"`];
85
+ }
86
+ if (typeof value === "number") {
87
+ return [`${prefix}${key}=${value}`];
88
+ }
89
+ if (typeof value === "boolean") {
90
+ return [`${prefix}${value ? "" : "no-"}${key}`];
91
+ }
92
+ throw new Error(`Unsupported type ${typeof value} for key ${key}`);
93
+ });
94
+ }
95
+ function toUnixPath(path) {
96
+ return path.replace(/\\/g, "/");
97
+ }
98
+ function toUnixNewlines(text) {
99
+ return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
100
+ }
101
+ function fromJsonLines(jsonLines) {
102
+ const unifiedNewLines = toUnixNewlines(jsonLines).trim();
103
+ return JSON.parse(`[${unifiedNewLines.split("\n").join(",")}]`);
104
+ }
105
+ function toJsonLines(json) {
106
+ return json.map((item) => JSON.stringify(item)).join("\n");
107
+ }
108
+ function capitalize(text) {
109
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
110
+ 1
111
+ )}`;
112
+ }
113
+ function apostrophize(text, upperCase) {
114
+ const lastCharMatch = text.match(/(\w)\W*$/);
115
+ const lastChar = lastCharMatch?.[1] ?? "";
116
+ return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
117
+ }
118
+ function toNumberPrecision(value, decimalPlaces) {
119
+ return Number(
120
+ `${Math.round(
121
+ Number.parseFloat(`${value}e${decimalPlaces}`)
122
+ )}e-${decimalPlaces}`
123
+ );
124
+ }
125
+ function toOrdinal(value) {
126
+ if (value % 10 === 1 && value % 100 !== 11) {
127
+ return `${value}st`;
128
+ }
129
+ if (value % 10 === 2 && value % 100 !== 12) {
130
+ return `${value}nd`;
131
+ }
132
+ if (value % 10 === 3 && value % 100 !== 13) {
133
+ return `${value}rd`;
134
+ }
135
+ return `${value}th`;
136
+ }
137
+
138
+ // packages/utils/src/lib/table.ts
139
+ function rowToStringArray({ rows, columns = [] }) {
140
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
141
+ throw new TypeError(
142
+ "Column can`t be object when rows are primitive values"
143
+ );
144
+ }
145
+ return rows.map((row) => {
146
+ if (Array.isArray(row)) {
147
+ return row.map(String);
148
+ }
149
+ const objectRow = row;
150
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
151
+ return Object.values(objectRow).map(String);
152
+ }
153
+ return columns.map(
154
+ ({ key }) => String(objectRow[key])
155
+ );
156
+ });
157
+ }
158
+ function columnsToStringArray({ rows, columns = [] }) {
159
+ const firstRow = rows.at(0);
160
+ const primitiveRows = Array.isArray(firstRow);
161
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
162
+ throw new Error("invalid union type. Caught by model parsing.");
163
+ }
164
+ if (columns.length === 0) {
165
+ if (Array.isArray(firstRow)) {
166
+ return firstRow.map((_, idx) => String(idx));
167
+ }
168
+ return Object.keys(firstRow);
169
+ }
170
+ if (typeof columns.at(0) === "string") {
171
+ return columns.map(String);
172
+ }
173
+ const cols = columns;
174
+ return cols.map(({ label, key }) => label ?? capitalize(key));
175
+ }
176
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
177
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
178
+ if (typeof column === "string") {
179
+ return column;
180
+ } else if (typeof column === "object") {
181
+ return column.align ?? "center";
182
+ } else {
183
+ return "center";
184
+ }
185
+ }
186
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
187
+ const column = columns.at(targetIdx);
188
+ if (column == null) {
189
+ return "center";
190
+ } else if (typeof column === "string") {
191
+ return column;
192
+ } else if (typeof column === "object") {
193
+ return column.align ?? "center";
194
+ } else {
195
+ return "center";
196
+ }
197
+ }
198
+ function getColumnAlignments({
199
+ rows,
200
+ columns = []
201
+ }) {
202
+ if (rows.at(0) == null) {
203
+ throw new Error("first row can`t be undefined.");
204
+ }
205
+ if (Array.isArray(rows.at(0))) {
206
+ const firstPrimitiveRow = rows.at(0);
207
+ return Array.from({ length: firstPrimitiveRow.length }).map(
208
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
209
+ );
210
+ }
211
+ const firstObject = rows.at(0);
212
+ return Object.keys(firstObject).map(
213
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
214
+ );
215
+ }
216
+
217
+ // packages/utils/src/lib/text-formats/html/table.ts
218
+ function wrap(elem, content) {
219
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
220
+ }
221
+ function wrapRow(content) {
222
+ const elem = "tr";
223
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
224
+ }
225
+ function table(tableData) {
226
+ if (tableData.rows.length === 0) {
227
+ throw new Error("Data can't be empty");
228
+ }
229
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
230
+ const tableHeaderRow = wrapRow(tableHeaderCols);
231
+ const tableBody = rowToStringArray(tableData).map((arr) => {
232
+ const columns = arr.map((s) => wrap("td", s)).join("");
233
+ return wrapRow(columns);
234
+ }).join("");
235
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
236
+ }
237
+
238
+ // packages/utils/src/lib/text-formats/md/font-style.ts
239
+ var boldWrap = "**";
240
+ function bold2(text) {
241
+ return `${boldWrap}${text}${boldWrap}`;
242
+ }
243
+ var italicWrap = "_";
244
+ function italic2(text) {
245
+ return `${italicWrap}${text}${italicWrap}`;
246
+ }
247
+ var strikeThroughWrap = "~";
248
+ function strikeThrough(text) {
249
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
250
+ }
251
+ var codeWrap = "`";
252
+ function code2(text) {
253
+ return `${codeWrap}${text}${codeWrap}`;
254
+ }
255
+
256
+ // packages/utils/src/lib/text-formats/md/headline.ts
257
+ function headline(text, hierarchy = 1) {
258
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
259
+ }
260
+ function h(text, hierarchy = 1) {
261
+ return headline(text, hierarchy);
262
+ }
263
+ function h1(text) {
264
+ return headline(text, 1);
265
+ }
266
+ function h2(text) {
267
+ return headline(text, 2);
268
+ }
269
+ function h3(text) {
270
+ return headline(text, 3);
271
+ }
272
+ function h4(text) {
273
+ return headline(text, 4);
274
+ }
275
+ function h5(text) {
276
+ return headline(text, 5);
277
+ }
278
+ function h6(text) {
279
+ return headline(text, 6);
280
+ }
281
+
282
+ // packages/utils/src/lib/text-formats/md/image.ts
283
+ function image(src, alt) {
284
+ return `![${alt}](${src})`;
285
+ }
286
+
287
+ // packages/utils/src/lib/text-formats/md/link.ts
288
+ function link2(href, text) {
289
+ return `[${text || href}](${href})`;
290
+ }
291
+
292
+ // packages/utils/src/lib/text-formats/md/list.ts
293
+ function li(text, order = "unordered") {
294
+ const style = order === "unordered" ? "-" : "- [ ]";
295
+ return `${style} ${text}`;
296
+ }
297
+ function indentation(text, level = 1) {
298
+ return `${TAB.repeat(level)}${text}`;
299
+ }
300
+
301
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
302
+ function paragraphs(...sections) {
303
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
304
+ }
305
+
306
+ // packages/utils/src/lib/text-formats/md/section.ts
307
+ function section(...contents) {
308
+ return `${lines(...contents)}${NEW_LINE}`;
309
+ }
310
+ function lines(...contents) {
311
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
312
+ }
313
+
314
+ // packages/utils/src/lib/text-formats/md/table.ts
315
+ var alignString = /* @__PURE__ */ new Map([
316
+ ["left", ":--"],
317
+ ["center", ":--:"],
318
+ ["right", "--:"]
319
+ ]);
320
+ function tableRow(rows) {
321
+ return `|${rows.join("|")}|`;
322
+ }
323
+ function table2(data) {
324
+ if (data.rows.length === 0) {
325
+ throw new Error("Data can't be empty");
326
+ }
327
+ const alignmentRow = getColumnAlignments(data).map(
328
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
329
+ );
330
+ return section(
331
+ `${lines(
332
+ tableRow(columnsToStringArray(data)),
333
+ tableRow(alignmentRow),
334
+ ...rowToStringArray(data).map(tableRow)
335
+ )}`
336
+ );
337
+ }
338
+
339
+ // packages/utils/src/lib/text-formats/index.ts
340
+ var md = {
341
+ bold: bold2,
342
+ italic: italic2,
343
+ strikeThrough,
344
+ code: code2,
345
+ link: link2,
346
+ image,
347
+ headline,
348
+ h,
349
+ h1,
350
+ h2,
351
+ h3,
352
+ h4,
353
+ h5,
354
+ h6,
355
+ indentation,
356
+ lines,
357
+ li,
358
+ section,
359
+ paragraphs,
360
+ table: table2
361
+ };
362
+ var html = {
363
+ bold,
364
+ italic,
365
+ code,
366
+ link,
367
+ details,
368
+ table
369
+ };
3
370
 
4
371
  // packages/models/src/lib/implementation/schemas.ts
5
372
  import { MATERIAL_ICONS } from "vscode-material-icons";
@@ -66,6 +433,7 @@ function missingRefsForCategoriesErrorMsg(categories, plugins) {
66
433
  }
67
434
 
68
435
  // packages/models/src/lib/implementation/schemas.ts
436
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
69
437
  function executionMetaSchema(options = {
70
438
  descriptionDate: "Execution start date and time",
71
439
  descriptionDuration: "Execution duration in ms"
@@ -157,6 +525,7 @@ function hasNonZeroWeightedRef(refs) {
157
525
  }
158
526
 
159
527
  // packages/models/src/lib/audit.ts
528
+ import { z as z2 } from "zod";
160
529
  var auditSchema = z2.object({
161
530
  slug: slugSchema.describe("ID (unique within plugin)")
162
531
  }).merge(
@@ -186,7 +555,7 @@ function getDuplicateSlugsInAudits(audits) {
186
555
  }
187
556
 
188
557
  // packages/models/src/lib/audit-output.ts
189
- import { z as z4 } from "zod";
558
+ import { z as z5 } from "zod";
190
559
 
191
560
  // packages/models/src/lib/issue.ts
192
561
  import { z as z3 } from "zod";
@@ -217,16 +586,61 @@ var issueSchema = z3.object(
217
586
  { description: "Issue information" }
218
587
  );
219
588
 
589
+ // packages/models/src/lib/table.ts
590
+ import { z as z4 } from "zod";
591
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
592
+ description: "Cell alignment"
593
+ });
594
+ var tableColumnObjectSchema = z4.object({
595
+ key: z4.string(),
596
+ label: z4.string().optional(),
597
+ align: tableAlignmentSchema.optional()
598
+ });
599
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
600
+ description: "Object row"
601
+ });
602
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
603
+ description: "Primitive row"
604
+ });
605
+ var tableSharedSchema = z4.object({
606
+ title: z4.string().optional().describe("Display title for table")
607
+ });
608
+ var tablePrimitiveSchema = tableSharedSchema.merge(
609
+ z4.object(
610
+ {
611
+ columns: z4.array(tableAlignmentSchema).optional(),
612
+ rows: z4.array(tableRowPrimitiveSchema)
613
+ },
614
+ { description: "Table with primitive rows and optional alignment columns" }
615
+ )
616
+ );
617
+ var tableObjectSchema = tableSharedSchema.merge(
618
+ z4.object(
619
+ {
620
+ columns: z4.union([
621
+ z4.array(tableAlignmentSchema),
622
+ z4.array(tableColumnObjectSchema)
623
+ ]).optional(),
624
+ rows: z4.array(tableRowObjectSchema)
625
+ },
626
+ {
627
+ description: "Table with object rows and optional alignment or object columns"
628
+ }
629
+ )
630
+ );
631
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
632
+
220
633
  // packages/models/src/lib/audit-output.ts
221
634
  var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
222
- var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
223
- var auditDetailsSchema = z4.object(
635
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
636
+ var auditDetailsSchema = z5.object(
224
637
  {
225
- issues: z4.array(issueSchema, { description: "List of findings" })
638
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
639
+ table: tableSchema("Table of related findings").optional()
226
640
  },
227
641
  { description: "Detailed information" }
228
642
  );
229
- var auditOutputSchema = z4.object(
643
+ var auditOutputSchema = z5.object(
230
644
  {
231
645
  slug: slugSchema.describe("Reference to audit"),
232
646
  displayValue: auditDisplayValueSchema,
@@ -236,7 +650,7 @@ var auditOutputSchema = z4.object(
236
650
  },
237
651
  { description: "Audit information" }
238
652
  );
239
- var auditOutputsSchema = z4.array(auditOutputSchema, {
653
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
240
654
  description: "List of JSON formatted audit output emitted by the runner process of a plugin"
241
655
  }).refine(
242
656
  (audits) => !getDuplicateSlugsInAudits2(audits),
@@ -253,13 +667,13 @@ function getDuplicateSlugsInAudits2(audits) {
253
667
  }
254
668
 
255
669
  // packages/models/src/lib/category-config.ts
256
- import { z as z5 } from "zod";
670
+ import { z as z6 } from "zod";
257
671
  var categoryRefSchema = weightedRefSchema(
258
672
  "Weighted references to audits and/or groups for the category",
259
673
  "Slug of an audit or group (depending on `type`)"
260
674
  ).merge(
261
- z5.object({
262
- type: z5.enum(["audit", "group"], {
675
+ z6.object({
676
+ type: z6.enum(["audit", "group"], {
263
677
  description: "Discriminant for reference kind, affects where `slug` is looked up"
264
678
  }),
265
679
  plugin: slugSchema.describe(
@@ -280,8 +694,8 @@ var categoryConfigSchema = scorableSchema(
280
694
  description: "Meta info for category"
281
695
  })
282
696
  ).merge(
283
- z5.object({
284
- isBinary: z5.boolean({
697
+ z6.object({
698
+ isBinary: z6.boolean({
285
699
  description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
286
700
  }).optional()
287
701
  })
@@ -297,7 +711,7 @@ function getDuplicateRefsInCategoryMetrics(metrics) {
297
711
  metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
298
712
  );
299
713
  }
300
- var categoriesSchema = z5.array(categoryConfigSchema, {
714
+ var categoriesSchema = z6.array(categoryConfigSchema, {
301
715
  description: "Categorization of individual audits"
302
716
  }).refine(
303
717
  (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
@@ -316,18 +730,18 @@ function getDuplicateSlugCategories(categories) {
316
730
  }
317
731
 
318
732
  // packages/models/src/lib/commit.ts
319
- import { z as z6 } from "zod";
320
- var commitSchema = z6.object(
733
+ import { z as z7 } from "zod";
734
+ var commitSchema = z7.object(
321
735
  {
322
- hash: z6.string({ description: "Commit SHA (full)" }).regex(
736
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
323
737
  /^[\da-f]{40}$/,
324
738
  "Commit SHA should be a 40-character hexadecimal string"
325
739
  ),
326
- message: z6.string({ description: "Commit message" }),
327
- date: z6.coerce.date({
740
+ message: z7.string({ description: "Commit message" }),
741
+ date: z7.coerce.date({
328
742
  description: "Date and time when commit was authored"
329
743
  }),
330
- author: z6.string({
744
+ author: z7.string({
331
745
  description: "Commit author name"
332
746
  }).trim()
333
747
  },
@@ -335,22 +749,22 @@ var commitSchema = z6.object(
335
749
  );
336
750
 
337
751
  // packages/models/src/lib/core-config.ts
338
- import { z as z12 } from "zod";
752
+ import { z as z13 } from "zod";
339
753
 
340
754
  // packages/models/src/lib/persist-config.ts
341
- import { z as z7 } from "zod";
342
- var formatSchema = z7.enum(["json", "md"]);
343
- var persistConfigSchema = z7.object({
755
+ import { z as z8 } from "zod";
756
+ var formatSchema = z8.enum(["json", "md"]);
757
+ var persistConfigSchema = z8.object({
344
758
  outputDir: filePathSchema.describe("Artifacts folder").optional(),
345
759
  filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
346
- format: z7.array(formatSchema).optional()
760
+ format: z8.array(formatSchema).optional()
347
761
  });
348
762
 
349
763
  // packages/models/src/lib/plugin-config.ts
350
- import { z as z10 } from "zod";
764
+ import { z as z11 } from "zod";
351
765
 
352
766
  // packages/models/src/lib/group.ts
353
- import { z as z8 } from "zod";
767
+ import { z as z9 } from "zod";
354
768
  var groupRefSchema = weightedRefSchema(
355
769
  "Weighted reference to a group",
356
770
  "Reference slug to a group within this plugin (e.g. 'max-lines')"
@@ -367,7 +781,7 @@ var groupSchema = scorableSchema(
367
781
  getDuplicateRefsInGroups,
368
782
  duplicateRefsInGroupsErrorMsg
369
783
  ).merge(groupMetaSchema);
370
- var groupsSchema = z8.array(groupSchema, {
784
+ var groupsSchema = z9.array(groupSchema, {
371
785
  description: "List of groups"
372
786
  }).optional().refine(
373
787
  (groups) => !getDuplicateSlugsInGroups(groups),
@@ -395,14 +809,14 @@ function getDuplicateSlugsInGroups(groups) {
395
809
  }
396
810
 
397
811
  // packages/models/src/lib/runner-config.ts
398
- import { z as z9 } from "zod";
399
- var outputTransformSchema = z9.function().args(z9.unknown()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
400
- var runnerConfigSchema = z9.object(
812
+ import { z as z10 } from "zod";
813
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
814
+ var runnerConfigSchema = z10.object(
401
815
  {
402
- command: z9.string({
816
+ command: z10.string({
403
817
  description: "Shell command to execute"
404
818
  }),
405
- args: z9.array(z9.string({ description: "Command arguments" })).optional(),
819
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
406
820
  outputFile: filePathSchema.describe("Output path"),
407
821
  outputTransform: outputTransformSchema.optional()
408
822
  },
@@ -410,8 +824,8 @@ var runnerConfigSchema = z9.object(
410
824
  description: "How to execute runner"
411
825
  }
412
826
  );
413
- var onProgressSchema = z9.function().args(z9.unknown()).returns(z9.void());
414
- var runnerFunctionSchema = z9.function().args(onProgressSchema.optional()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
827
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
828
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
415
829
 
416
830
  // packages/models/src/lib/plugin-config.ts
417
831
  var pluginMetaSchema = packageVersionSchema().merge(
@@ -422,13 +836,13 @@ var pluginMetaSchema = packageVersionSchema().merge(
422
836
  description: "Plugin metadata"
423
837
  })
424
838
  ).merge(
425
- z10.object({
839
+ z11.object({
426
840
  slug: slugSchema.describe("Unique plugin slug within core config"),
427
841
  icon: materialIconSchema
428
842
  })
429
843
  );
430
- var pluginDataSchema = z10.object({
431
- runner: z10.union([runnerConfigSchema, runnerFunctionSchema]),
844
+ var pluginDataSchema = z11.object({
845
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
432
846
  audits: pluginAuditsSchema,
433
847
  groups: groupsSchema
434
848
  });
@@ -454,22 +868,22 @@ function getMissingRefsFromGroups(pluginCfg) {
454
868
  }
455
869
 
456
870
  // packages/models/src/lib/upload-config.ts
457
- import { z as z11 } from "zod";
458
- var uploadConfigSchema = z11.object({
871
+ import { z as z12 } from "zod";
872
+ var uploadConfigSchema = z12.object({
459
873
  server: urlSchema.describe("URL of deployed portal API"),
460
- apiKey: z11.string({
874
+ apiKey: z12.string({
461
875
  description: "API key with write access to portal (use `process.env` for security)"
462
876
  }),
463
877
  organization: slugSchema.describe(
464
878
  "Organization slug from Code PushUp portal"
465
879
  ),
466
880
  project: slugSchema.describe("Project slug from Code PushUp portal"),
467
- timeout: z11.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
881
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
468
882
  });
469
883
 
470
884
  // packages/models/src/lib/core-config.ts
471
- var unrefinedCoreConfigSchema = z12.object({
472
- plugins: z12.array(pluginConfigSchema, {
885
+ var unrefinedCoreConfigSchema = z13.object({
886
+ plugins: z13.array(pluginConfigSchema, {
473
887
  description: "List of plugins to be used (official, community-provided, or custom)"
474
888
  }).min(1),
475
889
  /** portal configuration for persisting results */
@@ -492,7 +906,7 @@ function refineCoreConfig(schema) {
492
906
  }
493
907
 
494
908
  // packages/models/src/lib/report.ts
495
- import { z as z13 } from "zod";
909
+ import { z as z14 } from "zod";
496
910
  var auditReportSchema = auditSchema.merge(auditOutputSchema);
497
911
  var pluginReportSchema = pluginMetaSchema.merge(
498
912
  executionMetaSchema({
@@ -500,9 +914,9 @@ var pluginReportSchema = pluginMetaSchema.merge(
500
914
  descriptionDuration: "Duration of the plugin run in ms"
501
915
  })
502
916
  ).merge(
503
- z13.object({
504
- audits: z13.array(auditReportSchema).min(1),
505
- groups: z13.array(groupSchema).optional()
917
+ z14.object({
918
+ audits: z14.array(auditReportSchema).min(1),
919
+ groups: z14.array(groupSchema).optional()
506
920
  })
507
921
  ).refine(
508
922
  (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
@@ -536,10 +950,10 @@ var reportSchema = packageVersionSchema({
536
950
  descriptionDuration: "Duration of the collect run in ms"
537
951
  })
538
952
  ).merge(
539
- z13.object(
953
+ z14.object(
540
954
  {
541
- categories: z13.array(categoryConfigSchema),
542
- plugins: z13.array(pluginReportSchema).min(1),
955
+ categories: z14.array(categoryConfigSchema),
956
+ plugins: z14.array(pluginReportSchema).min(1),
543
957
  commit: commitSchema.describe("Git commit for which report was collected").nullable()
544
958
  },
545
959
  { description: "Collect output data" }
@@ -555,36 +969,40 @@ var reportSchema = packageVersionSchema({
555
969
  );
556
970
 
557
971
  // packages/models/src/lib/reports-diff.ts
558
- import { z as z14 } from "zod";
972
+ import { z as z15 } from "zod";
559
973
  function makeComparisonSchema(schema) {
560
974
  const sharedDescription = schema.description || "Result";
561
- return z14.object({
975
+ return z15.object({
562
976
  before: schema.describe(`${sharedDescription} (source commit)`),
563
977
  after: schema.describe(`${sharedDescription} (target commit)`)
564
978
  });
565
979
  }
566
980
  function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
567
- return z14.object(
981
+ return z15.object(
568
982
  {
569
- changed: z14.array(diffSchema),
570
- unchanged: z14.array(resultSchema),
571
- added: z14.array(resultSchema),
572
- removed: z14.array(resultSchema)
983
+ changed: z15.array(diffSchema),
984
+ unchanged: z15.array(resultSchema),
985
+ added: z15.array(resultSchema),
986
+ removed: z15.array(resultSchema)
573
987
  },
574
988
  { description }
575
989
  );
576
990
  }
577
- var scorableMetaSchema = z14.object({ slug: slugSchema, title: titleSchema });
991
+ var scorableMetaSchema = z15.object({
992
+ slug: slugSchema,
993
+ title: titleSchema,
994
+ docsUrl: docsUrlSchema
995
+ });
578
996
  var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
579
- z14.object({
580
- plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
997
+ z15.object({
998
+ plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
581
999
  })
582
1000
  );
583
1001
  var scorableDiffSchema = scorableMetaSchema.merge(
584
- z14.object({
1002
+ z15.object({
585
1003
  scores: makeComparisonSchema(scoreSchema).merge(
586
- z14.object({
587
- diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
1004
+ z15.object({
1005
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
588
1006
  })
589
1007
  ).describe("Score comparison")
590
1008
  })
@@ -595,10 +1013,10 @@ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
595
1013
  var categoryDiffSchema = scorableDiffSchema;
596
1014
  var groupDiffSchema = scorableWithPluginDiffSchema;
597
1015
  var auditDiffSchema = scorableWithPluginDiffSchema.merge(
598
- z14.object({
1016
+ z15.object({
599
1017
  values: makeComparisonSchema(auditValueSchema).merge(
600
- z14.object({
601
- diff: z14.number().int().describe("Value change (`values.after - values.before`)")
1018
+ z15.object({
1019
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
602
1020
  })
603
1021
  ).describe("Audit `value` comparison"),
604
1022
  displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
@@ -607,15 +1025,15 @@ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
607
1025
  })
608
1026
  );
609
1027
  var categoryResultSchema = scorableMetaSchema.merge(
610
- z14.object({ score: scoreSchema })
1028
+ z15.object({ score: scoreSchema })
611
1029
  );
612
1030
  var groupResultSchema = scorableWithPluginMetaSchema.merge(
613
- z14.object({ score: scoreSchema })
1031
+ z15.object({ score: scoreSchema })
614
1032
  );
615
1033
  var auditResultSchema = scorableWithPluginMetaSchema.merge(
616
1034
  auditOutputSchema.pick({ score: true, value: true, displayValue: true })
617
1035
  );
618
- var reportsDiffSchema = z14.object({
1036
+ var reportsDiffSchema = z15.object({
619
1037
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
620
1038
  categories: makeArraysComparisonSchema(
621
1039
  categoryDiffSchema,
@@ -778,40 +1196,48 @@ import chalk from "chalk";
778
1196
 
779
1197
  // packages/utils/src/lib/reports/constants.ts
780
1198
  var TERMINAL_WIDTH = 80;
781
- var NEW_LINE = "\n";
782
1199
  var SCORE_COLOR_RANGE = {
783
1200
  GREEN_MIN: 0.9,
784
1201
  YELLOW_MIN: 0.5
785
1202
  };
1203
+ var CATEGORIES_TITLE = "\u{1F3F7} Categories";
786
1204
  var FOOTER_PREFIX = "Made with \u2764 by";
787
1205
  var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
788
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
1206
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
789
1207
  var reportHeadlineText = "Code PushUp Report";
790
1208
  var reportOverviewTableHeaders = [
791
- "\u{1F3F7} Category",
792
- "\u2B50 Score",
793
- "\u{1F6E1} Audits"
1209
+ {
1210
+ key: "category",
1211
+ label: "\u{1F3F7} Category",
1212
+ align: "left"
1213
+ },
1214
+ {
1215
+ key: "score",
1216
+ label: "\u2B50 Score"
1217
+ },
1218
+ {
1219
+ key: "audits",
1220
+ label: "\u{1F6E1} Audits"
1221
+ }
794
1222
  ];
795
1223
  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)"
1224
+ var issuesTableHeadings = [
1225
+ {
1226
+ key: "severity",
1227
+ label: "Severity"
1228
+ },
1229
+ {
1230
+ key: "message",
1231
+ label: "Message"
1232
+ },
1233
+ {
1234
+ key: "file",
1235
+ label: "Source file"
1236
+ },
1237
+ {
1238
+ key: "line",
1239
+ label: "Line(s)"
1240
+ }
815
1241
  ];
816
1242
 
817
1243
  // packages/utils/src/lib/logging.ts
@@ -837,7 +1263,7 @@ function logListItem(args) {
837
1263
  singletonisaacUi.rows = [];
838
1264
  singletonUiInstance?.logger.log(content);
839
1265
  }
840
- function link(text) {
1266
+ function link3(text) {
841
1267
  return chalk.underline(chalk.blueBright(text));
842
1268
  }
843
1269
 
@@ -904,7 +1330,7 @@ async function ensureDirectoryExists(baseDir) {
904
1330
  await mkdir(baseDir, { recursive: true });
905
1331
  return;
906
1332
  } catch (error) {
907
- ui().logger.error(error.message);
1333
+ ui().logger.info(error.message);
908
1334
  if (error.code !== "EEXIST") {
909
1335
  throw error;
910
1336
  }
@@ -969,127 +1395,40 @@ async function crawlFileSystem(options) {
969
1395
  return resultsNestedArray.flat();
970
1396
  }
971
1397
  function findLineNumberInText(content, pattern) {
972
- const lines = content.split(/\r?\n/);
973
- const lineNumber = lines.findIndex((line) => line.includes(pattern)) + 1;
1398
+ const lines6 = content.split(/\r?\n/);
1399
+ const lineNumber = lines6.findIndex((line) => line.includes(pattern)) + 1;
974
1400
  return lineNumber === 0 ? null : lineNumber;
975
1401
  }
976
1402
 
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
998
- };
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 `![${alt}](${src})`;
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
- }
1067
-
1068
1403
  // packages/utils/src/lib/reports/utils.ts
1404
+ var { image: image2, bold: boldMd } = md;
1069
1405
  function formatReportScore(score) {
1070
1406
  return Math.round(score * 100).toString();
1071
1407
  }
1072
1408
  function formatScoreWithColor(score, options) {
1073
- const styledNumber = options?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1074
- return `${getRoundScoreMarker(score)} ${styledNumber}`;
1075
- }
1076
- function getRoundScoreMarker(score) {
1077
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1078
- return "\u{1F7E2}";
1079
- }
1080
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1081
- return "\u{1F7E1}";
1409
+ const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
1410
+ return `${scoreMarker(score)} ${styledNumber}`;
1411
+ }
1412
+ var MARKERS = {
1413
+ circle: {
1414
+ red: "\u{1F534}",
1415
+ yellow: "\u{1F7E1}",
1416
+ green: "\u{1F7E2}"
1417
+ },
1418
+ square: {
1419
+ red: "\u{1F7E5}",
1420
+ yellow: "\u{1F7E8}",
1421
+ green: "\u{1F7E9}"
1082
1422
  }
1083
- return "\u{1F534}";
1084
- }
1085
- function getSquaredScoreMarker(score) {
1423
+ };
1424
+ function scoreMarker(score, markerType = "circle") {
1086
1425
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1087
- return "\u{1F7E9}";
1426
+ return MARKERS[markerType].green;
1088
1427
  }
1089
1428
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1090
- return "\u{1F7E8}";
1429
+ return MARKERS[markerType].yellow;
1091
1430
  }
1092
- return "\u{1F7E5}";
1431
+ return MARKERS[markerType].red;
1093
1432
  }
1094
1433
  function getDiffMarker(diff) {
1095
1434
  if (diff > 0) {
@@ -1105,7 +1444,7 @@ function colorByScoreDiff(text, diff) {
1105
1444
  return shieldsBadge(text, color);
1106
1445
  }
1107
1446
  function shieldsBadge(text, color) {
1108
- return image(
1447
+ return image2(
1109
1448
  `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1110
1449
  text
1111
1450
  );
@@ -1115,7 +1454,7 @@ function formatDiffNumber(diff) {
1115
1454
  const sign = diff < 0 ? "\u2212" : "+";
1116
1455
  return `${sign}${number}`;
1117
1456
  }
1118
- function getSeverityIcon(severity) {
1457
+ function severityMarker(severity) {
1119
1458
  if (severity === "error") {
1120
1459
  return "\u{1F6A8}";
1121
1460
  }
@@ -1306,13 +1645,13 @@ function executeProcess(cfg) {
1306
1645
  process2.on("error", (err) => {
1307
1646
  stderr += err.toString();
1308
1647
  });
1309
- process2.on("close", (code) => {
1648
+ process2.on("close", (code3) => {
1310
1649
  const timings = { date, duration: calcDuration(start) };
1311
- if (code === 0 || ignoreExitCode) {
1650
+ if (code3 === 0 || ignoreExitCode) {
1312
1651
  onComplete?.();
1313
- resolve({ code, stdout, stderr, ...timings });
1652
+ resolve({ code: code3, stdout, stderr, ...timings });
1314
1653
  } else {
1315
- const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
1654
+ const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
1316
1655
  onError?.(errorMsg);
1317
1656
  reject(errorMsg);
1318
1657
  }
@@ -1328,165 +1667,181 @@ function filterItemRefsBy(items, refFilterFn) {
1328
1667
  })).filter((item) => item.refs.length);
1329
1668
  }
1330
1669
 
1331
- // packages/utils/src/lib/git.ts
1670
+ // packages/utils/src/lib/git/git.ts
1332
1671
  import { isAbsolute, join as join3, relative } from "node:path";
1333
1672
  import { simpleGit } from "simple-git";
1334
-
1335
- // packages/utils/src/lib/transform.ts
1336
- import { platform } from "node:os";
1337
- function toArray(val) {
1338
- return Array.isArray(val) ? val : [val];
1339
- }
1340
- function objectToKeys(obj) {
1341
- return Object.keys(obj);
1342
- }
1343
- function objectToEntries(obj) {
1344
- return Object.entries(obj);
1345
- }
1346
- function objectFromEntries(entries) {
1347
- return Object.fromEntries(entries);
1348
- }
1349
- function countOccurrences(values) {
1350
- return values.reduce(
1351
- (acc, value) => ({ ...acc, [value]: (acc[value] ?? 0) + 1 }),
1352
- {}
1353
- );
1673
+ function getGitRoot(git = simpleGit()) {
1674
+ return git.revparse("--show-toplevel");
1354
1675
  }
1355
- function distinct(array) {
1356
- return [...new Set(array)];
1676
+ function formatGitPath(path, gitRoot) {
1677
+ const absolutePath = isAbsolute(path) ? path : join3(process.cwd(), path);
1678
+ const relativePath = relative(gitRoot, absolutePath);
1679
+ return toUnixPath(relativePath);
1357
1680
  }
1358
- function deepClone(obj) {
1359
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1681
+ async function toGitPath(path, git = simpleGit()) {
1682
+ const gitRoot = await getGitRoot(git);
1683
+ return formatGitPath(path, gitRoot);
1360
1684
  }
1361
- function factorOf(items, filterFn) {
1362
- const itemCount = items.length;
1363
- if (!itemCount) {
1364
- return 1;
1685
+ var GitStatusError = class _GitStatusError extends Error {
1686
+ static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
1687
+ static getReducedStatus(status) {
1688
+ return Object.fromEntries(
1689
+ Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
1690
+ (entry) => {
1691
+ const value = entry[1];
1692
+ if (value == null) {
1693
+ return false;
1694
+ }
1695
+ if (Array.isArray(value) && value.length === 0) {
1696
+ return false;
1697
+ }
1698
+ if (typeof value === "number" && value === 0) {
1699
+ return false;
1700
+ }
1701
+ return !(typeof value === "boolean" && !value);
1702
+ }
1703
+ )
1704
+ );
1365
1705
  }
1366
- const filterCount = items.filter(filterFn).length;
1367
- return filterCount === 0 ? 1 : (itemCount - filterCount) / itemCount;
1368
- }
1369
- function objectToCliArgs(params) {
1370
- if (!params) {
1371
- return [];
1706
+ constructor(status) {
1707
+ super(
1708
+ `Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
1709
+ ${JSON.stringify(
1710
+ _GitStatusError.getReducedStatus(status),
1711
+ null,
1712
+ 2
1713
+ )}`
1714
+ );
1715
+ }
1716
+ };
1717
+ async function guardAgainstLocalChanges(git = simpleGit()) {
1718
+ const status = await git.status(["-s"]);
1719
+ if (status.files.length > 0) {
1720
+ throw new GitStatusError(status);
1372
1721
  }
1373
- return Object.entries(params).flatMap(([key, value]) => {
1374
- if (key === "_") {
1375
- return Array.isArray(value) ? value : [`${value}`];
1376
- }
1377
- const prefix = key.length === 1 ? "-" : "--";
1378
- if (Array.isArray(value)) {
1379
- return value.map((v) => `${prefix}${key}="${v}"`);
1380
- }
1381
- if (Array.isArray(value)) {
1382
- return value.map((v) => `${prefix}${key}="${v}"`);
1383
- }
1384
- if (typeof value === "string") {
1385
- return [`${prefix}${key}="${value}"`];
1386
- }
1387
- if (typeof value === "number") {
1388
- return [`${prefix}${key}=${value}`];
1389
- }
1390
- if (typeof value === "boolean") {
1391
- return [`${prefix}${value ? "" : "no-"}${key}`];
1392
- }
1393
- throw new Error(`Unsupported type ${typeof value} for key ${key}`);
1394
- });
1395
- }
1396
- function toUnixPath(path) {
1397
- return path.replace(/\\/g, "/");
1398
- }
1399
- function toUnixNewlines(text) {
1400
- return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
1401
- }
1402
- function fromJsonLines(jsonLines) {
1403
- const unifiedNewLines = toUnixNewlines(jsonLines).trim();
1404
- return JSON.parse(`[${unifiedNewLines.split("\n").join(",")}]`);
1405
- }
1406
- function toJsonLines(json) {
1407
- return json.map((item) => JSON.stringify(item)).join("\n");
1408
- }
1409
- function capitalize(text) {
1410
- return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
1411
- 1
1412
- )}`;
1413
- }
1414
- function apostrophize(text, upperCase) {
1415
- const lastCharMatch = text.match(/(\w)\W*$/);
1416
- const lastChar = lastCharMatch?.[1] ?? "";
1417
- return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
1418
- }
1419
- function toNumberPrecision(value, decimalPlaces) {
1420
- return Number(
1421
- `${Math.round(
1422
- Number.parseFloat(`${value}e${decimalPlaces}`)
1423
- )}e-${decimalPlaces}`
1424
- );
1425
1722
  }
1426
- function toOrdinal(value) {
1427
- if (value % 10 === 1 && value % 100 !== 11) {
1428
- return `${value}st`;
1723
+ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1724
+ if (forceCleanStatus) {
1725
+ await git.raw(["reset", "--hard"]);
1726
+ await git.clean(["f", "d"]);
1727
+ ui().logger.info(`git status cleaned`);
1429
1728
  }
1430
- if (value % 10 === 2 && value % 100 !== 12) {
1431
- return `${value}nd`;
1729
+ await guardAgainstLocalChanges(git);
1730
+ await git.checkout(branchOrHash);
1731
+ }
1732
+
1733
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1734
+ import { simpleGit as simpleGit2 } from "simple-git";
1735
+
1736
+ // packages/utils/src/lib/semver.ts
1737
+ import { rcompare, valid } from "semver";
1738
+ function normalizeSemver(semverString) {
1739
+ if (semverString.startsWith("v") || semverString.startsWith("V")) {
1740
+ return semverString.slice(1);
1432
1741
  }
1433
- if (value % 10 === 3 && value % 100 !== 13) {
1434
- return `${value}rd`;
1742
+ if (semverString.includes("@")) {
1743
+ return semverString.split("@").at(-1) ?? "";
1435
1744
  }
1436
- return `${value}th`;
1745
+ return semverString;
1746
+ }
1747
+ function isSemver(semverString = "") {
1748
+ return valid(normalizeSemver(semverString)) != null;
1749
+ }
1750
+ function sortSemvers(semverStrings) {
1751
+ return semverStrings.map(normalizeSemver).filter(isSemver).sort(rcompare);
1437
1752
  }
1438
1753
 
1439
- // packages/utils/src/lib/git.ts
1440
- async function getLatestCommit(git = simpleGit()) {
1754
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1755
+ async function getLatestCommit(git = simpleGit2()) {
1441
1756
  const log2 = await git.log({
1442
1757
  maxCount: 1,
1758
+ // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1443
1759
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1444
1760
  });
1445
- if (!log2.latest) {
1446
- return null;
1447
- }
1448
1761
  return commitSchema.parse(log2.latest);
1449
1762
  }
1450
- function getGitRoot(git = simpleGit()) {
1451
- return git.revparse("--show-toplevel");
1452
- }
1453
- function formatGitPath(path, gitRoot) {
1454
- const absolutePath = isAbsolute(path) ? path : join3(process.cwd(), path);
1455
- const relativePath = relative(gitRoot, absolutePath);
1456
- return toUnixPath(relativePath);
1763
+ async function getCurrentBranchOrTag(git = simpleGit2()) {
1764
+ return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
1765
+ // @TODO use simple git
1766
+ await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1457
1767
  }
1458
- async function toGitPath(path, git = simpleGit()) {
1459
- const gitRoot = await getGitRoot(git);
1460
- return formatGitPath(path, gitRoot);
1461
- }
1462
- async function guardAgainstLocalChanges(git = simpleGit()) {
1463
- const isClean = await git.status(["-s"]).then((r) => r.files.length === 0);
1464
- if (!isClean) {
1768
+ function validateFilter({ from, to }) {
1769
+ if (to && !from) {
1465
1770
  throw new Error(
1466
- "Working directory needs to be clean before we you can proceed. Commit your local changes or stash them."
1771
+ `filter needs the "from" option defined to accept the "to" option.
1772
+ `
1467
1773
  );
1468
1774
  }
1469
1775
  }
1470
- async function getCurrentBranchOrTag(git = simpleGit()) {
1471
- try {
1472
- const branch = await git.branch().then((r) => r.current);
1473
- if (branch) {
1474
- return branch;
1475
- } else {
1476
- return await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1477
- }
1478
- } catch {
1479
- throw new Error("Could not get current tag or branch.");
1776
+ function filterLogs(allTags, opt) {
1777
+ if (!opt) {
1778
+ return allTags;
1480
1779
  }
1780
+ validateFilter(opt);
1781
+ const { from, to, maxCount } = opt;
1782
+ const finIndex = (tagName, fallback) => {
1783
+ const idx = allTags.indexOf(tagName ?? "");
1784
+ if (idx > -1) {
1785
+ return idx;
1786
+ }
1787
+ return fallback;
1788
+ };
1789
+ const fromIndex = finIndex(from, 0);
1790
+ const toIndex = finIndex(to, void 0);
1791
+ return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
1481
1792
  }
1482
- async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1483
- if (forceCleanStatus) {
1484
- await git.raw(["reset", "--hard"]);
1485
- await git.clean(["f", "d"]);
1486
- ui().logger.info(`git status cleaned`);
1793
+ async function getHashFromTag(tag, git = simpleGit2()) {
1794
+ const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
1795
+ const hash = tagDetails.trim();
1796
+ return {
1797
+ hash: hash.split("\n").at(-1) ?? "",
1798
+ message: tag
1799
+ };
1800
+ }
1801
+ async function getSemverTags(opt = {}, git = simpleGit2()) {
1802
+ validateFilter(opt);
1803
+ const { targetBranch, ...options } = opt;
1804
+ let currentBranch;
1805
+ if (targetBranch) {
1806
+ currentBranch = await getCurrentBranchOrTag(git);
1807
+ await git.checkout(targetBranch);
1808
+ }
1809
+ const tagsRaw = await git.tag([
1810
+ "--merged",
1811
+ targetBranch ?? await getCurrentBranchOrTag(git)
1812
+ ]);
1813
+ const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
1814
+ const relevantTags = filterLogs(allTags, options);
1815
+ const tagsWithHashes = await Promise.all(
1816
+ relevantTags.map((tag) => getHashFromTag(tag, git))
1817
+ );
1818
+ if (currentBranch) {
1819
+ await git.checkout(currentBranch);
1820
+ }
1821
+ return tagsWithHashes;
1822
+ }
1823
+ async function getHashes(options = {}, git = simpleGit2()) {
1824
+ const { targetBranch, from, to, maxCount, ...opt } = options;
1825
+ validateFilter({ from, to });
1826
+ let currentBranch;
1827
+ if (targetBranch) {
1828
+ currentBranch = await getCurrentBranchOrTag(git);
1829
+ await git.checkout(targetBranch);
1830
+ }
1831
+ const logs = await git.log({
1832
+ ...opt,
1833
+ format: {
1834
+ hash: "%H",
1835
+ message: "%s"
1836
+ },
1837
+ from,
1838
+ to,
1839
+ maxCount
1840
+ });
1841
+ if (targetBranch) {
1842
+ await git.checkout(currentBranch);
1487
1843
  }
1488
- await guardAgainstLocalChanges(git);
1489
- await git.checkout(branchOrHash);
1844
+ return [...logs.all];
1490
1845
  }
1491
1846
 
1492
1847
  // packages/utils/src/lib/group-by-status.ts
@@ -1564,44 +1919,65 @@ function listAuditsFromAllPlugins(report) {
1564
1919
  );
1565
1920
  }
1566
1921
 
1567
- // packages/utils/src/lib/reports/generate-md-report.ts
1568
- function generateMdReport(report) {
1569
- const printCategories = report.categories.length > 0;
1570
- return (
1571
- // header section
1572
- // eslint-disable-next-line prefer-template
1573
- reportToHeaderSection() + NEW_LINE + // categories overview section
1574
- (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1575
- (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1576
- reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
1577
- reportToAboutSection(report) + NEW_LINE + NEW_LINE + // footer section
1578
- `${FOOTER_PREFIX} ${link2(README_LINK, "Code PushUp")}`
1922
+ // packages/utils/src/lib/reports/formatting.ts
1923
+ var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
1924
+ function tableSection(tableData, options) {
1925
+ if (tableData.rows.length === 0) {
1926
+ return "";
1927
+ }
1928
+ const { level = 4 } = options ?? {};
1929
+ const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
1930
+ return lines2(
1931
+ tableData.title && render(tableData.title, level),
1932
+ table3(tableData)
1579
1933
  );
1580
1934
  }
1581
- function reportToHeaderSection() {
1582
- return headline(reportHeadlineText) + NEW_LINE;
1935
+ function metaDescription({
1936
+ docsUrl,
1937
+ description
1938
+ }) {
1939
+ if (docsUrl) {
1940
+ const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
1941
+ if (!description) {
1942
+ return section2(docsLink);
1943
+ }
1944
+ const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
1945
+ return section2(`${parsedDescription}${docsLink}`);
1946
+ }
1947
+ if (description && description.trim().length > 0) {
1948
+ return section2(description);
1949
+ }
1950
+ return "";
1583
1951
  }
1584
- function reportToOverviewSection(report) {
1952
+
1953
+ // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1954
+ var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
1955
+ function categoriesOverviewSection(report) {
1585
1956
  const { categories, plugins } = report;
1586
- const tableContent = [
1587
- reportOverviewTableHeaders,
1588
- ...categories.map(({ title, refs, score }) => [
1589
- link2(`#${slugify(title)}`, title),
1590
- `${getRoundScoreMarker(score)} ${style(formatReportScore(score))}`,
1591
- countCategoryAudits(refs, plugins).toString()
1592
- ])
1593
- ];
1594
- return tableMd(tableContent, ["l", "c", "c"]);
1957
+ if (categories.length > 0 && plugins.length > 0) {
1958
+ const tableContent = {
1959
+ columns: reportOverviewTableHeaders,
1960
+ rows: categories.map(({ title, refs, score }) => ({
1961
+ // The heading "ID" is inferred from the heading text in Markdown.
1962
+ category: link5(`#${slugify(title)}`, title),
1963
+ score: `${scoreMarker(score)}${SPACE}${boldMd2(
1964
+ formatReportScore(score)
1965
+ )}`,
1966
+ audits: countCategoryAudits(refs, plugins).toString()
1967
+ }))
1968
+ };
1969
+ return tableSection(tableContent);
1970
+ }
1971
+ return "";
1595
1972
  }
1596
- function reportToCategoriesSection(report) {
1973
+ function categoriesDetailsSection(report) {
1597
1974
  const { categories, plugins } = report;
1598
- const categoryDetails = categories.reduce((acc, category) => {
1599
- const categoryTitle = h3(category.title);
1600
- const categoryScore = `${getRoundScoreMarker(
1975
+ const categoryDetails = categories.flatMap((category) => {
1976
+ const categoryTitle = h32(category.title);
1977
+ const categoryScore = `${scoreMarker(
1601
1978
  category.score
1602
- )} Score: ${style(formatReportScore(category.score))}`;
1603
- const categoryDocs = getDocsAndDescription(category);
1604
- const categoryMDItems = category.refs.reduce((refAcc, ref) => {
1979
+ )}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
1980
+ const categoryMDItems = category.refs.map((ref) => {
1605
1981
  if (ref.type === "group") {
1606
1982
  const group = getSortableGroupByRef(ref, plugins);
1607
1983
  const groupAudits = group.refs.map(
@@ -1611,151 +1987,240 @@ function reportToCategoriesSection(report) {
1611
1987
  )
1612
1988
  );
1613
1989
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1614
- const mdGroupItem = groupItemToCategorySection(
1615
- group,
1616
- groupAudits,
1617
- pluginTitle
1618
- );
1619
- return refAcc + mdGroupItem + NEW_LINE;
1990
+ return categoryGroupItem(group, groupAudits, pluginTitle);
1620
1991
  } else {
1621
1992
  const audit = getSortableAuditByRef(ref, plugins);
1622
1993
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1623
- const mdAuditItem = auditItemToCategorySection(audit, pluginTitle);
1624
- return refAcc + mdAuditItem + NEW_LINE;
1994
+ return categoryRef(audit, pluginTitle);
1625
1995
  }
1626
- }, "");
1627
- return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + categoryMDItems;
1628
- }, "");
1629
- return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1630
- }
1631
- function auditItemToCategorySection(audit, pluginTitle) {
1632
- const auditTitle = link2(
1633
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1634
- audit.title
1996
+ });
1997
+ return section3(
1998
+ categoryTitle,
1999
+ metaDescription(category),
2000
+ categoryScore,
2001
+ ...categoryMDItems
2002
+ );
2003
+ });
2004
+ return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
2005
+ }
2006
+ function categoryRef({ title, score, value, displayValue }, pluginTitle) {
2007
+ const auditTitleAsLink = link5(
2008
+ `#${slugify(title)}-${slugify(pluginTitle)}`,
2009
+ title
1635
2010
  );
1636
- return li(
1637
- `${getSquaredScoreMarker(
1638
- audit.score
1639
- )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`
2011
+ const marker = scoreMarker(score, "square");
2012
+ return li2(
2013
+ `${marker}${SPACE}${auditTitleAsLink}${SPACE}(_${pluginTitle}_) - ${boldMd2(
2014
+ (displayValue || value).toString()
2015
+ )}`
1640
2016
  );
1641
2017
  }
1642
- function groupItemToCategorySection(group, groupAudits, pluginTitle) {
1643
- const groupScore = Number(formatReportScore(group.score || 0));
1644
- const groupTitle = li(
1645
- `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`
2018
+ function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
2019
+ const groupTitle = li2(
2020
+ `${scoreMarker(score)}${SPACE}${title}${SPACE}(_${pluginTitle}_)`
1646
2021
  );
1647
- const auditTitles = groupAudits.reduce((acc, audit) => {
1648
- const auditTitle = link2(
1649
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1650
- audit.title
1651
- );
1652
- return `${acc} ${li(
1653
- `${getSquaredScoreMarker(audit.score)} ${auditTitle} - ${getAuditResult(
1654
- audit
1655
- )}`
1656
- )}${NEW_LINE}`;
1657
- }, "");
1658
- return groupTitle + NEW_LINE + auditTitles;
1659
- }
1660
- function reportToAuditsSection(report) {
1661
- const auditsSection = report.plugins.reduce((pluginAcc, plugin) => {
1662
- const auditsData = plugin.audits.reduce((auditAcc, audit) => {
1663
- const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1664
- plugin.slug,
1665
- report.plugins
1666
- )})`;
1667
- return auditAcc + h3(auditTitle) + NEW_LINE + NEW_LINE + reportToDetailsSection(audit) + NEW_LINE + NEW_LINE + getDocsAndDescription(audit);
1668
- }, "");
1669
- return pluginAcc + auditsData;
1670
- }, "");
1671
- return h2("\u{1F6E1}\uFE0F Audits") + NEW_LINE + NEW_LINE + auditsSection;
1672
- }
1673
- function reportToDetailsSection(audit) {
1674
- const detailsTitle = `${getSquaredScoreMarker(audit.score)} ${getAuditResult(
1675
- audit,
1676
- true
1677
- )} (score: ${formatReportScore(audit.score)})`;
1678
- if (!audit.details?.issues.length) {
1679
- return detailsTitle;
1680
- }
1681
- const detailsTableData = [
1682
- detailsTableHeaders,
1683
- ...audit.details.issues.map((issue) => {
1684
- const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1685
- const message = issue.message;
1686
- if (!issue.source) {
1687
- return [severity, message, "", ""];
1688
- }
1689
- const file = `<code>${issue.source.file}</code>`;
1690
- if (!issue.source.position) {
1691
- return [severity, message, file, ""];
1692
- }
1693
- const { startLine, endLine } = issue.source.position;
1694
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1695
- return [severity, message, file, line];
1696
- })
1697
- ];
1698
- const detailsTable = `<h4>Issues</h4>${tableHtml(detailsTableData)}`;
1699
- return details(detailsTitle, detailsTable);
1700
- }
1701
- function reportToAboutSection(report) {
1702
- const date = formatDate(/* @__PURE__ */ new Date());
1703
- const { duration, version, commit, plugins, categories } = report;
1704
- const commitInfo = commit ? `${commit.message} (${commit.hash})` : "N/A";
1705
- const reportMetaTable = [
1706
- reportMetaTableHeaders,
1707
- [
1708
- commitInfo,
1709
- style(version || "", ["c"]),
1710
- formatDuration(duration),
1711
- plugins.length.toString(),
1712
- categories.length.toString(),
1713
- plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1714
- ]
1715
- ];
1716
- const pluginMetaTable = [
1717
- pluginMetaTableHeaders,
1718
- ...plugins.map((plugin) => [
1719
- plugin.title,
1720
- plugin.audits.length.toString(),
1721
- style(plugin.version || "", ["c"]),
1722
- formatDuration(plugin.duration)
1723
- ])
1724
- ];
1725
- return (
1726
- // eslint-disable-next-line prefer-template
1727
- h2("About") + NEW_LINE + NEW_LINE + `Report was created by [Code PushUp](${README_LINK}) on ${date}.` + NEW_LINE + NEW_LINE + tableMd(reportMetaTable, ["l", "c", "c", "c", "c", "c"]) + NEW_LINE + NEW_LINE + "The following plugins were run:" + NEW_LINE + NEW_LINE + tableMd(pluginMetaTable, ["l", "c", "c", "c"])
2022
+ const auditTitles = groupAudits.map(
2023
+ ({ title: auditTitle, score: auditScore, value, displayValue }) => {
2024
+ const auditTitleLink = link5(
2025
+ `#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
2026
+ auditTitle
2027
+ );
2028
+ const marker = scoreMarker(auditScore, "square");
2029
+ return indentation2(
2030
+ li2(
2031
+ `${marker}${SPACE}${auditTitleLink} - ${boldMd2(
2032
+ String(displayValue ?? value)
2033
+ )}`
2034
+ )
2035
+ );
2036
+ }
1728
2037
  );
2038
+ return lines3(groupTitle, ...auditTitles);
1729
2039
  }
1730
- function getDocsAndDescription({
1731
- docsUrl,
1732
- description
2040
+
2041
+ // packages/utils/src/lib/reports/generate-md-report.ts
2042
+ var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
2043
+ var { bold: boldHtml, details: details2 } = html;
2044
+ function auditDetailsAuditValue({
2045
+ score,
2046
+ value,
2047
+ displayValue
1733
2048
  }) {
1734
- if (docsUrl) {
1735
- const docsLink = link2(docsUrl, "\u{1F4D6} Docs");
1736
- if (!description) {
1737
- return docsLink + NEW_LINE + NEW_LINE;
1738
- }
1739
- if (description.endsWith("```")) {
1740
- return description + NEW_LINE + NEW_LINE + docsLink + NEW_LINE + NEW_LINE;
1741
- }
1742
- return `${description} ${docsLink}${NEW_LINE}${NEW_LINE}`;
1743
- }
1744
- if (description) {
1745
- return description + NEW_LINE + NEW_LINE;
2049
+ return `${scoreMarker(score, "square")} ${boldHtml(
2050
+ String(displayValue ?? value)
2051
+ )} (score: ${formatReportScore(score)})`;
2052
+ }
2053
+ function generateMdReport(report) {
2054
+ const printCategories = report.categories.length > 0;
2055
+ return lines4(
2056
+ h12(reportHeadlineText),
2057
+ printCategories ? categoriesOverviewSection(report) : "",
2058
+ printCategories ? categoriesDetailsSection(report) : "",
2059
+ auditsSection(report),
2060
+ aboutSection(report),
2061
+ `${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
2062
+ );
2063
+ }
2064
+ function auditDetailsIssues(issues = []) {
2065
+ if (issues.length === 0) {
2066
+ return "";
1746
2067
  }
1747
- return "";
2068
+ const detailsTableData = {
2069
+ title: "Issues",
2070
+ columns: issuesTableHeadings,
2071
+ rows: issues.map(
2072
+ ({ severity: severityVal, message, source: sourceVal }) => {
2073
+ const severity = `${severityMarker(severityVal)} <i>${severityVal}</i>`;
2074
+ if (!sourceVal) {
2075
+ return { severity, message, file: "", line: "" };
2076
+ }
2077
+ const file = `<code>${sourceVal.file}</code>`;
2078
+ if (!sourceVal.position) {
2079
+ return { severity, message, file, line: "" };
2080
+ }
2081
+ const { startLine, endLine } = sourceVal.position;
2082
+ const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
2083
+ return { severity, message, file, line };
2084
+ }
2085
+ )
2086
+ };
2087
+ return tableSection(detailsTableData);
2088
+ }
2089
+ function auditDetails(audit) {
2090
+ const { table: table5, issues = [] } = audit.details ?? {};
2091
+ const detailsValue = auditDetailsAuditValue(audit);
2092
+ if (issues.length === 0 && table5 == null) {
2093
+ return section4(detailsValue);
2094
+ }
2095
+ const tableSectionContent = table5 == null ? "" : tableSection(table5);
2096
+ const issuesSectionContent = issues.length > 0 ? auditDetailsIssues(issues) : "";
2097
+ return details2(
2098
+ detailsValue,
2099
+ lines4(tableSectionContent, issuesSectionContent)
2100
+ );
2101
+ }
2102
+ function auditsSection({
2103
+ plugins
2104
+ }) {
2105
+ const content = plugins.flatMap(
2106
+ ({ slug, audits }) => audits.flatMap((audit) => {
2107
+ const auditTitle = `${audit.title}${SPACE}(${getPluginNameFromSlug(
2108
+ slug,
2109
+ plugins
2110
+ )})`;
2111
+ const detailsContent = auditDetails(audit);
2112
+ const descriptionContent = metaDescription(audit);
2113
+ return [h33(auditTitle), detailsContent, descriptionContent];
2114
+ })
2115
+ );
2116
+ return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
2117
+ }
2118
+ function aboutSection(report) {
2119
+ const { date, plugins } = report;
2120
+ const reportMetaTable = reportMetaData(report);
2121
+ const pluginMetaTable = reportPluginMeta({ plugins });
2122
+ return lines4(
2123
+ h23("About"),
2124
+ section4(
2125
+ `Report was created by [Code PushUp](${README_LINK}) on ${formatDate(
2126
+ new Date(date)
2127
+ )}.`
2128
+ ),
2129
+ tableSection(pluginMetaTable),
2130
+ tableSection(reportMetaTable)
2131
+ );
2132
+ }
2133
+ function reportPluginMeta({ plugins }) {
2134
+ return {
2135
+ columns: [
2136
+ {
2137
+ key: "plugin",
2138
+ align: "left"
2139
+ },
2140
+ {
2141
+ key: "audits"
2142
+ },
2143
+ {
2144
+ key: "version"
2145
+ },
2146
+ {
2147
+ key: "duration"
2148
+ }
2149
+ ],
2150
+ rows: plugins.map(
2151
+ ({
2152
+ title: pluginTitle,
2153
+ audits,
2154
+ version: pluginVersion,
2155
+ duration: pluginDuration
2156
+ }) => ({
2157
+ plugin: pluginTitle,
2158
+ audits: audits.length.toString(),
2159
+ version: codeMd(pluginVersion || ""),
2160
+ duration: formatDuration(pluginDuration)
2161
+ })
2162
+ )
2163
+ };
1748
2164
  }
1749
- function getAuditResult(audit, isHtml = false) {
1750
- const { displayValue, value } = audit;
1751
- return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
2165
+ function reportMetaData({
2166
+ commit,
2167
+ version,
2168
+ duration,
2169
+ plugins,
2170
+ categories
2171
+ }) {
2172
+ const commitInfo = commit ? `${commit.message}${SPACE}(${commit.hash})` : "N/A";
2173
+ return {
2174
+ columns: [
2175
+ {
2176
+ key: "commit",
2177
+ align: "left"
2178
+ },
2179
+ {
2180
+ key: "version"
2181
+ },
2182
+ {
2183
+ key: "duration"
2184
+ },
2185
+ {
2186
+ key: "plugins"
2187
+ },
2188
+ {
2189
+ key: "categories"
2190
+ },
2191
+ {
2192
+ key: "audits"
2193
+ }
2194
+ ],
2195
+ rows: [
2196
+ {
2197
+ commit: commitInfo,
2198
+ version: codeMd(version || ""),
2199
+ duration: formatDuration(duration),
2200
+ plugins: plugins.length,
2201
+ categories: categories.length,
2202
+ audits: plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
2203
+ }
2204
+ ]
2205
+ };
1752
2206
  }
1753
2207
 
1754
2208
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
2209
+ var {
2210
+ h1: h13,
2211
+ h2: h24,
2212
+ lines: lines5,
2213
+ link: link7,
2214
+ bold: boldMd3,
2215
+ italic: italicMd,
2216
+ table: table4,
2217
+ section: section5
2218
+ } = md;
2219
+ var { details: details3 } = html;
1755
2220
  var MAX_ROWS = 100;
1756
2221
  function generateMdReportsDiff(diff) {
1757
- return paragraphs(
1758
- formatDiffHeaderSection(diff),
2222
+ return lines5(
2223
+ section5(formatDiffHeaderSection(diff)),
1759
2224
  formatDiffCategoriesSection(diff),
1760
2225
  formatDiffGroupsSection(diff),
1761
2226
  formatDiffAuditsSection(diff)
@@ -1763,12 +2228,12 @@ function generateMdReportsDiff(diff) {
1763
2228
  }
1764
2229
  function formatDiffHeaderSection(diff) {
1765
2230
  const outcomeTexts = {
1766
- positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1767
- negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1768
- mixed: `\u{1F928} Code PushUp report has both ${style(
2231
+ positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
2232
+ negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
2233
+ mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
1769
2234
  "improvements and regressions"
1770
2235
  )}`,
1771
- unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
2236
+ unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
1772
2237
  };
1773
2238
  const outcome = mergeDiffOutcomes(
1774
2239
  changesToDiffOutcomes([
@@ -1778,8 +2243,8 @@ function formatDiffHeaderSection(diff) {
1778
2243
  ])
1779
2244
  );
1780
2245
  const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1781
- return paragraphs(
1782
- h1("Code PushUp"),
2246
+ return lines5(
2247
+ h13("Code PushUp"),
1783
2248
  diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1784
2249
  );
1785
2250
  }
@@ -1790,102 +2255,104 @@ function formatDiffCategoriesSection(diff) {
1790
2255
  if (categoriesCount === 0) {
1791
2256
  return "";
1792
2257
  }
1793
- return paragraphs(
1794
- h2("\u{1F3F7}\uFE0F Categories"),
1795
- categoriesCount > 0 && tableMd(
1796
- [
1797
- [
1798
- "\u{1F3F7}\uFE0F Category",
1799
- hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1800
- "\u2B50 Previous score",
1801
- "\u{1F504} Score change"
1802
- ],
1803
- ...sortChanges(changed).map((category) => [
1804
- category.title,
1805
- formatScoreWithColor(category.scores.after),
1806
- formatScoreWithColor(category.scores.before, { skipBold: true }),
1807
- formatScoreChange(category.scores.diff)
1808
- ]),
1809
- ...added.map((category) => [
1810
- category.title,
1811
- formatScoreWithColor(category.score),
1812
- style("n/a (\\*)", ["i"]),
1813
- style("n/a (\\*)", ["i"])
1814
- ]),
1815
- ...unchanged.map((category) => [
1816
- category.title,
1817
- formatScoreWithColor(category.score),
1818
- formatScoreWithColor(category.score, { skipBold: true }),
1819
- "\u2013"
1820
- ])
1821
- ].map((row) => hasChanges ? row : row.slice(0, 2)),
1822
- hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1823
- ),
1824
- added.length > 0 && style("(\\*) New category.", ["i"])
2258
+ const columns = [
2259
+ { key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
2260
+ { key: "after", label: hasChanges ? "\u2B50 Current score" : "\u2B50 Score" },
2261
+ { key: "before", label: "\u2B50 Previous score" },
2262
+ { key: "change", label: "\u{1F504} Score change" }
2263
+ ];
2264
+ return lines5(
2265
+ h24("\u{1F3F7}\uFE0F Categories"),
2266
+ categoriesCount > 0 && table4({
2267
+ columns: hasChanges ? columns : columns.slice(0, 2),
2268
+ rows: [
2269
+ ...sortChanges(changed).map((category) => ({
2270
+ category: formatTitle(category),
2271
+ after: formatScoreWithColor(category.scores.after),
2272
+ before: formatScoreWithColor(category.scores.before, {
2273
+ skipBold: true
2274
+ }),
2275
+ change: formatScoreChange(category.scores.diff)
2276
+ })),
2277
+ ...added.map((category) => ({
2278
+ category: formatTitle(category),
2279
+ after: formatScoreWithColor(category.score),
2280
+ before: italicMd("n/a (\\*)"),
2281
+ change: italicMd("n/a (\\*)")
2282
+ })),
2283
+ ...unchanged.map((category) => ({
2284
+ category: formatTitle(category),
2285
+ after: formatScoreWithColor(category.score),
2286
+ before: formatScoreWithColor(category.score, { skipBold: true }),
2287
+ change: "\u2013"
2288
+ }))
2289
+ ].map(
2290
+ (row) => hasChanges ? row : { category: row.category, after: row.after }
2291
+ )
2292
+ }),
2293
+ added.length > 0 && section5(italicMd("(\\*) New category."))
1825
2294
  );
1826
2295
  }
1827
2296
  function formatDiffGroupsSection(diff) {
1828
2297
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1829
2298
  return "";
1830
2299
  }
1831
- return paragraphs(
1832
- h2("\u{1F5C3}\uFE0F Groups"),
2300
+ return lines5(
2301
+ h24("\u{1F5C3}\uFE0F Groups"),
1833
2302
  formatGroupsOrAuditsDetails("group", diff.groups, {
1834
- headings: [
1835
- "\u{1F50C} Plugin",
1836
- "\u{1F5C3}\uFE0F Group",
1837
- "\u2B50 Current score",
1838
- "\u2B50 Previous score",
1839
- "\u{1F504} Score change"
2303
+ columns: [
2304
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2305
+ { key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
2306
+ { key: "after", label: "\u2B50 Current score" },
2307
+ { key: "before", label: "\u2B50 Previous score" },
2308
+ { key: "change", label: "\u{1F504} Score change" }
1840
2309
  ],
1841
- rows: sortChanges(diff.groups.changed).map((group) => [
1842
- group.plugin.title,
1843
- group.title,
1844
- formatScoreWithColor(group.scores.after),
1845
- formatScoreWithColor(group.scores.before, { skipBold: true }),
1846
- formatScoreChange(group.scores.diff)
1847
- ]),
1848
- align: ["l", "l", "c", "c", "c"]
2310
+ rows: sortChanges(diff.groups.changed).map((group) => ({
2311
+ plugin: formatTitle(group.plugin),
2312
+ group: formatTitle(group),
2313
+ after: formatScoreWithColor(group.scores.after),
2314
+ before: formatScoreWithColor(group.scores.before, { skipBold: true }),
2315
+ change: formatScoreChange(group.scores.diff)
2316
+ }))
1849
2317
  })
1850
2318
  );
1851
2319
  }
1852
2320
  function formatDiffAuditsSection(diff) {
1853
- return paragraphs(
1854
- h2("\u{1F6E1}\uFE0F Audits"),
2321
+ return lines5(
2322
+ h24("\u{1F6E1}\uFE0F Audits"),
1855
2323
  formatGroupsOrAuditsDetails("audit", diff.audits, {
1856
- headings: [
1857
- "\u{1F50C} Plugin",
1858
- "\u{1F6E1}\uFE0F Audit",
1859
- "\u{1F4CF} Current value",
1860
- "\u{1F4CF} Previous value",
1861
- "\u{1F504} Value change"
2324
+ columns: [
2325
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2326
+ { key: "audit", label: "\u{1F6E1}\uFE0F Audit", align: "left" },
2327
+ { key: "after", label: "\u{1F4CF} Current value" },
2328
+ { key: "before", label: "\u{1F4CF} Previous value" },
2329
+ { key: "change", label: "\u{1F504} Value change" }
1862
2330
  ],
1863
- rows: sortChanges(diff.audits.changed).map((audit) => [
1864
- audit.plugin.title,
1865
- audit.title,
1866
- `${getSquaredScoreMarker(audit.scores.after)} ${style(
2331
+ rows: sortChanges(diff.audits.changed).map((audit) => ({
2332
+ plugin: formatTitle(audit.plugin),
2333
+ audit: formatTitle(audit),
2334
+ after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
1867
2335
  audit.displayValues.after || audit.values.after.toString()
1868
2336
  )}`,
1869
- `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1870
- formatValueChange(audit)
1871
- ]),
1872
- align: ["l", "l", "c", "c", "c"]
2337
+ before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2338
+ change: formatValueChange(audit)
2339
+ }))
1873
2340
  })
1874
2341
  );
1875
2342
  }
1876
- function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1877
- return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
2343
+ function formatGroupsOrAuditsDetails(token, { changed, unchanged }, tableData) {
2344
+ return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details3(
1878
2345
  summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1879
- paragraphs(
1880
- tableMd(
1881
- [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1882
- table.align
1883
- ),
1884
- changed.length > MAX_ROWS && style(
2346
+ lines5(
2347
+ table4({
2348
+ ...tableData,
2349
+ rows: tableData.rows.slice(0, MAX_ROWS)
2350
+ // use never to avoid typing problem
2351
+ }),
2352
+ changed.length > MAX_ROWS && italicMd(
1885
2353
  `Only the ${MAX_ROWS} most affected ${pluralize(
1886
2354
  token
1887
- )} are listed above for brevity.`,
1888
- ["i"]
2355
+ )} are listed above for brevity.`
1889
2356
  ),
1890
2357
  unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1891
2358
  )
@@ -1906,11 +2373,13 @@ function formatValueChange({
1906
2373
  return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1907
2374
  }
1908
2375
  function summarizeUnchanged(token, { changed, unchanged }) {
1909
- return [
1910
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1911
- unchanged.length === 1 ? "is" : "are",
1912
- "unchanged."
1913
- ].join(" ");
2376
+ return section5(
2377
+ [
2378
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2379
+ unchanged.length === 1 ? "is" : "are",
2380
+ "unchanged."
2381
+ ].join(" ")
2382
+ );
1914
2383
  }
1915
2384
  function summarizeDiffOutcomes(outcomes, token) {
1916
2385
  return objectToEntries(countDiffOutcomes(outcomes)).filter(
@@ -1930,6 +2399,15 @@ function summarizeDiffOutcomes(outcomes, token) {
1930
2399
  }
1931
2400
  }).join(", ");
1932
2401
  }
2402
+ function formatTitle({
2403
+ title,
2404
+ docsUrl
2405
+ }) {
2406
+ if (docsUrl) {
2407
+ return link7(docsUrl, title);
2408
+ }
2409
+ return title;
2410
+ }
1933
2411
  function sortChanges(changes) {
1934
2412
  return [...changes].sort(
1935
2413
  (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
@@ -1977,7 +2455,7 @@ function log(msg = "") {
1977
2455
  }
1978
2456
  function logStdoutSummary(report) {
1979
2457
  const printCategories = report.categories.length > 0;
1980
- log(reportToHeaderSection2(report));
2458
+ log(reportToHeaderSection(report));
1981
2459
  log();
1982
2460
  logPlugins(report);
1983
2461
  if (printCategories) {
@@ -1986,7 +2464,7 @@ function logStdoutSummary(report) {
1986
2464
  log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1987
2465
  log();
1988
2466
  }
1989
- function reportToHeaderSection2(report) {
2467
+ function reportToHeaderSection(report) {
1990
2468
  const { packageName, version } = report;
1991
2469
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version}`;
1992
2470
  }
@@ -2026,16 +2504,16 @@ function logCategories({ categories, plugins }) {
2026
2504
  applyScoreColor({ score }),
2027
2505
  countCategoryAudits(refs, plugins)
2028
2506
  ]);
2029
- const table = ui().table();
2030
- table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2031
- table.head(
2507
+ const table5 = ui().table();
2508
+ table5.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2509
+ table5.head(
2032
2510
  reportRawOverviewTableHeaders.map((heading, idx) => ({
2033
2511
  content: chalk4.cyan(heading),
2034
2512
  hAlign: hAlign(idx)
2035
2513
  }))
2036
2514
  );
2037
2515
  rows.forEach(
2038
- (row) => table.row(
2516
+ (row) => table5.row(
2039
2517
  row.map((content, idx) => ({
2040
2518
  content: content.toString(),
2041
2519
  hAlign: hAlign(idx)
@@ -2044,19 +2522,19 @@ function logCategories({ categories, plugins }) {
2044
2522
  );
2045
2523
  log(chalk4.magentaBright.bold("Categories"));
2046
2524
  log();
2047
- table.render();
2525
+ table5.render();
2048
2526
  log();
2049
2527
  }
2050
2528
  function applyScoreColor({ score, text }) {
2051
2529
  const formattedScore = text ?? formatReportScore(score);
2052
- const style2 = text ? chalk4 : chalk4.bold;
2530
+ const style = text ? chalk4 : chalk4.bold;
2053
2531
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
2054
- return style2.green(formattedScore);
2532
+ return style.green(formattedScore);
2055
2533
  }
2056
2534
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
2057
- return style2.yellow(formattedScore);
2535
+ return style.yellow(formattedScore);
2058
2536
  }
2059
- return style2.red(formattedScore);
2537
+ return style.red(formattedScore);
2060
2538
  }
2061
2539
 
2062
2540
  // packages/utils/src/lib/reports/scoring.ts
@@ -2218,8 +2696,11 @@ var verboseUtils = (verbose = false) => ({
2218
2696
  export {
2219
2697
  CODE_PUSHUP_DOMAIN,
2220
2698
  FOOTER_PREFIX,
2699
+ NEW_LINE,
2221
2700
  ProcessError,
2222
2701
  README_LINK,
2702
+ SPACE,
2703
+ TAB,
2223
2704
  TERMINAL_WIDTH,
2224
2705
  apostrophize,
2225
2706
  calcDuration,
@@ -2245,13 +2726,19 @@ export {
2245
2726
  generateMdReportsDiff,
2246
2727
  getCurrentBranchOrTag,
2247
2728
  getGitRoot,
2729
+ getHashFromTag,
2730
+ getHashes,
2248
2731
  getLatestCommit,
2249
2732
  getProgressBar,
2733
+ getSemverTags,
2250
2734
  groupByStatus,
2735
+ guardAgainstLocalChanges,
2736
+ html,
2251
2737
  importEsmModule,
2252
2738
  isPromiseFulfilledResult,
2253
2739
  isPromiseRejectedResult,
2254
- link,
2740
+ isSemver,
2741
+ link3 as link,
2255
2742
  listAuditsFromAllPlugins,
2256
2743
  listGroupsFromAllPlugins,
2257
2744
  loadReport,
@@ -2259,6 +2746,8 @@ export {
2259
2746
  logMultipleResults,
2260
2747
  logStdoutSummary,
2261
2748
  matchArrayItemsByKey,
2749
+ md,
2750
+ normalizeSemver,
2262
2751
  objectFromEntries,
2263
2752
  objectToCliArgs,
2264
2753
  objectToEntries,
@@ -2273,6 +2762,7 @@ export {
2273
2762
  scoreReport,
2274
2763
  slugify,
2275
2764
  sortReport,
2765
+ sortSemvers,
2276
2766
  toArray,
2277
2767
  toGitPath,
2278
2768
  toJsonLines,