@code-pushup/cli 0.39.0 → 0.42.1

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 CHANGED
@@ -6,9 +6,6 @@ import { hideBin } from "yargs/helpers";
6
6
  // packages/cli/src/lib/autorun/autorun-command.ts
7
7
  import chalk7 from "chalk";
8
8
 
9
- // packages/models/src/lib/audit.ts
10
- import { z as z2 } from "zod";
11
-
12
9
  // packages/models/src/lib/implementation/schemas.ts
13
10
  import { MATERIAL_ICONS } from "vscode-material-icons";
14
11
  import { z } from "zod";
@@ -74,6 +71,7 @@ function missingRefsForCategoriesErrorMsg(categories, plugins) {
74
71
  }
75
72
 
76
73
  // packages/models/src/lib/implementation/schemas.ts
74
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
77
75
  function executionMetaSchema(options2 = {
78
76
  descriptionDate: "Execution start date and time",
79
77
  descriptionDuration: "Execution duration in ms"
@@ -165,6 +163,7 @@ function hasNonZeroWeightedRef(refs) {
165
163
  }
166
164
 
167
165
  // packages/models/src/lib/audit.ts
166
+ import { z as z2 } from "zod";
168
167
  var auditSchema = z2.object({
169
168
  slug: slugSchema.describe("ID (unique within plugin)")
170
169
  }).merge(
@@ -194,7 +193,7 @@ function getDuplicateSlugsInAudits(audits) {
194
193
  }
195
194
 
196
195
  // packages/models/src/lib/audit-output.ts
197
- import { z as z4 } from "zod";
196
+ import { z as z5 } from "zod";
198
197
 
199
198
  // packages/models/src/lib/issue.ts
200
199
  import { z as z3 } from "zod";
@@ -225,16 +224,61 @@ var issueSchema = z3.object(
225
224
  { description: "Issue information" }
226
225
  );
227
226
 
227
+ // packages/models/src/lib/table.ts
228
+ import { z as z4 } from "zod";
229
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
230
+ description: "Cell alignment"
231
+ });
232
+ var tableColumnObjectSchema = z4.object({
233
+ key: z4.string(),
234
+ label: z4.string().optional(),
235
+ align: tableAlignmentSchema.optional()
236
+ });
237
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
238
+ description: "Object row"
239
+ });
240
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
241
+ description: "Primitive row"
242
+ });
243
+ var tableSharedSchema = z4.object({
244
+ title: z4.string().optional().describe("Display title for table")
245
+ });
246
+ var tablePrimitiveSchema = tableSharedSchema.merge(
247
+ z4.object(
248
+ {
249
+ columns: z4.array(tableAlignmentSchema).optional(),
250
+ rows: z4.array(tableRowPrimitiveSchema)
251
+ },
252
+ { description: "Table with primitive rows and optional alignment columns" }
253
+ )
254
+ );
255
+ var tableObjectSchema = tableSharedSchema.merge(
256
+ z4.object(
257
+ {
258
+ columns: z4.union([
259
+ z4.array(tableAlignmentSchema),
260
+ z4.array(tableColumnObjectSchema)
261
+ ]).optional(),
262
+ rows: z4.array(tableRowObjectSchema)
263
+ },
264
+ {
265
+ description: "Table with object rows and optional alignment or object columns"
266
+ }
267
+ )
268
+ );
269
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
270
+
228
271
  // packages/models/src/lib/audit-output.ts
229
272
  var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
