@code-pushup/core 0.39.0 → 0.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,6 +1,3 @@
1
- // packages/models/src/lib/audit.ts
2
- import { z as z2 } from "zod";
3
-
4
1
  // packages/models/src/lib/implementation/schemas.ts
5
2
  import { MATERIAL_ICONS } from "vscode-material-icons";
6
3
  import { z } from "zod";
@@ -66,6 +63,7 @@ function missingRefsForCategoriesErrorMsg(categories, plugins) {
66
63
  }
67
64
 
68
65
  // packages/models/src/lib/implementation/schemas.ts
66
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
69
67
  function executionMetaSchema(options = {
70
68
  descriptionDate: "Execution start date and time",
71
69
  descriptionDuration: "Execution duration in ms"
@@ -157,6 +155,7 @@ function hasNonZeroWeightedRef(refs) {
157
155
  }
158
156
 
159
157
  // packages/models/src/lib/audit.ts
158
+ import { z as z2 } from "zod";
160
159
  var auditSchema = z2.object({
161
160
  slug: slugSchema.describe("ID (unique within plugin)")
162
161
  }).merge(
@@ -186,7 +185,7 @@ function getDuplicateSlugsInAudits(audits) {
186
185
  }
187
186
 
188
187
  // packages/models/src/lib/audit-output.ts
189
- import { z as z4 } from "zod";
188
+ import { z as z5 } from "zod";
190
189
 
191
190
  // packages/models/src/lib/issue.ts
192
191
  import { z as z3 } from "zod";
@@ -217,16 +216,61 @@ var issueSchema = z3.object(
217
216
  { description: "Issue information" }
218
217
  );
219
218
 
219
+ // packages/models/src/lib/table.ts
220
+ import { z as z4 } from "zod";
221
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
222
+ description: "Cell alignment"
223
+ });
224
+ var tableColumnObjectSchema = z4.object({
225
+ key: z4.string(),
226
+ label: z4.string().optional(),
227
+ align: tableAlignmentSchema.optional()
228
+ });
229
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
230
+ description: "Object row"
231
+ });
232
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
233
+ description: "Primitive row"
234
+ });
235
+ var tableSharedSchema = z4.object({
236
+ title: z4.string().optional().describe("Display title for table")
237
+ });
238
+ var tablePrimitiveSchema = tableSharedSchema.merge(
239
+ z4.object(
240
+ {
241
+ columns: z4.array(tableAlignmentSchema).optional(),
242
+ rows: z4.array(tableRowPrimitiveSchema)
243
+ },
244
+ { description: "Table with primitive rows and optional alignment columns" }
245
+ )
246
+ );
247
+ var tableObjectSchema = tableSharedSchema.merge(
248
+ z4.object(
249
+ {
250
+ columns: z4.union([
251
+ z4.array(tableAlignmentSchema),
252
+ z4.array(tableColumnObjectSchema)
253
+ ]).optional(),
254
+ rows: z4.array(tableRowObjectSchema)
255
+ },
256
+ {
257
+ description: "Table with object rows and optional alignment or object columns"
258
+ }
259
+ )
260
+ );
261
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
262
+
220
263
  // packages/models/src/lib/audit-output.ts
221
264
  var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
