@code-pushup/cli 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.
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,36 +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({ slug: slugSchema, title: titleSchema });
638
+ var scorableMetaSchema = z15.object({
639
+ slug: slugSchema,
640
+ title: titleSchema,
641
+ docsUrl: docsUrlSchema
642
+ });
595
643
  var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
596
- z14.object({
597
- plugin: pluginMetaSchema.pick({ slug: true, title: true }).describe("Plugin which defines it")
644
+ z15.object({
645
+ plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
598
646
  })
599
647
  );
600
648
  var scorableDiffSchema = scorableMetaSchema.merge(
601
- z14.object({
649
+ z15.object({
602
650
  scores: makeComparisonSchema(scoreSchema).merge(
603
- z14.object({
604
- 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`)")
605
653
  })
606
654
  ).describe("Score comparison")
607
655
  })
@@ -612,10 +660,10 @@ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
612
660
  var categoryDiffSchema = scorableDiffSchema;
613
661
  var groupDiffSchema = scorableWithPluginDiffSchema;
614
662
  var auditDiffSchema = scorableWithPluginDiffSchema.merge(
615
- z14.object({
663
+ z15.object({
616
664
  values: makeComparisonSchema(auditValueSchema).merge(
617
- z14.object({
618
- 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`)")
619
667
  })
620
668
  ).describe("Audit `value` comparison"),
621
669
  displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
@@ -624,15 +672,15 @@ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
624
672
  })
625
673
  );
626
674
  var categoryResultSchema = scorableMetaSchema.merge(
627
- z14.object({ score: scoreSchema })
675
+ z15.object({ score: scoreSchema })
628
676
  );
629
677
  var groupResultSchema = scorableWithPluginMetaSchema.merge(
630
- z14.object({ score: scoreSchema })
678
+ z15.object({ score: scoreSchema })
631
679
  );
632
680
  var auditResultSchema = scorableWithPluginMetaSchema.merge(
633
681
  auditOutputSchema.pick({ score: true, value: true, displayValue: true })
634
682
  );
635
- var reportsDiffSchema = z14.object({
683
+ var reportsDiffSchema = z15.object({
636
684
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
637
685
  categories: makeArraysComparisonSchema(
638
686
  categoryDiffSchema,
@@ -661,6 +709,291 @@ var reportsDiffSchema = z14.object({
661
709
  })
662
710
  );
663
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
+
664
997
  // packages/utils/src/lib/diff.ts
665
998
  function matchArrayItemsByKey({
666
999
  before,
@@ -778,40 +1111,48 @@ import chalk from "chalk";
778
1111
 
779
1112
  // packages/utils/src/lib/reports/constants.ts
780
1113
  var TERMINAL_WIDTH = 80;
781
- var NEW_LINE = "\n";
782
1114
  var SCORE_COLOR_RANGE = {
783
1115
  GREEN_MIN: 0.9,
784
1116
  YELLOW_MIN: 0.5
785
1117
  };
1118
+ var CATEGORIES_TITLE = "\u{1F3F7} Categories";
786
1119
  var FOOTER_PREFIX = "Made with \u2764 by";
787
1120
  var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
788
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
1121
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
789
1122
  var reportHeadlineText = "Code PushUp Report";
790
1123
  var reportOverviewTableHeaders = [
791
- "\u{1F3F7} Category",
792
- "\u2B50 Score",
793
- "\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
+ }
794
1137
  ];
795
1138
  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)"
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
+ }
815
1156
  ];
816
1157
 
817
1158
  // packages/utils/src/lib/logging.ts
@@ -837,7 +1178,7 @@ function logListItem(args) {
837
1178
  singletonisaacUi.rows = [];
838
1179
  singletonUiInstance?.logger.log(content);
839
1180
  }
840
- function link(text) {
1181
+ function link3(text) {
841
1182
  return chalk.underline(chalk.blueBright(text));
842
1183
  }
843
1184
 
@@ -904,7 +1245,7 @@ async function ensureDirectoryExists(baseDir) {
904
1245
  await mkdir(baseDir, { recursive: true });
905
1246
  return;
906
1247
  } catch (error) {
907
- ui().logger.error(error.message);
1248
+ ui().logger.info(error.message);
908
1249
  if (error.code !== "EEXIST") {
909
1250
  throw error;
910
1251
  }
@@ -940,122 +1281,35 @@ async function importEsmModule(options2) {
940
1281
  return mod.default;
941
1282
  }
942
1283
 
943
- // packages/utils/src/lib/reports/md/details.ts
944
- function details(title, content, cfg = { open: false }) {
945
- return `<details${cfg.open ? " open" : ""}>
946
- <summary>${title}</summary>
947
-
948
- ${content}
949
-
950
- </details>
951
- `;
952
- }
953
-
954
- // packages/utils/src/lib/reports/md/font-style.ts
955
- var stylesMap = {
956
- i: "_",
957
- // italic
958
- b: "**",
959
- // bold
960
- s: "~",
961
- // strike through
962
- c: "`"
963
- // code
964
- };
965
- function style(text, styles = ["b"]) {
966
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
967
- }
968
-
969
- // packages/utils/src/lib/reports/md/headline.ts
970
- function headline(text, hierarchy = 1) {
971
- return `${"#".repeat(hierarchy)} ${text}`;
972
- }
973
- function h1(text) {
974
- return headline(text, 1);
975
- }
976
- function h2(text) {
977
- return headline(text, 2);
978
- }
979
- function h3(text) {
980
- return headline(text, 3);
981
- }
982
-
983
- // packages/utils/src/lib/reports/md/image.ts
984
- function image(src, alt) {
985
- return `![${alt}](${src})`;
986
- }
987
-
988
- // packages/utils/src/lib/reports/md/link.ts
989
- function link2(href, text) {
990
- return `[${text || href}](${href})`;
991
- }
992
-
993
- // packages/utils/src/lib/reports/md/list.ts
994
- function li(text, order = "unordered") {
995
- const style2 = order === "unordered" ? "-" : "- [ ]";
996
- return `${style2} ${text}`;
997
- }
998
-
999
- // packages/utils/src/lib/reports/md/paragraphs.ts
1000
- function paragraphs(...sections) {
1001
- return sections.filter(Boolean).join("\n\n");
1002
- }
1003
-
1004
- // packages/utils/src/lib/reports/md/table.ts
1005
- var alignString = /* @__PURE__ */ new Map([
1006
- ["l", ":--"],
1007
- ["c", ":--:"],
1008
- ["r", "--:"]
1009
- ]);
1010
- function tableMd(data, align) {
1011
- if (data.length === 0) {
1012
- throw new Error("Data can't be empty");
1013
- }
1014
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1015
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1016
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1017
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1018
- }
1019
- function tableHtml(data) {
1020
- if (data.length === 0) {
1021
- throw new Error("Data can't be empty");
1022
- }
1023
- const tableContent = data.map((arr, index) => {
1024
- if (index === 0) {
1025
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1026
- return `<tr>${headerRow}</tr>`;
1027
- }
1028
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1029
- return `<tr>${row}</tr>`;
1030
- });
1031
- return `<table>${tableContent.join("")}</table>`;
1032
- }
1033
-
1034
1284
  // packages/utils/src/lib/reports/utils.ts
1285
+ var { image: image2, bold: boldMd } = md;
1035
1286
  function formatReportScore(score) {
1036
1287
  return Math.round(score * 100).toString();
1037
1288
  }
1038
1289
  function formatScoreWithColor(score, options2) {
1039
- const styledNumber = options2?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1040
- return `${getRoundScoreMarker(score)} ${styledNumber}`;
1041
- }
1042
- function getRoundScoreMarker(score) {
1043
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1044
- return "\u{1F7E2}";
1045
- }
1046
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1047
- 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}"
1048
1303
  }
1049
- return "\u{1F534}";
1050
- }
1051
- function getSquaredScoreMarker(score) {
1304
+ };
1305
+ function scoreMarker(score, markerType = "circle") {
1052
1306
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1053
- return "\u{1F7E9}";
1307
+ return MARKERS[markerType].green;
1054
1308
  }
1055
1309
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1056
- return "\u{1F7E8}";
1310
+ return MARKERS[markerType].yellow;
1057
1311
  }
1058
- return "\u{1F7E5}";
1312
+ return MARKERS[markerType].red;
1059
1313
  }
1060
1314
  function getDiffMarker(diff) {
1061
1315
  if (diff > 0) {
@@ -1071,7 +1325,7 @@ function colorByScoreDiff(text, diff) {
1071
1325
  return shieldsBadge(text, color);
1072
1326
  }
1073
1327
  function shieldsBadge(text, color) {
1074
- return image(
1328
+ return image2(
1075
1329
  `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1076
1330
  text
1077
1331
  );
@@ -1081,7 +1335,7 @@ function formatDiffNumber(diff) {
1081
1335
  const sign = diff < 0 ? "\u2212" : "+";
1082
1336
  return `${sign}${number}`;
1083
1337
  }
1084
- function getSeverityIcon(severity) {
1338
+ function severityMarker(severity) {
1085
1339
  if (severity === "error") {
1086
1340
  return "\u{1F6A8}";
1087
1341
  }
@@ -1254,12 +1508,12 @@ var ProcessError = class extends Error {
1254
1508
  }
1255
1509
  };
1256
1510
  function executeProcess(cfg) {
1257
- const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
1511
+ const { observer, cwd, command: command2, args, ignoreExitCode = false } = cfg;
1258
1512
  const { onStdout, onError, onComplete } = observer ?? {};
1259
1513
  const date = (/* @__PURE__ */ new Date()).toISOString();
1260
1514
  const start = performance.now();
1261
1515
  return new Promise((resolve, reject) => {
1262
- const process2 = spawn(command, args, { cwd, shell: true });
1516
+ const process2 = spawn(command2, args, { cwd, shell: true });
1263
1517
  let stdout = "";
1264
1518
  let stderr = "";
1265
1519
  process2.stdout.on("data", (data) => {
@@ -1272,13 +1526,13 @@ function executeProcess(cfg) {
1272
1526
  process2.on("error", (err) => {
1273
1527
  stderr += err.toString();
1274
1528
  });
1275
- process2.on("close", (code) => {
1529
+ process2.on("close", (code3) => {
1276
1530
  const timings = { date, duration: calcDuration(start) };
1277
- if (code === 0 || ignoreExitCode) {
1531
+ if (code3 === 0 || ignoreExitCode) {
1278
1532
  onComplete?.();
1279
- resolve({ code, stdout, stderr, ...timings });
1533
+ resolve({ code: code3, stdout, stderr, ...timings });
1280
1534
  } else {
1281
- const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
1535
+ const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
1282
1536
  onError?.(errorMsg);
1283
1537
  reject(errorMsg);
1284
1538
  }
@@ -1294,42 +1548,174 @@ function filterItemRefsBy(items, refFilterFn) {
1294
1548
  })).filter((item) => item.refs.length);
1295
1549
  }
1296
1550
 
1297
- // packages/utils/src/lib/git.ts
1551
+ // packages/utils/src/lib/git/git.ts
1298
1552
  import { isAbsolute, join as join2, relative } from "node:path";
1299
1553
  import { simpleGit } from "simple-git";
1300
-
1301
- // packages/utils/src/lib/transform.ts
1302
- function toArray(val) {
1303
- return Array.isArray(val) ? val : [val];
1554
+ function getGitRoot(git = simpleGit()) {
1555
+ return git.revparse("--show-toplevel");
1304
1556
  }
1305
- function objectToEntries(obj) {
1306
- return Object.entries(obj);
1557
+ function formatGitPath(path, gitRoot) {
1558
+ const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
1559
+ const relativePath = relative(gitRoot, absolutePath);
1560
+ return toUnixPath(relativePath);
1307
1561
  }
1308
- function deepClone(obj) {
1309
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1562
+ var GitStatusError = class _GitStatusError extends Error {
1563
+ static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
1564
+ static getReducedStatus(status) {
1565
+ return Object.fromEntries(
1566
+ Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
1567
+ (entry) => {
1568
+ const value = entry[1];
1569
+ if (value == null) {
1570
+ return false;
1571
+ }
1572
+ if (Array.isArray(value) && value.length === 0) {
1573
+ return false;
1574
+ }
1575
+ if (typeof value === "number" && value === 0) {
1576
+ return false;
1577
+ }
1578
+ return !(typeof value === "boolean" && !value);
1579
+ }
1580
+ )
1581
+ );
1582
+ }
1583
+ constructor(status) {
1584
+ super(
1585
+ `Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
1586
+ ${JSON.stringify(
1587
+ _GitStatusError.getReducedStatus(status),
1588
+ null,
1589
+ 2
1590
+ )}`
1591
+ );
1592
+ }
1593
+ };
1594
+ async function guardAgainstLocalChanges(git = simpleGit()) {
1595
+ const status = await git.status(["-s"]);
1596
+ if (status.files.length > 0) {
1597
+ throw new GitStatusError(status);
1598
+ }
1310
1599
  }
1311
- function toUnixPath(path) {
1312
- return path.replace(/\\/g, "/");
1600
+ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1601
+ if (forceCleanStatus) {
1602
+ await git.raw(["reset", "--hard"]);
1603
+ await git.clean(["f", "d"]);
1604
+ ui().logger.info(`git status cleaned`);
1605
+ }
1606
+ await guardAgainstLocalChanges(git);
1607
+ await git.checkout(branchOrHash);
1313
1608
  }
1314
1609
 
1315
- // packages/utils/src/lib/git.ts
1316
- async function getLatestCommit(git = simpleGit()) {
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()) {
1317
1630
  const log2 = await git.log({
1318
1631
  maxCount: 1,
1632
+ // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1319
1633
  format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1320
1634
  });
1321
- if (!log2.latest) {
1322
- return null;
1323
- }
1324
1635
  return commitSchema.parse(log2.latest);
1325
1636
  }
1326
- function getGitRoot(git = simpleGit()) {
1327
- return git.revparse("--show-toplevel");
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());
1328
1641
  }
1329
- function formatGitPath(path, gitRoot) {
1330
- const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
1331
- const relativePath = relative(gitRoot, absolutePath);
1332
- return toUnixPath(relativePath);
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];
1333
1719
  }
1334
1720
 
1335
1721
  // packages/utils/src/lib/group-by-status.ts
@@ -1407,44 +1793,65 @@ function listAuditsFromAllPlugins(report) {
1407
1793
  );
1408
1794
  }
1409
1795
 
1410
- // packages/utils/src/lib/reports/generate-md-report.ts
1411
- function generateMdReport(report) {
1412
- const printCategories = report.categories.length > 0;
1413
- return (
1414
- // header section
1415
- // eslint-disable-next-line prefer-template
1416
- reportToHeaderSection() + NEW_LINE + // categories overview section
1417
- (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1418
- (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1419
- reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
1420
- reportToAboutSection(report) + NEW_LINE + NEW_LINE + // footer section
1421
- `${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)
1422
1807
  );
1423
1808
  }
1424
- function reportToHeaderSection() {
1425
- 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 "";
1426
1825
  }
1427
- 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) {
1428
1830
  const { categories, plugins } = report;
1429
- const tableContent = [
1430
- reportOverviewTableHeaders,
1431
- ...categories.map(({ title, refs, score }) => [
1432
- link2(`#${slugify(title)}`, title),
1433
- `${getRoundScoreMarker(score)} ${style(formatReportScore(score))}`,
1434
- countCategoryAudits(refs, plugins).toString()
1435
- ])
1436
- ];
1437
- 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 "";
1438
1846
  }
1439
- function reportToCategoriesSection(report) {
1847
+ function categoriesDetailsSection(report) {
1440
1848
  const { categories, plugins } = report;
1441
- const categoryDetails = categories.reduce((acc, category) => {
1442
- const categoryTitle = h3(category.title);
1443
- const categoryScore = `${getRoundScoreMarker(
1849
+ const categoryDetails = categories.flatMap((category) => {
1850
+ const categoryTitle = h32(category.title);
1851
+ const categoryScore = `${scoreMarker(
1444
1852
  category.score
1445
- )} Score: ${style(formatReportScore(category.score))}`;
1446
- const categoryDocs = getDocsAndDescription(category);
1447
- const categoryMDItems = category.refs.reduce((refAcc, ref) => {
1853
+ )}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
1854
+ const categoryMDItems = category.refs.map((ref) => {
1448
1855
  if (ref.type === "group") {
1449
1856
  const group = getSortableGroupByRef(ref, plugins);
1450
1857
  const groupAudits = group.refs.map(
@@ -1454,151 +1861,240 @@ function reportToCategoriesSection(report) {
1454
1861
  )
1455
1862
  );
1456
1863
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1457
- const mdGroupItem = groupItemToCategorySection(
1458
- group,
1459
- groupAudits,
1460
- pluginTitle
1461
- );
1462
- return refAcc + mdGroupItem + NEW_LINE;
1864
+ return categoryGroupItem(group, groupAudits, pluginTitle);
1463
1865
  } else {
1464
1866
  const audit = getSortableAuditByRef(ref, plugins);
1465
1867
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1466
- const mdAuditItem = auditItemToCategorySection(audit, pluginTitle);
1467
- return refAcc + mdAuditItem + NEW_LINE;
1868
+ return categoryRef(audit, pluginTitle);
1468
1869
  }
1469
- }, "");
1470
- return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + categoryMDItems;
1471
- }, "");
1472
- return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1473
- }
1474
- function auditItemToCategorySection(audit, pluginTitle) {
1475
- const auditTitle = link2(
1476
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1477
- audit.title
1478
- );
1479
- return li(
1480
- `${getSquaredScoreMarker(
1481
- audit.score
1482
- )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`
1483
- );
1484
- }
1485
- function groupItemToCategorySection(group, groupAudits, pluginTitle) {
1486
- const groupScore = Number(formatReportScore(group.score || 0));
1487
- const groupTitle = li(
1488
- `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`
1489
- );
1490
- const auditTitles = groupAudits.reduce((acc, audit) => {
1491
- const auditTitle = link2(
1492
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1493
- audit.title
1870
+ });
1871
+ return section3(
1872
+ categoryTitle,
1873
+ metaDescription(category),
1874
+ categoryScore,
1875
+ ...categoryMDItems
1494
1876
  );
1495
- return `${acc} ${li(
1496
- `${getSquaredScoreMarker(audit.score)} ${auditTitle} - ${getAuditResult(
1497
- audit
1498
- )}`
1499
- )}${NEW_LINE}`;
1500
- }, "");
1501
- return groupTitle + NEW_LINE + auditTitles;
1502
- }
1503
- function reportToAuditsSection(report) {
1504
- const auditsSection = report.plugins.reduce((pluginAcc, plugin) => {
1505
- const auditsData = plugin.audits.reduce((auditAcc, audit) => {
1506
- const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1507
- plugin.slug,
1508
- report.plugins
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
+ )}`
1890
+ );
1891
+ }
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
+ );
1910
+ }
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 "";
1941
+ }
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
+ );
1975
+ }
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
1509
1984
  )})`;
1510
- return auditAcc + h3(auditTitle) + NEW_LINE + NEW_LINE + reportToDetailsSection(audit) + NEW_LINE + NEW_LINE + getDocsAndDescription(audit);
1511
- }, "");
1512
- return pluginAcc + auditsData;
1513
- }, "");
1514
- return h2("\u{1F6E1}\uFE0F Audits") + NEW_LINE + NEW_LINE + auditsSection;
1515
- }
1516
- function reportToDetailsSection(audit) {
1517
- const detailsTitle = `${getSquaredScoreMarker(audit.score)} ${getAuditResult(
1518
- audit,
1519
- true
1520
- )} (score: ${formatReportScore(audit.score)})`;
1521
- if (!audit.details?.issues.length) {
1522
- return detailsTitle;
1523
- }
1524
- const detailsTableData = [
1525
- detailsTableHeaders,
1526
- ...audit.details.issues.map((issue) => {
1527
- const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1528
- const message = issue.message;
1529
- if (!issue.source) {
1530
- return [severity, message, "", ""];
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"
1531
2067
  }
1532
- const file = `<code>${issue.source.file}</code>`;
1533
- if (!issue.source.position) {
1534
- return [severity, message, file, ""];
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()
1535
2077
  }
1536
- const { startLine, endLine } = issue.source.position;
1537
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1538
- return [severity, message, file, line];
1539
- })
1540
- ];
1541
- const detailsTable = `<h4>Issues</h4>${tableHtml(detailsTableData)}`;
1542
- return details(detailsTitle, detailsTable);
1543
- }
1544
- function reportToAboutSection(report) {
1545
- const date = formatDate(/* @__PURE__ */ new Date());
1546
- const { duration, version: version2, commit, plugins, categories } = report;
1547
- const commitInfo = commit ? `${commit.message} (${commit.hash})` : "N/A";
1548
- const reportMetaTable = [
1549
- reportMetaTableHeaders,
1550
- [
1551
- commitInfo,
1552
- style(version2 || "", ["c"]),
1553
- formatDuration(duration),
1554
- plugins.length.toString(),
1555
- categories.length.toString(),
1556
- plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1557
2078
  ]
1558
- ];
1559
- const pluginMetaTable = [
1560
- pluginMetaTableHeaders,
1561
- ...plugins.map((plugin) => [
1562
- plugin.title,
1563
- plugin.audits.length.toString(),
1564
- style(plugin.version || "", ["c"]),
1565
- formatDuration(plugin.duration)
1566
- ])
1567
- ];
1568
- return (
1569
- // eslint-disable-next-line prefer-template
1570
- 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"])
1571
- );
1572
- }
1573
- function getDocsAndDescription({
1574
- docsUrl,
1575
- description
1576
- }) {
1577
- if (docsUrl) {
1578
- const docsLink = link2(docsUrl, "\u{1F4D6} Docs");
1579
- if (!description) {
1580
- return docsLink + NEW_LINE + NEW_LINE;
1581
- }
1582
- if (description.endsWith("```")) {
1583
- return description + NEW_LINE + NEW_LINE + docsLink + NEW_LINE + NEW_LINE;
1584
- }
1585
- return `${description} ${docsLink}${NEW_LINE}${NEW_LINE}`;
1586
- }
1587
- if (description) {
1588
- return description + NEW_LINE + NEW_LINE;
1589
- }
1590
- return "";
1591
- }
1592
- function getAuditResult(audit, isHtml = false) {
1593
- const { displayValue, value } = audit;
1594
- return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
2079
+ };
1595
2080
  }
