@code-pushup/utils 0.50.0 → 0.52.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
@@ -155,9 +155,27 @@ function hasNonZeroWeightedRef(refs) {
155
155
  return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
156
156
  }
157
157
 
158
- // packages/models/src/lib/audit.ts
158
+ // packages/models/src/lib/source.ts
159
159
  import { z as z2 } from "zod";
160
- var auditSchema = z2.object({
160
+ var sourceFileLocationSchema = z2.object(
161
+ {
162
+ file: filePathSchema.describe("Relative path to source file in Git repo"),
163
+ position: z2.object(
164
+ {
165
+ startLine: positiveIntSchema.describe("Start line"),
166
+ startColumn: positiveIntSchema.describe("Start column").optional(),
167
+ endLine: positiveIntSchema.describe("End line").optional(),
168
+ endColumn: positiveIntSchema.describe("End column").optional()
169
+ },
170
+ { description: "Location in file" }
171
+ ).optional()
172
+ },
173
+ { description: "Source file location" }
174
+ );
175
+
176
+ // packages/models/src/lib/audit.ts
177
+ import { z as z3 } from "zod";
178
+ var auditSchema = z3.object({
161
179
  slug: slugSchema.describe("ID (unique within plugin)")
162
180
  }).merge(
163
181
  metaSchema({
@@ -167,7 +185,7 @@ var auditSchema = z2.object({
167
185
  description: "List of scorable metrics for the given plugin"
168
186
  })
169
187
  );
170
- var pluginAuditsSchema = z2.array(auditSchema, {
188
+ var pluginAuditsSchema = z3.array(auditSchema, {
171
189
  description: "List of audits maintained in a plugin"
172
190
  }).min(1).refine(
173
191
  (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
@@ -186,31 +204,16 @@ function getDuplicateSlugsInAudits(audits) {
186
204
  }
187
205
 
188
206
  // packages/models/src/lib/audit-output.ts
189
- import { z as z5 } from "zod";
207
+ import { z as z6 } from "zod";
190
208
 
191
209
  // packages/models/src/lib/issue.ts
192
- import { z as z3 } from "zod";
193
- var sourceFileLocationSchema = z3.object(
194
- {
195
- file: filePathSchema.describe("Relative path to source file in Git repo"),
196
- position: z3.object(
197
- {
198
- startLine: positiveIntSchema.describe("Start line"),
199
- startColumn: positiveIntSchema.describe("Start column").optional(),
200
- endLine: positiveIntSchema.describe("End line").optional(),
201
- endColumn: positiveIntSchema.describe("End column").optional()
202
- },
203
- { description: "Location in file" }
204
- ).optional()
205
- },
206
- { description: "Source file location" }
207
- );
208
- var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
210
+ import { z as z4 } from "zod";
211
+ var issueSeveritySchema = z4.enum(["info", "warning", "error"], {
209
212
  description: "Severity level"
210
213
  });
211
- var issueSchema = z3.object(
214
+ var issueSchema = z4.object(
212
215
  {
213
- message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
216
+ message: z4.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
214
217
  severity: issueSeveritySchema,
215
218
  source: sourceFileLocationSchema.optional()
216
219
  },
@@ -218,60 +221,60 @@ var issueSchema = z3.object(
218
221
  );
219
222
 
220
223
  // packages/models/src/lib/table.ts
221
- import { z as z4 } from "zod";
222
- var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
224
+ import { z as z5 } from "zod";
225
+ var tableAlignmentSchema = z5.enum(["left", "center", "right"], {
223
226
  description: "Cell alignment"
224
227
  });
225
- var tableColumnObjectSchema = z4.object({
226
- key: z4.string(),
227
- label: z4.string().optional(),
228
+ var tableColumnObjectSchema = z5.object({
229
+ key: z5.string(),
230
+ label: z5.string().optional(),
228
231
  align: tableAlignmentSchema.optional()
229
232
  });
230
- var tableRowObjectSchema = z4.record(tableCellValueSchema, {
233
+ var tableRowObjectSchema = z5.record(tableCellValueSchema, {
231
234
  description: "Object row"
232
235
  });
233
- var tableRowPrimitiveSchema = z4.array(tableCellValueSchema, {
236
+ var tableRowPrimitiveSchema = z5.array(tableCellValueSchema, {
234
237
  description: "Primitive row"
235
238
  });
236
- var tableSharedSchema = z4.object({
237
- title: z4.string().optional().describe("Display title for table")
239
+ var tableSharedSchema = z5.object({
240
+ title: z5.string().optional().describe("Display title for table")
238
241
  });
239
242
  var tablePrimitiveSchema = tableSharedSchema.merge(
240
- z4.object(
243
+ z5.object(
241
244
  {
242
- columns: z4.array(tableAlignmentSchema).optional(),
243
- rows: z4.array(tableRowPrimitiveSchema)
245
+ columns: z5.array(tableAlignmentSchema).optional(),
246
+ rows: z5.array(tableRowPrimitiveSchema)
244
247
  },
245
248
  { description: "Table with primitive rows and optional alignment columns" }
246
249
  )
247
250
  );
248
251
  var tableObjectSchema = tableSharedSchema.merge(
249
- z4.object(
252
+ z5.object(
250
253
  {
251
- columns: z4.union([
252
- z4.array(tableAlignmentSchema),
253
- z4.array(tableColumnObjectSchema)
254
+ columns: z5.union([
255
+ z5.array(tableAlignmentSchema),
256
+ z5.array(tableColumnObjectSchema)
254
257
  ]).optional(),
255
- rows: z4.array(tableRowObjectSchema)
258
+ rows: z5.array(tableRowObjectSchema)
256
259
  },
257
260
  {
258
261
  description: "Table with object rows and optional alignment or object columns"
259
262
  }
260
263
  )
261
264
  );
262
- var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
265
+ var tableSchema = (description = "Table information") => z5.union([tablePrimitiveSchema, tableObjectSchema], { description });
263
266
 
264
267
  // packages/models/src/lib/audit-output.ts
265
268
  var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
266
- var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
267
- var auditDetailsSchema = z5.object(
269
+ var auditDisplayValueSchema = z6.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
270
+ var auditDetailsSchema = z6.object(
268
271
  {
269
- issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
272
+ issues: z6.array(issueSchema, { description: "List of findings" }).optional(),
270
273
  table: tableSchema("Table of related findings").optional()
271
274
  },
272
275
  { description: "Detailed information" }
273
276
  );
274
- var auditOutputSchema = z5.object(
277
+ var auditOutputSchema = z6.object(
275
278
  {
276
279
  slug: slugSchema.describe("Reference to audit"),
277
280
  displayValue: auditDisplayValueSchema,
@@ -281,7 +284,7 @@ var auditOutputSchema = z5.object(
281
284
  },
282
285
  { description: "Audit information" }
283
286
  );
284
- var auditOutputsSchema = z5.array(auditOutputSchema, {
287
+ var auditOutputsSchema = z6.array(auditOutputSchema, {
285
288
  description: "List of JSON formatted audit output emitted by the runner process of a plugin"
286
289
  }).refine(
287
290
  (audits) => !getDuplicateSlugsInAudits2(audits),
@@ -298,13 +301,13 @@ function getDuplicateSlugsInAudits2(audits) {
298
301
  }
299
302
 
300
303
  // packages/models/src/lib/category-config.ts
301
- import { z as z6 } from "zod";
304
+ import { z as z7 } from "zod";
302
305
  var categoryRefSchema = weightedRefSchema(
303
306
  "Weighted references to audits and/or groups for the category",
304
307
  "Slug of an audit or group (depending on `type`)"
305
308
  ).merge(
306
- z6.object({
307
- type: z6.enum(["audit", "group"], {
309
+ z7.object({
310
+ type: z7.enum(["audit", "group"], {
308
311
  description: "Discriminant for reference kind, affects where `slug` is looked up"
309
312
  }),
310
313
  plugin: slugSchema.describe(
@@ -325,8 +328,8 @@ var categoryConfigSchema = scorableSchema(
325
328
  description: "Meta info for category"
326
329
  })
327
330
  ).merge(
328
- z6.object({
329
- isBinary: z6.boolean({
331
+ z7.object({
332
+ isBinary: z7.boolean({
330
333
  description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
331
334
  }).optional()
332
335
  })
@@ -342,7 +345,7 @@ function getDuplicateRefsInCategoryMetrics(metrics) {
342
345
  metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
343
346
  );
344
347
  }
345
- var categoriesSchema = z6.array(categoryConfigSchema, {
348
+ var categoriesSchema = z7.array(categoryConfigSchema, {
346
349
  description: "Categorization of individual audits"
347
350
  }).refine(
348
351
  (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
@@ -361,18 +364,18 @@ function getDuplicateSlugCategories(categories) {
361
364
  }
362
365
 
363
366
  // packages/models/src/lib/commit.ts
364
- import { z as z7 } from "zod";
365
- var commitSchema = z7.object(
367
+ import { z as z8 } from "zod";
368
+ var commitSchema = z8.object(
366
369
  {
367
- hash: z7.string({ description: "Commit SHA (full)" }).regex(
370
+ hash: z8.string({ description: "Commit SHA (full)" }).regex(
368
371
  /^[\da-f]{40}$/,
369
372
  "Commit SHA should be a 40-character hexadecimal string"
370
373
  ),
371
- message: z7.string({ description: "Commit message" }),
372
- date: z7.coerce.date({
374
+ message: z8.string({ description: "Commit message" }),
375
+ date: z8.coerce.date({
373
376
  description: "Date and time when commit was authored"
374
377
  }),
375
- author: z7.string({
378
+ author: z8.string({
376
379
  description: "Commit author name"
377
380
  }).trim()
378
381
  },
@@ -380,22 +383,22 @@ var commitSchema = z7.object(
380
383
  );
381
384
 
382
385
  // packages/models/src/lib/core-config.ts
383
- import { z as z13 } from "zod";
386
+ import { z as z14 } from "zod";
384
387
 
385
388
  // packages/models/src/lib/persist-config.ts
386
- import { z as z8 } from "zod";
387
- var formatSchema = z8.enum(["json", "md"]);
388
- var persistConfigSchema = z8.object({
389
+ import { z as z9 } from "zod";
390
+ var formatSchema = z9.enum(["json", "md"]);
391
+ var persistConfigSchema = z9.object({
389
392
  outputDir: filePathSchema.describe("Artifacts folder").optional(),
390
393
  filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
391
- format: z8.array(formatSchema).optional()
394
+ format: z9.array(formatSchema).optional()
392
395
  });
393
396
 
394
397
  // packages/models/src/lib/plugin-config.ts
395
- import { z as z11 } from "zod";
398
+ import { z as z12 } from "zod";
396
399
 
397
400
  // packages/models/src/lib/group.ts
398
- import { z as z9 } from "zod";
401
+ import { z as z10 } from "zod";
399
402
  var groupRefSchema = weightedRefSchema(
400
403
  "Weighted reference to a group",
401
404
  "Reference slug to a group within this plugin (e.g. 'max-lines')"
@@ -412,7 +415,7 @@ var groupSchema = scorableSchema(
412
415
  getDuplicateRefsInGroups,
413
416
  duplicateRefsInGroupsErrorMsg
414
417
  ).merge(groupMetaSchema);
415
- var groupsSchema = z9.array(groupSchema, {
418
+ var groupsSchema = z10.array(groupSchema, {
416
419
  description: "List of groups"
417
420
  }).optional().refine(
418
421
  (groups) => !getDuplicateSlugsInGroups(groups),
@@ -440,14 +443,14 @@ function getDuplicateSlugsInGroups(groups) {
440
443
  }
441
444
 
442
445
  // packages/models/src/lib/runner-config.ts
443
- import { z as z10 } from "zod";
444
- var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
445
- var runnerConfigSchema = z10.object(
446
+ import { z as z11 } from "zod";
447
+ var outputTransformSchema = z11.function().args(z11.unknown()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
448
+ var runnerConfigSchema = z11.object(
446
449
  {
447
- command: z10.string({
450
+ command: z11.string({
448
451
  description: "Shell command to execute"
449
452
  }),
450
- args: z10.array(z10.string({ description: "Command arguments" })).optional(),
453
+ args: z11.array(z11.string({ description: "Command arguments" })).optional(),
451
454
  outputFile: filePathSchema.describe("Output path"),
452
455
  outputTransform: outputTransformSchema.optional()
453
456
  },
@@ -455,8 +458,8 @@ var runnerConfigSchema = z10.object(
455
458
  description: "How to execute runner"
456
459
  }
457
460
  );
458
- var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
459
- var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
461
+ var onProgressSchema = z11.function().args(z11.unknown()).returns(z11.void());
462
+ var runnerFunctionSchema = z11.function().args(onProgressSchema.optional()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
460
463
 
461
464
  // packages/models/src/lib/plugin-config.ts
462
465
  var pluginMetaSchema = packageVersionSchema().merge(
@@ -467,13 +470,13 @@ var pluginMetaSchema = packageVersionSchema().merge(
467
470
  description: "Plugin metadata"
468
471
  })
469
472
  ).merge(
470
- z11.object({
473
+ z12.object({
471
474
  slug: slugSchema.describe("Unique plugin slug within core config"),
472
475
  icon: materialIconSchema
473
476
  })
474
477
  );
475
- var pluginDataSchema = z11.object({
476
- runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
478
+ var pluginDataSchema = z12.object({
479
+ runner: z12.union([runnerConfigSchema, runnerFunctionSchema]),
477
480
  audits: pluginAuditsSchema,
478
481
  groups: groupsSchema
479
482
  });
@@ -499,22 +502,22 @@ function getMissingRefsFromGroups(pluginCfg) {
499
502
  }
500
503
 
501
504
  // packages/models/src/lib/upload-config.ts
502
- import { z as z12 } from "zod";
503
- var uploadConfigSchema = z12.object({
505
+ import { z as z13 } from "zod";
506
+ var uploadConfigSchema = z13.object({
504
507
  server: urlSchema.describe("URL of deployed portal API"),
505
- apiKey: z12.string({
508
+ apiKey: z13.string({
506
509
  description: "API key with write access to portal (use `process.env` for security)"
507
510
  }),
508
511
  organization: slugSchema.describe(
509
512
  "Organization slug from Code PushUp portal"
510
513
  ),
511
514
  project: slugSchema.describe("Project slug from Code PushUp portal"),
512
- timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
515
+ timeout: z13.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
513
516
  });
514
517
 
515
518
  // packages/models/src/lib/core-config.ts
516
- var unrefinedCoreConfigSchema = z13.object({
517
- plugins: z13.array(pluginConfigSchema, {
519
+ var unrefinedCoreConfigSchema = z14.object({
520
+ plugins: z14.array(pluginConfigSchema, {
518
521
  description: "List of plugins to be used (official, community-provided, or custom)"
519
522
  }).min(1),
520
523
  /** portal configuration for persisting results */
@@ -537,7 +540,7 @@ function refineCoreConfig(schema) {
537
540
  }
538
541
 
539
542
  // packages/models/src/lib/report.ts
540
- import { z as z14 } from "zod";
543
+ import { z as z15 } from "zod";
541
544
  var auditReportSchema = auditSchema.merge(auditOutputSchema);
542
545
  var pluginReportSchema = pluginMetaSchema.merge(
543
546
  executionMetaSchema({
@@ -545,9 +548,9 @@ var pluginReportSchema = pluginMetaSchema.merge(
545
548
  descriptionDuration: "Duration of the plugin run in ms"
546
549
  })
547
550
  ).merge(
548
- z14.object({
549
- audits: z14.array(auditReportSchema).min(1),
550
- groups: z14.array(groupSchema).optional()
551
+ z15.object({
552
+ audits: z15.array(auditReportSchema).min(1),
553
+ groups: z15.array(groupSchema).optional()
551
554
  })
552
555
  ).refine(
553
556
  (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
@@ -581,10 +584,10 @@ var reportSchema = packageVersionSchema({
581
584
  descriptionDuration: "Duration of the collect run in ms"
582
585
  })
583
586
  ).merge(
584
- z14.object(
587
+ z15.object(
585
588
  {
586
- categories: z14.array(categoryConfigSchema),
587
- plugins: z14.array(pluginReportSchema).min(1),
589
+ categories: z15.array(categoryConfigSchema),
590
+ plugins: z15.array(pluginReportSchema).min(1),
588
591
  commit: commitSchema.describe("Git commit for which report was collected").nullable()
589
592
  },
590
593
  { description: "Collect output data" }
@@ -600,40 +603,40 @@ var reportSchema = packageVersionSchema({
600
603
  );
601
604
 
602
605
  // packages/models/src/lib/reports-diff.ts
603
- import { z as z15 } from "zod";
606
+ import { z as z16 } from "zod";
604
607
  function makeComparisonSchema(schema) {
605
608
  const sharedDescription = schema.description || "Result";
606
- return z15.object({
609
+ return z16.object({
607
610
  before: schema.describe(`${sharedDescription} (source commit)`),
608
611
  after: schema.describe(`${sharedDescription} (target commit)`)
609
612
  });
610
613
  }
611
614
  function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
612
- return z15.object(
615
+ return z16.object(
613
616
  {
614
- changed: z15.array(diffSchema),
615
- unchanged: z15.array(resultSchema),
616
- added: z15.array(resultSchema),
617
- removed: z15.array(resultSchema)
617
+ changed: z16.array(diffSchema),
618
+ unchanged: z16.array(resultSchema),
619
+ added: z16.array(resultSchema),
620
+ removed: z16.array(resultSchema)
618
621
  },
619
622
  { description }
620
623
  );
621
624
  }
622
- var scorableMetaSchema = z15.object({
625
+ var scorableMetaSchema = z16.object({
623
626
  slug: slugSchema,
624
627
  title: titleSchema,
625
628
  docsUrl: docsUrlSchema
626
629
  });
627
630
  var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
628
- z15.object({
631
+ z16.object({
629
632
  plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
630
633
  })
631
634
  );
632
635
  var scorableDiffSchema = scorableMetaSchema.merge(
633
- z15.object({
636
+ z16.object({
634
637
  scores: makeComparisonSchema(scoreSchema).merge(
635
- z15.object({
636
- diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
638
+ z16.object({
639
+ diff: z16.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
637
640
  })
638
641
  ).describe("Score comparison")
639
642
  })
@@ -644,10 +647,10 @@ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
644
647
  var categoryDiffSchema = scorableDiffSchema;
645
648
  var groupDiffSchema = scorableWithPluginDiffSchema;
646
649
  var auditDiffSchema = scorableWithPluginDiffSchema.merge(
647
- z15.object({
650
+ z16.object({
648
651
  values: makeComparisonSchema(auditValueSchema).merge(
649
- z15.object({
650
- diff: z15.number().int().describe("Value change (`values.after - values.before`)")
652
+ z16.object({
653
+ diff: z16.number().int().describe("Value change (`values.after - values.before`)")
651
654
  })
652
655
  ).describe("Audit `value` comparison"),
653
656
  displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
@@ -656,18 +659,18 @@ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
656
659
  })
657
660
  );
658
661
  var categoryResultSchema = scorableMetaSchema.merge(
659
- z15.object({ score: scoreSchema })
662
+ z16.object({ score: scoreSchema })
660
663
  );
661
664
  var groupResultSchema = scorableWithPluginMetaSchema.merge(
662
- z15.object({ score: scoreSchema })
665
+ z16.object({ score: scoreSchema })
663
666
  );
664
667
  var auditResultSchema = scorableWithPluginMetaSchema.merge(
665
668
  auditOutputSchema.pick({ score: true, value: true, displayValue: true })
666
669
  );
667
- var reportsDiffSchema = z15.object({
670
+ var reportsDiffSchema = z16.object({
668
671
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
669
672
  portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
670
- label: z15.string().optional().describe("Label (e.g. project name)"),
673
+ label: z16.string().optional().describe("Label (e.g. project name)"),
671
674
  categories: makeArraysComparisonSchema(
672
675
  categoryDiffSchema,
673
676
  categoryResultSchema,
@@ -770,6 +773,7 @@ var FOOTER_PREFIX = "Made with \u2764 by";
770
773
  var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
771
774
  var README_LINK = "https://github.com/code-pushup/cli#readme";
772
775
  var REPORT_HEADLINE_TEXT = "Code PushUp Report";
776
+ var CODE_PUSHUP_UNICODE_LOGO = "<\u2713>";
773
777
  var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
774
778
  "Category",
775
779
  "Score",
@@ -840,9 +844,17 @@ function severityMarker(severity) {
840
844
  }
841
845
  return "\u2139\uFE0F";
842
846
  }
847
+ var MIN_NON_ZERO_RESULT = 0.1;
848
+ function roundValue(value) {
849
+ const roundedValue = Math.round(value * 10) / 10;
850
+ if (roundedValue === 0 && value !== 0) {
851
+ return MIN_NON_ZERO_RESULT * Math.sign(value);
852
+ }
853
+ return roundedValue;
854
+ }
843
855
  function formatScoreChange(diff) {
844
856
  const marker = getDiffMarker(diff);
845
- const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
857
+ const text = formatDiffNumber(roundValue(diff * 100));
846
858
  return colorByScoreDiff(`${marker} ${text}`, diff);
847
859
  }
848
860
  function formatValueChange({
@@ -850,7 +862,7 @@ function formatValueChange({
850
862
  scores
851
863
  }) {
852
864
  const marker = getDiffMarker(values.diff);
853
- const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
865
+ const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : roundValue(values.diff / values.before * 100);
854
866
  const text = `${formatDiffNumber(percentage)}\u2009%`;
855
867
  return colorByScoreDiff(`${marker} ${text}`, scores.diff);
856
868
  }
@@ -1263,6 +1275,9 @@ function findLineNumberInText(content, pattern) {
1263
1275
  function filePathToCliArg(path) {
1264
1276
  return `"${path}"`;
1265
1277
  }
1278
+ function projectToFilename(project) {
1279
+ return project.replace(/[/\\\s]+/g, "-").replace(/@/g, "");
1280
+ }
1266
1281
 
1267
1282
  // packages/utils/src/lib/filter.ts
1268
1283
  function filterItemRefsBy(items, refFilterFn) {
@@ -1907,6 +1922,29 @@ import {
1907
1922
  MarkdownDocument,
1908
1923
  md as md2
1909
1924
  } from "build-md";
1925
+ import { posix as pathPosix } from "node:path";
1926
+
1927
+ // packages/utils/src/lib/reports/ide-environment.ts
1928
+ function getEnvironmentType() {
1929
+ if (isVSCode()) {
1930
+ return "vscode";
1931
+ }
1932
+ if (isGitHub()) {
1933
+ return "github";
1934
+ }
1935
+ return "other";
1936
+ }
1937
+ function isVSCode() {
1938
+ return process.env["TERM_PROGRAM"] === "vscode";
1939
+ }
1940
+ function isGitHub() {
1941
+ return process.env["GITHUB_ACTIONS"] === "true";
1942
+ }
1943
+ function getGitHubBaseUrl() {
1944
+ return `${process.env["GITHUB_SERVER_URL"]}/${process.env["GITHUB_REPOSITORY"]}/blob/${process.env["GITHUB_SHA"]}`;
1945
+ }
1946
+
1947
+ // packages/utils/src/lib/reports/formatting.ts
1910
1948
  function tableSection(tableData, options) {
1911
1949
  if (tableData.rows.length === 0) {
1912
1950
  return null;
@@ -1944,6 +1982,44 @@ function metaDescription(audit) {
1944
1982
  }
1945
1983
  return "";
1946
1984
  }
1985
+ function linkToLocalSourceForIde(source, options) {
1986
+ const { file, position } = source;
1987
+ const { outputDir } = options ?? {};
1988
+ if (!outputDir) {
1989
+ return md2.code(file);
1990
+ }
1991
+ return md2.link(formatFileLink(file, position, outputDir), md2.code(file));
1992
+ }
1993
+ function formatSourceLine(position) {
1994
+ if (!position) {
1995
+ return "";
1996
+ }
1997
+ const { startLine, endLine } = position;
1998
+ return endLine && startLine !== endLine ? `${startLine}-${endLine}` : `${startLine}`;
1999
+ }
2000
+ function formatGitHubLink(file, position) {
2001
+ const baseUrl = getGitHubBaseUrl();
2002
+ if (!position) {
2003
+ return `${baseUrl}/${file}`;
2004
+ }
2005
+ const { startLine, endLine, startColumn, endColumn } = position;
2006
+ const start = startColumn ? `L${startLine}C${startColumn}` : `L${startLine}`;
2007
+ const end = endLine ? endColumn ? `L${endLine}C${endColumn}` : `L${endLine}` : "";
2008
+ const lineRange = end && start !== end ? `${start}-${end}` : start;
2009
+ return `${baseUrl}/${file}#${lineRange}`;
2010
+ }
2011
+ function formatFileLink(file, position, outputDir) {
2012
+ const relativePath = pathPosix.relative(outputDir, file);
2013
+ const env = getEnvironmentType();
2014
+ switch (env) {
2015
+ case "vscode":
2016
+ return position ? `${relativePath}#L${position.startLine}` : relativePath;
2017
+ case "github":
2018
+ return formatGitHubLink(file, position);
2019
+ default:
2020
+ return relativePath;
2021
+ }
2022
+ }
1947
2023
 
1948
2024
  // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1949
2025
  import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
@@ -2145,16 +2221,16 @@ function auditDetailsAuditValue({
2145
2221
  String(displayValue ?? value)
2146
2222
  )} (score: ${formatReportScore(score)})`;
2147
2223
  }
2148
- function generateMdReport(report) {
2224
+ function generateMdReport(report, options) {
2149
2225
  return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
2150
2226
  report.categories.length > 0,
2151
2227
  (doc) => doc.$concat(
2152
2228
  categoriesOverviewSection(report),
2153
2229
  categoriesDetailsSection(report)
2154
2230
  )
2155
- ).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
2231
+ ).$concat(auditsSection(report, options), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
2156
2232
  }
2157
- function auditDetailsIssues(issues = []) {
2233
+ function auditDetailsIssues(issues = [], options) {
2158
2234
  if (issues.length === 0) {
2159
2235
  return null;
2160
2236
  }
@@ -2170,39 +2246,36 @@ function auditDetailsIssues(issues = []) {
2170
2246
  if (!source) {
2171
2247
  return [severity, message];
2172
2248
  }
2173
- const file = md4.code(source.file);
2249
+ const file = linkToLocalSourceForIde(source, options);
2174
2250
  if (!source.position) {
2175
2251
  return [severity, message, file];
2176
2252
  }
2177
- const { startLine, endLine } = source.position;
2178
- const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
2253
+ const line = formatSourceLine(source.position);
2179
2254
  return [severity, message, file, line];
2180
2255
  })
2181
2256
  );
2182
2257
  }
2183
- function auditDetails(audit) {
2258
+ function auditDetails(audit, options) {
2184
2259
  const { table: table2, issues = [] } = audit.details ?? {};
2185
2260
  const detailsValue = auditDetailsAuditValue(audit);
2186
2261
  if (issues.length === 0 && !table2?.rows.length) {
2187
2262
  return new MarkdownDocument3().paragraph(detailsValue);
2188
2263
  }
2189
2264
  const tableSectionContent = table2 && tableSection(table2);
2190
- const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
2265
+ const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues, options);
2191
2266
  return new MarkdownDocument3().details(
2192
2267
  detailsValue,
2193
2268
  new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
2194
2269
  );
2195
2270
  }
2196
- function auditsSection({
2197
- plugins
2198
- }) {
2271
+ function auditsSection({ plugins }, options) {
2199
2272
  return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
2200
2273
  plugins.flatMap(
2201
2274
  (plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
2202
2275
  ),
2203
2276
  (doc, { plugin, ...audit }) => {
2204
2277
  const auditTitle = `${audit.title} (${plugin.title})`;
2205
- const detailsContent = auditDetails(audit);
2278
+ const detailsContent = auditDetails(audit, options);
2206
2279
  const descriptionContent = metaDescription(audit);
2207
2280
  return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
2208
2281
  }
@@ -2464,15 +2537,6 @@ function createDiffCategoriesSection(diff, options) {
2464
2537
  function createCategoriesTable(diff, options) {
2465
2538
  const { changed, unchanged, added } = diff.categories;
2466
2539
  const { hasChanges, skipUnchanged } = options;
2467
- const columns = [
2468
- { heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
2469
- {
2470
- heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
2471
- alignment: "center"
2472
- },
2473
- { heading: "\u2B50 Current score", alignment: "center" },
2474
- { heading: "\u{1F504} Score change", alignment: "center" }
2475
- ];
2476
2540
  const rows = [
2477
2541
  ...sortChanges(changed).map((category) => [
2478
2542
  formatTitle(category),
@@ -2495,6 +2559,18 @@ function createCategoriesTable(diff, options) {
2495
2559
  "\u2013"
2496
2560
  ])
2497
2561
  ];
2562
+ if (rows.length === 0) {
2563
+ return [[], []];
2564
+ }
2565
+ const columns = [
2566
+ { heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
2567
+ {
2568
+ heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
2569
+ alignment: "center"
2570
+ },
2571
+ { heading: "\u2B50 Current score", alignment: "center" },
2572
+ { heading: "\u{1F504} Score change", alignment: "center" }
2573
+ ];
2498
2574
  return [
2499
2575
  hasChanges ? columns : columns.slice(0, 2),
2500
2576
  rows.map((row) => hasChanges ? row : row.slice(0, 2))
@@ -2585,11 +2661,11 @@ import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
2585
2661
  function log(msg = "") {
2586
2662
  ui().logger.log(msg);
2587
2663
  }
2588
- function logStdoutSummary(report) {
2664
+ function logStdoutSummary(report, verbose = false) {
2589
2665
  const printCategories = report.categories.length > 0;
2590
2666
  log(reportToHeaderSection(report));
2591
2667
  log();
2592
- logPlugins(report);
2668
+ logPlugins(report.plugins, verbose);
2593
2669
  if (printCategories) {
2594
2670
  logCategories(report);
2595
2671
  }
@@ -2600,36 +2676,49 @@ function reportToHeaderSection(report) {
2600
2676
  const { packageName, version } = report;
2601
2677
  return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version}`;
2602
2678
  }
2603
- function logPlugins(report) {
2604
- const { plugins } = report;
2679
+ function logPlugins(plugins, verbose) {
2605
2680
  plugins.forEach((plugin) => {
2606
2681
  const { title, audits } = plugin;
2607
- log();
2608
- log(bold4.magentaBright(`${title} audits`));
2609
- log();
2610
- audits.forEach((audit) => {
2611
- ui().row([
2612
- {
2613
- text: applyScoreColor({ score: audit.score, text: "\u25CF" }),
2614
- width: 2,
2615
- padding: [0, 1, 0, 0]
2616
- },
2617
- {
2618
- text: audit.title,
2619
- // eslint-disable-next-line no-magic-numbers
2620
- padding: [0, 3, 0, 0]
2621
- },
2622
- {
2623
- text: cyanBright(audit.displayValue || `${audit.value}`),
2624
- // eslint-disable-next-line no-magic-numbers
2625
- width: 20,
2626
- padding: [0, 0, 0, 0]
2627
- }
2628
- ]);
2629
- });
2682
+ const filteredAudits = verbose ? audits : audits.filter(({ score }) => score !== 1);
2683
+ const diff = audits.length - filteredAudits.length;
2684
+ logAudits(title, filteredAudits);
2685
+ if (diff > 0) {
2686
+ const notice = filteredAudits.length === 0 ? `... All ${diff} audits have perfect scores ...` : `... ${diff} audits with perfect scores omitted for brevity ...`;
2687
+ logRow(1, notice);
2688
+ }
2630
2689
  log();
2631
2690
  });
2632
2691
  }
2692
+ function logAudits(pluginTitle, audits) {
2693
+ log();
2694
+ log(bold4.magentaBright(`${pluginTitle} audits`));
2695
+ log();
2696
+ audits.forEach(({ score, title, displayValue, value }) => {
2697
+ logRow(score, title, displayValue || `${value}`);
2698
+ });
2699
+ }
2700
+ function logRow(score, title, value) {
2701
+ ui().row([
2702
+ {
2703
+ text: applyScoreColor({ score, text: "\u25CF" }),
2704
+ width: 2,
2705
+ padding: [0, 1, 0, 0]
2706
+ },
2707
+ {
2708
+ text: title,
2709
+ // eslint-disable-next-line no-magic-numbers
2710
+ padding: [0, 3, 0, 0]
2711
+ },
2712
+ ...value ? [
2713
+ {
2714
+ text: cyanBright(value),
2715
+ // eslint-disable-next-line no-magic-numbers
2716
+ width: 20,
2717
+ padding: [0, 0, 0, 0]
2718
+ }
2719
+ ] : []
2720
+ ]);
2721
+ }
2633
2722
  function logCategories({ categories, plugins }) {
2634
2723
  const hAlign = (idx) => idx === 0 ? "left" : "right";
2635
2724
  const rows = categories.map(({ title, score, refs, isBinary }) => [
@@ -2774,6 +2863,7 @@ var verboseUtils = (verbose = false) => ({
2774
2863
  });
2775
2864
  export {
2776
2865
  CODE_PUSHUP_DOMAIN,
2866
+ CODE_PUSHUP_UNICODE_LOGO,
2777
2867
  FOOTER_PREFIX,
2778
2868
  HIERARCHY,
2779
2869
  NEW_LINE,
@@ -2837,6 +2927,7 @@ export {
2837
2927
  pluginWorkDir,
2838
2928
  pluralize,
2839
2929
  pluralizeToken,
2930
+ projectToFilename,
2840
2931
  readJsonFile,
2841
2932
  readTextFile,
2842
2933
  removeDirectoryIfExists,
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@code-pushup/utils",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Low-level utilities (helper functions, etc.) used by Code PushUp CLI",
5
5
  "dependencies": {
6
- "@code-pushup/models": "0.50.0",
6
+ "@code-pushup/models": "0.52.0",
7
7
  "@isaacs/cliui": "^8.0.2",
8
8
  "@poppinss/cliui": "^6.4.0",
9
9
  "ansis": "^3.3.0",
@@ -24,6 +24,9 @@
24
24
  "url": "git+https://github.com/code-pushup/cli.git",
25
25
  "directory": "packages/utils"
26
26
  },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
27
30
  "type": "module",
28
31
  "main": "./index.js",
29
32
  "types": "./src/index.d.ts"
package/src/index.d.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  export { exists } from '@code-pushup/models';
2
- export { type Diff, comparePairs, matchArrayItemsByKey } from './lib/diff';
2
+ export { comparePairs, matchArrayItemsByKey, type Diff } from './lib/diff';
3
3
  export { stringifyError } from './lib/errors';
4
- export { type ProcessConfig, ProcessError, type ProcessObserver, type ProcessResult, executeProcess, } from './lib/execute-process';
5
- export { type CrawlFileSystemOptions, type FileResult, type MultipleFileResults, crawlFileSystem, directoryExists, ensureDirectoryExists, fileExists, filePathToCliArg, findLineNumberInText, importModule, logMultipleFileResults, pluginWorkDir, readJsonFile, readTextFile, removeDirectoryIfExists, } from './lib/file-system';
4
+ export { ProcessError, executeProcess, type ProcessConfig, type ProcessObserver, type ProcessResult, } from './lib/execute-process';
5
+ export { crawlFileSystem, directoryExists, ensureDirectoryExists, fileExists, filePathToCliArg, findLineNumberInText, importModule, logMultipleFileResults, pluginWorkDir, projectToFilename, readJsonFile, readTextFile, removeDirectoryIfExists, type CrawlFileSystemOptions, type FileResult, type MultipleFileResults, } from './lib/file-system';
6
6
  export { filterItemRefsBy } from './lib/filter';
7
7
  export { formatBytes, formatDuration, pluralize, pluralizeToken, slugify, truncateDescription, truncateIssueMessage, truncateText, truncateTitle, } from './lib/formatting';
8
8
  export { formatGitPath, getGitRoot, guardAgainstLocalChanges, safeCheckout, toGitPath, } from './lib/git/git';
9
- export { type LogResult, getCurrentBranchOrTag, getHashFromTag, getHashes, getLatestCommit, getSemverTags, } from './lib/git/git.commits-and-tags';
9
+ export { getCurrentBranchOrTag, getHashFromTag, getHashes, getLatestCommit, getSemverTags, type LogResult, } from './lib/git/git.commits-and-tags';
10
10
  export { groupByStatus } from './lib/group-by-status';
11
11
  export { isPromiseFulfilledResult, isPromiseRejectedResult, } from './lib/guards';
12
12
  export { logMultipleResults } from './lib/log-results';
13
- export { type CliUi, type Column, link, ui } from './lib/logging';
13
+ export { link, ui, type CliUi, type Column } from './lib/logging';
14
14
  export { mergeConfigs } from './lib/merge-configs';
15
- export { type ProgressBar, getProgressBar } from './lib/progress';
16
- export { CODE_PUSHUP_DOMAIN, FOOTER_PREFIX, README_LINK, TERMINAL_WIDTH, } from './lib/reports/constants';
15
+ export { getProgressBar, type ProgressBar } from './lib/progress';
16
+ export { CODE_PUSHUP_DOMAIN, CODE_PUSHUP_UNICODE_LOGO, FOOTER_PREFIX, README_LINK, TERMINAL_WIDTH, } from './lib/reports/constants';
17
17
  export { listAuditsFromAllPlugins, listGroupsFromAllPlugins, } from './lib/reports/flatten-plugins';
18
18
  export { generateMdReport } from './lib/reports/generate-md-report';
19
19
  export { generateMdReportsDiff, generateMdReportsDiffForMonorepo, } from './lib/reports/generate-md-reports-diff';
@@ -25,6 +25,6 @@ export type { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/rep
25
25
  export { calcDuration, compareIssueSeverity, formatReportScore, } from './lib/reports/utils';
26
26
  export { isSemver, normalizeSemver, sortSemvers } from './lib/semver';
27
27
  export * from './lib/text-formats';
28
- export { type CliArgsObject, capitalize, countOccurrences, distinct, factorOf, fromJsonLines, objectFromEntries, objectToCliArgs, objectToEntries, objectToKeys, toArray, toJsonLines, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
28
+ export { capitalize, countOccurrences, distinct, factorOf, fromJsonLines, objectFromEntries, objectToCliArgs, objectToEntries, objectToKeys, toArray, toJsonLines, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, type CliArgsObject, } from './lib/transform';
29
29
  export type { ExcludeNullFromPropertyTypes, ExtractArray, ExtractArrays, ItemOrArray, Prettify, WithRequired, } from './lib/types';
30
30
  export { verboseUtils } from './lib/verbose-utils';
@@ -18,3 +18,4 @@ export type CrawlFileSystemOptions<T> = {
18
18
  export declare function crawlFileSystem<T = string>(options: CrawlFileSystemOptions<T>): Promise<T[]>;
19
19
  export declare function findLineNumberInText(content: string, pattern: string): number | null;
20
20
  export declare function filePathToCliArg(path: string): string;
21
+ export declare function projectToFilename(project: string): string;
@@ -7,4 +7,5 @@ export declare const FOOTER_PREFIX = "Made with \u2764 by";
7
7
  export declare const CODE_PUSHUP_DOMAIN = "code-pushup.dev";
8
8
  export declare const README_LINK = "https://github.com/code-pushup/cli#readme";
9
9
  export declare const REPORT_HEADLINE_TEXT = "Code PushUp Report";
10
+ export declare const CODE_PUSHUP_UNICODE_LOGO = "<\u2713>";
10
11
  export declare const REPORT_RAW_OVERVIEW_TABLE_HEADERS: string[];
@@ -1,6 +1,19 @@
1
1
  import { type HeadingLevel, type InlineText, MarkdownDocument } from 'build-md';
2
- import type { AuditReport, Table } from '@code-pushup/models';
2
+ import type { AuditReport, SourceFileLocation, Table } from '@code-pushup/models';
3
+ import type { MdReportOptions } from './types';
3
4
  export declare function tableSection(tableData: Table, options?: {
4
5
  level?: HeadingLevel;
5
6
  }): MarkdownDocument | null;
6
7
  export declare function metaDescription(audit: Pick<AuditReport, 'docsUrl' | 'description'>): InlineText;
8
+ /**
9
+ * Link to local source for IDE
10
+ * @param source
11
+ * @param reportLocation
12
+ *
13
+ * @example
14
+ * linkToLocalSourceInIde({ file: 'src/index.ts' }, { outputDir: '.code-pushup' }) // [`src/index.ts`](../src/index.ts)
15
+ */
16
+ export declare function linkToLocalSourceForIde(source: SourceFileLocation, options?: Pick<MdReportOptions, 'outputDir'>): InlineText;
17
+ export declare function formatSourceLine(position: SourceFileLocation['position']): string;
18
+ export declare function formatGitHubLink(file: string, position: SourceFileLocation['position']): string;
19
+ export declare function formatFileLink(file: string, position: SourceFileLocation['position'], outputDir: string): string;
@@ -1,11 +1,11 @@
1
1
  import { type InlineText, MarkdownDocument } from 'build-md';
2
2
  import type { AuditReport, Issue, Report } from '@code-pushup/models';
3
- import type { ScoredReport } from './types';
3
+ import type { MdReportOptions, ScoredReport } from './types';
4
4
  export declare function auditDetailsAuditValue({ score, value, displayValue, }: AuditReport): InlineText;
5
- export declare function generateMdReport(report: ScoredReport): string;
6
- export declare function auditDetailsIssues(issues?: Issue[]): MarkdownDocument | null;
7
- export declare function auditDetails(audit: AuditReport): MarkdownDocument;
8
- export declare function auditsSection({ plugins, }: Pick<ScoredReport, 'plugins'>): MarkdownDocument;
5
+ export declare function generateMdReport(report: ScoredReport, options?: MdReportOptions): string;
6
+ export declare function auditDetailsIssues(issues?: Issue[], options?: MdReportOptions): MarkdownDocument | null;
7
+ export declare function auditDetails(audit: AuditReport, options?: MdReportOptions): MarkdownDocument;
8
+ export declare function auditsSection({ plugins }: Pick<ScoredReport, 'plugins'>, options?: MdReportOptions): MarkdownDocument;
9
9
  export declare function aboutSection(report: Omit<ScoredReport, 'packageName'>): MarkdownDocument;
10
10
  export declare function pluginMetaTable({ plugins, }: Pick<Report, 'plugins'>): Parameters<MarkdownDocument['table']>;
11
11
  export declare function reportMetaTable({ commit, version, duration, plugins, categories, }: Pick<ScoredReport, 'date' | 'duration' | 'version' | 'commit' | 'plugins' | 'categories'>): Parameters<MarkdownDocument['table']>;
@@ -0,0 +1,3 @@
1
+ import type { IdeEnvironment } from './types';
2
+ export declare function getEnvironmentType(): IdeEnvironment;
3
+ export declare function getGitHubBaseUrl(): string;
@@ -1,4 +1,5 @@
1
1
  import type { ScoredReport } from './types';
2
- export declare function logStdoutSummary(report: ScoredReport): void;
2
+ export declare function logStdoutSummary(report: ScoredReport, verbose?: boolean): void;
3
+ export declare function logPlugins(plugins: ScoredReport['plugins'], verbose: boolean): void;
3
4
  export declare function logCategories({ categories, plugins }: ScoredReport): void;
4
5
  export declare function binaryIconPrefix(score: number, isBinary: boolean | undefined): string;
@@ -1,4 +1,4 @@
1
- import type { AuditReport, CategoryConfig, Group, PluginReport, Report } from '@code-pushup/models';
1
+ import type { AuditReport, CategoryConfig, Group, PersistConfig, PluginReport, Report } from '@code-pushup/models';
2
2
  export type ScoredCategoryConfig = CategoryConfig & {
3
3
  score: number;
4
4
  };
@@ -20,3 +20,5 @@ export type SortableAuditReport = AuditReport & {
20
20
  plugin: string;
21
21
  };
22
22
  export type DiffOutcome = 'positive' | 'negative' | 'mixed' | 'unchanged';
23
+ export type MdReportOptions = Pick<PersistConfig, 'outputDir'>;
24
+ export type IdeEnvironment = 'vscode' | 'github' | 'other';
@@ -15,6 +15,7 @@ export declare function colorByScoreDiff(text: string, diff: number): InlineText
15
15
  export declare function shieldsBadge(text: string, color: string): InlineText;
16
16
  export declare function formatDiffNumber(diff: number): string;
17
17
  export declare function severityMarker(severity: 'info' | 'warning' | 'error'): string;
18
+ export declare function roundValue(value: number): number;
18
19
  export declare function formatScoreChange(diff: number): InlineText;
19
20
  export declare function formatValueChange({ values, scores, }: Pick<AuditDiff, 'values' | 'scores'>): InlineText;
20
21
  export declare function calcDuration(start: number, stop?: number): number;