230
- var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
231
- var auditDetailsSchema = z4.object(
273
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
274
+ var auditDetailsSchema = z5.object(
232
275
  {
233
- issues: z4.array(issueSchema, { description: "List of findings" })
276
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
277
+ table: tableSchema("Table of related findings").optional()
234
278
  },
235
279
  { description: "Detailed information" }
236
280
  );
237
- var auditOutputSchema = z4.object(
281
+ var auditOutputSchema = z5.object(
238
282
  {
239
283
  slug: slugSchema.describe("Reference to audit"),
240
284
  displayValue: auditDisplayValueSchema,
@@ -244,7 +288,7 @@ var auditOutputSchema = z4.object(
244
288
  },
245
289
  { description: "Audit information" }
246
290
  );
247
- var auditOutputsSchema = z4.array(auditOutputSchema, {
291
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
248
292
  description: "List of JSON formatted audit output emitted by the runner process of a plugin"
249
293
  }).refine(
250
294
  (audits) => !getDuplicateSlugsInAudits2(audits),
@@ -261,13 +305,13 @@ function getDuplicateSlugsInAudits2(audits) {
261
305
  }
262
306
 
263
307
  // packages/models/src/lib/category-config.ts
264
- import { z as z5 } from "zod";
308
+ import { z as z6 } from "zod";
265
309
  var categoryRefSchema = weightedRefSchema(
266
310
  "Weighted references to audits and/or groups for the category",
267
311
  "Slug of an audit or group (depending on `type`)"
268
312
  ).merge(
269
- z5.object({
270
- type: z5.enum(["audit", "group"], {
313
+ z6.object({
314
+ type: z6.enum(["audit", "group"], {
271
315
  description: "Discriminant for reference kind, affects where `slug` is looked up"
272
316
  }),
273
317
  plugin: slugSchema.describe(
@@ -288,8 +332,8 @@ var categoryConfigSchema = scorableSchema(
288
332
  description: "Meta info for category"
289
333
  })
290
334
  ).merge(
291
- z5.object({
292
- isBinary: z5.boolean({
335
+ z6.object({
336
+ isBinary: z6.boolean({
293
337
  description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
294
338
  }).optional()
295
339
  })
@@ -305,7 +349,7 @@ function getDuplicateRefsInCategoryMetrics(metrics) {
305
349
  metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
306
350
  );
307
351
  }
308
- var categoriesSchema = z5.array(categoryConfigSchema, {
352
+ var categoriesSchema = z6.array(categoryConfigSchema, {
309
353
  description: "Categorization of individual audits"
310
354
  }).refine(
311
355
  (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
@@ -324,18 +368,18 @@ function getDuplicateSlugCategories(categories) {
324
368
  }
325
369
 
326
370
  // packages/models/src/lib/commit.ts
327
- import { z as z6 } from "zod";
328
- var commitSchema = z6.object(
371
+ import { z as z7 } from "zod";
372
+ var commitSchema = z7.object(
329
373
  {
330
- hash: z6.string({ description: "Commit SHA (full)" }).regex(
374
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
331
375
  /^[\da-f]{40}$/,
332
376
  "Commit SHA should be a 40-character hexadecimal string"
333
377
  ),
334
- message: z6.string({ description: "Commit message" }),
335
- date: z6.coerce.date({
378
+ message: z7.string({ description: "Commit message" }),
379
+ date: z7.coerce.date({
336
380
  description: "Date and time when commit was authored"
337
381
  }),
338
- author: z6.string({
382
+ author: z7.string({
339
383
  description: "Commit author name"
340
384
  }).trim()
341
385
  },
@@ -343,22 +387,22 @@ var commitSchema = z6.object(
343
387
  );
344
388
 
345
389
  // packages/models/src/lib/core-config.ts
346
- import { z as z12 } from "zod";
390
+ import { z as z13 } from "zod";
347
391
 
348
392
  // packages/models/src/lib/persist-config.ts
349
- import { z as z7 } from "zod";
350
- var formatSchema = z7.enum(["json", "md"]);
351
- var persistConfigSchema = z7.object({
393
+ import { z as z8 } from "zod";
394
+ var formatSchema = z8.enum(["json", "md"]);
395
+ var persistConfigSchema = z8.object({
352
396
  outputDir: filePathSchema.describe("Artifacts folder").optional(),
353
397
  filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
354
- format: z7.array(formatSchema).optional()
398
+ format: z8.array(formatSchema).optional()
355
399
  });
356
400
 
357
401
  // packages/models/src/lib/plugin-config.ts
358
- import { z as z10 } from "zod";
402
+ import { z as z11 } from "zod";
359
403
 
360
404
  // packages/models/src/lib/group.ts
361
- import { z as z8 } from "zod";
405
+ import { z as z9 } from "zod";
362
406
  var groupRefSchema = weightedRefSchema(
363
407
  "Weighted reference to a group",
364
408
  "Reference slug to a group within this plugin (e.g. 'max-lines')"
@@ -375,7 +419,7 @@ var groupSchema = scorableSchema(
375
419
  getDuplicateRefsInGroups,
376
420
  duplicateRefsInGroupsErrorMsg
377
421
  ).merge(groupMetaSchema);
378
- var groupsSchema = z8.array(groupSchema, {
422
+ var groupsSchema = z9.array(groupSchema, {
379
423
  description: "List of groups"
380
424
  }).optional().refine(
381
425
  (groups2) => !getDuplicateSlugsInGroups(groups2),
@@ -403,14 +447,14 @@ function getDuplicateSlugsInGroups(groups2) {
403
447
  }
404
448
 
405
449
  // packages/models/src/lib/runner-config.ts
406
- import { z as z9 } from "zod";
407
- var outputTransformSchema = z9.function().args(z9.unknown()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
408
- var runnerConfigSchema = z9.object(
450
+ import { z as z10 } from "zod";
451
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
452
+ var runnerConfigSchema = z10.object(
409
453
  {
410
- command: z9.string({
454
+ command: z10.string({
411
455
  description: "Shell command to execute"
412
456
  }),
413
- args: z9.array(z9.string({ description: "Command arguments" })).optional(),
457
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
414
458
  outputFile: filePathSchema.describe("Output path"),
415
459
  outputTransform: outputTransformSchema.optional()
416
460
  },
@@ -418,8 +462,8 @@ var runnerConfigSchema = z9.object(
418
462
  description: "How to execute runner"
419
463
  }
420
464
  );
421
- var onProgressSchema = z9.function().args(z9.unknown()).returns(z9.void());
422
- var runnerFunctionSchema = z9.function().args(onProgressSchema.optional()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
465
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
466
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
423
467
 
424
468
  // packages/models/src/lib/plugin-config.ts
425
469
  var pluginMetaSchema = packageVersionSchema().merge(
@@ -430,13 +474,13 @@ var pluginMetaSchema = packageVersionSchema().merge(
430
474
  description: "Plugin metadata"
431
475
  })
432
476
  ).merge(
433
- z10.object({
477
+ z11.object({
434
478
  slug: slugSchema.describe("Unique plugin slug within core config"),
435
479
  icon: materialIconSchema
436
480
  })
437
481
  );
438
- var pluginDataSchema = z10.object({
439
- runner: z10.union([runnerConfigSchema, runnerFunctionSchema]),
482
+ var pluginDataSchema = z11.object({
483
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
440
484
  audits: pluginAuditsSchema,
441
485
  groups: groupsSchema
442
486
  });
@@ -462,22 +506,22 @@ function getMissingRefsFromGroups(pluginCfg) {
462
506
  }
463
507
 
464
508
  // packages/models/src/lib/upload-config.ts
465
- import { z as z11 } from "zod";
466
- var uploadConfigSchema = z11.object({
509
+ import { z as z12 } from "zod";
510
+ var uploadConfigSchema = z12.object({
467
511
  server: urlSchema.describe("URL of deployed portal API"),
468
- apiKey: z11.string({
512
+ apiKey: z12.string({
469
513
  description: "API key with write access to portal (use `process.env` for security)"
470
514
  }),
471
515
  organization: slugSchema.describe(
472
516
  "Organization slug from Code PushUp portal"
473
517
  ),
474
518
  project: slugSchema.describe("Project slug from Code PushUp portal"),
475
- timeout: z11.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
519
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
476
520
  });
477
521
 
478
522
  // packages/models/src/lib/core-config.ts
479
- var unrefinedCoreConfigSchema = z12.object({
480
- plugins: z12.array(pluginConfigSchema, {
523
+ var unrefinedCoreConfigSchema = z13.object({
524
+ plugins: z13.array(pluginConfigSchema, {
481
525
  description: "List of plugins to be used (official, community-provided, or custom)"
482
526
  }).min(1),
483
527
  /** portal configuration for persisting results */
@@ -509,7 +553,7 @@ var DEFAULT_PERSIST_FILENAME = "report";
509
553
  var DEFAULT_PERSIST_FORMAT = ["json", "md"];
510
554
 
511
555
  // packages/models/src/lib/report.ts
512
- import { z as z13 } from "zod";
556
+ import { z as z14 } from "zod";
513
557
  var auditReportSchema = auditSchema.merge(auditOutputSchema);
514
558
  var pluginReportSchema = pluginMetaSchema.merge(
515
559
  executionMetaSchema({
@@ -517,9 +561,9 @@ var pluginReportSchema = pluginMetaSchema.merge(
517
561
  descriptionDuration: "Duration of the plugin run in ms"
518
562
  })
519
563
  ).merge(
520
- z13.object({
521
- audits: z13.array(auditReportSchema).min(1),
522
- groups: z13.array(groupSchema).optional()
564
+ z14.object({
565
+ audits: z14.array(auditReportSchema).min(1),
566
+ groups: z14.array(groupSchema).optional()
523
567
  })
524
568
  ).refine(
525
569
  (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
@@ -553,10 +597,10 @@ var reportSchema = packageVersionSchema({
553
597
  descriptionDuration: "Duration of the collect run in ms"
554
598
  })
555
599
  ).merge(
556
- z13.object(
600
+ z14.object(
557
601
  {
558
- categories: z13.array(categoryConfigSchema),
559
- plugins: z13.array(pluginReportSchema).min(1),
602
+ categories: z14.array(categoryConfigSchema),
603
+ plugins: z14.array(pluginReportSchema).min(1),
560
604
  commit: commitSchema.describe("Git commit for which report was collected").nullable()
561
605
  },
562
606
  { description: "Collect output data" }
@@ -572,40 +616,40 @@ var reportSchema = packageVersionSchema({
572
616
  );
573
617
 
574
618
  // packages/models/src/lib/reports-diff.ts
575
- import { z as z14 } from "zod";
619
+ import { z as z15 } from "zod";
576
620
  function makeComparisonSchema(schema) {
577
621
  const sharedDescription = schema.description || "Result";
578
- return z14.object({
622
+ return z15.object({
579
623
  before: schema.describe(`${sharedDescription} (source commit)`),
580
624
  after: schema.describe(`${sharedDescription} (target commit)`)
581
625
  });
582
626
  }
583
627
  function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
584
- return z14.object(
628
+ return z15.object(
585
629
  {
586
- changed: z14.array(diffSchema),
587
- unchanged: z14.array(resultSchema),
588
- added: z14.array(resultSchema),
589
- removed: z14.array(resultSchema)
630
+ changed: z15.array(diffSchema),
631
+ unchanged: z15.array(resultSchema),
632
+ added: z15.array(resultSchema),
633
+ removed: z15.array(resultSchema)
590
634
  },
591
635
  { description }
592
636
  );
593
637
  }
594
- var scorableMetaSchema = z14.object({
638
+ var scorableMetaSchema = z15.object({
595
639
  slug: slugSchema,
596
640
  title: titleSchema,
597
641
  docsUrl: docsUrlSchema
598
642
  });
599
643
  var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
600
- z14.object({
644
+ z15.object({
601
645
  plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
602
646
  })
603
647
  );
604
648
  var scorableDiffSchema = scorableMetaSchema.merge(
605
- z14.object({
649
+ z15.object({
606
650
  scores: makeComparisonSchema(scoreSchema).merge(
607
- z14.object({
608
- diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
651
+ z15.object({
652
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
609
653
  })
610
654
  ).describe("Score comparison")
611
655
  })
@@ -616,10 +660,10 @@ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
616
660
  var categoryDiffSchema = scorableDiffSchema;
617
661
  var groupDiffSchema = scorableWithPluginDiffSchema;
618
662
  var auditDiffSchema = scorableWithPluginDiffSchema.merge(
619
- z14.object({
663
+ z15.object({
620
664
  values: makeComparisonSchema(auditValueSchema).merge(
621
- z14.object({
622
- diff: z14.number().int().describe("Value change (`values.after - values.before`)")
665
+ z15.object({
666
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
623
667
  })
624
668
  ).describe("Audit `value` comparison"),
625
669
  displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
@@ -628,15 +672,15 @@ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
628
672
  })
629
673
  );
630
674
  var categoryResultSchema = scorableMetaSchema.merge(
631
- z14.object({ score: scoreSchema })
675
+ z15.object({ score: scoreSchema })
632
676
  );
633
677
  var groupResultSchema = scorableWithPluginMetaSchema.merge(
634
- z14.object({ score: scoreSchema })
678
+ z15.object({ score: scoreSchema })
635
679
  );
636
680
  var auditResultSchema = scorableWithPluginMetaSchema.merge(
637
681
  auditOutputSchema.pick({ score: true, value: true, displayValue: true })
638
682
  );
639
- var reportsDiffSchema = z14.object({
683
+ var reportsDiffSchema = z15.object({
640
684
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
641
685
  categories: makeArraysComparisonSchema(
642
686
  categoryDiffSchema,
@@ -665,6 +709,291 @@ var reportsDiffSchema = z14.object({
665
709
  })
666
710
  );
667
711
 
712
+ // packages/utils/src/lib/text-formats/constants.ts
713
+ var NEW_LINE = "\n";
714
+ var TAB = " ";
715
+ var SPACE = " ";
716
+
717
+ // packages/utils/src/lib/text-formats/html/details.ts
718
+ function details(title, content, cfg = { open: false }) {
719
+ 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.
720
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
721
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
722
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
723
+ NEW_LINE}`;
724
+ }
725
+
726
+ // packages/utils/src/lib/text-formats/html/font-style.ts
727
+ var boldElement = "b";
728
+ function bold(text) {
729
+ return `<${boldElement}>${text}</${boldElement}>`;
730
+ }
731
+ var italicElement = "i";
732
+ function italic(text) {
733
+ return `<${italicElement}>${text}</${italicElement}>`;
734
+ }
735
+ var codeElement = "code";
736
+ function code(text) {
737
+ return `<${codeElement}>${text}</${codeElement}>`;
738
+ }
739
+
740
+ // packages/utils/src/lib/text-formats/html/link.ts
741
+ function link(href, text) {
742
+ return `<a href="${href}">${text || href}"</a>`;
743
+ }
744
+
745
+ // packages/utils/src/lib/transform.ts
746
+ function toArray(val) {
747
+ return Array.isArray(val) ? val : [val];
748
+ }
749
+ function objectToEntries(obj) {
750
+ return Object.entries(obj);
751
+ }
752
+ function deepClone(obj) {
753
+ return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
754
+ }
755
+ function toUnixPath(path) {
756
+ return path.replace(/\\/g, "/");
757
+ }
758
+ function capitalize(text) {
759
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
760
+ 1
761
+ )}`;
762
+ }
763
+
764
+ // packages/utils/src/lib/table.ts
765
+ function rowToStringArray({ rows, columns = [] }) {
766
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
767
+ throw new TypeError(
768
+ "Column can`t be object when rows are primitive values"
769
+ );
770
+ }
771
+ return rows.map((row) => {
772
+ if (Array.isArray(row)) {
773
+ return row.map(String);
774
+ }
775
+ const objectRow = row;
776
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
777
+ return Object.values(objectRow).map(String);
778
+ }
779
+ return columns.map(
780
+ ({ key }) => String(objectRow[key])
781
+ );
782
+ });
783
+ }
784
+ function columnsToStringArray({ rows, columns = [] }) {
785
+ const firstRow = rows.at(0);
786
+ const primitiveRows = Array.isArray(firstRow);
787
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
788
+ throw new Error("invalid union type. Caught by model parsing.");
789
+ }
790
+ if (columns.length === 0) {
791
+ if (Array.isArray(firstRow)) {
792
+ return firstRow.map((_, idx) => String(idx));
793
+ }
794
+ return Object.keys(firstRow);
795
+ }
796
+ if (typeof columns.at(0) === "string") {
797
+ return columns.map(String);
798
+ }
799
+ const cols = columns;
800
+ return cols.map(({ label, key }) => label ?? capitalize(key));
801
+ }
802
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
803
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
804
+ if (typeof column === "string") {
805
+ return column;
806
+ } else if (typeof column === "object") {
807
+ return column.align ?? "center";
808
+ } else {
809
+ return "center";
810
+ }
811
+ }
812
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
813
+ const column = columns.at(targetIdx);
814
+ if (column == null) {
815
+ return "center";
816
+ } else if (typeof column === "string") {
817
+ return column;
818
+ } else if (typeof column === "object") {
819
+ return column.align ?? "center";
820
+ } else {
821
+ return "center";
822
+ }
823
+ }
824
+ function getColumnAlignments({
825
+ rows,
826
+ columns = []
827
+ }) {
828
+ if (rows.at(0) == null) {
829
+ throw new Error("first row can`t be undefined.");
830
+ }
831
+ if (Array.isArray(rows.at(0))) {
832
+ const firstPrimitiveRow = rows.at(0);
833
+ return Array.from({ length: firstPrimitiveRow.length }).map(
834
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
835
+ );
836
+ }
837
+ const firstObject = rows.at(0);
838
+ return Object.keys(firstObject).map(
839
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
840
+ );
841
+ }
842
+
843
+ // packages/utils/src/lib/text-formats/html/table.ts
844
+ function wrap(elem, content) {
845
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
846
+ }
847
+ function wrapRow(content) {
848
+ const elem = "tr";
849
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
850
+ }
851
+ function table(tableData) {
852
+ if (tableData.rows.length === 0) {
853
+ throw new Error("Data can't be empty");
854
+ }
855
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
856
+ const tableHeaderRow = wrapRow(tableHeaderCols);
857
+ const tableBody = rowToStringArray(tableData).map((arr) => {
858
+ const columns = arr.map((s) => wrap("td", s)).join("");
859
+ return wrapRow(columns);
860
+ }).join("");
861
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
862
+ }
863
+
864
+ // packages/utils/src/lib/text-formats/md/font-style.ts
865
+ var boldWrap = "**";
866
+ function bold2(text) {
867
+ return `${boldWrap}${text}${boldWrap}`;
868
+ }
869
+ var italicWrap = "_";
870
+ function italic2(text) {
871
+ return `${italicWrap}${text}${italicWrap}`;
872
+ }
873
+ var strikeThroughWrap = "~";
874
+ function strikeThrough(text) {
875
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
876
+ }
877
+ var codeWrap = "`";
878
+ function code2(text) {
879
+ return `${codeWrap}${text}${codeWrap}`;
880
+ }
881
+
882
+ // packages/utils/src/lib/text-formats/md/headline.ts
883
+ function headline(text, hierarchy = 1) {
884
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
885
+ }
886
+ function h(text, hierarchy = 1) {
887
+ return headline(text, hierarchy);
888
+ }
889
+ function h1(text) {
890
+ return headline(text, 1);
891
+ }
892
+ function h2(text) {
893
+ return headline(text, 2);
894
+ }
895
+ function h3(text) {
896
+ return headline(text, 3);
897
+ }
898
+ function h4(text) {
899
+ return headline(text, 4);
900
+ }
901
+ function h5(text) {
902
+ return headline(text, 5);
903
+ }
904
+ function h6(text) {
905
+ return headline(text, 6);
906
+ }
907
+
908
+ // packages/utils/src/lib/text-formats/md/image.ts
909
+ function image(src, alt) {
910
+ return `![${alt}](${src})`;
911
+ }
912
+
913
+ // packages/utils/src/lib/text-formats/md/link.ts
914
+ function link2(href, text) {
915
+ return `[${text || href}](${href})`;
916
+ }
917
+
918
+ // packages/utils/src/lib/text-formats/md/list.ts
919
+ function li(text, order = "unordered") {
920
+ const style = order === "unordered" ? "-" : "- [ ]";
921
+ return `${style} ${text}`;
922
+ }
923
+ function indentation(text, level = 1) {
924
+ return `${TAB.repeat(level)}${text}`;
925
+ }
926
+
927
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
928
+ function paragraphs(...sections) {
929
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
930
+ }
931
+
932
+ // packages/utils/src/lib/text-formats/md/section.ts
933
+ function section(...contents) {
934
+ return `${lines(...contents)}${NEW_LINE}`;
935
+ }
936
+ function lines(...contents) {
937
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
938
+ }
939
+
940
+ // packages/utils/src/lib/text-formats/md/table.ts
941
+ var alignString = /* @__PURE__ */ new Map([
942
+ ["left", ":--"],
943
+ ["center", ":--:"],
944
+ ["right", "--:"]
945
+ ]);
946
+ function tableRow(rows) {
947
+ return `|${rows.join("|")}|`;
948
+ }
949
+ function table2(data) {
950
+ if (data.rows.length === 0) {
951
+ throw new Error("Data can't be empty");
952
+ }
953
+ const alignmentRow = getColumnAlignments(data).map(
954
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
955
+ );
956
+ return section(
957
+ `${lines(
958
+ tableRow(columnsToStringArray(data)),
959
+ tableRow(alignmentRow),
960
+ ...rowToStringArray(data).map(tableRow)
961
+ )}`
962
+ );
963
+ }
964
+
965
+ // packages/utils/src/lib/text-formats/index.ts
966
+ var md = {
967
+ bold: bold2,
968
+ italic: italic2,
969
+ strikeThrough,
970
+ code: code2,
971
+ link: link2,
972
+ image,
973
+ headline,
974
+ h,
975
+ h1,
976
+ h2,
977
+ h3,
978
+ h4,
979
+ h5,
980
+ h6,
981
+ indentation,
982
+ lines,
983
+ li,
984
+ section,
985
+ paragraphs,
986
+ table: table2
987
+ };
988
+ var html = {
989
+ bold,
990
+ italic,
991
+ code,
992
+ link,
993
+ details,
994
+ table
995
+ };
996
+
668
997
  // packages/utils/src/lib/diff.ts
669
998
  function matchArrayItemsByKey({
670
999
  before,
@@ -782,40 +1111,48 @@ import chalk from "chalk";
782
1111
 
783
1112
  // packages/utils/src/lib/reports/constants.ts
784
1113
  var TERMINAL_WIDTH = 80;
785
- var NEW_LINE = "\n";
786
1114
  var SCORE_COLOR_RANGE = {
787
1115
  GREEN_MIN: 0.9,
788
1116
  YELLOW_MIN: 0.5
789
1117
  };
1118
+ var CATEGORIES_TITLE = "\u{1F3F7} Categories";
790
1119
  var FOOTER_PREFIX = "Made with \u2764 by";
791
1120
  var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
792
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
1121
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
793
1122
  var reportHeadlineText = "Code PushUp Report";
794
1123
  var reportOverviewTableHeaders = [
795
- "\u{1F3F7} Category",
796
- "\u2B50 Score",
797
- "\u{1F6E1} Audits"
1124
+ {
1125
+ key: "category",
1126
+ label: "\u{1F3F7} Category",
1127
+ align: "left"
1128
+ },
1129
+ {
1130
+ key: "score",
1131
+ label: "\u2B50 Score"
1132
+ },
1133
+ {
1134
+ key: "audits",
1135
+ label: "\u{1F6E1} Audits"
1136
+ }
798
1137
  ];
799
1138
  var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
800
- var reportMetaTableHeaders = [
801
- "Commit",
802
- "Version",
803
- "Duration",
804
- "Plugins",
805
- "Categories",
806
- "Audits"
807
- ];
808
- var pluginMetaTableHeaders = [
809
- "Plugin",
810
- "Audits",
811
- "Version",
812
- "Duration"
813
- ];
814
- var detailsTableHeaders = [
815
- "Severity",
816
- "Message",
817
- "Source file",
818
- "Line(s)"
1139
+ var issuesTableHeadings = [
1140
+ {
1141
+ key: "severity",
1142
+ label: "Severity"
1143
+ },
1144
+ {
1145
+ key: "message",
1146
+ label: "Message"
1147
+ },
1148
+ {
1149
+ key: "file",
1150
+ label: "Source file"
1151
+ },
1152
+ {
1153
+ key: "line",
1154
+ label: "Line(s)"
1155
+ }
819
1156
  ];
820
1157
 
821
1158
  // packages/utils/src/lib/logging.ts
@@ -841,7 +1178,7 @@ function logListItem(args) {
841
1178
  singletonisaacUi.rows = [];
842
1179
  singletonUiInstance?.logger.log(content);
843
1180
  }
844
- function link(text) {
1181
+ function link3(text) {
845
1182
  return chalk.underline(chalk.blueBright(text));
846
1183
  }
847
1184
 
@@ -944,122 +1281,35 @@ async function importEsmModule(options2) {
944
1281
  return mod.default;
945
1282
  }
946
1283
 
947
- // packages/utils/src/lib/reports/md/details.ts
948
- function details(title, content, cfg = { open: false }) {
949
- return `<details${cfg.open ? " open" : ""}>
950
- <summary>${title}</summary>
951
-
952
- ${content}
953
-
954
- </details>
955
- `;
956
- }
957
-
958
- // packages/utils/src/lib/reports/md/font-style.ts
959
- var stylesMap = {
960
- i: "_",
961
- // italic
962
- b: "**",
963
- // bold
964
- s: "~",
965
- // strike through
966
- c: "`"
967
- // code
968
- };
969
- function style(text, styles = ["b"]) {
970
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
971
- }
972
-
973
- // packages/utils/src/lib/reports/md/headline.ts
974
- function headline(text, hierarchy = 1) {
975
- return `${"#".repeat(hierarchy)} ${text}`;
976
- }
977
- function h1(text) {
978
- return headline(text, 1);
979
- }
980
- function h2(text) {
981
- return headline(text, 2);
982
- }
983
- function h3(text) {
984
- return headline(text, 3);
985
- }
986
-
987
- // packages/utils/src/lib/reports/md/image.ts
988
- function image(src, alt) {
989
- return `![${alt}](${src})`;
990
- }
991
-
992
- // packages/utils/src/lib/reports/md/link.ts
993
- function link2(href, text) {
994
- return `[${text || href}](${href})`;
995
- }
996
-
997
- // packages/utils/src/lib/reports/md/list.ts
998
- function li(text, order = "unordered") {
999
- const style2 = order === "unordered" ? "-" : "- [ ]";
1000
- return `${style2} ${text}`;
1001
- }
1002
-
1003
- // packages/utils/src/lib/reports/md/paragraphs.ts
1004
- function paragraphs(...sections) {
1005
- return sections.filter(Boolean).join("\n\n");
1006
- }
1007
-
1008
- // packages/utils/src/lib/reports/md/table.ts
1009
- var alignString = /* @__PURE__ */ new Map([
1010
- ["l", ":--"],
1011
- ["c", ":--:"],
1012
- ["r", "--:"]
1013
- ]);
1014
- function tableMd(data, align) {
1015
- if (data.length === 0) {
1016
- throw new Error("Data can't be empty");
1017
- }
1018
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1019
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1020
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1021
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1022
- }
1023
- function tableHtml(data) {
1024
- if (data.length === 0) {
1025
- throw new Error("Data can't be empty");
1026
- }
1027
- const tableContent = data.map((arr, index) => {
1028
- if (index === 0) {
1029
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1030
- return `<tr>${headerRow}</tr>`;
1031
- }
1032
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1033
- return `<tr>${row}</tr>`;
1034
- });
1035
- return `<table>${tableContent.join("")}</table>`;
1036
- }
1037
-
1038
1284
  // packages/utils/src/lib/reports/utils.ts
1285
+ var { image: image2, bold: boldMd } = md;
1039
1286
  function formatReportScore(score) {
1040
1287
  return Math.round(score * 100).toString();
1041
1288
  }
1042
1289
  function formatScoreWithColor(score, options2) {
1043
- const styledNumber = options2?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1044
- return `${getRoundScoreMarker(score)} ${styledNumber}`;
1045
- }
1046
- function getRoundScoreMarker(score) {
1047
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1048
- return "\u{1F7E2}";
1049
- }
1050
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1051
- return "\u{1F7E1}";
1290
+ const styledNumber = options2?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
1291
+ return `${scoreMarker(score)} ${styledNumber}`;
1292
+ }
1293
+ var MARKERS = {
1294
+ circle: {
1295
+ red: "\u{1F534}",
1296
+ yellow: "\u{1F7E1}",
1297
+ green: "\u{1F7E2}"
1298
+ },
1299
+ square: {
1300
+ red: "\u{1F7E5}",
1301
+ yellow: "\u{1F7E8}",
1302
+ green: "\u{1F7E9}"
1052
1303
  }
1053
- return "\u{1F534}";
1054
- }
1055
- function getSquaredScoreMarker(score) {
1304
+ };
1305
+ function scoreMarker(score, markerType = "circle") {
1056
1306
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1057
- return "\u{1F7E9}";
1307
+ return MARKERS[markerType].green;
1058
1308
  }
1059
1309
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1060
- return "\u{1F7E8}";
1310
+ return MARKERS[markerType].yellow;
1061
1311
  }
1062
- return "\u{1F7E5}";
1312
+ return MARKERS[markerType].red;
1063
1313
  }
1064
1314
  function getDiffMarker(diff) {
1065
1315
  if (diff > 0) {
@@ -1075,7 +1325,7 @@ function colorByScoreDiff(text, diff) {
1075
1325
  return shieldsBadge(text, color);
1076
1326
  }
1077
1327
  function shieldsBadge(text, color) {
1078
- return image(
1328
+ return image2(
1079
1329
  `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1080
1330
  text
1081
1331
  );
@@ -1085,7 +1335,7 @@ function formatDiffNumber(diff) {
1085
1335
  const sign = diff < 0 ? "\u2212" : "+";
1086
1336
  return `${sign}${number}`;
1087
1337
  }
1088
- function getSeverityIcon(severity) {
1338
+ function severityMarker(severity) {
1089
1339
  if (severity === "error") {
1090
1340
  return "\u{1F6A8}";
1091
1341
  }
@@ -1258,12 +1508,12 @@ var ProcessError = class extends Error {
1258
1508
  }
1259
1509
  };
1260
1510
  function executeProcess(cfg) {
1261
- const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
1511
+ const { observer, cwd, command: command2, args, ignoreExitCode = false } = cfg;
1262
1512
  const { onStdout, onError, onComplete } = observer ?? {};
1263
1513
  const date = (/* @__PURE__ */ new Date()).toISOString();
1264
1514
  const start = performance.now();
1265
1515
  return new Promise((resolve, reject) => {
1266
- const process2 = spawn(command, args, { cwd, shell: true });
1516
+ const process2 = spawn(command2, args, { cwd, shell: true });
1267
1517
  let stdout = "";
1268
1518
  let stderr = "";
1269
1519
  process2.stdout.on("data", (data) => {
@@ -1276,13 +1526,13 @@ function executeProcess(cfg) {
1276
1526
  process2.on("error", (err) => {
1277
1527
  stderr += err.toString();
1278
1528
  });
1279
- process2.on("close", (code) => {
1529
+ process2.on("close", (code3) => {
1280
1530
  const timings = { date, duration: calcDuration(start) };
1281
- if (code === 0 || ignoreExitCode) {
1531
+ if (code3 === 0 || ignoreExitCode) {
1282
1532
  onComplete?.();
1283
- resolve({ code, stdout, stderr, ...timings });
1533
+ resolve({ code: code3, stdout, stderr, ...timings });
1284
1534
  } else {
1285
- const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
1535
+ const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
1286
1536
  onError?.(errorMsg);
1287
1537
  reject(errorMsg);
1288
1538
  }
@@ -1298,33 +1548,9 @@ function filterItemRefsBy(items, refFilterFn) {
1298
1548
  })).filter((item) => item.refs.length);
1299
1549
  }
1300
1550
 
1301
- // packages/utils/src/lib/git.ts
1551
+ // packages/utils/src/lib/git/git.ts
1302
1552
  import { isAbsolute, join as join2, relative } from "node:path";
1303
1553
  import { simpleGit } from "simple-git";
1304
-
1305
- // packages/utils/src/lib/transform.ts
1306
- function toArray(val) {
1307
- return Array.isArray(val) ? val : [val];
1308
- }
1309
- function objectToEntries(obj) {
1310
- return Object.entries(obj);
1311
- }
1312
- function deepClone(obj) {
1313
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1314
- }
1315
- function toUnixPath(path) {
1316
- return path.replace(/\\/g, "/");
1317
- }
1318
-
1319
- // packages/utils/src/lib/git.ts
1320
- async function getLatestCommit(git = simpleGit()) {
1321
- const log2 = await git.log({
1322
- maxCount: 1,
1323
- // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1324
- format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1325
- });
1326
- return commitSchema.parse(log2.latest);
1327
- }
1328
1554
  function getGitRoot(git = simpleGit()) {
1329
1555
  return git.revparse("--show-toplevel");
1330
1556
  }
@@ -1371,11 +1597,6 @@ async function guardAgainstLocalChanges(git = simpleGit()) {
1371
1597
  throw new GitStatusError(status);
1372
1598
  }
1373
1599
  }
1374
- async function getCurrentBranchOrTag(git = simpleGit()) {
1375
- return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
1376
- // @TODO use simple git
1377
- await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1378
- }
1379
1600
  async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1380
1601
  if (forceCleanStatus) {
1381
1602
  await git.raw(["reset", "--hard"]);
@@ -1386,6 +1607,117 @@ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simple
1386
1607
  await git.checkout(branchOrHash);
1387
1608
  }
1388
1609
 
1610
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1611
+ import { simpleGit as simpleGit2 } from "simple-git";
1612
+
1613
+ // packages/utils/src/lib/semver.ts
1614
+ import { rcompare, valid } from "semver";
1615
+ function normalizeSemver(semverString) {
1616
+ if (semverString.startsWith("v") || semverString.startsWith("V")) {
1617
+ return semverString.slice(1);
1618
+ }
1619
+ if (semverString.includes("@")) {
1620
+ return semverString.split("@").at(-1) ?? "";
1621
+ }
1622
+ return semverString;
1623
+ }
1624
+ function isSemver(semverString = "") {
1625
+ return valid(normalizeSemver(semverString)) != null;
1626
+ }
1627
+
1628
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1629
+ async function getLatestCommit(git = simpleGit2()) {
1630
+ const log2 = await git.log({
1631
+ maxCount: 1,
1632
+ // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1633
+ format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1634
+ });
1635
+ return commitSchema.parse(log2.latest);
1636
+ }
1637
+ async function getCurrentBranchOrTag(git = simpleGit2()) {
1638
+ return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
1639
+ // @TODO use simple git
1640
+ await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1641
+ }
1642
+ function validateFilter({ from, to }) {
1643
+ if (to && !from) {
1644
+ throw new Error(
1645
+ `filter needs the "from" option defined to accept the "to" option.
1646
+ `
1647
+ );
1648
+ }
1649
+ }
1650
+ function filterLogs(allTags, opt) {
1651
+ if (!opt) {
1652
+ return allTags;
1653
+ }
1654
+ validateFilter(opt);
1655
+ const { from, to, maxCount } = opt;
1656
+ const finIndex = (tagName, fallback) => {
1657
+ const idx = allTags.indexOf(tagName ?? "");
1658
+ if (idx > -1) {
1659
+ return idx;
1660
+ }
1661
+ return fallback;
1662
+ };
1663
+ const fromIndex = finIndex(from, 0);
1664
+ const toIndex = finIndex(to, void 0);
1665
+ return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
1666
+ }
1667
+ async function getHashFromTag(tag, git = simpleGit2()) {
1668
+ const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
1669
+ const hash = tagDetails.trim();
1670
+ return {
1671
+ hash: hash.split("\n").at(-1) ?? "",
1672
+ message: tag
1673
+ };
1674
+ }
1675
+ async function getSemverTags(opt = {}, git = simpleGit2()) {
1676
+ validateFilter(opt);
1677
+ const { targetBranch, ...options2 } = opt;
1678
+ let currentBranch;
1679
+ if (targetBranch) {
1680
+ currentBranch = await getCurrentBranchOrTag(git);
1681
+ await git.checkout(targetBranch);
1682
+ }
1683
+ const tagsRaw = await git.tag([
1684
+ "--merged",
1685
+ targetBranch ?? await getCurrentBranchOrTag(git)
1686
+ ]);
1687
+ const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
1688
+ const relevantTags = filterLogs(allTags, options2);
1689
+ const tagsWithHashes = await Promise.all(
1690
+ relevantTags.map((tag) => getHashFromTag(tag, git))
1691
+ );
1692
+ if (currentBranch) {
1693
+ await git.checkout(currentBranch);
1694
+ }
1695
+ return tagsWithHashes;
1696
+ }
1697
+ async function getHashes(options2 = {}, git = simpleGit2()) {
1698
+ const { targetBranch, from, to, maxCount, ...opt } = options2;
1699
+ validateFilter({ from, to });
1700
+ let currentBranch;
1701
+ if (targetBranch) {
1702
+ currentBranch = await getCurrentBranchOrTag(git);
1703
+ await git.checkout(targetBranch);
1704
+ }
1705
+ const logs = await git.log({
1706
+ ...opt,
1707
+ format: {
1708
+ hash: "%H",
1709
+ message: "%s"
1710
+ },
1711
+ from,
1712
+ to,
1713
+ maxCount
1714
+ });
1715
+ if (targetBranch) {
1716
+ await git.checkout(currentBranch);
1717
+ }
1718
+ return [...logs.all];
1719
+ }
1720
+
1389
1721
  // packages/utils/src/lib/group-by-status.ts
1390
1722
  function groupByStatus(results) {
1391
1723
  return results.reduce(
@@ -1461,44 +1793,65 @@ function listAuditsFromAllPlugins(report) {
1461
1793
  );
1462
1794
  }
1463
1795
 
1464
- // packages/utils/src/lib/reports/generate-md-report.ts
1465
- function generateMdReport(report) {
1466
- const printCategories = report.categories.length > 0;
1467
- return (
1468
- // header section
1469
- // eslint-disable-next-line prefer-template
1470
- reportToHeaderSection() + NEW_LINE + // categories overview section
1471
- (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1472
- (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1473
- reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
1474
- reportToAboutSection(report) + NEW_LINE + NEW_LINE + // footer section
1475
- `${FOOTER_PREFIX} ${link2(README_LINK, "Code PushUp")}`
1796
+ // packages/utils/src/lib/reports/formatting.ts
1797
+ var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
1798
+ function tableSection(tableData, options2) {
1799
+ if (tableData.rows.length === 0) {
1800
+ return "";
1801
+ }
1802
+ const { level = 4 } = options2 ?? {};
1803
+ const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
1804
+ return lines2(
1805
+ tableData.title && render(tableData.title, level),
1806
+ table3(tableData)
1476
1807
  );
1477
1808
  }
1478
- function reportToHeaderSection() {
1479
- return headline(reportHeadlineText) + NEW_LINE;
1809
+ function metaDescription({
1810
+ docsUrl,
1811
+ description
1812
+ }) {
1813
+ if (docsUrl) {
1814
+ const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
1815
+ if (!description) {
1816
+ return section2(docsLink);
1817
+ }
1818
+ const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
1819
+ return section2(`${parsedDescription}${docsLink}`);
1820
+ }
1821
+ if (description && description.trim().length > 0) {
1822
+ return section2(description);
1823
+ }
1824
+ return "";
1480
1825
  }
1481
- function reportToOverviewSection(report) {
1826
+
1827
+ // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1828
+ var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
1829
+ function categoriesOverviewSection(report) {
1482
1830
  const { categories, plugins } = report;
1483
- const tableContent = [
1484
- reportOverviewTableHeaders,
1485
- ...categories.map(({ title, refs, score }) => [
1486
- link2(`#${slugify(title)}`, title),
1487
- `${getRoundScoreMarker(score)} ${style(formatReportScore(score))}`,
1488
- countCategoryAudits(refs, plugins).toString()
1489
- ])
1490
- ];
1491
- return tableMd(tableContent, ["l", "c", "c"]);
1831
+ if (categories.length > 0 && plugins.length > 0) {
1832
+ const tableContent = {
1833
+ columns: reportOverviewTableHeaders,
1834
+ rows: categories.map(({ title, refs, score }) => ({
1835
+ // The heading "ID" is inferred from the heading text in Markdown.
1836
+ category: link5(`#${slugify(title)}`, title),
1837
+ score: `${scoreMarker(score)}${SPACE}${boldMd2(
1838
+ formatReportScore(score)
1839
+ )}`,
1840
+ audits: countCategoryAudits(refs, plugins).toString()
1841
+ }))
1842
+ };
1843
+ return tableSection(tableContent);
1844
+ }
1845
+ return "";
1492
1846
  }
1493
- function reportToCategoriesSection(report) {
1847
+ function categoriesDetailsSection(report) {
1494
1848
  const { categories, plugins } = report;
1495
- const categoryDetails = categories.reduce((acc, category) => {
1496
- const categoryTitle = h3(category.title);
1497
- const categoryScore = `${getRoundScoreMarker(
1849
+ const categoryDetails = categories.flatMap((category) => {
1850
+ const categoryTitle = h32(category.title);
1851
+ const categoryScore = `${scoreMarker(
1498
1852
  category.score
1499
- )} Score: ${style(formatReportScore(category.score))}`;
1500
- const categoryDocs = getDocsAndDescription(category);
1501
- const categoryMDItems = category.refs.reduce((refAcc, ref) => {
1853
+ )}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
1854
+ const categoryMDItems = category.refs.map((ref) => {
1502
1855
  if (ref.type === "group") {
1503
1856
  const group = getSortableGroupByRef(ref, plugins);
1504
1857
  const groupAudits = group.refs.map(
@@ -1508,151 +1861,240 @@ function reportToCategoriesSection(report) {
1508
1861
  )
1509
1862
  );
1510
1863
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1511
- const mdGroupItem = groupItemToCategorySection(
1512
- group,
1513
- groupAudits,
1514
- pluginTitle
1515
- );
1516
- return refAcc + mdGroupItem + NEW_LINE;
1864
+ return categoryGroupItem(group, groupAudits, pluginTitle);
1517
1865
  } else {
1518
1866
  const audit = getSortableAuditByRef(ref, plugins);
1519
1867
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1520
- const mdAuditItem = auditItemToCategorySection(audit, pluginTitle);
1521
- return refAcc + mdAuditItem + NEW_LINE;
1868
+ return categoryRef(audit, pluginTitle);
1522
1869
  }
1523
- }, "");
1524
- return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + categoryMDItems;
1525
- }, "");
1526
- return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1527
- }
1528
- function auditItemToCategorySection(audit, pluginTitle) {
1529
- const auditTitle = link2(
1530
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1531
- audit.title
1532
- );
1533
- return li(
1534
- `${getSquaredScoreMarker(
1535
- audit.score
1536
- )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`
1537
- );
1538
- }
1539
- function groupItemToCategorySection(group, groupAudits, pluginTitle) {
1540
- const groupScore = Number(formatReportScore(group.score || 0));
1541
- const groupTitle = li(
1542
- `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`
1543
- );
1544
- const auditTitles = groupAudits.reduce((acc, audit) => {
1545
- const auditTitle = link2(
1546
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1547
- audit.title
1870
+ });
1871
+ return section3(
1872
+ categoryTitle,
1873
+ metaDescription(category),
1874
+ categoryScore,
1875
+ ...categoryMDItems
1548
1876
  );
1549
- return `${acc} ${li(
1550
- `${getSquaredScoreMarker(audit.score)} ${auditTitle} - ${getAuditResult(
1551
- audit
1552
- )}`
1553
- )}${NEW_LINE}`;
1554
- }, "");
1555
- return groupTitle + NEW_LINE + auditTitles;
1556
- }
1557
- function reportToAuditsSection(report) {
1558
- const auditsSection = report.plugins.reduce((pluginAcc, plugin) => {
1559
- const auditsData = plugin.audits.reduce((auditAcc, audit) => {
1560
- const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1561
- plugin.slug,
1562
- report.plugins
1563
- )})`;
1564
- return auditAcc + h3(auditTitle) + NEW_LINE + NEW_LINE + reportToDetailsSection(audit) + NEW_LINE + NEW_LINE + getDocsAndDescription(audit);
1565
- }, "");
1566
- return pluginAcc + auditsData;
1567
- }, "");
1568
- return h2("\u{1F6E1}\uFE0F Audits") + NEW_LINE + NEW_LINE + auditsSection;
1569
- }
1570
- function reportToDetailsSection(audit) {
1571
- const detailsTitle = `${getSquaredScoreMarker(audit.score)} ${getAuditResult(
1572
- audit,
1573
- true
1574
- )} (score: ${formatReportScore(audit.score)})`;
1575
- if (!audit.details?.issues.length) {
1576
- return detailsTitle;
1577
- }
1578
- const detailsTableData = [
1579
- detailsTableHeaders,
1580
- ...audit.details.issues.map((issue) => {
1581
- const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1582
- const message = issue.message;
1583
- if (!issue.source) {
1584
- return [severity, message, "", ""];
1585
- }
1586
- const file = `<code>${issue.source.file}</code>`;
1587
- if (!issue.source.position) {
1588
- return [severity, message, file, ""];
1589
- }
1590
- const { startLine, endLine } = issue.source.position;
1591
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1592
- return [severity, message, file, line];
1593
- })
1594
- ];
1595
- const detailsTable = `<h4>Issues</h4>${tableHtml(detailsTableData)}`;
1596
- return details(detailsTitle, detailsTable);
1597
- }
1598
- function reportToAboutSection(report) {
1599
- const date = formatDate(/* @__PURE__ */ new Date());
1600
- const { duration, version: version2, commit, plugins, categories } = report;
1601
- const commitInfo = commit ? `${commit.message} (${commit.hash})` : "N/A";
1602
- const reportMetaTable = [
1603
- reportMetaTableHeaders,
1604
- [
1605
- commitInfo,
1606
- style(version2 || "", ["c"]),
1607
- formatDuration(duration),
1608
- plugins.length.toString(),
1609
- categories.length.toString(),
1610
- plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1611
- ]
1612
- ];
1613
- const pluginMetaTable = [
1614
- pluginMetaTableHeaders,
1615
- ...plugins.map((plugin) => [
1616
- plugin.title,
1617
- plugin.audits.length.toString(),
1618
- style(plugin.version || "", ["c"]),
1619
- formatDuration(plugin.duration)
1620
- ])
1621
- ];
1622
- return (
1623
- // eslint-disable-next-line prefer-template
1624
- 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"])
1877
+ });
1878
+ return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
1879
+ }
1880
+ function categoryRef({ title, score, value, displayValue }, pluginTitle) {
1881
+ const auditTitleAsLink = link5(
1882
+ `#${slugify(title)}-${slugify(pluginTitle)}`,
1883
+ title
1884
+ );
1885
+ const marker = scoreMarker(score, "square");
1886
+ return li2(
1887
+ `${marker}${SPACE}${auditTitleAsLink}${SPACE}(_${pluginTitle}_) - ${boldMd2(
1888
+ (displayValue || value).toString()
1889
+ )}`
1625
1890
  );
1626
1891
  }
1627
- function getDocsAndDescription({
1628
- docsUrl,
1629
- description
1630
- }) {
1631
- if (docsUrl) {
1632
- const docsLink = link2(docsUrl, "\u{1F4D6} Docs");
1633
- if (!description) {
1634
- return docsLink + NEW_LINE + NEW_LINE;
1635
- }
1636
- if (description.endsWith("```")) {
1637
- return description + NEW_LINE + NEW_LINE + docsLink + NEW_LINE + NEW_LINE;
1892
+ function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
1893
+ const groupTitle = li2(
1894
+ `${scoreMarker(score)}${SPACE}${title}${SPACE}(_${pluginTitle}_)`
1895
+ );
1896
+ const auditTitles = groupAudits.map(
1897
+ ({ title: auditTitle, score: auditScore, value, displayValue }) => {
1898
+ const auditTitleLink = link5(
1899
+ `#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
1900
+ auditTitle
1901
+ );
1902
+ const marker = scoreMarker(auditScore, "square");
1903
+ return indentation2(
1904
+ li2(
1905
+ `${marker}${SPACE}${auditTitleLink} - ${boldMd2(
1906
+ String(displayValue ?? value)
1907
+ )}`
1908
+ )
1909
+ );
1638
1910
  }
1639
- return `${description} ${docsLink}${NEW_LINE}${NEW_LINE}`;
1640
- }
1641
- if (description) {
1642
- return description + NEW_LINE + NEW_LINE;
1911
+ );
1912
+ return lines3(groupTitle, ...auditTitles);
1913
+ }
1914
+
1915
+ // packages/utils/src/lib/reports/generate-md-report.ts
1916
+ var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
1917
+ var { bold: boldHtml, details: details2 } = html;
1918
+ function auditDetailsAuditValue({
1919
+ score,
1920
+ value,
1921
+ displayValue
1922
+ }) {
1923
+ return `${scoreMarker(score, "square")} ${boldHtml(
1924
+ String(displayValue ?? value)
1925
+ )} (score: ${formatReportScore(score)})`;
1926
+ }
1927
+ function generateMdReport(report) {
1928
+ const printCategories = report.categories.length > 0;
1929
+ return lines4(
1930
+ h12(reportHeadlineText),
1931
+ printCategories ? categoriesOverviewSection(report) : "",
1932
+ printCategories ? categoriesDetailsSection(report) : "",
1933
+ auditsSection(report),
1934
+ aboutSection(report),
1935
+ `${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
1936
+ );
1937
+ }
1938
+ function auditDetailsIssues(issues = []) {
1939
+ if (issues.length === 0) {
1940
+ return "";
1643
1941
  }
1644
- return "";
1942
+ const detailsTableData = {
1943
+ title: "Issues",
1944
+ columns: issuesTableHeadings,
1945
+ rows: issues.map(
1946
+ ({ severity: severityVal, message, source: sourceVal }) => {
1947
+ const severity = `${severityMarker(severityVal)} <i>${severityVal}</i>`;
1948
+ if (!sourceVal) {
1949
+ return { severity, message, file: "", line: "" };
1950
+ }
1951
+ const file = `<code>${sourceVal.file}</code>`;
1952
+ if (!sourceVal.position) {
1953
+ return { severity, message, file, line: "" };
1954
+ }
1955
+ const { startLine, endLine } = sourceVal.position;
1956
+ const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1957
+ return { severity, message, file, line };
1958
+ }
1959
+ )
1960
+ };
1961
+ return tableSection(detailsTableData);
1962
+ }
1963
+ function auditDetails(audit) {
1964
+ const { table: table5, issues = [] } = audit.details ?? {};
1965
+ const detailsValue = auditDetailsAuditValue(audit);
1966
+ if (issues.length === 0 && table5 == null) {
1967
+ return section4(detailsValue);
1968
+ }
1969
+ const tableSectionContent = table5 == null ? "" : tableSection(table5);
1970
+ const issuesSectionContent = issues.length > 0 ? auditDetailsIssues(issues) : "";
1971
+ return details2(
1972
+ detailsValue,
1973
+ lines4(tableSectionContent, issuesSectionContent)
1974
+ );
1645
1975
  }
1646
- function getAuditResult(audit, isHtml = false) {
1647
- const { displayValue, value } = audit;
1648
- return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1976
+ function auditsSection({
1977
+ plugins
1978
+ }) {
1979
+ const content = plugins.flatMap(
1980
+ ({ slug, audits }) => audits.flatMap((audit) => {
1981
+ const auditTitle = `${audit.title}${SPACE}(${getPluginNameFromSlug(
1982
+ slug,
1983
+ plugins
1984
+ )})`;
1985
+ const detailsContent = auditDetails(audit);
1986
+ const descriptionContent = metaDescription(audit);
1987
+ return [h33(auditTitle), detailsContent, descriptionContent];
1988
+ })
1989
+ );
1990
+ return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
1991
+ }
1992
+ function aboutSection(report) {
1993
+ const { date, plugins } = report;
1994
+ const reportMetaTable = reportMetaData(report);
1995
+ const pluginMetaTable = reportPluginMeta({ plugins });
1996
+ return lines4(
1997
+ h23("About"),
1998
+ section4(
1999
+ `Report was created by [Code PushUp](${README_LINK}) on ${formatDate(
2000
+ new Date(date)
2001
+ )}.`
2002
+ ),
2003
+ tableSection(pluginMetaTable),
2004
+ tableSection(reportMetaTable)
2005
+ );
2006
+ }
2007
+ function reportPluginMeta({ plugins }) {
2008
+ return {
2009
+ columns: [
2010
+ {
2011
+ key: "plugin",
2012
+ align: "left"
2013
+ },
2014
+ {
2015
+ key: "audits"
2016
+ },
2017
+ {
2018
+ key: "version"
2019
+ },
2020
+ {
2021
+ key: "duration"
2022
+ }
2023
+ ],
2024
+ rows: plugins.map(
2025
+ ({
2026
+ title: pluginTitle,
2027
+ audits,
2028
+ version: pluginVersion,
2029
+ duration: pluginDuration
2030
+ }) => ({
2031
+ plugin: pluginTitle,
2032
+ audits: audits.length.toString(),
2033
+ version: codeMd(pluginVersion || ""),
2034
+ duration: formatDuration(pluginDuration)
2035
+ })
2036
+ )
2037
+ };
2038
+ }
2039
+ function reportMetaData({
2040
+ commit,
2041
+ version: version2,
2042
+ duration,
2043
+ plugins,
2044
+ categories
2045
+ }) {
2046
+ const commitInfo = commit ? `${commit.message}${SPACE}(${commit.hash})` : "N/A";
2047
+ return {
2048
+ columns: [
2049
+ {
2050
+ key: "commit",
2051
+ align: "left"
2052
+ },
2053
+ {
2054
+ key: "version"
2055
+ },
2056
+ {
2057
+ key: "duration"
2058
+ },
2059
+ {
2060
+ key: "plugins"
2061
+ },
2062
+ {
2063
+ key: "categories"
2064
+ },
2065
+ {
2066
+ key: "audits"
2067
+ }
2068
+ ],
2069
+ rows: [
2070
+ {
2071
+ commit: commitInfo,
2072
+ version: codeMd(version2 || ""),
2073
+ duration: formatDuration(duration),
2074
+ plugins: plugins.length,
2075
+ categories: categories.length,
2076
+ audits: plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
2077
+ }
2078
+ ]
2079
+ };
1649
2080
  }
1650
2081
 
1651
2082
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
2083
+ var {
2084
+ h1: h13,
2085
+ h2: h24,
2086
+ lines: lines5,
2087
+ link: link7,
2088
+ bold: boldMd3,
2089
+ italic: italicMd,
2090
+ table: table4,
2091
+ section: section5
2092
+ } = md;
2093
+ var { details: details3 } = html;
1652
2094
  var MAX_ROWS = 100;
1653
2095
  function generateMdReportsDiff(diff) {
1654
- return paragraphs(
1655
- formatDiffHeaderSection(diff),
2096
+ return lines5(
2097
+ section5(formatDiffHeaderSection(diff)),
1656
2098
  formatDiffCategoriesSection(diff),
1657
2099
  formatDiffGroupsSection(diff),
1658
2100
  formatDiffAuditsSection(diff)
@@ -1660,12 +2102,12 @@ function generateMdReportsDiff(diff) {
1660
2102
  }
1661
2103
  function formatDiffHeaderSection(diff) {
1662
2104
  const outcomeTexts = {
1663
- positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1664
- negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1665
- mixed: `\u{1F928} Code PushUp report has both ${style(
2105
+ positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
2106
+ negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
2107
+ mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
1666
2108
  "improvements and regressions"
1667
2109
  )}`,
1668
- unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
2110
+ unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
1669
2111
  };
1670
2112
  const outcome = mergeDiffOutcomes(
1671
2113
  changesToDiffOutcomes([
@@ -1675,8 +2117,8 @@ function formatDiffHeaderSection(diff) {
1675
2117
  ])
1676
2118
  );
1677
2119
  const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1678
- return paragraphs(
1679
- h1("Code PushUp"),
2120
+ return lines5(
2121
+ h13("Code PushUp"),
1680
2122
  diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1681
2123
  );
1682
2124
  }
@@ -1687,102 +2129,104 @@ function formatDiffCategoriesSection(diff) {
1687
2129
  if (categoriesCount === 0) {
1688
2130
  return "";
1689
2131
  }
1690
- return paragraphs(
1691
- h2("\u{1F3F7}\uFE0F Categories"),
1692
- categoriesCount > 0 && tableMd(
1693
- [
1694
- [
1695
- "\u{1F3F7}\uFE0F Category",
1696
- hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1697
- "\u2B50 Previous score",
1698
- "\u{1F504} Score change"
1699
- ],
1700
- ...sortChanges(changed).map((category) => [
1701
- formatTitle(category),
1702
- formatScoreWithColor(category.scores.after),
1703
- formatScoreWithColor(category.scores.before, { skipBold: true }),
1704
- formatScoreChange(category.scores.diff)
1705
- ]),
1706
- ...added.map((category) => [
1707
- formatTitle(category),
1708
- formatScoreWithColor(category.score),
1709
- style("n/a (\\*)", ["i"]),
1710
- style("n/a (\\*)", ["i"])
1711
- ]),
1712
- ...unchanged.map((category) => [
1713
- formatTitle(category),
1714
- formatScoreWithColor(category.score),
1715
- formatScoreWithColor(category.score, { skipBold: true }),
1716
- "\u2013"
1717
- ])
1718
- ].map((row) => hasChanges ? row : row.slice(0, 2)),
1719
- hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1720
- ),
1721
- added.length > 0 && style("(\\*) New category.", ["i"])
2132
+ const columns = [
2133
+ { key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
2134
+ { key: "after", label: hasChanges ? "\u2B50 Current score" : "\u2B50 Score" },
2135
+ { key: "before", label: "\u2B50 Previous score" },
2136
+ { key: "change", label: "\u{1F504} Score change" }
2137
+ ];
2138
+ return lines5(
2139
+ h24("\u{1F3F7}\uFE0F Categories"),
2140
+ categoriesCount > 0 && table4({
2141
+ columns: hasChanges ? columns : columns.slice(0, 2),
2142
+ rows: [
2143
+ ...sortChanges(changed).map((category) => ({
2144
+ category: formatTitle(category),
2145
+ after: formatScoreWithColor(category.scores.after),
2146
+ before: formatScoreWithColor(category.scores.before, {
2147
+ skipBold: true
2148
+ }),
2149
+ change: formatScoreChange(category.scores.diff)
2150
+ })),
2151
+ ...added.map((category) => ({
2152
+ category: formatTitle(category),
2153
+ after: formatScoreWithColor(category.score),
2154
+ before: italicMd("n/a (\\*)"),
2155
+ change: italicMd("n/a (\\*)")
2156
+ })),
2157
+ ...unchanged.map((category) => ({
2158
+ category: formatTitle(category),
2159
+ after: formatScoreWithColor(category.score),
2160
+ before: formatScoreWithColor(category.score, { skipBold: true }),
2161
+ change: "\u2013"
2162
+ }))
2163
+ ].map(
2164
+ (row) => hasChanges ? row : { category: row.category, after: row.after }
2165
+ )
2166
+ }),
2167
+ added.length > 0 && section5(italicMd("(\\*) New category."))
1722
2168
  );
1723
2169
  }
1724
2170
  function formatDiffGroupsSection(diff) {
1725
2171
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1726
2172
  return "";
1727
2173
  }
1728
- return paragraphs(
1729
- h2("\u{1F5C3}\uFE0F Groups"),
2174
+ return lines5(
2175
+ h24("\u{1F5C3}\uFE0F Groups"),
1730
2176
  formatGroupsOrAuditsDetails("group", diff.groups, {
1731
- headings: [
1732
- "\u{1F50C} Plugin",
1733
- "\u{1F5C3}\uFE0F Group",
1734
- "\u2B50 Current score",
1735
- "\u2B50 Previous score",
1736
- "\u{1F504} Score change"
2177
+ columns: [
2178
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2179
+ { key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
2180
+ { key: "after", label: "\u2B50 Current score" },
2181
+ { key: "before", label: "\u2B50 Previous score" },
2182
+ { key: "change", label: "\u{1F504} Score change" }
1737
2183
  ],
1738
- rows: sortChanges(diff.groups.changed).map((group) => [
1739
- formatTitle(group.plugin),
1740
- formatTitle(group),
1741
- formatScoreWithColor(group.scores.after),
1742
- formatScoreWithColor(group.scores.before, { skipBold: true }),
1743
- formatScoreChange(group.scores.diff)
1744
- ]),
1745
- align: ["l", "l", "c", "c", "c"]
2184
+ rows: sortChanges(diff.groups.changed).map((group) => ({
2185
+ plugin: formatTitle(group.plugin),
2186
+ group: formatTitle(group),
2187
+ after: formatScoreWithColor(group.scores.after),
2188
+ before: formatScoreWithColor(group.scores.before, { skipBold: true }),
2189
+ change: formatScoreChange(group.scores.diff)
2190
+ }))
1746
2191
  })
1747
2192
  );
1748
2193
  }
1749
2194
  function formatDiffAuditsSection(diff) {
1750
- return paragraphs(
1751
- h2("\u{1F6E1}\uFE0F Audits"),
2195
+ return lines5(
2196
+ h24("\u{1F6E1}\uFE0F Audits"),
1752
2197
  formatGroupsOrAuditsDetails("audit", diff.audits, {
1753
- headings: [
1754
- "\u{1F50C} Plugin",
1755
- "\u{1F6E1}\uFE0F Audit",
1756
- "\u{1F4CF} Current value",
1757
- "\u{1F4CF} Previous value",
1758
- "\u{1F504} Value change"
2198
+ columns: [
2199
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2200
+ { key: "audit", label: "\u{1F6E1}\uFE0F Audit", align: "left" },
2201
+ { key: "after", label: "\u{1F4CF} Current value" },
2202
+ { key: "before", label: "\u{1F4CF} Previous value" },
2203
+ { key: "change", label: "\u{1F504} Value change" }
1759
2204
  ],
1760
- rows: sortChanges(diff.audits.changed).map((audit) => [
1761
- formatTitle(audit.plugin),
1762
- formatTitle(audit),
1763
- `${getSquaredScoreMarker(audit.scores.after)} ${style(
2205
+ rows: sortChanges(diff.audits.changed).map((audit) => ({
2206
+ plugin: formatTitle(audit.plugin),
2207
+ audit: formatTitle(audit),
2208
+ after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
1764
2209
  audit.displayValues.after || audit.values.after.toString()
1765
2210
  )}`,
1766
- `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1767
- formatValueChange(audit)
1768
- ]),
1769
- align: ["l", "l", "c", "c", "c"]
2211
+ before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2212
+ change: formatValueChange(audit)
2213
+ }))
1770
2214
  })
1771
2215
  );
1772
2216
  }
1773
- function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1774
- return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
2217
+ function formatGroupsOrAuditsDetails(token, { changed, unchanged }, tableData) {
2218
+ return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details3(
1775
2219
  summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1776
- paragraphs(
1777
- tableMd(
1778
- [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1779
- table.align
1780
- ),
1781
- changed.length > MAX_ROWS && style(
2220
+ lines5(
2221
+ table4({
2222
+ ...tableData,
2223
+ rows: tableData.rows.slice(0, MAX_ROWS)
2224
+ // use never to avoid typing problem
2225
+ }),
2226
+ changed.length > MAX_ROWS && italicMd(
1782
2227
  `Only the ${MAX_ROWS} most affected ${pluralize(
1783
2228
  token
1784
- )} are listed above for brevity.`,
1785
- ["i"]
2229
+ )} are listed above for brevity.`
1786
2230
  ),
1787
2231
  unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1788
2232
  )
@@ -1803,11 +2247,13 @@ function formatValueChange({
1803
2247
  return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1804
2248
  }
1805
2249
  function summarizeUnchanged(token, { changed, unchanged }) {
1806
- return [
1807
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1808
- unchanged.length === 1 ? "is" : "are",
1809
- "unchanged."
1810
- ].join(" ");
2250
+ return section5(
2251
+ [
2252
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2253
+ unchanged.length === 1 ? "is" : "are",
2254
+ "unchanged."
2255
+ ].join(" ")
2256
+ );
1811
2257
  }
1812
2258
  function summarizeDiffOutcomes(outcomes, token) {
1813
2259
  return objectToEntries(countDiffOutcomes(outcomes)).filter(
@@ -1832,7 +2278,7 @@ function formatTitle({
1832
2278
  docsUrl
1833
2279
  }) {
1834
2280
  if (docsUrl) {
1835
- return link2(docsUrl, title);
2281
+ return link7(docsUrl, title);
1836
2282
  }
1837
2283
  return title;
1838
2284
  }
@@ -1883,7 +2329,7 @@ function log(msg = "") {
1883
2329
  }
1884
2330
  function logStdoutSummary(report) {
1885
2331
  const printCategories = report.categories.length > 0;
1886
- log(reportToHeaderSection2(report));
2332
+ log(reportToHeaderSection(report));
1887
2333
  log();
1888
2334
  logPlugins(report);
1889
2335
  if (printCategories) {
@@ -1892,7 +2338,7 @@ function logStdoutSummary(report) {
1892
2338
  log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1893
2339
  log();
1894
2340
  }
1895
- function reportToHeaderSection2(report) {
2341
+ function reportToHeaderSection(report) {
1896
2342
  const { packageName, version: version2 } = report;
1897
2343
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1898
2344
  }
@@ -1932,16 +2378,16 @@ function logCategories({ categories, plugins }) {
1932
2378
  applyScoreColor({ score }),
1933
2379
  countCategoryAudits(refs, plugins)
1934
2380
  ]);
1935
- const table = ui().table();
1936
- table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1937
- table.head(
2381
+ const table5 = ui().table();
2382
+ table5.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2383
+ table5.head(
1938
2384
  reportRawOverviewTableHeaders.map((heading, idx) => ({
1939
2385
  content: chalk4.cyan(heading),
1940
2386
  hAlign: hAlign(idx)
1941
2387
  }))
1942
2388
  );
1943
2389
  rows.forEach(
1944
- (row) => table.row(
2390
+ (row) => table5.row(
1945
2391
  row.map((content, idx) => ({
1946
2392
  content: content.toString(),
1947
2393
  hAlign: hAlign(idx)
@@ -1950,19 +2396,19 @@ function logCategories({ categories, plugins }) {
1950
2396
  );
1951
2397
  log(chalk4.magentaBright.bold("Categories"));
1952
2398
  log();
1953
- table.render();
2399
+ table5.render();
1954
2400
  log();
1955
2401
  }
1956
2402
  function applyScoreColor({ score, text }) {
1957
2403
  const formattedScore = text ?? formatReportScore(score);
1958
- const style2 = text ? chalk4 : chalk4.bold;
2404
+ const style = text ? chalk4 : chalk4.bold;
1959
2405
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1960
- return style2.green(formattedScore);
2406
+ return style.green(formattedScore);
1961
2407
  }
1962
2408
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1963
- return style2.yellow(formattedScore);
2409
+ return style.yellow(formattedScore);
1964
2410
  }
1965
- return style2.red(formattedScore);
2411
+ return style.red(formattedScore);
1966
2412
  }
1967
2413
 
1968
2414
  // packages/utils/src/lib/reports/scoring.ts
@@ -2124,12 +2570,22 @@ var verboseUtils = (verbose = false) => ({
2124
2570
 
2125
2571
  // packages/core/package.json
2126
2572
  var name = "@code-pushup/core";
2127
- var version = "0.39.0";
2573
+ var version = "0.42.1";
2128
2574
 
2129
2575
  // packages/core/src/lib/implementation/execute-plugin.ts
2130
2576
  import chalk5 from "chalk";
2131
2577
 
2132
2578
  // packages/core/src/lib/normalize.ts
2579
+ function normalizeIssue(issue, gitRoot) {
2580
+ const { source, ...issueWithoutSource } = issue;
2581
+ return source == null ? issue : {
2582
+ ...issueWithoutSource,
2583
+ source: {
2584
+ ...source,
2585
+ file: formatGitPath(source.file, gitRoot)
2586
+ }
2587
+ };
2588
+ }
2133
2589
  async function normalizeAuditOutputs(audits) {
2134
2590
  const gitRoot = await getGitRoot();
2135
2591
  return audits.map((audit) => {
@@ -2141,13 +2597,7 @@ async function normalizeAuditOutputs(audits) {
2141
2597
  details: {
2142
2598
  ...audit.details,
2143
2599
  issues: audit.details.issues.map(
2144
- (issue) => issue.source == null ? issue : {
2145
- ...issue,
2146
- source: {
2147
- ...issue.source,
2148
- file: formatGitPath(issue.source.file, gitRoot)
2149
- }
2150
- }
2600
+ (issue) => normalizeIssue(issue, gitRoot)
2151
2601
  )
2152
2602
  }
2153
2603
  };
@@ -2157,9 +2607,9 @@ async function normalizeAuditOutputs(audits) {
2157
2607
  // packages/core/src/lib/implementation/runner.ts
2158
2608
  import { join as join3 } from "node:path";
2159
2609
  async function executeRunnerConfig(cfg, onProgress) {
2160
- const { args, command, outputFile, outputTransform } = cfg;
2610
+ const { args, command: command2, outputFile, outputTransform } = cfg;
2161
2611
  const { duration, date } = await executeProcess({
2162
- command,
2612
+ command: command2,
2163
2613
  args,
2164
2614
  observer: { onStdout: onProgress }
2165
2615
  });
@@ -2185,7 +2635,11 @@ async function executeRunnerFunction(runner, onProgress) {
2185
2635
  // packages/core/src/lib/implementation/execute-plugin.ts
2186
2636
  var PluginOutputMissingAuditError = class extends Error {
2187
2637
  constructor(auditSlug) {
2188
- super(`Audit metadata not found for slug ${auditSlug}`);
2638
+ super(
2639
+ `Audit metadata not present in plugin config. Missing slug: ${chalk5.bold(
2640
+ auditSlug
2641
+ )}`
2642
+ );
2189
2643
  }
2190
2644
  };
2191
2645
  async function executePlugin(pluginConfig, onProgress) {
@@ -2199,7 +2653,11 @@ async function executePlugin(pluginConfig, onProgress) {
2199
2653
  } = pluginConfig;
2200
2654
  const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
2201
2655
  const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
2202
- const auditOutputs = auditOutputsSchema.parse(unvalidatedAuditOutputs);
2656
+ const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);
2657
+ if (!result.success) {
2658
+ throw new Error(`Audit output is invalid: ${result.error.message}`);
2659
+ }
2660
+ const auditOutputs = result.data;
2203
2661
  auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
2204
2662
  const normalizedAuditOutputs = await normalizeAuditOutputs(auditOutputs);
2205
2663
  const auditReports = normalizedAuditOutputs.map(
@@ -2219,32 +2677,48 @@ async function executePlugin(pluginConfig, onProgress) {
2219
2677
  ...groups2 && { groups: groups2 }
2220
2678
  };
2221
2679
  }
2680
+ var wrapProgress = async (pluginCfg, steps, progressBar) => {
2681
+ progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2682
+ try {
2683
+ const pluginReport = await executePlugin(pluginCfg);
2684
+ progressBar?.incrementInSteps(steps);
2685
+ return pluginReport;
2686
+ } catch (error) {
2687
+ progressBar?.incrementInSteps(steps);
2688
+ throw new Error(
2689
+ error instanceof Error ? `- Plugin ${chalk5.bold(pluginCfg.title)} (${chalk5.bold(
2690
+ pluginCfg.slug
2691
+ )}) produced the following error:
2692
+ - ${error.message}` : String(error)
2693
+ );
2694
+ }
2695
+ };
2222
2696
  async function executePlugins(plugins, options2) {
2223
2697
  const { progress = false } = options2 ?? {};
2224
2698
  const progressBar = progress ? getProgressBar("Run plugins") : null;
2225
- const pluginsResult = await plugins.reduce(async (acc, pluginCfg) => {
2226
- progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2227
- try {
2228
- const pluginReport = await executePlugin(pluginCfg);
2229
- progressBar?.incrementInSteps(plugins.length);
2230
- return [...await acc, Promise.resolve(pluginReport)];
2231
- } catch (error) {
2232
- progressBar?.incrementInSteps(plugins.length);
2233
- return [
2234
- ...await acc,
2235
- Promise.reject(error instanceof Error ? error.message : String(error))
2236
- ];
2237
- }
2238
- }, Promise.resolve([]));
2699
+ const pluginsResult = await plugins.reduce(
2700
+ async (acc, pluginCfg) => [
2701
+ ...await acc,
2702
+ wrapProgress(pluginCfg, plugins.length, progressBar)
2703
+ ],
2704
+ Promise.resolve([])
2705
+ );
2239
2706
  progressBar?.endProgress("Done running plugins");
2240
2707
  const errorsTransform = ({ reason }) => String(reason);
2241
2708
  const results = await Promise.allSettled(pluginsResult);
2242
2709
  logMultipleResults(results, "Plugins", void 0, errorsTransform);
2243
2710
  const { fulfilled, rejected } = groupByStatus(results);
2244
2711
  if (rejected.length > 0) {
2245
- const errorMessages = rejected.map(({ reason }) => String(reason)).join(", ");
2712
+ const errorMessages = rejected.map(({ reason }) => String(reason)).join("\n");
2246
2713
  throw new Error(
2247
- `Plugins failed: ${rejected.length} errors: ${errorMessages}`
2714
+ `Executing ${pluralizeToken(
2715
+ "plugin",
2716
+ rejected.length
2717
+ )} failed.
2718
+
2719
+ ${errorMessages}
2720
+
2721
+ `
2248
2722
  );
2249
2723
  }
2250
2724
  return fulfilled.map((result) => result.value);
@@ -2541,9 +3015,6 @@ function reportsDiffToFileContent(reportsDiff, format) {
2541
3015
  }
2542
3016
  }
2543
3017
 
2544
- // packages/core/src/lib/history.ts
2545
- import { simpleGit as simpleGit2 } from "simple-git";
2546
-
2547
3018
  // packages/core/src/lib/upload.ts
2548
3019
  import {
2549
3020
  uploadToPortal
@@ -2589,17 +3060,33 @@ function groupToGQL(group) {
2589
3060
  };
2590
3061
  }
2591
3062
  function auditToGQL(audit) {
3063
+ const {
3064
+ slug,
3065
+ title,
3066
+ description,
3067
+ docsUrl,
3068
+ score,
3069
+ value,
3070
+ displayValue: formattedValue,
3071
+ details: details4
3072
+ } = audit;
3073
+ const {
3074
+ issues
3075
+ /*, table */
3076
+ } = details4 ?? {};
2592
3077
  return {
2593
- slug: audit.slug,
2594
- title: audit.title,
2595
- description: audit.description,
2596
- docsUrl: audit.docsUrl,
2597
- score: audit.score,
2598
- value: audit.value,
2599
- formattedValue: audit.displayValue,
2600
- ...audit.details && {
3078
+ slug,
3079
+ title,
3080
+ description,
3081
+ docsUrl,
3082
+ score,
3083
+ value,
3084
+ formattedValue,
3085
+ ...details4 && {
2601
3086
  details: {
2602
- issues: audit.details.issues.map(issueToGQL)
3087
+ ...issues && { issues: issues.map(issueToGQL) }
3088
+ // @TODO add when https://github.com/code-pushup/cli/issues/530 is implemented
3089
+ // ...(table ? {table} : {}),
2603
3090
  }
2604
3091
  }
2605
3092
  };
@@ -2706,24 +3193,6 @@ async function history(config, commits) {
2706
3193
  await safeCheckout(initialBranch, forceCleanStatus);
2707
3194
  return reports;
2708
3195
  }
2709
- async function getHashes(options2, git = simpleGit2()) {
2710
- const { from, to } = options2;
2711
- if (to && !from) {
2712
- throw new Error(
2713
- `git log command needs the "from" option defined to accept the "to" option.
2714
- `
2715
- );
2716
- }
2717
- const logs = await git.log({
2718
- ...options2,
2719
- from,
2720
- to
2721
- });
2722
- return prepareHashes(logs);
2723
- }
2724
- function prepareHashes(logs) {
2725
- return logs.all.map(({ hash }) => hash).reverse();
2726
- }
2727
3196
 
2728
3197
  // packages/core/src/lib/implementation/read-rc-file.ts
2729
3198
  import { join as join6 } from "node:path";
@@ -2774,7 +3243,7 @@ import chalk6 from "chalk";
2774
3243
  function renderConfigureCategoriesHint() {
2775
3244
  ui().logger.info(
2776
3245
  chalk6.gray(
2777
- `\u{1F4A1} Configure categories to see the scores in an overview table. See: ${link(
3246
+ `\u{1F4A1} Configure categories to see the scores in an overview table. See: ${link3(
2778
3247
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md"
2779
3248
  )}`
2780
3249
  )
@@ -2782,7 +3251,7 @@ function renderConfigureCategoriesHint() {
2782
3251
  }
2783
3252
  function uploadSuccessfulLog(url) {
2784
3253
  ui().logger.success("Upload successful!");
2785
- ui().logger.success(link(url));
3254
+ ui().logger.success(link3(url));
2786
3255
  }
2787
3256
  function collectSuccessfulLog() {
2788
3257
  ui().logger.success("Collecting report successful!");
@@ -2793,15 +3262,15 @@ function renderIntegratePortalHint() {
2793
3262
  "npx code-pushup upload"
2794
3263
  )}`
2795
3264
  ).add(
2796
- ` ${link(
3265
+ ` ${link3(
2797
3266
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
2798
3267
  )}`
2799
3268
  ).add(
2800
- `${chalk6.gray("\u276F")} ${chalk6.gray("Portal Integration")} - ${link(
3269
+ `${chalk6.gray("\u276F")} ${chalk6.gray("Portal Integration")} - ${link3(
2801
3270
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
2802
3271
  )}`
2803
3272
  ).add(
2804
- `${chalk6.gray("\u276F")} ${chalk6.gray("Upload Command")} - ${link(
3273
+ `${chalk6.gray("\u276F")} ${chalk6.gray("Upload Command")} - ${link3(
2805
3274
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
2806
3275
  )}`
2807
3276
  ).render();
@@ -2809,13 +3278,13 @@ function renderIntegratePortalHint() {
2809
3278
 
2810
3279
  // packages/cli/src/lib/autorun/autorun-command.ts
2811
3280
  function yargsAutorunCommandObject() {
2812
- const command = "autorun";
3281
+ const command2 = "autorun";
2813
3282
  return {
2814
- command,
3283
+ command: command2,
2815
3284
  describe: "Shortcut for running collect followed by upload",
2816
3285
  handler: async (args) => {
2817
3286
  ui().logger.log(chalk7.bold(CLI_NAME));
2818
- ui().logger.info(chalk7.gray(`Run ${command}...`));
3287
+ ui().logger.info(chalk7.gray(`Run ${command2}...`));
2819
3288
  const options2 = args;
2820
3289
  const optionsWithFormat = {
2821
3290
  ...options2,
@@ -2845,14 +3314,14 @@ function yargsAutorunCommandObject() {
2845
3314
  // packages/cli/src/lib/collect/collect-command.ts
2846
3315
  import chalk8 from "chalk";
2847
3316
  function yargsCollectCommandObject() {
2848
- const command = "collect";
3317
+ const command2 = "collect";
2849
3318
  return {
2850
- command,
3319
+ command: command2,
2851
3320
  describe: "Run Plugins and collect results",
2852
3321
  handler: async (args) => {
2853
3322
  const options2 = args;
2854
3323
  ui().logger.log(chalk8.bold(CLI_NAME));
2855
- ui().logger.info(chalk8.gray(`Run ${command}...`));
3324
+ ui().logger.info(chalk8.gray(`Run ${command2}...`));
2856
3325
  await collectAndPersistReports(options2);
2857
3326
  collectSuccessfulLog();
2858
3327
  if (options2.categories.length === 0) {
@@ -2871,7 +3340,7 @@ function renderUploadAutorunHint() {
2871
3340
  "Run upload to upload the created report to the server"
2872
3341
  )}`
2873
3342
  ).add(
2874
- ` ${link(
3343
+ ` ${link3(
2875
3344
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
2876
3345
  )}`
2877
3346
  ).add(
@@ -2879,7 +3348,7 @@ function renderUploadAutorunHint() {
2879
3348
  "Run collect & upload"
2880
3349
  )}`
2881
3350
  ).add(
2882
- ` ${link(
3351
+ ` ${link3(
2883
3352
  "https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command"
2884
3353
  )}`
2885
3354
  ).render();
@@ -2906,14 +3375,14 @@ function yargsCompareOptionsDefinition() {
2906
3375
 
2907
3376
  // packages/cli/src/lib/compare/compare-command.ts
2908
3377
  function yargsCompareCommandObject() {
2909
- const command = "compare";
3378
+ const command2 = "compare";
2910
3379
  return {
2911
- command,
3380
+ command: command2,
2912
3381
  describe: "Compare 2 report files and create a diff file",
2913
3382
  builder: yargsCompareOptionsDefinition(),
2914
3383
  handler: async (args) => {
2915
3384
  ui().logger.log(chalk9.bold(CLI_NAME));
2916
- ui().logger.info(chalk9.gray(`Run ${command}...`));
3385
+ ui().logger.info(chalk9.gray(`Run ${command2}...`));
2917
3386
  const options2 = args;
2918
3387
  const { before, after, persist } = options2;
2919
3388
  const outputPaths = await compareReportFiles({ before, after }, persist);
@@ -2970,8 +3439,12 @@ function yargsHistoryOptionsDefinition() {
2970
3439
  return {
2971
3440
  targetBranch: {
2972
3441
  describe: "Branch to crawl history",
2973
- type: "string",
2974
- default: "main"
3442
+ type: "string"
3443
+ },
3444
+ onlySemverTags: {
3445
+ describe: "Skip commits not tagged with a semantic version",
3446
+ type: "boolean",
3447
+ default: false
2975
3448
  },
2976
3449
  forceCleanStatus: {
2977
3450
  describe: "If we reset the status to a clean git history forcefully or not.",
@@ -3003,9 +3476,68 @@ function yargsHistoryOptionsDefinition() {
3003
3476
  };
3004
3477
  }
3005
3478
 
3479
+ // packages/cli/src/lib/history/utils.ts
3480
+ async function normalizeHashOptions(processArgs) {
3481
+ const {
3482
+ onlySemverTags,
3483
+ // overwritten
3484
+ maxCount,
3485
+ ...opt
3486
+ } = processArgs;
3487
+ let { from, to, ...processOptions } = opt;
3488
+ if (!onlySemverTags) {
3489
+ if (from && isSemver(from)) {
3490
+ const { hash } = await getHashFromTag(from);
3491
+ from = hash;
3492
+ }
3493
+ if (to && isSemver(to)) {
3494
+ const { hash } = await getHashFromTag(to);
3495
+ to = hash;
3496
+ }
3497
+ }
3498
+ return {
3499
+ ...processOptions,
3500
+ onlySemverTags,
3501
+ maxCount: maxCount && maxCount > 0 ? maxCount : void 0,
3502
+ from,
3503
+ to
3504
+ };
3505
+ }
3506
+
3006
3507
  // packages/cli/src/lib/history/history-command.ts
3508
+ var command = "history";
3509
+ async function handler(args) {
3510
+ ui().logger.info(chalk10.bold(CLI_NAME));
3511
+ ui().logger.info(chalk10.gray(`Run ${command}`));
3512
+ const currentBranch = await getCurrentBranchOrTag();
3513
+ const { targetBranch: rawTargetBranch, ...opt } = args;
3514
+ const {
3515
+ targetBranch,
3516
+ from,
3517
+ to,
3518
+ maxCount,
3519
+ onlySemverTags,
3520
+ ...historyOptions
3521
+ } = await normalizeHashOptions({
3522
+ ...opt,
3523
+ targetBranch: rawTargetBranch ?? currentBranch
3524
+ });
3525
+ const filterOptions = { targetBranch, from, to, maxCount };
3526
+ const results = onlySemverTags ? await getSemverTags(filterOptions) : await getHashes(filterOptions);
3527
+ try {
3528
+ const reports = await history(
3529
+ {
3530
+ targetBranch,
3531
+ ...historyOptions
3532
+ },
3533
+ results.map(({ hash }) => hash)
3534
+ );
3535
+ ui().logger.log(`Reports: ${reports.length}`);
3536
+ } finally {
3537
+ await safeCheckout(currentBranch);
3538
+ }
3539
+ }
3007
3540
  function yargsHistoryCommandObject() {
3008
- const command = "history";
3009
3541
  return {
3010
3542
  command,
3011
3543
  describe: "Collect reports for commit history",
@@ -3020,41 +3552,15 @@ function yargsHistoryCommandObject() {
3020
3552
  );
3021
3553
  return yargs2;
3022
3554
  },
3023
- handler: async (args) => {
3024
- ui().logger.info(chalk10.bold(CLI_NAME));
3025
- ui().logger.info(chalk10.gray(`Run ${command}`));
3026
- const currentBranch = await getCurrentBranchOrTag();
3027
- const {
3028
- targetBranch = currentBranch,
3029
- forceCleanStatus,
3030
- maxCount,
3031
- from,
3032
- to,
3033
- ...restOptions
3034
- } = args;
3035
- const commits = await getHashes({ maxCount, from, to });
3036
- try {
3037
- const reports = await history(
3038
- {
3039
- ...restOptions,
3040
- targetBranch,
3041
- forceCleanStatus
3042
- },
3043
- commits
3044
- );
3045
- ui().logger.log(`Reports: ${reports.length}`);
3046
- } finally {
3047
- await safeCheckout(currentBranch);
3048
- }
3049
- }
3555
+ handler
3050
3556
  };
3051
3557
  }
3052
3558
 
3053
3559
  // packages/cli/src/lib/print-config/print-config-command.ts
3054
3560
  function yargsConfigCommandObject() {
3055
- const command = "print-config";
3561
+ const command2 = "print-config";
3056
3562
  return {
3057
- command,
3563
+ command: command2,
3058
3564
  describe: "Print config",
3059
3565
  handler: (yargsArgs) => {
3060
3566
  const { _, $0, ...args } = yargsArgs;
@@ -3067,13 +3573,13 @@ function yargsConfigCommandObject() {
3067
3573
  // packages/cli/src/lib/upload/upload-command.ts
3068
3574
  import chalk11 from "chalk";
3069
3575
  function yargsUploadCommandObject() {
3070
- const command = "upload";
3576
+ const command2 = "upload";
3071
3577
  return {
3072
- command,
3578
+ command: command2,
3073
3579
  describe: "Upload report results to the portal",
3074
3580
  handler: async (args) => {
3075
3581
  ui().logger.log(chalk11.bold(CLI_NAME));
3076
- ui().logger.info(chalk11.gray(`Run ${command}...`));
3582
+ ui().logger.info(chalk11.gray(`Run ${command2}...`));
3077
3583
  const options2 = args;
3078
3584
  if (options2.upload == null) {
3079
3585
  renderIntegratePortalHint();