1596
2081
 
1597
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;
1598
2094
  var MAX_ROWS = 100;
1599
2095
  function generateMdReportsDiff(diff) {
1600
- return paragraphs(
1601
- formatDiffHeaderSection(diff),
2096
+ return lines5(
2097
+ section5(formatDiffHeaderSection(diff)),
1602
2098
  formatDiffCategoriesSection(diff),
1603
2099
  formatDiffGroupsSection(diff),
1604
2100
  formatDiffAuditsSection(diff)
@@ -1606,12 +2102,12 @@ function generateMdReportsDiff(diff) {
1606
2102
  }
1607
2103
  function formatDiffHeaderSection(diff) {
1608
2104
  const outcomeTexts = {
1609
- positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1610
- negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1611
- 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(
1612
2108
  "improvements and regressions"
1613
2109
  )}`,
1614
- unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
2110
+ unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
1615
2111
  };
1616
2112
  const outcome = mergeDiffOutcomes(
1617
2113
  changesToDiffOutcomes([
@@ -1621,8 +2117,8 @@ function formatDiffHeaderSection(diff) {
1621
2117
  ])
1622
2118
  );
1623
2119
  const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1624
- return paragraphs(
1625
- h1("Code PushUp"),
2120
+ return lines5(
2121
+ h13("Code PushUp"),
1626
2122
  diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1627
2123
  );
1628
2124
  }
@@ -1633,102 +2129,104 @@ function formatDiffCategoriesSection(diff) {
1633
2129
  if (categoriesCount === 0) {
1634
2130
  return "";
1635
2131
  }
1636
- return paragraphs(
1637
- h2("\u{1F3F7}\uFE0F Categories"),
1638
- categoriesCount > 0 && tableMd(
1639
- [
1640
- [
1641
- "\u{1F3F7}\uFE0F Category",
1642
- hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1643
- "\u2B50 Previous score",
1644
- "\u{1F504} Score change"
1645
- ],
1646
- ...sortChanges(changed).map((category) => [
1647
- category.title,
1648
- formatScoreWithColor(category.scores.after),
1649
- formatScoreWithColor(category.scores.before, { skipBold: true }),
1650
- formatScoreChange(category.scores.diff)
1651
- ]),
1652
- ...added.map((category) => [
1653
- category.title,
1654
- formatScoreWithColor(category.score),
1655
- style("n/a (\\*)", ["i"]),
1656
- style("n/a (\\*)", ["i"])
1657
- ]),
1658
- ...unchanged.map((category) => [
1659
- category.title,
1660
- formatScoreWithColor(category.score),
1661
- formatScoreWithColor(category.score, { skipBold: true }),
1662
- "\u2013"
1663
- ])
1664
- ].map((row) => hasChanges ? row : row.slice(0, 2)),
1665
- hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1666
- ),
1667
- 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."))
1668
2168
  );
1669
2169
  }
1670
2170
  function formatDiffGroupsSection(diff) {
1671
2171
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1672
2172
  return "";
1673
2173
  }
1674
- return paragraphs(
1675
- h2("\u{1F5C3}\uFE0F Groups"),
2174
+ return lines5(
2175
+ h24("\u{1F5C3}\uFE0F Groups"),
1676
2176
  formatGroupsOrAuditsDetails("group", diff.groups, {
1677
- headings: [
1678
- "\u{1F50C} Plugin",
1679
- "\u{1F5C3}\uFE0F Group",
1680
- "\u2B50 Current score",
1681
- "\u2B50 Previous score",
1682
- "\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" }
1683
2183
  ],
1684
- rows: sortChanges(diff.groups.changed).map((group) => [
1685
- group.plugin.title,
1686
- group.title,
1687
- formatScoreWithColor(group.scores.after),
1688
- formatScoreWithColor(group.scores.before, { skipBold: true }),
1689
- formatScoreChange(group.scores.diff)
1690
- ]),
1691
- 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
+ }))
1692
2191
  })
1693
2192
  );
1694
2193
  }
1695
2194
  function formatDiffAuditsSection(diff) {
1696
- return paragraphs(
1697
- h2("\u{1F6E1}\uFE0F Audits"),
2195
+ return lines5(
2196
+ h24("\u{1F6E1}\uFE0F Audits"),
1698
2197
  formatGroupsOrAuditsDetails("audit", diff.audits, {
1699
- headings: [
1700
- "\u{1F50C} Plugin",
1701
- "\u{1F6E1}\uFE0F Audit",
1702
- "\u{1F4CF} Current value",
1703
- "\u{1F4CF} Previous value",
1704
- "\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" }
1705
2204
  ],
1706
- rows: sortChanges(diff.audits.changed).map((audit) => [
1707
- audit.plugin.title,
1708
- audit.title,
1709
- `${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(
1710
2209
  audit.displayValues.after || audit.values.after.toString()
1711
2210
  )}`,
1712
- `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1713
- formatValueChange(audit)
1714
- ]),
1715
- 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
+ }))
1716
2214
  })