222
- var auditDisplayValueSchema = z4.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
223
- var auditDetailsSchema = z4.object(
265
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
266
+ var auditDetailsSchema = z5.object(
224
267
  {
225
- issues: z4.array(issueSchema, { description: "List of findings" })
268
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
269
+ table: tableSchema("Table of related findings").optional()
226
270
  },
227
271
  { description: "Detailed information" }
228
272
  );
229
- var auditOutputSchema = z4.object(
273
+ var auditOutputSchema = z5.object(
230
274
  {
231
275
  slug: slugSchema.describe("Reference to audit"),
232
276
  displayValue: auditDisplayValueSchema,
@@ -236,7 +280,7 @@ var auditOutputSchema = z4.object(
236
280
  },
237
281
  { description: "Audit information" }
238
282
  );
239
- var auditOutputsSchema = z4.array(auditOutputSchema, {
283
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
240
284
  description: "List of JSON formatted audit output emitted by the runner process of a plugin"
241
285
  }).refine(
242
286
  (audits) => !getDuplicateSlugsInAudits2(audits),
@@ -253,13 +297,13 @@ function getDuplicateSlugsInAudits2(audits) {
253
297
  }
254
298
 
255
299
  // packages/models/src/lib/category-config.ts
256
- import { z as z5 } from "zod";
300
+ import { z as z6 } from "zod";
257
301
  var categoryRefSchema = weightedRefSchema(
258
302
  "Weighted references to audits and/or groups for the category",
259
303
  "Slug of an audit or group (depending on `type`)"
260
304
  ).merge(
261
- z5.object({
262
- type: z5.enum(["audit", "group"], {
305
+ z6.object({
306
+ type: z6.enum(["audit", "group"], {
263
307
  description: "Discriminant for reference kind, affects where `slug` is looked up"
264
308
  }),
265
309
  plugin: slugSchema.describe(
@@ -280,8 +324,8 @@ var categoryConfigSchema = scorableSchema(
280
324
  description: "Meta info for category"
281
325
  })
282
326
  ).merge(
283
- z5.object({
284
- isBinary: z5.boolean({
327
+ z6.object({
328
+ isBinary: z6.boolean({
285
329
  description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
286
330
  }).optional()
287
331
  })
@@ -297,7 +341,7 @@ function getDuplicateRefsInCategoryMetrics(metrics) {
297
341
  metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
298
342
  );
299
343
  }
300
- var categoriesSchema = z5.array(categoryConfigSchema, {
344
+ var categoriesSchema = z6.array(categoryConfigSchema, {
301
345
  description: "Categorization of individual audits"
302
346
  }).refine(
303
347
  (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
@@ -316,18 +360,18 @@ function getDuplicateSlugCategories(categories) {
316
360
  }
317
361
 
318
362
  // packages/models/src/lib/commit.ts
319
- import { z as z6 } from "zod";
320
- var commitSchema = z6.object(
363
+ import { z as z7 } from "zod";
364
+ var commitSchema = z7.object(
321
365
  {
322
- hash: z6.string({ description: "Commit SHA (full)" }).regex(
366
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
323
367
  /^[\da-f]{40}$/,
324
368
  "Commit SHA should be a 40-character hexadecimal string"
325
369
  ),
326
- message: z6.string({ description: "Commit message" }),
327
- date: z6.coerce.date({
370
+ message: z7.string({ description: "Commit message" }),
371
+ date: z7.coerce.date({
328
372
  description: "Date and time when commit was authored"
329
373
  }),
330
- author: z6.string({
374
+ author: z7.string({
331
375
  description: "Commit author name"
332
376
  }).trim()
333
377
  },
@@ -335,22 +379,22 @@ var commitSchema = z6.object(
335
379
  );
336
380
 
337
381
  // packages/models/src/lib/core-config.ts
338
- import { z as z12 } from "zod";
382
+ import { z as z13 } from "zod";
339
383
 
340
384
  // packages/models/src/lib/persist-config.ts
341
- import { z as z7 } from "zod";
342
- var formatSchema = z7.enum(["json", "md"]);
343
- var persistConfigSchema = z7.object({
385
+ import { z as z8 } from "zod";
386
+ var formatSchema = z8.enum(["json", "md"]);
387
+ var persistConfigSchema = z8.object({
344
388
  outputDir: filePathSchema.describe("Artifacts folder").optional(),
345
389
  filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
346
- format: z7.array(formatSchema).optional()
390
+ format: z8.array(formatSchema).optional()
347
391
  });
348
392
 
349
393
  // packages/models/src/lib/plugin-config.ts
350
- import { z as z10 } from "zod";
394
+ import { z as z11 } from "zod";
351
395
 
352
396
  // packages/models/src/lib/group.ts
353
- import { z as z8 } from "zod";
397
+ import { z as z9 } from "zod";
354
398
  var groupRefSchema = weightedRefSchema(
355
399
  "Weighted reference to a group",
356
400
  "Reference slug to a group within this plugin (e.g. 'max-lines')"
@@ -367,7 +411,7 @@ var groupSchema = scorableSchema(
367
411
  getDuplicateRefsInGroups,
368
412
  duplicateRefsInGroupsErrorMsg
369
413
  ).merge(groupMetaSchema);
370
- var groupsSchema = z8.array(groupSchema, {
414
+ var groupsSchema = z9.array(groupSchema, {
371
415
  description: "List of groups"
372
416
  }).optional().refine(
373
417
  (groups) => !getDuplicateSlugsInGroups(groups),
@@ -395,14 +439,14 @@ function getDuplicateSlugsInGroups(groups) {
395
439
  }
396
440
 
397
441
  // packages/models/src/lib/runner-config.ts
398
- import { z as z9 } from "zod";
399
- var outputTransformSchema = z9.function().args(z9.unknown()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
400
- var runnerConfigSchema = z9.object(
442
+ import { z as z10 } from "zod";
443
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
444
+ var runnerConfigSchema = z10.object(
401
445
  {
402
- command: z9.string({
446
+ command: z10.string({
403
447
  description: "Shell command to execute"
404
448
  }),
405
- args: z9.array(z9.string({ description: "Command arguments" })).optional(),
449
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
406
450
  outputFile: filePathSchema.describe("Output path"),
407
451
  outputTransform: outputTransformSchema.optional()
408
452
  },
@@ -410,8 +454,8 @@ var runnerConfigSchema = z9.object(
410
454
  description: "How to execute runner"
411
455
  }
412
456
  );
413
- var onProgressSchema = z9.function().args(z9.unknown()).returns(z9.void());
414
- var runnerFunctionSchema = z9.function().args(onProgressSchema.optional()).returns(z9.union([auditOutputsSchema, z9.promise(auditOutputsSchema)]));
457
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
458
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
415
459
 
416
460
  // packages/models/src/lib/plugin-config.ts
417
461
  var pluginMetaSchema = packageVersionSchema().merge(
@@ -422,13 +466,13 @@ var pluginMetaSchema = packageVersionSchema().merge(
422
466
  description: "Plugin metadata"
423
467
  })
424
468
  ).merge(
425
- z10.object({
469
+ z11.object({
426
470
  slug: slugSchema.describe("Unique plugin slug within core config"),
427
471
  icon: materialIconSchema
428
472
  })
429
473
  );
430
- var pluginDataSchema = z10.object({
431
- runner: z10.union([runnerConfigSchema, runnerFunctionSchema]),
474
+ var pluginDataSchema = z11.object({
475
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
432
476
  audits: pluginAuditsSchema,
433
477
  groups: groupsSchema
434
478
  });
@@ -454,22 +498,22 @@ function getMissingRefsFromGroups(pluginCfg) {
454
498
  }
455
499
 
456
500
  // packages/models/src/lib/upload-config.ts
457
- import { z as z11 } from "zod";
458
- var uploadConfigSchema = z11.object({
501
+ import { z as z12 } from "zod";
502
+ var uploadConfigSchema = z12.object({
459
503
  server: urlSchema.describe("URL of deployed portal API"),
460
- apiKey: z11.string({
504
+ apiKey: z12.string({
461
505
  description: "API key with write access to portal (use `process.env` for security)"
462
506
  }),
463
507
  organization: slugSchema.describe(
464
508
  "Organization slug from Code PushUp portal"
465
509
  ),
466
510
  project: slugSchema.describe("Project slug from Code PushUp portal"),
467
- timeout: z11.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
511
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
468
512
  });
469
513
 
470
514
  // packages/models/src/lib/core-config.ts
471
- var unrefinedCoreConfigSchema = z12.object({
472
- plugins: z12.array(pluginConfigSchema, {
515
+ var unrefinedCoreConfigSchema = z13.object({
516
+ plugins: z13.array(pluginConfigSchema, {
473
517
  description: "List of plugins to be used (official, community-provided, or custom)"
474
518
  }).min(1),
475
519
  /** portal configuration for persisting results */
@@ -496,7 +540,7 @@ var CONFIG_FILE_NAME = "code-pushup.config";
496
540
  var SUPPORTED_CONFIG_FILE_FORMATS = ["ts", "mjs", "js"];
497
541
 
498
542
  // packages/models/src/lib/report.ts
499
- import { z as z13 } from "zod";
543
+ import { z as z14 } from "zod";
500
544
  var auditReportSchema = auditSchema.merge(auditOutputSchema);
501
545
  var pluginReportSchema = pluginMetaSchema.merge(
502
546
  executionMetaSchema({
@@ -504,9 +548,9 @@ var pluginReportSchema = pluginMetaSchema.merge(
504
548
  descriptionDuration: "Duration of the plugin run in ms"
505
549
  })
506
550
  ).merge(
507
- z13.object({
508
- audits: z13.array(auditReportSchema).min(1),
509
- groups: z13.array(groupSchema).optional()
551
+ z14.object({
552
+ audits: z14.array(auditReportSchema).min(1),
553
+ groups: z14.array(groupSchema).optional()
510
554
  })
511
555
  ).refine(
512
556
  (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
@@ -540,10 +584,10 @@ var reportSchema = packageVersionSchema({
540
584
  descriptionDuration: "Duration of the collect run in ms"
541
585
  })
542
586
  ).merge(
543
- z13.object(
587
+ z14.object(
544
588
  {
545
- categories: z13.array(categoryConfigSchema),
546
- plugins: z13.array(pluginReportSchema).min(1),
589
+ categories: z14.array(categoryConfigSchema),
590
+ plugins: z14.array(pluginReportSchema).min(1),
547
591
  commit: commitSchema.describe("Git commit for which report was collected").nullable()
548
592
  },
549
593
  { description: "Collect output data" }
@@ -559,40 +603,40 @@ var reportSchema = packageVersionSchema({
559
603
  );
560
604
 
561
605
  // packages/models/src/lib/reports-diff.ts
562
- import { z as z14 } from "zod";
606
+ import { z as z15 } from "zod";
563
607
  function makeComparisonSchema(schema) {
564
608
  const sharedDescription = schema.description || "Result";
565
- return z14.object({
609
+ return z15.object({
566
610
  before: schema.describe(`${sharedDescription} (source commit)`),
567
611
  after: schema.describe(`${sharedDescription} (target commit)`)
568
612
  });
569
613
  }
570
614
  function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
571
- return z14.object(
615
+ return z15.object(
572
616
  {
573
- changed: z14.array(diffSchema),
574
- unchanged: z14.array(resultSchema),
575
- added: z14.array(resultSchema),
576
- removed: z14.array(resultSchema)
617
+ changed: z15.array(diffSchema),
618
+ unchanged: z15.array(resultSchema),
619
+ added: z15.array(resultSchema),
620
+ removed: z15.array(resultSchema)
577
621
  },
578
622
  { description }
579
623
  );
580
624
  }
581
- var scorableMetaSchema = z14.object({
625
+ var scorableMetaSchema = z15.object({
582
626
  slug: slugSchema,
583
627
  title: titleSchema,
584
628
  docsUrl: docsUrlSchema
585
629
  });
586
630
  var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
587
- z14.object({
631
+ z15.object({
588
632
  plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
589
633
  })
590
634
  );
591
635
  var scorableDiffSchema = scorableMetaSchema.merge(
592
- z14.object({
636
+ z15.object({
593
637
  scores: makeComparisonSchema(scoreSchema).merge(
594
- z14.object({
595
- diff: z14.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
638
+ z15.object({
639
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
596
640
  })
597
641
  ).describe("Score comparison")
598
642
  })
@@ -603,10 +647,10 @@ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
603
647
  var categoryDiffSchema = scorableDiffSchema;
604
648
  var groupDiffSchema = scorableWithPluginDiffSchema;
605
649
  var auditDiffSchema = scorableWithPluginDiffSchema.merge(
606
- z14.object({
650
+ z15.object({
607
651
  values: makeComparisonSchema(auditValueSchema).merge(
608
- z14.object({
609
- diff: z14.number().int().describe("Value change (`values.after - values.before`)")
652
+ z15.object({
653
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
610
654
  })
611
655
  ).describe("Audit `value` comparison"),
612
656
  displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
@@ -615,15 +659,15 @@ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
615
659
  })
616
660
  );
617
661
  var categoryResultSchema = scorableMetaSchema.merge(
618
- z14.object({ score: scoreSchema })
662
+ z15.object({ score: scoreSchema })
619
663
  );
620
664
  var groupResultSchema = scorableWithPluginMetaSchema.merge(
621
- z14.object({ score: scoreSchema })
665
+ z15.object({ score: scoreSchema })
622
666
  );
623
667
  var auditResultSchema = scorableWithPluginMetaSchema.merge(
624
668
  auditOutputSchema.pick({ score: true, value: true, displayValue: true })
625
669
  );
626
- var reportsDiffSchema = z14.object({
670
+ var reportsDiffSchema = z15.object({
627
671
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
628
672
  categories: makeArraysComparisonSchema(
629
673
  categoryDiffSchema,
@@ -652,6 +696,288 @@ var reportsDiffSchema = z14.object({
652
696
  })
653
697
  );
654
698
 
699
+ // packages/utils/src/lib/text-formats/constants.ts
700
+ var NEW_LINE = "\n";
701
+ var TAB = " ";
702
+ var SPACE = " ";
703
+
704
+ // packages/utils/src/lib/text-formats/html/details.ts
705
+ function details(title, content, cfg = { open: false }) {
706
+ 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.
707
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
708
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
709
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
710
+ NEW_LINE}`;
711
+ }
712
+
713
+ // packages/utils/src/lib/text-formats/html/font-style.ts
714
+ var boldElement = "b";
715
+ function bold(text) {
716
+ return `<${boldElement}>${text}</${boldElement}>`;
717
+ }
718
+ var italicElement = "i";
719
+ function italic(text) {
720
+ return `<${italicElement}>${text}</${italicElement}>`;
721
+ }
722
+ var codeElement = "code";
723
+ function code(text) {
724
+ return `<${codeElement}>${text}</${codeElement}>`;
725
+ }
726
+
727
+ // packages/utils/src/lib/text-formats/html/link.ts
728
+ function link(href, text) {
729
+ return `<a href="${href}">${text || href}"</a>`;
730
+ }
731
+
732
+ // packages/utils/src/lib/transform.ts
733
+ function objectToEntries(obj) {
734
+ return Object.entries(obj);
735
+ }
736
+ function deepClone(obj) {
737
+ return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
738
+ }
739
+ function toUnixPath(path) {
740
+ return path.replace(/\\/g, "/");
741
+ }
742
+ function capitalize(text) {
743
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
744
+ 1
745
+ )}`;
746
+ }
747
+
748
+ // packages/utils/src/lib/table.ts
749
+ function rowToStringArray({ rows, columns = [] }) {
750
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
751
+ throw new TypeError(
752
+ "Column can`t be object when rows are primitive values"
753
+ );
754
+ }
755
+ return rows.map((row) => {
756
+ if (Array.isArray(row)) {
757
+ return row.map(String);
758
+ }
759
+ const objectRow = row;
760
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
761
+ return Object.values(objectRow).map(String);
762
+ }
763
+ return columns.map(
764
+ ({ key }) => String(objectRow[key])
765
+ );
766
+ });
767
+ }
768
+ function columnsToStringArray({ rows, columns = [] }) {
769
+ const firstRow = rows.at(0);
770
+ const primitiveRows = Array.isArray(firstRow);
771
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
772
+ throw new Error("invalid union type. Caught by model parsing.");
773
+ }
774
+ if (columns.length === 0) {
775
+ if (Array.isArray(firstRow)) {
776
+ return firstRow.map((_, idx) => String(idx));
777
+ }
778
+ return Object.keys(firstRow);
779
+ }
780
+ if (typeof columns.at(0) === "string") {
781
+ return columns.map(String);
782
+ }
783
+ const cols = columns;
784
+ return cols.map(({ label, key }) => label ?? capitalize(key));
785
+ }
786
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
787
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
788
+ if (typeof column === "string") {
789
+ return column;
790
+ } else if (typeof column === "object") {
791
+ return column.align ?? "center";
792
+ } else {
793
+ return "center";
794
+ }
795
+ }
796
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
797
+ const column = columns.at(targetIdx);
798
+ if (column == null) {
799
+ return "center";
800
+ } else if (typeof column === "string") {
801
+ return column;
802
+ } else if (typeof column === "object") {
803
+ return column.align ?? "center";
804
+ } else {
805
+ return "center";
806
+ }
807
+ }
808
+ function getColumnAlignments({
809
+ rows,
810
+ columns = []
811
+ }) {
812
+ if (rows.at(0) == null) {
813
+ throw new Error("first row can`t be undefined.");
814
+ }
815
+ if (Array.isArray(rows.at(0))) {
816
+ const firstPrimitiveRow = rows.at(0);
817
+ return Array.from({ length: firstPrimitiveRow.length }).map(
818
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
819
+ );
820
+ }
821
+ const firstObject = rows.at(0);
822
+ return Object.keys(firstObject).map(
823
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
824
+ );
825
+ }
826
+
827
+ // packages/utils/src/lib/text-formats/html/table.ts
828
+ function wrap(elem, content) {
829
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
830
+ }
831
+ function wrapRow(content) {
832
+ const elem = "tr";
833
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
834
+ }
835
+ function table(tableData) {
836
+ if (tableData.rows.length === 0) {
837
+ throw new Error("Data can't be empty");
838
+ }
839
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
840
+ const tableHeaderRow = wrapRow(tableHeaderCols);
841
+ const tableBody = rowToStringArray(tableData).map((arr) => {
842
+ const columns = arr.map((s) => wrap("td", s)).join("");
843
+ return wrapRow(columns);
844
+ }).join("");
845
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
846
+ }
847
+
848
+ // packages/utils/src/lib/text-formats/md/font-style.ts
849
+ var boldWrap = "**";
850
+ function bold2(text) {
851
+ return `${boldWrap}${text}${boldWrap}`;
852
+ }
853
+ var italicWrap = "_";
854
+ function italic2(text) {
855
+ return `${italicWrap}${text}${italicWrap}`;
856
+ }
857
+ var strikeThroughWrap = "~";
858
+ function strikeThrough(text) {
859
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
860
+ }
861
+ var codeWrap = "`";
862
+ function code2(text) {
863
+ return `${codeWrap}${text}${codeWrap}`;
864
+ }
865
+
866
+ // packages/utils/src/lib/text-formats/md/headline.ts
867
+ function headline(text, hierarchy = 1) {
868
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
869
+ }
870
+ function h(text, hierarchy = 1) {
871
+ return headline(text, hierarchy);
872
+ }
873
+ function h1(text) {
874
+ return headline(text, 1);
875
+ }
876
+ function h2(text) {
877
+ return headline(text, 2);
878
+ }
879
+ function h3(text) {
880
+ return headline(text, 3);
881
+ }
882
+ function h4(text) {
883
+ return headline(text, 4);
884
+ }
885
+ function h5(text) {
886
+ return headline(text, 5);
887
+ }
888
+ function h6(text) {
889
+ return headline(text, 6);
890
+ }
891
+
892
+ // packages/utils/src/lib/text-formats/md/image.ts
893
+ function image(src, alt) {
894
+ return `![${alt}](${src})`;
895
+ }
896
+
897
+ // packages/utils/src/lib/text-formats/md/link.ts
898
+ function link2(href, text) {
899
+ return `[${text || href}](${href})`;
900
+ }
901
+
902
+ // packages/utils/src/lib/text-formats/md/list.ts
903
+ function li(text, order = "unordered") {
904
+ const style = order === "unordered" ? "-" : "- [ ]";
905
+ return `${style} ${text}`;
906
+ }
907
+ function indentation(text, level = 1) {
908
+ return `${TAB.repeat(level)}${text}`;
909
+ }
910
+
911
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
912
+ function paragraphs(...sections) {
913
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
914
+ }
915
+
916
+ // packages/utils/src/lib/text-formats/md/section.ts
917
+ function section(...contents) {
918
+ return `${lines(...contents)}${NEW_LINE}`;
919
+ }
920
+ function lines(...contents) {
921
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
922
+ }
923
+
924
+ // packages/utils/src/lib/text-formats/md/table.ts
925
+ var alignString = /* @__PURE__ */ new Map([
926
+ ["left", ":--"],
927
+ ["center", ":--:"],
928
+ ["right", "--:"]
929
+ ]);
930
+ function tableRow(rows) {
931
+ return `|${rows.join("|")}|`;
932
+ }
933
+ function table2(data) {
934
+ if (data.rows.length === 0) {
935
+ throw new Error("Data can't be empty");
936
+ }
937
+ const alignmentRow = getColumnAlignments(data).map(
938
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
939
+ );
940
+ return section(
941
+ `${lines(
942
+ tableRow(columnsToStringArray(data)),
943
+ tableRow(alignmentRow),
944
+ ...rowToStringArray(data).map(tableRow)
945
+ )}`
946
+ );
947
+ }
948
+
949
+ // packages/utils/src/lib/text-formats/index.ts
950
+ var md = {
951
+ bold: bold2,
952
+ italic: italic2,
953
+ strikeThrough,
954
+ code: code2,
955
+ link: link2,
956
+ image,
957
+ headline,
958
+ h,
959
+ h1,
960
+ h2,
961
+ h3,
962
+ h4,
963
+ h5,
964
+ h6,
965
+ indentation,
966
+ lines,
967
+ li,
968
+ section,
969
+ paragraphs,
970
+ table: table2
971
+ };
972
+ var html = {
973
+ bold,
974
+ italic,
975
+ code,
976
+ link,
977
+ details,
978
+ table
979
+ };
980
+
655
981
  // packages/utils/src/lib/diff.ts
656
982
  function matchArrayItemsByKey({
657
983
  before,
@@ -769,40 +1095,48 @@ import chalk from "chalk";
769
1095
 
770
1096
  // packages/utils/src/lib/reports/constants.ts
771
1097
  var TERMINAL_WIDTH = 80;
772
- var NEW_LINE = "\n";
773
1098
  var SCORE_COLOR_RANGE = {
774
1099
  GREEN_MIN: 0.9,
775
1100
  YELLOW_MIN: 0.5
776
1101
  };
1102
+ var CATEGORIES_TITLE = "\u{1F3F7} Categories";
777
1103
  var FOOTER_PREFIX = "Made with \u2764 by";
778
1104
  var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
779
- var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
1105
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
780
1106
  var reportHeadlineText = "Code PushUp Report";
781
1107
  var reportOverviewTableHeaders = [
782
- "\u{1F3F7} Category",
783
- "\u2B50 Score",
784
- "\u{1F6E1} Audits"
1108
+ {
1109
+ key: "category",
1110
+ label: "\u{1F3F7} Category",
1111
+ align: "left"
1112
+ },
1113
+ {
1114
+ key: "score",
1115
+ label: "\u2B50 Score"
1116
+ },
1117
+ {
1118
+ key: "audits",
1119
+ label: "\u{1F6E1} Audits"
1120
+ }
785
1121
  ];
786
1122
  var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
787
- var reportMetaTableHeaders = [
788
- "Commit",
789
- "Version",
790
- "Duration",
791
- "Plugins",
792
- "Categories",
793
- "Audits"
794
- ];
795
- var pluginMetaTableHeaders = [
796
- "Plugin",
797
- "Audits",
798
- "Version",
799
- "Duration"
800
- ];
801
- var detailsTableHeaders = [
802
- "Severity",
803
- "Message",
804
- "Source file",
805
- "Line(s)"
1123
+ var issuesTableHeadings = [
1124
+ {
1125
+ key: "severity",
1126
+ label: "Severity"
1127
+ },
1128
+ {
1129
+ key: "message",
1130
+ label: "Message"
1131
+ },
1132
+ {
1133
+ key: "file",
1134
+ label: "Source file"
1135
+ },
1136
+ {
1137
+ key: "line",
1138
+ label: "Line(s)"
1139
+ }
806
1140
  ];
807
1141
 
808
1142
  // packages/utils/src/lib/logging.ts
@@ -928,122 +1262,35 @@ async function importEsmModule(options) {
928
1262
  return mod.default;
929
1263
  }
930
1264
 
931
- // packages/utils/src/lib/reports/md/details.ts
932
- function details(title, content, cfg = { open: false }) {
933
- return `<details${cfg.open ? " open" : ""}>
934
- <summary>${title}</summary>
935
-
936
- ${content}
937
-
938
- </details>
939
- `;
940
- }
941
-
942
- // packages/utils/src/lib/reports/md/font-style.ts
943
- var stylesMap = {
944
- i: "_",
945
- // italic
946
- b: "**",
947
- // bold
948
- s: "~",
949
- // strike through
950
- c: "`"
951
- // code
952
- };
953
- function style(text, styles = ["b"]) {
954
- return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
955
- }
956
-
957
- // packages/utils/src/lib/reports/md/headline.ts
958
- function headline(text, hierarchy = 1) {
959
- return `${"#".repeat(hierarchy)} ${text}`;
960
- }
961
- function h1(text) {
962
- return headline(text, 1);
963
- }
964
- function h2(text) {
965
- return headline(text, 2);
966
- }
967
- function h3(text) {
968
- return headline(text, 3);
969
- }
970
-
971
- // packages/utils/src/lib/reports/md/image.ts
972
- function image(src, alt) {
973
- return `![${alt}](${src})`;
974
- }
975
-
976
- // packages/utils/src/lib/reports/md/link.ts
977
- function link(href, text) {
978
- return `[${text || href}](${href})`;
979
- }
980
-
981
- // packages/utils/src/lib/reports/md/list.ts
982
- function li(text, order = "unordered") {
983
- const style2 = order === "unordered" ? "-" : "- [ ]";
984
- return `${style2} ${text}`;
985
- }
986
-
987
- // packages/utils/src/lib/reports/md/paragraphs.ts
988
- function paragraphs(...sections) {
989
- return sections.filter(Boolean).join("\n\n");
990
- }
991
-
992
- // packages/utils/src/lib/reports/md/table.ts
993
- var alignString = /* @__PURE__ */ new Map([
994
- ["l", ":--"],
995
- ["c", ":--:"],
996
- ["r", "--:"]
997
- ]);
998
- function tableMd(data, align) {
999
- if (data.length === 0) {
1000
- throw new Error("Data can't be empty");
1001
- }
1002
- const alignmentSetting = align ?? data[0]?.map(() => "c");
1003
- const tableContent = data.map((arr) => `|${arr.join("|")}|`);
1004
- const alignmentRow = `|${alignmentSetting?.map((s) => alignString.get(s)).join("|")}|`;
1005
- return tableContent[0] + NEW_LINE + alignmentRow + NEW_LINE + tableContent.slice(1).join(NEW_LINE);
1006
- }
1007
- function tableHtml(data) {
1008
- if (data.length === 0) {
1009
- throw new Error("Data can't be empty");
1010
- }
1011
- const tableContent = data.map((arr, index) => {
1012
- if (index === 0) {
1013
- const headerRow = arr.map((s) => `<th>${s}</th>`).join("");
1014
- return `<tr>${headerRow}</tr>`;
1015
- }
1016
- const row = arr.map((s) => `<td>${s}</td>`).join("");
1017
- return `<tr>${row}</tr>`;
1018
- });
1019
- return `<table>${tableContent.join("")}</table>`;
1020
- }
1021
-
1022
1265
  // packages/utils/src/lib/reports/utils.ts
1266
+ var { image: image2, bold: boldMd } = md;
1023
1267
  function formatReportScore(score) {
1024
1268
  return Math.round(score * 100).toString();
1025
1269
  }
1026
1270
  function formatScoreWithColor(score, options) {
1027
- const styledNumber = options?.skipBold ? formatReportScore(score) : style(formatReportScore(score));
1028
- return `${getRoundScoreMarker(score)} ${styledNumber}`;
1029
- }
1030
- function getRoundScoreMarker(score) {
1031
- if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1032
- return "\u{1F7E2}";
1033
- }
1034
- if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1035
- return "\u{1F7E1}";
1271
+ const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
1272
+ return `${scoreMarker(score)} ${styledNumber}`;
1273
+ }
1274
+ var MARKERS = {
1275
+ circle: {
1276
+ red: "\u{1F534}",
1277
+ yellow: "\u{1F7E1}",
1278
+ green: "\u{1F7E2}"
1279
+ },
1280
+ square: {
1281
+ red: "\u{1F7E5}",
1282
+ yellow: "\u{1F7E8}",
1283
+ green: "\u{1F7E9}"
1036
1284
  }
1037
- return "\u{1F534}";
1038
- }
1039
- function getSquaredScoreMarker(score) {
1285
+ };
1286
+ function scoreMarker(score, markerType = "circle") {
1040
1287
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1041
- return "\u{1F7E9}";
1288
+ return MARKERS[markerType].green;
1042
1289
  }
1043
1290
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1044
- return "\u{1F7E8}";
1291
+ return MARKERS[markerType].yellow;
1045
1292
  }
1046
- return "\u{1F7E5}";
1293
+ return MARKERS[markerType].red;
1047
1294
  }
1048
1295
  function getDiffMarker(diff) {
1049
1296
  if (diff > 0) {
@@ -1059,7 +1306,7 @@ function colorByScoreDiff(text, diff) {
1059
1306
  return shieldsBadge(text, color);
1060
1307
  }
1061
1308
  function shieldsBadge(text, color) {
1062
- return image(
1309
+ return image2(
1063
1310
  `https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
1064
1311
  text
1065
1312
  );
@@ -1069,7 +1316,7 @@ function formatDiffNumber(diff) {
1069
1316
  const sign = diff < 0 ? "\u2212" : "+";
1070
1317
  return `${sign}${number}`;
1071
1318
  }
1072
- function getSeverityIcon(severity) {
1319
+ function severityMarker(severity) {
1073
1320
  if (severity === "error") {
1074
1321
  return "\u{1F6A8}";
1075
1322
  }
@@ -1260,13 +1507,13 @@ function executeProcess(cfg) {
1260
1507
  process2.on("error", (err) => {
1261
1508
  stderr += err.toString();
1262
1509
  });
1263
- process2.on("close", (code) => {
1510
+ process2.on("close", (code3) => {
1264
1511
  const timings = { date, duration: calcDuration(start) };
1265
- if (code === 0 || ignoreExitCode) {
1512
+ if (code3 === 0 || ignoreExitCode) {
1266
1513
  onComplete?.();
1267
- resolve({ code, stdout, stderr, ...timings });
1514
+ resolve({ code: code3, stdout, stderr, ...timings });
1268
1515
  } else {
1269
- const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
1516
+ const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
1270
1517
  onError?.(errorMsg);
1271
1518
  reject(errorMsg);
1272
1519
  }
@@ -1274,30 +1521,9 @@ function executeProcess(cfg) {
1274
1521
  });
1275
1522
  }
1276
1523
 
1277
- // packages/utils/src/lib/git.ts
1524
+ // packages/utils/src/lib/git/git.ts
1278
1525
  import { isAbsolute, join as join2, relative } from "node:path";
1279
1526
  import { simpleGit } from "simple-git";
1280
-
1281
- // packages/utils/src/lib/transform.ts
1282
- function objectToEntries(obj) {
1283
- return Object.entries(obj);
1284
- }
1285
- function deepClone(obj) {
1286
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1287
- }
1288
- function toUnixPath(path) {
1289
- return path.replace(/\\/g, "/");
1290
- }
1291
-
1292
- // packages/utils/src/lib/git.ts
1293
- async function getLatestCommit(git = simpleGit()) {
1294
- const log2 = await git.log({
1295
- maxCount: 1,
1296
- // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1297
- format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1298
- });
1299
- return commitSchema.parse(log2.latest);
1300
- }
1301
1527
  function getGitRoot(git = simpleGit()) {
1302
1528
  return git.revparse("--show-toplevel");
1303
1529
  }
@@ -1344,11 +1570,6 @@ async function guardAgainstLocalChanges(git = simpleGit()) {
1344
1570
  throw new GitStatusError(status);
1345
1571
  }
1346
1572
  }
1347
- async function getCurrentBranchOrTag(git = simpleGit()) {
1348
- return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
1349
- // @TODO use simple git
1350
- await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1351
- }
1352
1573
  async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
1353
1574
  if (forceCleanStatus) {
1354
1575
  await git.raw(["reset", "--hard"]);
@@ -1359,6 +1580,27 @@ async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simple
1359
1580
  await git.checkout(branchOrHash);
1360
1581
  }
1361
1582
 
1583
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1584
+ import { simpleGit as simpleGit2 } from "simple-git";
1585
+
1586
+ // packages/utils/src/lib/semver.ts
1587
+ import { rcompare, valid } from "semver";
1588
+
1589
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1590
+ async function getLatestCommit(git = simpleGit2()) {
1591
+ const log2 = await git.log({
1592
+ maxCount: 1,
1593
+ // git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
1594
+ format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
1595
+ });
1596
+ return commitSchema.parse(log2.latest);
1597
+ }
1598
+ async function getCurrentBranchOrTag(git = simpleGit2()) {
1599
+ return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
1600
+ // @TODO use simple git
1601
+ await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
1602
+ }
1603
+
1362
1604
  // packages/utils/src/lib/group-by-status.ts
1363
1605
  function groupByStatus(results) {
1364
1606
  return results.reduce(
@@ -1434,44 +1676,65 @@ function listAuditsFromAllPlugins(report) {
1434
1676
  );
1435
1677
  }
1436
1678
 
1437
- // packages/utils/src/lib/reports/generate-md-report.ts
1438
- function generateMdReport(report) {
1439
- const printCategories = report.categories.length > 0;
1440
- return (
1441
- // header section
1442
- // eslint-disable-next-line prefer-template
1443
- reportToHeaderSection() + NEW_LINE + // categories overview section
1444
- (printCategories ? reportToOverviewSection(report) + NEW_LINE + NEW_LINE : "") + // categories section
1445
- (printCategories ? reportToCategoriesSection(report) + NEW_LINE + NEW_LINE : "") + // audits section
1446
- reportToAuditsSection(report) + NEW_LINE + NEW_LINE + // about section
1447
- reportToAboutSection(report) + NEW_LINE + NEW_LINE + // footer section
1448
- `${FOOTER_PREFIX} ${link(README_LINK, "Code PushUp")}`
1679
+ // packages/utils/src/lib/reports/formatting.ts
1680
+ var { headline: headline2, lines: lines2, link: link3, section: section2, table: table3 } = md;
1681
+ function tableSection(tableData, options) {
1682
+ if (tableData.rows.length === 0) {
1683
+ return "";
1684
+ }
1685
+ const { level = 4 } = options ?? {};
1686
+ const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
1687
+ return lines2(
1688
+ tableData.title && render(tableData.title, level),
1689
+ table3(tableData)
1449
1690
  );
1450
1691
  }
1451
- function reportToHeaderSection() {
1452
- return headline(reportHeadlineText) + NEW_LINE;
1692
+ function metaDescription({
1693
+ docsUrl,
1694
+ description
1695
+ }) {
1696
+ if (docsUrl) {
1697
+ const docsLink = link3(docsUrl, "\u{1F4D6} Docs");
1698
+ if (!description) {
1699
+ return section2(docsLink);
1700
+ }
1701
+ const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
1702
+ return section2(`${parsedDescription}${docsLink}`);
1703
+ }
1704
+ if (description && description.trim().length > 0) {
1705
+ return section2(description);
1706
+ }
1707
+ return "";
1453
1708
  }
1454
- function reportToOverviewSection(report) {
1709
+
1710
+ // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1711
+ var { link: link4, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
1712
+ function categoriesOverviewSection(report) {
1455
1713
  const { categories, plugins } = report;
1456
- const tableContent = [
1457
- reportOverviewTableHeaders,
1458
- ...categories.map(({ title, refs, score }) => [
1459
- link(`#${slugify(title)}`, title),
1460
- `${getRoundScoreMarker(score)} ${style(formatReportScore(score))}`,
1461
- countCategoryAudits(refs, plugins).toString()
1462
- ])
1463
- ];
1464
- return tableMd(tableContent, ["l", "c", "c"]);
1714
+ if (categories.length > 0 && plugins.length > 0) {
1715
+ const tableContent = {
1716
+ columns: reportOverviewTableHeaders,
1717
+ rows: categories.map(({ title, refs, score }) => ({
1718
+ // The heading "ID" is inferred from the heading text in Markdown.
1719
+ category: link4(`#${slugify(title)}`, title),
1720
+ score: `${scoreMarker(score)}${SPACE}${boldMd2(
1721
+ formatReportScore(score)
1722
+ )}`,
1723
+ audits: countCategoryAudits(refs, plugins).toString()
1724
+ }))
1725
+ };
1726
+ return tableSection(tableContent);
1727
+ }
1728
+ return "";
1465
1729
  }
1466
- function reportToCategoriesSection(report) {
1730
+ function categoriesDetailsSection(report) {
1467
1731
  const { categories, plugins } = report;
1468
- const categoryDetails = categories.reduce((acc, category) => {
1469
- const categoryTitle = h3(category.title);
1470
- const categoryScore = `${getRoundScoreMarker(
1732
+ const categoryDetails = categories.flatMap((category) => {
1733
+ const categoryTitle = h32(category.title);
1734
+ const categoryScore = `${scoreMarker(
1471
1735
  category.score
1472
- )} Score: ${style(formatReportScore(category.score))}`;
1473
- const categoryDocs = getDocsAndDescription(category);
1474
- const categoryMDItems = category.refs.reduce((refAcc, ref) => {
1736
+ )}${SPACE}Score: ${boldMd2(formatReportScore(category.score))}`;
1737
+ const categoryMDItems = category.refs.map((ref) => {
1475
1738
  if (ref.type === "group") {
1476
1739
  const group = getSortableGroupByRef(ref, plugins);
1477
1740
  const groupAudits = group.refs.map(
@@ -1481,151 +1744,240 @@ function reportToCategoriesSection(report) {
1481
1744
  )
1482
1745
  );
1483
1746
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1484
- const mdGroupItem = groupItemToCategorySection(
1485
- group,
1486
- groupAudits,
1487
- pluginTitle
1488
- );
1489
- return refAcc + mdGroupItem + NEW_LINE;
1747
+ return categoryGroupItem(group, groupAudits, pluginTitle);
1490
1748
  } else {
1491
1749
  const audit = getSortableAuditByRef(ref, plugins);
1492
1750
  const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
1493
- const mdAuditItem = auditItemToCategorySection(audit, pluginTitle);
1494
- return refAcc + mdAuditItem + NEW_LINE;
1751
+ return categoryRef(audit, pluginTitle);
1495
1752
  }
1496
- }, "");
1497
- return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + categoryMDItems;
1498
- }, "");
1499
- return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1500
- }
1501
- function auditItemToCategorySection(audit, pluginTitle) {
1502
- const auditTitle = link(
1503
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1504
- audit.title
1753
+ });
1754
+ return section3(
1755
+ categoryTitle,
1756
+ metaDescription(category),
1757
+ categoryScore,
1758
+ ...categoryMDItems
1759
+ );
1760
+ });
1761
+ return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
1762
+ }
1763
+ function categoryRef({ title, score, value, displayValue }, pluginTitle) {
1764
+ const auditTitleAsLink = link4(
1765
+ `#${slugify(title)}-${slugify(pluginTitle)}`,
1766
+ title
1505
1767
  );
1506
- return li(
1507
- `${getSquaredScoreMarker(
1508
- audit.score
1509
- )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`
1768
+ const marker = scoreMarker(score, "square");
1769
+ return li2(
1770
+ `${marker}${SPACE}${auditTitleAsLink}${SPACE}(_${pluginTitle}_) - ${boldMd2(
1771
+ (displayValue || value).toString()
1772
+ )}`
1510
1773
  );
1511
1774
  }
1512
- function groupItemToCategorySection(group, groupAudits, pluginTitle) {
1513
- const groupScore = Number(formatReportScore(group.score || 0));
1514
- const groupTitle = li(
1515
- `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`
1775
+ function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
1776
+ const groupTitle = li2(
1777
+ `${scoreMarker(score)}${SPACE}${title}${SPACE}(_${pluginTitle}_)`
1516
1778
  );
1517
- const auditTitles = groupAudits.reduce((acc, audit) => {
1518
- const auditTitle = link(
1519
- `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1520
- audit.title
1521
- );
1522
- return `${acc} ${li(
1523
- `${getSquaredScoreMarker(audit.score)} ${auditTitle} - ${getAuditResult(
1524
- audit
1525
- )}`
1526
- )}${NEW_LINE}`;
1527
- }, "");
1528
- return groupTitle + NEW_LINE + auditTitles;
1529
- }
1530
- function reportToAuditsSection(report) {
1531
- const auditsSection = report.plugins.reduce((pluginAcc, plugin) => {
1532
- const auditsData = plugin.audits.reduce((auditAcc, audit) => {
1533
- const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1534
- plugin.slug,
1535
- report.plugins
1536
- )})`;
1537
- return auditAcc + h3(auditTitle) + NEW_LINE + NEW_LINE + reportToDetailsSection(audit) + NEW_LINE + NEW_LINE + getDocsAndDescription(audit);
1538
- }, "");
1539
- return pluginAcc + auditsData;
1540
- }, "");
1541
- return h2("\u{1F6E1}\uFE0F Audits") + NEW_LINE + NEW_LINE + auditsSection;
1542
- }
1543
- function reportToDetailsSection(audit) {
1544
- const detailsTitle = `${getSquaredScoreMarker(audit.score)} ${getAuditResult(
1545
- audit,
1546
- true
1547
- )} (score: ${formatReportScore(audit.score)})`;
1548
- if (!audit.details?.issues.length) {
1549
- return detailsTitle;
1550
- }
1551
- const detailsTableData = [
1552
- detailsTableHeaders,
1553
- ...audit.details.issues.map((issue) => {
1554
- const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1555
- const message = issue.message;
1556
- if (!issue.source) {
1557
- return [severity, message, "", ""];
1558
- }
1559
- const file = `<code>${issue.source.file}</code>`;
1560
- if (!issue.source.position) {
1561
- return [severity, message, file, ""];
1562
- }
1563
- const { startLine, endLine } = issue.source.position;
1564
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1565
- return [severity, message, file, line];
1566
- })
1567
- ];
1568
- const detailsTable = `<h4>Issues</h4>${tableHtml(detailsTableData)}`;
1569
- return details(detailsTitle, detailsTable);
1570
- }
1571
- function reportToAboutSection(report) {
1572
- const date = formatDate(/* @__PURE__ */ new Date());
1573
- const { duration, version: version2, commit, plugins, categories } = report;
1574
- const commitInfo = commit ? `${commit.message} (${commit.hash})` : "N/A";
1575
- const reportMetaTable = [
1576
- reportMetaTableHeaders,
1577
- [
1578
- commitInfo,
1579
- style(version2 || "", ["c"]),
1580
- formatDuration(duration),
1581
- plugins.length.toString(),
1582
- categories.length.toString(),
1583
- plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1584
- ]
1585
- ];
1586
- const pluginMetaTable = [
1587
- pluginMetaTableHeaders,
1588
- ...plugins.map((plugin) => [
1589
- plugin.title,
1590
- plugin.audits.length.toString(),
1591
- style(plugin.version || "", ["c"]),
1592
- formatDuration(plugin.duration)
1593
- ])
1594
- ];
1595
- return (
1596
- // eslint-disable-next-line prefer-template
1597
- 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"])
1779
+ const auditTitles = groupAudits.map(
1780
+ ({ title: auditTitle, score: auditScore, value, displayValue }) => {
1781
+ const auditTitleLink = link4(
1782
+ `#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
1783
+ auditTitle
1784
+ );
1785
+ const marker = scoreMarker(auditScore, "square");
1786
+ return indentation2(
1787
+ li2(
1788
+ `${marker}${SPACE}${auditTitleLink} - ${boldMd2(
1789
+ String(displayValue ?? value)
1790
+ )}`
1791
+ )
1792
+ );
1793
+ }
1598
1794
  );
1795
+ return lines3(groupTitle, ...auditTitles);
1599
1796
  }
1600
- function getDocsAndDescription({
1601
- docsUrl,
1602
- description
1797
+
1798
+ // packages/utils/src/lib/reports/generate-md-report.ts
1799
+ var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link5, section: section4, code: codeMd } = md;
1800
+ var { bold: boldHtml, details: details2 } = html;
1801
+ function auditDetailsAuditValue({
1802
+ score,
1803
+ value,
1804
+ displayValue
1603
1805
  }) {
1604
- if (docsUrl) {
1605
- const docsLink = link(docsUrl, "\u{1F4D6} Docs");
1606
- if (!description) {
1607
- return docsLink + NEW_LINE + NEW_LINE;
1608
- }
1609
- if (description.endsWith("```")) {
1610
- return description + NEW_LINE + NEW_LINE + docsLink + NEW_LINE + NEW_LINE;
1611
- }
1612
- return `${description} ${docsLink}${NEW_LINE}${NEW_LINE}`;
1613
- }
1614
- if (description) {
1615
- return description + NEW_LINE + NEW_LINE;
1806
+ return `${scoreMarker(score, "square")} ${boldHtml(
1807
+ String(displayValue ?? value)
1808
+ )} (score: ${formatReportScore(score)})`;
1809
+ }
1810
+ function generateMdReport(report) {
1811
+ const printCategories = report.categories.length > 0;
1812
+ return lines4(
1813
+ h12(reportHeadlineText),
1814
+ printCategories ? categoriesOverviewSection(report) : "",
1815
+ printCategories ? categoriesDetailsSection(report) : "",
1816
+ auditsSection(report),
1817
+ aboutSection(report),
1818
+ `${FOOTER_PREFIX}${SPACE}${link5(README_LINK, "Code PushUp")}`
1819
+ );
1820
+ }
1821
+ function auditDetailsIssues(issues = []) {
1822
+ if (issues.length === 0) {
1823
+ return "";
1616
1824
  }
1617
- return "";
1825
+ const detailsTableData = {
1826
+ title: "Issues",
1827
+ columns: issuesTableHeadings,
1828
+ rows: issues.map(
1829
+ ({ severity: severityVal, message, source: sourceVal }) => {
1830
+ const severity = `${severityMarker(severityVal)} <i>${severityVal}</i>`;
1831
+ if (!sourceVal) {
1832
+ return { severity, message, file: "", line: "" };
1833
+ }
1834
+ const file = `<code>${sourceVal.file}</code>`;
1835
+ if (!sourceVal.position) {
1836
+ return { severity, message, file, line: "" };
1837
+ }
1838
+ const { startLine, endLine } = sourceVal.position;
1839
+ const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1840
+ return { severity, message, file, line };
1841
+ }
1842
+ )
1843
+ };
1844
+ return tableSection(detailsTableData);
1845
+ }
1846
+ function auditDetails(audit) {
1847
+ const { table: table5, issues = [] } = audit.details ?? {};
1848
+ const detailsValue = auditDetailsAuditValue(audit);
1849
+ if (issues.length === 0 && table5 == null) {
1850
+ return section4(detailsValue);
1851
+ }
1852
+ const tableSectionContent = table5 == null ? "" : tableSection(table5);
1853
+ const issuesSectionContent = issues.length > 0 ? auditDetailsIssues(issues) : "";
1854
+ return details2(
1855
+ detailsValue,
1856
+ lines4(tableSectionContent, issuesSectionContent)
1857
+ );
1618
1858
  }
1619
- function getAuditResult(audit, isHtml = false) {
1620
- const { displayValue, value } = audit;
1621
- return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1859
+ function auditsSection({
1860
+ plugins
1861
+ }) {
1862
+ const content = plugins.flatMap(
1863
+ ({ slug, audits }) => audits.flatMap((audit) => {
1864
+ const auditTitle = `${audit.title}${SPACE}(${getPluginNameFromSlug(
1865
+ slug,
1866
+ plugins
1867
+ )})`;
1868
+ const detailsContent = auditDetails(audit);
1869
+ const descriptionContent = metaDescription(audit);
1870
+ return [h33(auditTitle), detailsContent, descriptionContent];
1871
+ })
1872
+ );
1873
+ return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
1874
+ }
1875
+ function aboutSection(report) {
1876
+ const { date, plugins } = report;
1877
+ const reportMetaTable = reportMetaData(report);
1878
+ const pluginMetaTable = reportPluginMeta({ plugins });
1879
+ return lines4(
1880
+ h23("About"),
1881
+ section4(
1882
+ `Report was created by [Code PushUp](${README_LINK}) on ${formatDate(
1883
+ new Date(date)
1884
+ )}.`
1885
+ ),
1886
+ tableSection(pluginMetaTable),
1887
+ tableSection(reportMetaTable)
1888
+ );
1889
+ }
1890
+ function reportPluginMeta({ plugins }) {
1891
+ return {
1892
+ columns: [
1893
+ {
1894
+ key: "plugin",
1895
+ align: "left"
1896
+ },
1897
+ {
1898
+ key: "audits"
1899
+ },
1900
+ {
1901
+ key: "version"
1902
+ },
1903
+ {
1904
+ key: "duration"
1905
+ }
1906
+ ],
1907
+ rows: plugins.map(
1908
+ ({
1909
+ title: pluginTitle,
1910
+ audits,
1911
+ version: pluginVersion,
1912
+ duration: pluginDuration
1913
+ }) => ({
1914
+ plugin: pluginTitle,
1915
+ audits: audits.length.toString(),
1916
+ version: codeMd(pluginVersion || ""),
1917
+ duration: formatDuration(pluginDuration)
1918
+ })
1919
+ )
1920
+ };
1921
+ }
1922
+ function reportMetaData({
1923
+ commit,
1924
+ version: version2,
1925
+ duration,
1926
+ plugins,
1927
+ categories
1928
+ }) {
1929
+ const commitInfo = commit ? `${commit.message}${SPACE}(${commit.hash})` : "N/A";
1930
+ return {
1931
+ columns: [
1932
+ {
1933
+ key: "commit",
1934
+ align: "left"
1935
+ },
1936
+ {
1937
+ key: "version"
1938
+ },
1939
+ {
1940
+ key: "duration"
1941
+ },
1942
+ {
1943
+ key: "plugins"
1944
+ },
1945
+ {
1946
+ key: "categories"
1947
+ },
1948
+ {
1949
+ key: "audits"
1950
+ }
1951
+ ],
1952
+ rows: [
1953
+ {
1954
+ commit: commitInfo,
1955
+ version: codeMd(version2 || ""),
1956
+ duration: formatDuration(duration),
1957
+ plugins: plugins.length,
1958
+ categories: categories.length,
1959
+ audits: plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1960
+ }
1961
+ ]
1962
+ };
1622
1963
  }
1623
1964
 
1624
1965
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1966
+ var {
1967
+ h1: h13,
1968
+ h2: h24,
1969
+ lines: lines5,
1970
+ link: link6,
1971
+ bold: boldMd3,
1972
+ italic: italicMd,
1973
+ table: table4,
1974
+ section: section5
1975
+ } = md;
1976
+ var { details: details3 } = html;
1625
1977
  var MAX_ROWS = 100;
1626
1978
  function generateMdReportsDiff(diff) {
1627
- return paragraphs(
1628
- formatDiffHeaderSection(diff),
1979
+ return lines5(
1980
+ section5(formatDiffHeaderSection(diff)),
1629
1981
  formatDiffCategoriesSection(diff),
1630
1982
  formatDiffGroupsSection(diff),
1631
1983
  formatDiffAuditsSection(diff)
@@ -1633,12 +1985,12 @@ function generateMdReportsDiff(diff) {
1633
1985
  }
1634
1986
  function formatDiffHeaderSection(diff) {
1635
1987
  const outcomeTexts = {
1636
- positive: `\u{1F973} Code PushUp report has ${style("improved")}`,
1637
- negative: `\u{1F61F} Code PushUp report has ${style("regressed")}`,
1638
- mixed: `\u{1F928} Code PushUp report has both ${style(
1988
+ positive: `\u{1F973} Code PushUp report has ${boldMd3("improved")}`,
1989
+ negative: `\u{1F61F} Code PushUp report has ${boldMd3("regressed")}`,
1990
+ mixed: `\u{1F928} Code PushUp report has both ${boldMd3(
1639
1991
  "improvements and regressions"
1640
1992
  )}`,
1641
- unchanged: `\u{1F610} Code PushUp report is ${style("unchanged")}`
1993
+ unchanged: `\u{1F610} Code PushUp report is ${boldMd3("unchanged")}`
1642
1994
  };
1643
1995
  const outcome = mergeDiffOutcomes(
1644
1996
  changesToDiffOutcomes([
@@ -1648,8 +2000,8 @@ function formatDiffHeaderSection(diff) {
1648
2000
  ])
1649
2001
  );
1650
2002
  const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1651
- return paragraphs(
1652
- h1("Code PushUp"),
2003
+ return lines5(
2004
+ h13("Code PushUp"),
1653
2005
  diff.commits ? `${outcomeTexts[outcome]} \u2013 ${styleCommits(diff.commits)}.` : `${outcomeTexts[outcome]}.`
1654
2006
  );
1655
2007
  }
@@ -1660,102 +2012,104 @@ function formatDiffCategoriesSection(diff) {
1660
2012
  if (categoriesCount === 0) {
1661
2013
  return "";
1662
2014
  }
1663
- return paragraphs(
1664
- h2("\u{1F3F7}\uFE0F Categories"),
1665
- categoriesCount > 0 && tableMd(
1666
- [
1667
- [
1668
- "\u{1F3F7}\uFE0F Category",
1669
- hasChanges ? "\u2B50 Current score" : "\u2B50 Score",
1670
- "\u2B50 Previous score",
1671
- "\u{1F504} Score change"
1672
- ],
1673
- ...sortChanges(changed).map((category) => [
1674
- formatTitle(category),
1675
- formatScoreWithColor(category.scores.after),
1676
- formatScoreWithColor(category.scores.before, { skipBold: true }),
1677
- formatScoreChange(category.scores.diff)
1678
- ]),
1679
- ...added.map((category) => [
1680
- formatTitle(category),
1681
- formatScoreWithColor(category.score),
1682
- style("n/a (\\*)", ["i"]),
1683
- style("n/a (\\*)", ["i"])
1684
- ]),
1685
- ...unchanged.map((category) => [
1686
- formatTitle(category),
1687
- formatScoreWithColor(category.score),
1688
- formatScoreWithColor(category.score, { skipBold: true }),
1689
- "\u2013"
1690
- ])
1691
- ].map((row) => hasChanges ? row : row.slice(0, 2)),
1692
- hasChanges ? ["l", "c", "c", "c"] : ["l", "c"]
1693
- ),
1694
- added.length > 0 && style("(\\*) New category.", ["i"])
2015
+ const columns = [
2016
+ { key: "category", label: "\u{1F3F7}\uFE0F Category", align: "left" },
2017
+ { key: "after", label: hasChanges ? "\u2B50 Current score" : "\u2B50 Score" },
2018
+ { key: "before", label: "\u2B50 Previous score" },
2019
+ { key: "change", label: "\u{1F504} Score change" }
2020
+ ];
2021
+ return lines5(
2022
+ h24("\u{1F3F7}\uFE0F Categories"),
2023
+ categoriesCount > 0 && table4({
2024
+ columns: hasChanges ? columns : columns.slice(0, 2),
2025
+ rows: [
2026
+ ...sortChanges(changed).map((category) => ({
2027
+ category: formatTitle(category),
2028
+ after: formatScoreWithColor(category.scores.after),
2029
+ before: formatScoreWithColor(category.scores.before, {
2030
+ skipBold: true
2031
+ }),
2032
+ change: formatScoreChange(category.scores.diff)
2033
+ })),
2034
+ ...added.map((category) => ({
2035
+ category: formatTitle(category),
2036
+ after: formatScoreWithColor(category.score),
2037
+ before: italicMd("n/a (\\*)"),
2038
+ change: italicMd("n/a (\\*)")
2039
+ })),
2040
+ ...unchanged.map((category) => ({
2041
+ category: formatTitle(category),
2042
+ after: formatScoreWithColor(category.score),
2043
+ before: formatScoreWithColor(category.score, { skipBold: true }),
2044
+ change: "\u2013"
2045
+ }))
2046
+ ].map(
2047
+ (row) => hasChanges ? row : { category: row.category, after: row.after }
2048
+ )
2049
+ }),
2050
+ added.length > 0 && section5(italicMd("(\\*) New category."))
1695
2051
  );
1696
2052
  }
1697
2053
  function formatDiffGroupsSection(diff) {
1698
2054
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1699
2055
  return "";
1700
2056
  }
1701
- return paragraphs(
1702
- h2("\u{1F5C3}\uFE0F Groups"),
2057
+ return lines5(
2058
+ h24("\u{1F5C3}\uFE0F Groups"),
1703
2059
  formatGroupsOrAuditsDetails("group", diff.groups, {
1704
- headings: [
1705
- "\u{1F50C} Plugin",
1706
- "\u{1F5C3}\uFE0F Group",
1707
- "\u2B50 Current score",
1708
- "\u2B50 Previous score",
1709
- "\u{1F504} Score change"
2060
+ columns: [
2061
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2062
+ { key: "group", label: "\u{1F5C3}\uFE0F Group", align: "left" },
2063
+ { key: "after", label: "\u2B50 Current score" },
2064
+ { key: "before", label: "\u2B50 Previous score" },
2065
+ { key: "change", label: "\u{1F504} Score change" }
1710
2066
  ],
1711
- rows: sortChanges(diff.groups.changed).map((group) => [
1712
- formatTitle(group.plugin),
1713
- formatTitle(group),
1714
- formatScoreWithColor(group.scores.after),
1715
- formatScoreWithColor(group.scores.before, { skipBold: true }),
1716
- formatScoreChange(group.scores.diff)
1717
- ]),
1718
- align: ["l", "l", "c", "c", "c"]
2067
+ rows: sortChanges(diff.groups.changed).map((group) => ({
2068
+ plugin: formatTitle(group.plugin),
2069
+ group: formatTitle(group),
2070
+ after: formatScoreWithColor(group.scores.after),
2071
+ before: formatScoreWithColor(group.scores.before, { skipBold: true }),
2072
+ change: formatScoreChange(group.scores.diff)
2073
+ }))
1719
2074
  })
1720
2075
  );
1721
2076
  }
1722
2077
  function formatDiffAuditsSection(diff) {
1723
- return paragraphs(
1724
- h2("\u{1F6E1}\uFE0F Audits"),
2078
+ return lines5(
2079
+ h24("\u{1F6E1}\uFE0F Audits"),
1725
2080
  formatGroupsOrAuditsDetails("audit", diff.audits, {
1726
- headings: [
1727
- "\u{1F50C} Plugin",
1728
- "\u{1F6E1}\uFE0F Audit",
1729
- "\u{1F4CF} Current value",
1730
- "\u{1F4CF} Previous value",
1731
- "\u{1F504} Value change"
2081
+ columns: [
2082
+ { key: "plugin", label: "\u{1F50C} Plugin", align: "left" },
2083
+ { key: "audit", label: "\u{1F6E1}\uFE0F Audit", align: "left" },
2084
+ { key: "after", label: "\u{1F4CF} Current value" },
2085
+ { key: "before", label: "\u{1F4CF} Previous value" },
2086
+ { key: "change", label: "\u{1F504} Value change" }
1732
2087
  ],
1733
- rows: sortChanges(diff.audits.changed).map((audit) => [
1734
- formatTitle(audit.plugin),
1735
- formatTitle(audit),
1736
- `${getSquaredScoreMarker(audit.scores.after)} ${style(
2088
+ rows: sortChanges(diff.audits.changed).map((audit) => ({
2089
+ plugin: formatTitle(audit.plugin),
2090
+ audit: formatTitle(audit),
2091
+ after: `${scoreMarker(audit.scores.after, "square")} ${boldMd3(
1737
2092
  audit.displayValues.after || audit.values.after.toString()
1738
2093
  )}`,
1739
- `${getSquaredScoreMarker(audit.scores.before)} ${audit.displayValues.before || audit.values.before.toString()}`,
1740
- formatValueChange(audit)
1741
- ]),
1742
- align: ["l", "l", "c", "c", "c"]
2094
+ before: `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2095
+ change: formatValueChange(audit)
2096
+ }))
1743
2097
  })
1744
2098
  );
1745
2099
  }
1746
- function formatGroupsOrAuditsDetails(token, { changed, unchanged }, table) {
1747
- return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details(
2100
+ function formatGroupsOrAuditsDetails(token, { changed, unchanged }, tableData) {
2101
+ return changed.length === 0 ? summarizeUnchanged(token, { changed, unchanged }) : details3(
1748
2102
  summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1749
- paragraphs(
1750
- tableMd(
1751
- [table.headings, ...table.rows.slice(0, MAX_ROWS)],
1752
- table.align
1753
- ),
1754
- changed.length > MAX_ROWS && style(
2103
+ lines5(
2104
+ table4({
2105
+ ...tableData,
2106
+ rows: tableData.rows.slice(0, MAX_ROWS)
2107
+ // use never to avoid typing problem
2108
+ }),
2109
+ changed.length > MAX_ROWS && italicMd(
1755
2110
  `Only the ${MAX_ROWS} most affected ${pluralize(
1756
2111
  token
1757
- )} are listed above for brevity.`,
1758
- ["i"]
2112
+ )} are listed above for brevity.`
1759
2113
  ),
1760
2114
  unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1761
2115
  )
@@ -1776,11 +2130,13 @@ function formatValueChange({
1776
2130
  return colorByScoreDiff(`${marker} ${text}`, scores.diff);
1777
2131
  }
1778
2132
  function summarizeUnchanged(token, { changed, unchanged }) {
1779
- return [
1780
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1781
- unchanged.length === 1 ? "is" : "are",
1782
- "unchanged."
1783
- ].join(" ");
2133
+ return section5(
2134
+ [
2135
+ changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2136
+ unchanged.length === 1 ? "is" : "are",
2137
+ "unchanged."
2138
+ ].join(" ")
2139
+ );
1784
2140
  }
1785
2141
  function summarizeDiffOutcomes(outcomes, token) {
1786
2142
  return objectToEntries(countDiffOutcomes(outcomes)).filter(
@@ -1805,7 +2161,7 @@ function formatTitle({
1805
2161
  docsUrl
1806
2162
  }) {
1807
2163
  if (docsUrl) {
1808
- return link(docsUrl, title);
2164
+ return link6(docsUrl, title);
1809
2165
  }
1810
2166
  return title;
1811
2167
  }
@@ -1856,7 +2212,7 @@ function log(msg = "") {
1856
2212
  }
1857
2213
  function logStdoutSummary(report) {
1858
2214
  const printCategories = report.categories.length > 0;
1859
- log(reportToHeaderSection2(report));
2215
+ log(reportToHeaderSection(report));
1860
2216
  log();
1861
2217
  logPlugins(report);
1862
2218
  if (printCategories) {
@@ -1865,7 +2221,7 @@ function logStdoutSummary(report) {
1865
2221
  log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1866
2222
  log();
1867
2223
  }
1868
- function reportToHeaderSection2(report) {
2224
+ function reportToHeaderSection(report) {
1869
2225
  const { packageName, version: version2 } = report;
1870
2226
  return `${chalk4.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1871
2227
  }
@@ -1905,16 +2261,16 @@ function logCategories({ categories, plugins }) {
1905
2261
  applyScoreColor({ score }),
1906
2262
  countCategoryAudits(refs, plugins)
1907
2263
  ]);
1908
- const table = ui().table();
1909
- table.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
1910
- table.head(
2264
+ const table5 = ui().table();
2265
+ table5.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
2266
+ table5.head(
1911
2267
  reportRawOverviewTableHeaders.map((heading, idx) => ({
1912
2268
  content: chalk4.cyan(heading),
1913
2269
  hAlign: hAlign(idx)
1914
2270
  }))
1915
2271
  );
1916
2272
  rows.forEach(
1917
- (row) => table.row(
2273
+ (row) => table5.row(
1918
2274
  row.map((content, idx) => ({
1919
2275
  content: content.toString(),
1920
2276
  hAlign: hAlign(idx)
@@ -1923,19 +2279,19 @@ function logCategories({ categories, plugins }) {
1923
2279
  );
1924
2280
  log(chalk4.magentaBright.bold("Categories"));
1925
2281
  log();
1926
- table.render();
2282
+ table5.render();
1927
2283
  log();
1928
2284
  }
1929
2285
  function applyScoreColor({ score, text }) {
1930
2286
  const formattedScore = text ?? formatReportScore(score);
1931
- const style2 = text ? chalk4 : chalk4.bold;
2287
+ const style = text ? chalk4 : chalk4.bold;
1932
2288
  if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
1933
- return style2.green(formattedScore);
2289
+ return style.green(formattedScore);
1934
2290
  }
1935
2291
  if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
1936
- return style2.yellow(formattedScore);
2292
+ return style.yellow(formattedScore);
1937
2293
  }
1938
- return style2.red(formattedScore);
2294
+ return style.red(formattedScore);
1939
2295
  }
1940
2296
 
1941
2297
  // packages/utils/src/lib/reports/scoring.ts
@@ -2097,12 +2453,22 @@ var verboseUtils = (verbose = false) => ({
2097
2453
 
2098
2454
  // packages/core/package.json
2099
2455
  var name = "@code-pushup/core";
2100
- var version = "0.39.0";
2456
+ var version = "0.42.1";
2101
2457
 
2102
2458
  // packages/core/src/lib/implementation/execute-plugin.ts
2103
2459
  import chalk5 from "chalk";
2104
2460
 
2105
2461
  // packages/core/src/lib/normalize.ts
2462
+ function normalizeIssue(issue, gitRoot) {
2463
+ const { source, ...issueWithoutSource } = issue;
2464
+ return source == null ? issue : {
2465
+ ...issueWithoutSource,
2466
+ source: {
2467
+ ...source,
2468
+ file: formatGitPath(source.file, gitRoot)
2469
+ }
2470
+ };
2471
+ }
2106
2472
  async function normalizeAuditOutputs(audits) {
2107
2473
  const gitRoot = await getGitRoot();
2108
2474
  return audits.map((audit) => {
@@ -2114,13 +2480,7 @@ async function normalizeAuditOutputs(audits) {
2114
2480
  details: {
2115
2481
  ...audit.details,
2116
2482
  issues: audit.details.issues.map(
2117
- (issue) => issue.source == null ? issue : {
2118
- ...issue,
2119
- source: {
2120
- ...issue.source,
2121
- file: formatGitPath(issue.source.file, gitRoot)
2122
- }
2123
- }
2483
+ (issue) => normalizeIssue(issue, gitRoot)
2124
2484
  )
2125
2485
  }
2126
2486
  };
@@ -2158,7 +2518,11 @@ async function executeRunnerFunction(runner, onProgress) {
2158
2518
  // packages/core/src/lib/implementation/execute-plugin.ts
2159
2519
  var PluginOutputMissingAuditError = class extends Error {
2160
2520
  constructor(auditSlug) {
2161
- super(`Audit metadata not found for slug ${auditSlug}`);
2521
+ super(
2522
+ `Audit metadata not present in plugin config. Missing slug: ${chalk5.bold(
2523
+ auditSlug
2524
+ )}`
2525
+ );
2162
2526
  }
2163
2527
  };
2164
2528
  async function executePlugin(pluginConfig, onProgress) {
@@ -2172,7 +2536,11 @@ async function executePlugin(pluginConfig, onProgress) {
2172
2536
  } = pluginConfig;
2173
2537
  const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
2174
2538
  const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
2175
- const auditOutputs = auditOutputsSchema.parse(unvalidatedAuditOutputs);
2539
+ const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);
2540
+ if (!result.success) {
2541
+ throw new Error(`Audit output is invalid: ${result.error.message}`);
2542
+ }
2543
+ const auditOutputs = result.data;
2176
2544
  auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
2177
2545
  const normalizedAuditOutputs = await normalizeAuditOutputs(auditOutputs);
2178
2546
  const auditReports = normalizedAuditOutputs.map(
@@ -2192,32 +2560,48 @@ async function executePlugin(pluginConfig, onProgress) {
2192
2560
  ...groups && { groups }
2193
2561
  };
2194
2562
  }
2563
+ var wrapProgress = async (pluginCfg, steps, progressBar) => {
2564
+ progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2565
+ try {
2566
+ const pluginReport = await executePlugin(pluginCfg);
2567
+ progressBar?.incrementInSteps(steps);
2568
+ return pluginReport;
2569
+ } catch (error) {
2570
+ progressBar?.incrementInSteps(steps);
2571
+ throw new Error(
2572
+ error instanceof Error ? `- Plugin ${chalk5.bold(pluginCfg.title)} (${chalk5.bold(
2573
+ pluginCfg.slug
2574
+ )}) produced the following error:
2575
+ - ${error.message}` : String(error)
2576
+ );
2577
+ }
2578
+ };
2195
2579
  async function executePlugins(plugins, options) {
2196
2580
  const { progress = false } = options ?? {};
2197
2581
  const progressBar = progress ? getProgressBar("Run plugins") : null;
2198
- const pluginsResult = await plugins.reduce(async (acc, pluginCfg) => {
2199
- progressBar?.updateTitle(`Executing ${chalk5.bold(pluginCfg.title)}`);
2200
- try {
2201
- const pluginReport = await executePlugin(pluginCfg);
2202
- progressBar?.incrementInSteps(plugins.length);
2203
- return [...await acc, Promise.resolve(pluginReport)];
2204
- } catch (error) {
2205
- progressBar?.incrementInSteps(plugins.length);
2206
- return [
2207
- ...await acc,
2208
- Promise.reject(error instanceof Error ? error.message : String(error))
2209
- ];
2210
- }
2211
- }, Promise.resolve([]));
2582
+ const pluginsResult = await plugins.reduce(
2583
+ async (acc, pluginCfg) => [
2584
+ ...await acc,
2585
+ wrapProgress(pluginCfg, plugins.length, progressBar)
2586
+ ],
2587
+ Promise.resolve([])
2588
+ );
2212
2589
  progressBar?.endProgress("Done running plugins");
2213
2590
  const errorsTransform = ({ reason }) => String(reason);
2214
2591
  const results = await Promise.allSettled(pluginsResult);
2215
2592
  logMultipleResults(results, "Plugins", void 0, errorsTransform);
2216
2593
  const { fulfilled, rejected } = groupByStatus(results);
2217
2594
  if (rejected.length > 0) {
2218
- const errorMessages = rejected.map(({ reason }) => String(reason)).join(", ");
2595
+ const errorMessages = rejected.map(({ reason }) => String(reason)).join("\n");
2219
2596
  throw new Error(
2220
- `Plugins failed: ${rejected.length} errors: ${errorMessages}`
2597
+ `Executing ${pluralizeToken(
2598
+ "plugin",
2599
+ rejected.length
2600
+ )} failed.
2601
+
2602
+ ${errorMessages}
2603
+
2604
+ `
2221
2605
  );
2222
2606
  }
2223
2607
  return fulfilled.map((result) => result.value);
@@ -2514,9 +2898,6 @@ function reportsDiffToFileContent(reportsDiff, format) {
2514
2898
  }
2515
2899
  }
2516
2900
 
2517
- // packages/core/src/lib/history.ts
2518
- import { simpleGit as simpleGit2 } from "simple-git";
2519
-
2520
2901
  // packages/core/src/lib/upload.ts
2521
2902
  import {
2522
2903
  uploadToPortal
@@ -2562,17 +2943,33 @@ function groupToGQL(group) {
2562
2943
  };
2563
2944
  }
2564
2945
  function auditToGQL(audit) {
2946
+ const {
2947
+ slug,
2948
+ title,
2949
+ description,
2950
+ docsUrl,
2951
+ score,
2952
+ value,
2953
+ displayValue: formattedValue,
2954
+ details: details4
2955
+ } = audit;
2956
+ const {
2957
+ issues
2958
+ /*, table */
2959
+ } = details4 ?? {};
2565
2960
  return {
2566
- slug: audit.slug,
2567
- title: audit.title,
2568
- description: audit.description,
2569
- docsUrl: audit.docsUrl,
2570
- score: audit.score,
2571
- value: audit.value,
2572
- formattedValue: audit.displayValue,
2573
- ...audit.details && {
2961
+ slug,
2962
+ title,
2963
+ description,
2964
+ docsUrl,
2965
+ score,
2966
+ value,
2967
+ formattedValue,
2968
+ ...details4 && {
2574
2969
  details: {
2575
- issues: audit.details.issues.map(issueToGQL)
2970
+ ...issues && { issues: issues.map(issueToGQL) }
2971
+ // @TODO add when https://github.com/code-pushup/cli/issues/530 is implemented
2972
+ // ...(table ? {table} : {}),
2576
2973
  }
2577
2974
  }
2578
2975
  };
@@ -2679,24 +3076,6 @@ async function history(config, commits) {
2679
3076
  await safeCheckout(initialBranch, forceCleanStatus);
2680
3077
  return reports;
2681
3078
  }
2682
- async function getHashes(options, git = simpleGit2()) {
2683
- const { from, to } = options;
2684
- if (to && !from) {
2685
- throw new Error(
2686
- `git log command needs the "from" option defined to accept the "to" option.
2687
- `
2688
- );
2689
- }
2690
- const logs = await git.log({
2691
- ...options,
2692
- from,
2693
- to
2694
- });
2695
- return prepareHashes(logs);
2696
- }
2697
- function prepareHashes(logs) {
2698
- return logs.all.map(({ hash }) => hash).reverse();
2699
- }
2700
3079
 
2701
3080
  // packages/core/src/lib/implementation/read-rc-file.ts
2702
3081
  import { join as join6 } from "node:path";
@@ -2749,7 +3128,6 @@ export {
2749
3128
  compareReports,
2750
3129
  executePlugin,
2751
3130
  executePlugins,
2752
- getHashes,
2753
3131
  history,
2754
3132
  persistReport,
2755
3133
  readRcByPath,