1717
2215
  );
1718
2216
  }
1719
- function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1720
- 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(
1721
2219
  summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1722
- paragraphs(
1723
- tableMd(
1724
- [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1725
- table.align
1726
- ),
1727
- 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(
1728
2227
  `Only the ${MAX_ROWS} most affected ${pluralize(
1729
2228
  token
1730
- )} are listed above for brevity.`,
1731
- ["i"]
2229
+ )} are listed above for brevity.`
1732
2230
  ),
1733
2231
  unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1734
2232
  )
@@ -1749,11 +2247,13 @@ function formatValueChange({
1749
2247
  return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1750
2248
  }
1751
2249
  function summarizeUnchanged(token, { changed, unchanged }) {
1752
- return [
1753
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1754
- unchanged.length === 1 ? "is" : "are",
1755
- "unchanged."
1756
- ].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
+ );
1757
2257
  }
1758
2258
  function summarizeDiffOutcomes(outcomes, token) {
1759
2259
  return objectToEntries(countDiffOutcomes(outcomes)).filter(
@@ -1773,6 +2273,15 @@ function summarizeDiffOutcomes(outcomes, token) {
1773
2273
  }
1774
2274
  }).join(", ");
1775
2275
  }
2276
+ function formatTitle({
2277
+ title,
2278
+ docsUrl
2279
+ }) {
2280
+ if (docsUrl) {
2281
+ return link7(docsUrl, title);
2282
+ }
2283
+ return title;
2284
+ }
1776
2285
  function sortChanges(changes) {
1777
2286
  return [...changes].sort(
1778
2287
  (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
@@ -1820,7 +2329,7 @@ function log(msg = "") {
1820
2329
  }
1821
2330
  function logStdoutSummary(report) {
1822
2331
  const printCategories = report.categories.length > 0;
1823
- log(reportToHeaderSection2(report));
2332
+ log(reportToHeaderSection(report));
1824
2333
  log();
1825
2334
  logPlugins(report);
1826
2335
  if (printCategories) {
@@ -1829,7 +2338,7 @@ function logStdoutSummary(report) {
1829
2338
  log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1830
2339
  log();
1831
2340
  }
1832
- function reportToHeaderSection2(report) {
2341
+ function reportToHeaderSection(report) {
1833
2342
  const { packageName, version: version2 } = report;
1834
2343
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1835
2344
  }
@@ -1869,16 +2378,16 @@ function logCategories({ categories, plugins }) {
1869
2378
  applyScoreColor({ score }),
1870
2379
  countCategoryAudits(refs, plugins)
1871
2380
  ]);
1872
- const table = ui().table();
1873
- table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1874
- table.head(
2381
+ const table5 = ui().table();
2382
+ table5.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2383
+ table5.head(
1875
2384
  reportRawOverviewTableHeaders.map((heading, idx) => ({
1876
2385
  content: chalk4.cyan(heading),
1877
2386
  hAlign: hAlign(idx)
1878
2387
  }))
1879
2388
  );
1880
2389
  rows.forEach(
1881
- (row) => table.row(
2390
+ (row) => table5.row(
1882
2391
  row.map((content, idx) => ({
1883
2392
  content: content.toString(),
1884
2393
  hAlign: hAlign(idx)
@@ -1887,19 +2396,19 @@ function logCategories({ categories, plugins }) {
1887
2396
  );
1888
2397
  log(chalk4.magentaBright.bold("Categories"));
1889
2398
  log();
1890
- table.render();
2399
+ table5.render();
1891
2400
  log();
1892
2401
  }
1893
2402
  function applyScoreColor({ score, text }) {
1894
2403
  const formattedScore = text ?? formatReportScore(score);
1895
- const style2 = text ? chalk4 : chalk4.bold;
2404
+ const style = text ? chalk4 : chalk4.bold;
1896
2405
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1897
- return style2.green(formattedScore);
2406
+ return style.green(formattedScore);
1898
2407
  }
1899
2408
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1900
- return style2.yellow(formattedScore);
2409
+ return style.yellow(formattedScore);
1901
2410
  }
1902
- return style2.red(formattedScore);
2411
+ return style.red(formattedScore);
1903
2412
  }
1904
2413
 
1905
2414
  // packages/utils/src/lib/reports/scoring.ts
@@ -2061,12 +2570,22 @@ var verboseUtils = (verbose = false) => ({
2061
2570
 
2062
2571
  // packages/core/package.json
2063
2572
  var name = "@code-pushup/core";
2064
- var version = "0.35.0";
2573
+ var version = "0.42.0";
2065
2574
 
2066
2575
  // packages/core/src/lib/implementation/execute-plugin.ts
2067
2576
  import chalk5 from "chalk";
2068
2577
 
2069
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
+ }
2070
2589
  async function normalizeAuditOutputs(audits) {
2071
2590
  const gitRoot = await getGitRoot();
2072
2591
  return audits.map((audit) => {
@@ -2078,13 +2597,7 @@ async function normalizeAuditOutputs(audits) {
2078
2597
  details: {
2079
2598
  ...audit.details,
2080
2599
  issues: audit.details.issues.map(
2081
- (issue) => issue.source == null ? issue : {
2082
- ...issue,
2083
- source: {
2084
- ...issue.source,
2085
- file: formatGitPath(issue.source.file, gitRoot)
2086
- }
2087
- }
2600
+ (issue) => normalizeIssue(issue, gitRoot)
2088
2601
  )
2089
2602
  }
2090
2603
  };
@@ -2094,9 +2607,9 @@ async function normalizeAuditOutputs(audits) {
2094
2607
  // packages/core/src/lib/implementation/runner.ts
2095
2608
  import { join as join3 } from "node:path";
2096
2609
  async function executeRunnerConfig(cfg, onProgress) {
2097
- const { args, command, outputFile, outputTransform } = cfg;
2610
+ const { args, command: command2, outputFile, outputTransform } = cfg;
2098
2611
  const { duration, date } = await executeProcess({
2099
- command,
2612
+ command: command2,
2100
2613
  args,
2101
2614
  observer: { onStdout: onProgress }
2102
2615
  });
@@ -2122,7 +2635,11 @@ async function executeRunnerFunction(runner, onProgress) {
2122
2635
  // packages/core/src/lib/implementation/execute-plugin.ts
2123
2636
  var PluginOutputMissingAuditError = class extends Error {
2124
2637
  constructor(auditSlug) {
2125
- 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
+ );
2126
2643
  }
2127
2644
  };
2128
2645
  async function executePlugin(pluginConfig, onProgress) {
@@ -2136,7 +2653,11 @@ async function executePlugin(pluginConfig, onProgress) {
2136
2653
  } = pluginConfig;
2137
2654
  const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
2138
2655
  const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
2139
- 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;
2140
2661
  auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
2141
2662
  const normalizedAuditOutputs = await normalizeAuditOutputs(auditOutputs);
2142
2663
  const auditReports = normalizedAuditOutputs.map(
@@ -2156,32 +2677,48 @@ async function executePlugin(pluginConfig, onProgress) {
2156
2677
  ...groups2 && { groups: groups2 }
2157
2678
  };
2158
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
+ };
2159
2696
  async function executePlugins(plugins, options2) {
2160
2697
  const { progress = false } = options2 ?? {};
2161
2698
  const progressBar = progress ? getProgressBar("Run plugins") : null;
2162
- const pluginsResult = await plugins.reduce(async (acc, pluginCfg) => {
2163
- progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2164
- try {
2165
- const pluginReport = await executePlugin(pluginCfg);
2166
- progressBar?.incrementInSteps(plugins.length);
2167
- return [...await acc, Promise.resolve(pluginReport)];
2168
- } catch (error) {
2169
- progressBar?.incrementInSteps(plugins.length);
2170
- return [
2171
- ...await acc,
2172
- Promise.reject(error instanceof Error ? error.message : String(error))
2173
- ];
2174
- }
2175
- }, 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
+ );
2176
2706
  progressBar?.endProgress("Done running plugins");
2177
2707
  const errorsTransform = ({ reason }) => String(reason);
2178
2708
  const results = await Promise.allSettled(pluginsResult);
2179
2709
  logMultipleResults(results, "Plugins", void 0, errorsTransform);
2180
2710
  const { fulfilled, rejected } = groupByStatus(results);
2181
2711
  if (rejected.length > 0) {
2182
- const errorMessages = rejected.map(({ reason }) => String(reason)).join(", ");
2712
+ const errorMessages = rejected.map(({ reason }) => String(reason)).join("\n");
2183
2713
  throw new Error(
2184
- `Plugins failed: ${rejected.length} errors: ${errorMessages}`
2714
+ `Executing ${pluralizeToken(
2715
+ "plugin",
2716
+ rejected.length
2717
+ )} failed.
2718
+
2719
+ ${errorMessages}
2720
+
2721
+ `
2185
2722
  );
2186
2723
  }
2187
2724
  return fulfilled.map((result) => result.value);
@@ -2344,8 +2881,7 @@ function compareAudits2(reports) {
2344
2881
  }
2345
2882
  function categoryToResult(category) {
2346
2883
  return {
2347
- slug: category.slug,
2348
- title: category.title,
2884
+ ...selectMeta(category),
2349
2885
  score: category.score
2350
2886
  };
2351
2887
  }
@@ -2354,8 +2890,7 @@ function categoryPairToDiff({
2354
2890
  after
2355
2891
  }) {
2356
2892
  return {
2357
- slug: after.slug,
2358
- title: after.title,
2893
+ ...selectMeta(after),
2359
2894
  scores: {
2360
2895
  before: before.score,
2361
2896
  after: after.score,
@@ -2365,12 +2900,8 @@ function categoryPairToDiff({
2365
2900
  }
2366
2901
  function pluginGroupToResult({ group, plugin }) {
2367
2902
  return {
2368
- slug: group.slug,
2369
- title: group.title,
2370
- plugin: {
2371
- slug: plugin.slug,
2372
- title: plugin.title
2373
- },
2903
+ ...selectMeta(group),
2904
+ plugin: selectMeta(plugin),
2374
2905
  score: group.score
2375
2906
  };
2376
2907
  }
@@ -2379,12 +2910,8 @@ function pluginGroupPairToDiff({
2379
2910
  after
2380
2911
  }) {
2381
2912
  return {
2382
- slug: after.group.slug,
2383
- title: after.group.title,
2384
- plugin: {
2385
- slug: after.plugin.slug,
2386
- title: after.plugin.title
2387
- },
2913
+ ...selectMeta(after.group),
2914
+ plugin: selectMeta(after.plugin),
2388
2915
  scores: {
2389
2916
  before: before.group.score,
2390
2917
  after: after.group.score,
@@ -2394,12 +2921,8 @@ function pluginGroupPairToDiff({
2394
2921
  }
2395
2922
  function pluginAuditToResult({ audit, plugin }) {
2396
2923
  return {
2397
- slug: audit.slug,
2398
- title: audit.title,
2399
- plugin: {
2400
- slug: plugin.slug,
2401
- title: plugin.title
2402
- },
2924
+ ...selectMeta(audit),
2925
+ plugin: selectMeta(plugin),
2403
2926
  score: audit.score,
2404
2927
  value: audit.value,
2405
2928
  displayValue: audit.displayValue
@@ -2410,12 +2933,8 @@ function pluginAuditPairToDiff({
2410
2933
  after
2411
2934
  }) {
2412
2935
  return {
2413
- slug: after.audit.slug,
2414
- title: after.audit.title,
2415
- plugin: {
2416
- slug: after.plugin.slug,
2417
- title: after.plugin.title
2418
- },
2936
+ ...selectMeta(after.audit),
2937
+ plugin: selectMeta(after.plugin),
2419
2938
  scores: {
2420
2939
  before: before.audit.score,
2421
2940
  after: after.audit.score,
@@ -2432,6 +2951,15 @@ function pluginAuditPairToDiff({
2432
2951
  }
2433
2952
  };
2434
2953
  }
2954
+ function selectMeta(meta) {
2955
+ return {
2956
+ slug: meta.slug,
2957
+ title: meta.title,
2958
+ ...meta.docsUrl && {
2959
+ docsUrl: meta.docsUrl
2960
+ }
2961
+ };
2962
+ }
2435
2963
 
2436
2964
  // packages/core/src/lib/compare.ts
2437
2965
  async function compareReportFiles(inputPaths, persistConfig) {
@@ -2487,46 +3015,6 @@ function reportsDiffToFileContent(reportsDiff, format) {
2487
3015
  }
2488
3016
  }
2489
3017
 
2490
- // packages/core/src/lib/implementation/read-rc-file.ts
2491
- import { join as join6 } from "node:path";
2492
- var ConfigPathError = class extends Error {
2493
- constructor(configPath) {
2494
- super(`Provided path '${configPath}' is not valid.`);
2495
- }
2496
- };
2497
- async function readRcByPath(filepath, tsconfig) {
2498
- if (filepath.length === 0) {
2499
- throw new Error("The path to the configuration file is empty.");
2500
- }
2501
- if (!await fileExists(filepath)) {
2502
- throw new ConfigPathError(filepath);
2503
- }
2504
- const cfg = await importEsmModule({ filepath, tsconfig });
2505
- return coreConfigSchema.parse(cfg);
2506
- }
2507
- async function autoloadRc(tsconfig) {
2508
- let ext = "";
2509
- for (const extension of SUPPORTED_CONFIG_FILE_FORMATS) {
2510
- const path = `${CONFIG_FILE_NAME}.${extension}`;
2511
- const exists2 = await fileExists(path);
2512
- if (exists2) {
2513
- ext = extension;
2514
- break;
2515
- }
2516
- }
2517
- if (!ext) {
2518
- throw new Error(
2519
- `No file ${CONFIG_FILE_NAME}.(${SUPPORTED_CONFIG_FILE_FORMATS.join(
2520
- "|"
2521
- )}) present in ${process.cwd()}`
2522
- );
2523
- }
2524
- return readRcByPath(
2525
- join6(process.cwd(), `${CONFIG_FILE_NAME}.${ext}`),
2526
- tsconfig
2527
- );
2528
- }
2529
-
2530
3018
  // packages/core/src/lib/upload.ts
2531
3019
  import {
2532
3020
  uploadToPortal
@@ -2572,17 +3060,33 @@ function groupToGQL(group) {
2572
3060
  };
2573
3061
  }
2574
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 ?? {};
2575
3077
  return {
2576
- slug: audit.slug,
2577
- title: audit.title,
2578
- description: audit.description,
2579
- docsUrl: audit.docsUrl,
2580
- score: audit.score,
2581
- value: audit.value,
2582
- formattedValue: audit.displayValue,
2583
- ...audit.details && {
3078
+ slug,
3079
+ title,
3080
+ description,
3081
+ docsUrl,
3082
+ score,
3083
+ value,
3084
+ formattedValue,
3085
+ ...details4 && {
2584
3086
  details: {
2585
- 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} : {}),
2586
3090
  }
2587
3091
  }
2588
3092
  };
@@ -2606,6 +3110,7 @@ function categoryToGQL(category) {
2606
3110
  slug: category.slug,
2607
3111
  title: category.title,
2608
3112
  description: category.description,
3113
+ isBinary: category.isBinary,
2609
3114
  refs: category.refs.map((ref) => ({
2610
3115
  plugin: ref.plugin,
2611
3116
  type: categoryRefTypeToGQL(ref.type),
@@ -2655,6 +3160,80 @@ async function upload(options2, uploadFn = uploadToPortal) {
2655
3160
  return uploadFn({ apiKey, server, data, timeout });
2656
3161
  }
2657
3162
 
3163
+ // packages/core/src/lib/history.ts
3164
+ async function history(config, commits) {
3165
+ const initialBranch = await getCurrentBranchOrTag();
3166
+ const { skipUploads = false, forceCleanStatus, persist } = config;
3167
+ const reports = [];
3168
+ for (const commit of commits) {
3169
+ ui().logger.info(`Collect ${commit}`);
3170
+ await safeCheckout(commit, forceCleanStatus);
3171
+ const currentConfig = {
3172
+ ...config,
3173
+ persist: {
3174
+ ...persist,
3175
+ format: ["json"],
3176
+ filename: `${commit}-report`
3177
+ }
3178
+ };
3179
+ await collectAndPersistReports(currentConfig);
3180
+ if (skipUploads) {
3181
+ ui().logger.info("Upload is skipped because skipUploads is set to true.");
3182
+ } else {
3183
+ if (currentConfig.upload) {
3184
+ await upload(currentConfig);
3185
+ } else {
3186
+ ui().logger.info(
3187
+ "Upload is skipped because upload config is undefined."
3188
+ );
3189
+ }
3190
+ }
3191
+ reports.push(currentConfig.persist.filename);
3192
+ }
3193
+ await safeCheckout(initialBranch, forceCleanStatus);
3194
+ return reports;
3195
+ }
3196
+
3197
+ // packages/core/src/lib/implementation/read-rc-file.ts
3198
+ import { join as join6 } from "node:path";
3199
+ var ConfigPathError = class extends Error {
3200
+ constructor(configPath) {
3201
+ super(`Provided path '${configPath}' is not valid.`);
3202
+ }
3203
+ };
3204
+ async function readRcByPath(filepath, tsconfig) {
3205
+ if (filepath.length === 0) {
3206
+ throw new Error("The path to the configuration file is empty.");
3207
+ }
3208
+ if (!await fileExists(filepath)) {
3209
+ throw new ConfigPathError(filepath);
3210
+ }
3211
+ const cfg = await importEsmModule({ filepath, tsconfig });
3212
+ return coreConfigSchema.parse(cfg);
3213
+ }
3214
+ async function autoloadRc(tsconfig) {
3215
+ let ext = "";
3216
+ for (const extension of SUPPORTED_CONFIG_FILE_FORMATS) {
3217
+ const path = `${CONFIG_FILE_NAME}.${extension}`;
3218
+ const exists2 = await fileExists(path);
3219
+ if (exists2) {
3220
+ ext = extension;
3221
+ break;
3222
+ }
3223
+ }
3224
+ if (!ext) {
3225
+ throw new Error(
3226
+ `No file ${CONFIG_FILE_NAME}.(${SUPPORTED_CONFIG_FILE_FORMATS.join(
3227
+ "|"
3228
+ )}) present in ${process.cwd()}`
3229
+ );
3230
+ }
3231
+ return readRcByPath(
3232
+ join6(process.cwd(), `${CONFIG_FILE_NAME}.${ext}`),
3233
+ tsconfig
3234
+ );
3235
+ }
3236
+
2658
3237
  // packages/cli/src/lib/constants.ts
2659
3238
  var CLI_NAME = "Code PushUp CLI";
2660
3239
  var CLI_SCRIPT_NAME = "code-pushup";
@@ -2664,7 +3243,7 @@ import chalk6 from "chalk";
2664
3243
  function renderConfigureCategoriesHint() {
2665
3244
  ui().logger.info(
2666
3245
  chalk6.gray(
2667
- `\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(
2668
3247
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md"
2669
3248
  )}`
2670
3249
  )
@@ -2672,7 +3251,7 @@ function renderConfigureCategoriesHint() {
2672
3251
  }
2673
3252
  function uploadSuccessfulLog(url) {
2674
3253
  ui().logger.success("Upload successful!");
2675
- ui().logger.success(link(url));
3254
+ ui().logger.success(link3(url));
2676
3255
  }
2677
3256
  function collectSuccessfulLog() {
2678
3257
  ui().logger.success("Collecting report successful!");
@@ -2683,15 +3262,15 @@ function renderIntegratePortalHint() {
2683
3262
  "npx code-pushup upload"
2684
3263
  )}`
2685
3264
  ).add(
2686
- ` ${link(
3265
+ ` ${link3(
2687
3266
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
2688
3267
  )}`
2689
3268
  ).add(
2690
- `${chalk6.gray("\u276F")} ${chalk6.gray("Portal Integration")} - ${link(
3269
+ `${chalk6.gray("\u276F")} ${chalk6.gray("Portal Integration")} - ${link3(
2691
3270
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
2692
3271
  )}`
2693
3272
  ).add(
2694
- `${chalk6.gray("\u276F")} ${chalk6.gray("Upload Command")} - ${link(
3273
+ `${chalk6.gray("\u276F")} ${chalk6.gray("Upload Command")} - ${link3(
2695
3274
  "https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
2696
3275
  )}`
2697
3276
  ).render();
@@ -2699,13 +3278,13 @@ function renderIntegratePortalHint() {
2699
3278
 
2700
3279
  // packages/cli/src/lib/autorun/autorun-command.ts
2701
3280
  function yargsAutorunCommandObject() {
2702
- const command = "autorun";
3281
+ const command2 = "autorun";
2703
3282
  return {
2704
- command,
3283
+ command: command2,
2705
3284
  describe: "Shortcut for running collect followed by upload",
2706
3285
  handler: async (args) => {
2707
3286
  ui().logger.log(chalk7.bold(CLI_NAME));
2708
- ui().logger.info(chalk7.gray(`Run ${command}...`));
3287
+ ui().logger.info(chalk7.gray(`Run ${command2}...`));
2709
3288
  const options2 = args;
2710
3289
  const optionsWithFormat = {
2711
3290
  ...options2,
@@ -2735,14 +3314,14 @@ function yargsAutorunCommandObject() {
2735
3314
  // packages/cli/src/lib/collect/collect-command.ts
2736
3315
  import chalk8 from "chalk";
2737
3316
  function yargsCollectCommandObject() {
2738
- const command = "collect";
3317
+ const command2 = "collect";
2739
3318
  return {
2740
- command,
3319
+ command: command2,
2741
3320
  describe: "Run Plugins and collect results",
2742
3321
  handler: async (args) => {
2743
3322
  const options2 = args;
2744
3323
  ui().logger.log(chalk8.bold(CLI_NAME));
2745
- ui().logger.info(chalk8.gray(`Run ${command}...`));
3324
+ ui().logger.info(chalk8.gray(`Run ${command2}...`));
2746
3325
  await collectAndPersistReports(options2);
2747
3326
  collectSuccessfulLog();
2748
3327
  if (options2.categories.length === 0) {
@@ -2761,7 +3340,7 @@ function renderUploadAutorunHint() {
2761
3340
  "Run upload to upload the created report to the server"
2762
3341
  )}`
2763
3342
  ).add(
2764
- ` ${link(
3343
+ ` ${link3(
2765
3344
  "https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
2766
3345
  )}`
2767
3346
  ).add(
@@ -2769,7 +3348,7 @@ function renderUploadAutorunHint() {
2769
3348
  "Run collect & upload"
2770
3349
  )}`
2771
3350
  ).add(
2772
- ` ${link(
3351
+ ` ${link3(
2773
3352
  "https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command"
2774
3353
  )}`
2775
3354
  ).render();
@@ -2796,14 +3375,14 @@ function yargsCompareOptionsDefinition() {
2796
3375
 
2797
3376
  // packages/cli/src/lib/compare/compare-command.ts
2798
3377
  function yargsCompareCommandObject() {
2799
- const command = "compare";
3378
+ const command2 = "compare";
2800
3379
  return {
2801
- command,
3380
+ command: command2,
2802
3381
  describe: "Compare 2 report files and create a diff file",
2803
3382
  builder: yargsCompareOptionsDefinition(),
2804
3383
  handler: async (args) => {
2805
3384
  ui().logger.log(chalk9.bold(CLI_NAME));
2806
- ui().logger.info(chalk9.gray(`Run ${command}...`));
3385
+ ui().logger.info(chalk9.gray(`Run ${command2}...`));
2807
3386
  const options2 = args;
2808
3387
  const { before, after, persist } = options2;
2809
3388
  const outputPaths = await compareReportFiles({ before, after }, persist);
@@ -2814,6 +3393,9 @@ function yargsCompareCommandObject() {
2814
3393
  };
2815
3394
  }
2816
3395
 
3396
+ // packages/cli/src/lib/history/history-command.ts
3397
+ import chalk10 from "chalk";
3398
+
2817
3399
  // packages/cli/src/lib/implementation/global.utils.ts
2818
3400
  function filterKebabCaseKeys(obj) {
2819
3401
  return Object.entries(obj).filter(([key]) => !key.includes("-")).reduce(
@@ -2839,11 +3421,146 @@ function coerceArray(param) {
2839
3421
  return [...new Set(toArray(param).flatMap((f) => f.split(",")))];
2840
3422
  }
2841
3423
 
3424
+ // packages/cli/src/lib/implementation/only-plugins.options.ts
3425
+ var onlyPluginsOption = {
3426
+ describe: "List of plugins to run. If not set all plugins are run.",
3427
+ type: "array",
3428
+ default: [],
3429
+ coerce: coerceArray
3430
+ };
3431
+ function yargsOnlyPluginsOptionsDefinition() {
3432
+ return {
3433
+ onlyPlugins: onlyPluginsOption
3434
+ };
3435
+ }
3436
+
3437
+ // packages/cli/src/lib/history/history.options.ts
3438
+ function yargsHistoryOptionsDefinition() {
3439
+ return {
3440
+ targetBranch: {
3441
+ describe: "Branch to crawl history",
3442
+ type: "string"
3443
+ },
3444
+ onlySemverTags: {
3445
+ describe: "Skip commits not tagged with a semantic version",
3446
+ type: "boolean",
3447
+ default: false
3448
+ },
3449
+ forceCleanStatus: {
3450
+ describe: "If we reset the status to a clean git history forcefully or not.",
3451
+ type: "boolean",
3452
+ default: false
3453
+ },
3454
+ skipUploads: {
3455
+ describe: "Upload created reports",
3456
+ type: "boolean",
3457
+ default: false
3458
+ },
3459
+ maxCount: {
3460
+ // https://git-scm.com/docs/git-log#Documentation/git-log.txt---max-countltnumbergt
3461
+ describe: "Number of steps in history",
3462
+ type: "number",
3463
+ // eslint-disable-next-line no-magic-numbers
3464
+ default: 5
3465
+ },
3466
+ from: {
3467
+ // https://git-scm.com/docs/git-log#Documentation/git-log.txt-ltrevision-rangegt
3468
+ describe: "hash to first commit in history",
3469
+ type: "string"
3470
+ },
3471
+ to: {
3472
+ // https://git-scm.com/docs/git-log#Documentation/git-log.txt-ltrevision-rangegt
3473
+ describe: "hash to last commit in history",
3474
+ type: "string"
3475
+ }
3476
+ };
3477
+ }
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
+
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
+ }
3540
+ function yargsHistoryCommandObject() {
3541
+ return {
3542
+ command,
3543
+ describe: "Collect reports for commit history",
3544
+ builder: (yargs2) => {
3545
+ yargs2.options({
3546
+ ...yargsHistoryOptionsDefinition(),
3547
+ ...yargsOnlyPluginsOptionsDefinition()
3548
+ });
3549
+ yargs2.group(
3550
+ Object.keys(yargsHistoryOptionsDefinition()),
3551
+ "History Options:"
3552
+ );
3553
+ return yargs2;
3554
+ },
3555
+ handler
3556
+ };
3557
+ }
3558
+
2842
3559
  // packages/cli/src/lib/print-config/print-config-command.ts
2843
3560
  function yargsConfigCommandObject() {
2844
- const command = "print-config";
3561
+ const command2 = "print-config";
2845
3562
  return {
2846
- command,
3563
+ command: command2,
2847
3564
  describe: "Print config",
2848
3565
  handler: (yargsArgs) => {
2849
3566
  const { _, $0, ...args } = yargsArgs;
@@ -2854,15 +3571,15 @@ function yargsConfigCommandObject() {
2854
3571
  }
2855
3572
 
2856
3573
  // packages/cli/src/lib/upload/upload-command.ts
2857
- import chalk10 from "chalk";
3574
+ import chalk11 from "chalk";
2858
3575
  function yargsUploadCommandObject() {
2859
- const command = "upload";
3576
+ const command2 = "upload";
2860
3577
  return {
2861
- command,
3578
+ command: command2,
2862
3579
  describe: "Upload report results to the portal",
2863
3580
  handler: async (args) => {
2864
- ui().logger.log(chalk10.bold(CLI_NAME));
2865
- ui().logger.info(chalk10.gray(`Run ${command}...`));
3581
+ ui().logger.log(chalk11.bold(CLI_NAME));
3582
+ ui().logger.info(chalk11.gray(`Run ${command2}...`));
2866
3583
  const options2 = args;
2867
3584
  if (options2.upload == null) {
2868
3585
  renderIntegratePortalHint();
@@ -2883,6 +3600,7 @@ var commands = [
2883
3600
  yargsAutorunCommandObject(),
2884
3601
  yargsCollectCommandObject(),
2885
3602
  yargsUploadCommandObject(),
3603
+ yargsHistoryCommandObject(),
2886
3604
  yargsCompareCommandObject(),
2887
3605
  yargsConfigCommandObject()
2888
3606
  ];
@@ -2922,7 +3640,7 @@ async function coreConfigMiddleware(processArgs) {
2922
3640
  }
2923
3641
 
2924
3642
  // packages/cli/src/lib/implementation/only-plugins.utils.ts
2925
- import chalk11 from "chalk";
3643
+ import chalk12 from "chalk";
2926
3644
  function validateOnlyPluginsOption({
2927
3645
  plugins,
2928
3646
  categories
@@ -2936,7 +3654,7 @@ function validateOnlyPluginsOption({
2936
3654
  );
2937
3655
  if (missingPlugins.length > 0 && verbose) {
2938
3656
  ui().logger.info(
2939
- `${chalk11.yellow(
3657
+ `${chalk12.yellow(
2940
3658
  "\u26A0"
2941
3659
  )} The --onlyPlugin argument references plugins with "${missingPlugins.join(
2942
3660
  '", "'
@@ -3063,19 +3781,6 @@ function yargsGlobalOptionsDefinition() {
3063
3781
  };
3064
3782
  }
3065
3783
 
3066
- // packages/cli/src/lib/implementation/only-plugins.options.ts
3067
- var onlyPluginsOption = {
3068
- describe: "List of plugins to run. If not set all plugins are run.",
3069
- type: "array",
3070
- default: [],
3071
- coerce: coerceArray
3072
- };
3073
- function yargsOnlyPluginsOptionsDefinition() {
3074
- return {
3075
- onlyPlugins: onlyPluginsOption
3076
- };
3077
- }
3078
-
3079
3784
  // packages/cli/src/lib/options.ts
3080
3785
  var options = {
3081
3786
  ...yargsGlobalOptionsDefinition(),
@@ -3092,7 +3797,7 @@ var groups = {
3092
3797
  };
3093
3798
 
3094
3799
  // packages/cli/src/lib/yargs-cli.ts
3095
- import chalk12 from "chalk";
3800
+ import chalk13 from "chalk";
3096
3801
  import yargs from "yargs";
3097
3802
  function yargsCli(argv, cfg) {
3098
3803
  const { usageMessage, scriptName, noExitProcess } = cfg;
@@ -3112,7 +3817,7 @@ function yargsCli(argv, cfg) {
3112
3817
  (config) => Array.isArray(config) ? config.at(-1) : config
3113
3818
  ).options(options2).wrap(TERMINAL_WIDTH);
3114
3819
  if (usageMessage) {
3115
- cli2.usage(chalk12.bold(usageMessage));
3820
+ cli2.usage(chalk13.bold(usageMessage));
3116
3821
  }
3117
3822
  if (scriptName) {
3118
3823
  cli2.scriptName(scriptName);