@code-pushup/utils 0.8.9 → 0.8.11

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
@@ -38,6 +38,46 @@ function errorItems(items, transform = (items2) => items2.join(", ")) {
38
38
  function exists(value) {
39
39
  return value != null;
40
40
  }
41
+ function getMissingRefsForCategories(categories, plugins) {
42
+ const missingRefs = [];
43
+ const auditRefsFromCategory = categories.flatMap(
44
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
45
+ );
46
+ const auditRefsFromPlugins = plugins.flatMap(
47
+ ({ audits, slug: pluginSlug }) => {
48
+ return audits.map(({ slug }) => `${pluginSlug}/${slug}`);
49
+ }
50
+ );
51
+ const missingAuditRefs = hasMissingStrings(
52
+ auditRefsFromCategory,
53
+ auditRefsFromPlugins
54
+ );
55
+ if (Array.isArray(missingAuditRefs) && missingAuditRefs.length > 0) {
56
+ missingRefs.push(...missingAuditRefs);
57
+ }
58
+ const groupRefsFromCategory = categories.flatMap(
59
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
60
+ );
61
+ const groupRefsFromPlugins = plugins.flatMap(
62
+ ({ groups, slug: pluginSlug }) => {
63
+ return Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : [];
64
+ }
65
+ );
66
+ const missingGroupRefs = hasMissingStrings(
67
+ groupRefsFromCategory,
68
+ groupRefsFromPlugins
69
+ );
70
+ if (Array.isArray(missingGroupRefs) && missingGroupRefs.length > 0) {
71
+ missingRefs.push(...missingGroupRefs);
72
+ }
73
+ return missingRefs.length ? missingRefs : false;
74
+ }
75
+ function missingRefsForCategoriesErrorMsg(categories, plugins) {
76
+ const missingRefs = getMissingRefsForCategories(categories, plugins);
77
+ return `The following category references need to point to an audit or group: ${errorItems(
78
+ missingRefs
79
+ )}`;
80
+ }
41
81
 
42
82
  // packages/models/src/lib/implementation/schemas.ts
43
83
  function executionMetaSchema(options = {
@@ -96,15 +136,13 @@ function positiveIntSchema(description) {
96
136
  return z.number({ description }).int().nonnegative();
97
137
  }
98
138
  function packageVersionSchema(options) {
99
- let { versionDescription, optional } = options || {};
100
- versionDescription = versionDescription || "NPM version of the package";
101
- optional = !!optional;
139
+ const { versionDescription = "NPM version of the package", required } = options ?? {};
102
140
  const packageSchema = z.string({ description: "NPM package name" });
103
141
  const versionSchema = z.string({ description: versionDescription });
104
142
  return z.object(
105
143
  {
106
- packageName: optional ? packageSchema.optional() : packageSchema,
107
- version: optional ? versionSchema.optional() : versionSchema
144
+ packageName: required ? packageSchema : packageSchema.optional(),
145
+ version: required ? versionSchema : versionSchema.optional()
108
146
  },
109
147
  { description: "NPM package name and version of a published package" }
110
148
  );
@@ -166,7 +204,7 @@ var pluginAuditsSchema = z2.array(auditSchema, {
166
204
  );
167
205
  function duplicateSlugsInAuditsErrorMsg(audits) {
168
206
  const duplicateRefs = getDuplicateSlugsInAudits(audits);
169
- return `In plugin audits the slugs are not unique: ${errorItems(
207
+ return `In plugin audits the following slugs are not unique: ${errorItems(
170
208
  duplicateRefs
171
209
  )}`;
172
210
  }
@@ -355,7 +393,9 @@ function getDuplicateRefsInGroups(groups) {
355
393
  }
356
394
  function duplicateSlugsInGroupsErrorMsg(groups) {
357
395
  const duplicateRefs = getDuplicateSlugsInGroups(groups);
358
- return `In groups the slugs are not unique: ${errorItems(duplicateRefs)}`;
396
+ return `In groups the following slugs are not unique: ${errorItems(
397
+ duplicateRefs
398
+ )}`;
359
399
  }
360
400
  function getDuplicateSlugsInGroups(groups) {
361
401
  return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
@@ -381,9 +421,7 @@ var onProgressSchema = z8.function().args(z8.unknown()).returns(z8.void());
381
421
  var runnerFunctionSchema = z8.function().args(onProgressSchema.optional()).returns(z8.union([auditOutputsSchema, z8.promise(auditOutputsSchema)]));
382
422
 
383
423
  // packages/models/src/lib/plugin-config.ts
384
- var pluginMetaSchema = packageVersionSchema({
385
- optional: true
386
- }).merge(
424
+ var pluginMetaSchema = packageVersionSchema().merge(
387
425
  metaSchema({
388
426
  titleDescription: "Descriptive name",
389
427
  descriptionDescription: "Description (markdown)",
@@ -392,7 +430,7 @@ var pluginMetaSchema = packageVersionSchema({
392
430
  })
393
431
  ).merge(
394
432
  z9.object({
395
- slug: slugSchema("References plugin. ID (unique within core config)"),
433
+ slug: slugSchema("Unique plugin slug within core config"),
396
434
  icon: materialIconSchema
397
435
  })
398
436
  );
@@ -409,20 +447,17 @@ var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
409
447
  );
410
448
  function missingRefsFromGroupsErrorMsg(pluginCfg) {
411
449
  const missingRefs = getMissingRefsFromGroups(pluginCfg);
412
- return `In the groups, the following audit ref's needs to point to a audit in this plugin config: ${errorItems(
450
+ return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
413
451
  missingRefs
414
452
  )}`;
415
453
  }
416
454
  function getMissingRefsFromGroups(pluginCfg) {
417
- if (pluginCfg?.groups?.length && pluginCfg?.audits?.length) {
418
- const groups = pluginCfg?.groups || [];
419
- const audits = pluginCfg?.audits || [];
420
- return hasMissingStrings(
421
- groups.flatMap(({ refs: audits2 }) => audits2.map(({ slug: ref }) => ref)),
422
- audits.map(({ slug }) => slug)
423
- );
424
- }
425
- return false;
455
+ return hasMissingStrings(
456
+ pluginCfg.groups?.flatMap(
457
+ ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
458
+ ) ?? [],
459
+ pluginCfg.audits.map(({ slug }) => slug)
460
+ );
426
461
  }
427
462
 
428
463
  // packages/models/src/lib/upload-config.ts
@@ -432,19 +467,15 @@ var uploadConfigSchema = z10.object({
432
467
  apiKey: z10.string({
433
468
  description: "API key with write access to portal (use `process.env` for security)"
434
469
  }),
435
- organization: z10.string({
436
- description: "Organization in code versioning system"
437
- }),
438
- project: z10.string({
439
- description: "Project in code versioning system"
440
- })
470
+ organization: slugSchema("Organization slug from Code PushUp portal"),
471
+ project: slugSchema("Project slug from Code PushUp portal")
441
472
  });
442
473
 
443
474
  // packages/models/src/lib/core-config.ts
444
475
  var unrefinedCoreConfigSchema = z11.object({
445
476
  plugins: z11.array(pluginConfigSchema, {
446
477
  description: "List of plugins to be used (official, community-provided, or custom)"
447
- }),
478
+ }).min(1),
448
479
  /** portal configuration for persisting results */
449
480
  persist: persistConfigSchema.optional(),
450
481
  /** portal configuration for uploading results */
@@ -454,52 +485,15 @@ var unrefinedCoreConfigSchema = z11.object({
454
485
  var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
455
486
  function refineCoreConfig(schema) {
456
487
  return schema.refine(
457
- (coreCfg) => !getMissingRefsForCategories(coreCfg),
488
+ (coreCfg) => !getMissingRefsForCategories(coreCfg.categories, coreCfg.plugins),
458
489
  (coreCfg) => ({
459
- message: missingRefsForCategoriesErrorMsg(coreCfg)
490
+ message: missingRefsForCategoriesErrorMsg(
491
+ coreCfg.categories,
492
+ coreCfg.plugins
493
+ )
460
494
  })
461
495
  );
462
496
  }
463
- function missingRefsForCategoriesErrorMsg(coreCfg) {
464
- const missingRefs = getMissingRefsForCategories(coreCfg);
465
- return `In the categories, the following plugin refs do not exist in the provided plugins: ${errorItems(
466
- missingRefs
467
- )}`;
468
- }
469
- function getMissingRefsForCategories(coreCfg) {
470
- const missingRefs = [];
471
- const auditRefsFromCategory = coreCfg.categories.flatMap(
472
- ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
473
- );
474
- const auditRefsFromPlugins = coreCfg.plugins.flatMap(
475
- ({ audits, slug: pluginSlug }) => {
476
- return audits.map(({ slug }) => `${pluginSlug}/${slug}`);
477
- }
478
- );
479
- const missingAuditRefs = hasMissingStrings(
480
- auditRefsFromCategory,
481
- auditRefsFromPlugins
482
- );
483
- if (Array.isArray(missingAuditRefs) && missingAuditRefs.length > 0) {
484
- missingRefs.push(...missingAuditRefs);
485
- }
486
- const groupRefsFromCategory = coreCfg.categories.flatMap(
487
- ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
488
- );
489
- const groupRefsFromPlugins = coreCfg.plugins.flatMap(
490
- ({ groups, slug: pluginSlug }) => {
491
- return Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : [];
492
- }
493
- );
494
- const missingGroupRefs = hasMissingStrings(
495
- groupRefsFromCategory,
496
- groupRefsFromPlugins
497
- );
498
- if (Array.isArray(missingGroupRefs) && missingGroupRefs.length > 0) {
499
- missingRefs.push(...missingGroupRefs);
500
- }
501
- return missingRefs.length ? missingRefs : false;
502
- }
503
497
 
504
498
  // packages/models/src/lib/report.ts
505
499
  import { z as z12 } from "zod";
@@ -514,9 +508,32 @@ var pluginReportSchema = pluginMetaSchema.merge(
514
508
  audits: z12.array(auditReportSchema),
515
509
  groups: z12.array(groupSchema).optional()
516
510
  })
511
+ ).refine(
512
+ (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
513
+ (pluginReport) => ({
514
+ message: missingRefsFromGroupsErrorMsg2(
515
+ pluginReport.audits,
516
+ pluginReport.groups ?? []
517
+ )
518
+ })
517
519
  );
520
+ function missingRefsFromGroupsErrorMsg2(audits, groups) {
521
+ const missingRefs = getMissingRefsFromGroups2(audits, groups);
522
+ return `group references need to point to an existing audit in this plugin report: ${errorItems(
523
+ missingRefs
524
+ )}`;
525
+ }
526
+ function getMissingRefsFromGroups2(audits, groups) {
527
+ return hasMissingStrings(
528
+ groups.flatMap(
529
+ ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
530
+ ),
531
+ audits.map(({ slug }) => slug)
532
+ );
533
+ }
518
534
  var reportSchema = packageVersionSchema({
519
- versionDescription: "NPM version of the CLI"
535
+ versionDescription: "NPM version of the CLI",
536
+ required: true
520
537
  }).merge(
521
538
  executionMetaSchema({
522
539
  descriptionDate: "Start date and time of the collect run",
@@ -526,10 +543,18 @@ var reportSchema = packageVersionSchema({
526
543
  z12.object(
527
544
  {
528
545
  categories: z12.array(categoryConfigSchema),
529
- plugins: z12.array(pluginReportSchema)
546
+ plugins: z12.array(pluginReportSchema).min(1)
530
547
  },
531
548
  { description: "Collect output data" }
532
549
  )
550
+ ).refine(
551
+ (report) => !getMissingRefsForCategories(report.categories, report.plugins),
552
+ (report) => ({
553
+ message: missingRefsForCategoriesErrorMsg(
554
+ report.categories,
555
+ report.plugins
556
+ )
557
+ })
533
558
  );
534
559
 
535
560
  // packages/utils/src/lib/constants.ts
@@ -856,13 +881,13 @@ function getGroupWithAudits(refSlug, refPlugin, plugins) {
856
881
  },
857
882
  []
858
883
  );
859
- const audits = groupAudits.sort(sortCategoryAudits);
884
+ const audits = groupAudits.sort(compareCategoryAudits);
860
885
  return {
861
886
  ...groupWithAudits,
862
887
  audits
863
888
  };
864
889
  }
865
- function sortCategoryAudits(a, b) {
890
+ function compareCategoryAudits(a, b) {
866
891
  if (a.weight !== b.weight) {
867
892
  return b.weight - a.weight;
868
893
  }
@@ -874,7 +899,7 @@ function sortCategoryAudits(a, b) {
874
899
  }
875
900
  return a.title.localeCompare(b.title);
876
901
  }
877
- function sortAudits(a, b) {
902
+ function compareAudits(a, b) {
878
903
  if (a.score !== b.score) {
879
904
  return a.score - b.score;
880
905
  }
@@ -1168,23 +1193,18 @@ function reportToCategoriesSection(report) {
1168
1193
  category.score
1169
1194
  )} Score: ${style(formatReportScore(category.score))}`;
1170
1195
  const categoryDocs = getDocsAndDescription(category);
1171
- const auditsAndGroups = category.refs.reduce(
1172
- (acc2, ref) => ({
1173
- ...acc2,
1174
- ...ref.type === "group" ? {
1175
- groups: [
1176
- ...acc2.groups,
1177
- getGroupWithAudits(ref.slug, ref.plugin, plugins)
1178
- ]
1179
- } : {
1180
- audits: [...acc2.audits, getAuditByRef(ref, plugins)]
1181
- }
1182
- }),
1183
- { groups: [], audits: [] }
1184
- );
1185
- const audits = auditsAndGroups.audits.sort(sortCategoryAudits).map((audit) => auditItemToCategorySection(audit, plugins)).join(NEW_LINE);
1186
- const groups = auditsAndGroups.groups.map((group) => groupItemToCategorySection(group, plugins)).join("");
1187
- return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + groups + NEW_LINE + audits;
1196
+ const categoryMDItems = category.refs.reduce((acc2, ref) => {
1197
+ if (ref.type === "group") {
1198
+ const group = getGroupWithAudits(ref.slug, ref.plugin, plugins);
1199
+ const mdGroupItem = groupItemToCategorySection(group, plugins);
1200
+ return acc2 + mdGroupItem + NEW_LINE;
1201
+ } else {
1202
+ const audit = getAuditByRef(ref, plugins);
1203
+ const mdAuditItem = auditItemToCategorySection(audit, plugins);
1204
+ return acc2 + mdAuditItem + NEW_LINE;
1205
+ }
1206
+ }, "");
1207
+ return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + categoryMDItems;
1188
1208
  }, "");
1189
1209
  return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1190
1210
  }
@@ -1223,7 +1243,7 @@ function groupItemToCategorySection(group, plugins) {
1223
1243
  }
1224
1244
  function reportToAuditsSection(report) {
1225
1245
  const auditsSection = report.plugins.reduce((acc, plugin) => {
1226
- const auditsData = plugin.audits.sort(sortAudits).reduce((acc2, audit) => {
1246
+ const auditsData = plugin.audits.reduce((acc2, audit) => {
1227
1247
  const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1228
1248
  audit.plugin,
1229
1249
  report.plugins
@@ -1246,7 +1266,7 @@ function reportToAuditsSection(report) {
1246
1266
  }
1247
1267
  const detailsTableData = [
1248
1268
  detailsTableHeaders,
1249
- ...audit.details.issues.sort(compareIssues).map((issue) => {
1269
+ ...audit.details.issues.map((issue) => {
1250
1270
  const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1251
1271
  const message = issue.message;
1252
1272
  if (!issue.source) {
@@ -1350,7 +1370,7 @@ function reportToDetailSection(report) {
1350
1370
  output += addLine(chalk3.magentaBright.bold(`${title} audits`));
1351
1371
  output += addLine();
1352
1372
  const ui = cliui({ width: 80 });
1353
- audits.sort(sortAudits).forEach(({ score, title: title2, displayValue, value }) => {
1373
+ audits.forEach(({ score, title: title2, displayValue, value }) => {
1354
1374
  ui.div(
1355
1375
  {
1356
1376
  text: withColor({ score, text: "\u25CF" }),
@@ -1573,6 +1593,56 @@ var verboseUtils = (verbose) => ({
1573
1593
  log: getLogVerbose(verbose),
1574
1594
  exec: getExecVerbose(verbose)
1575
1595
  });
1596
+
1597
+ // packages/utils/src/lib/sort-report.ts
1598
+ function sortReport(report) {
1599
+ const { categories, plugins } = report;
1600
+ const sortedCategories = categories.map((category) => {
1601
+ const { audits, groups } = category.refs.reduce(
1602
+ (acc, ref) => ({
1603
+ ...acc,
1604
+ ...ref.type === "group" ? {
1605
+ groups: [
1606
+ ...acc.groups,
1607
+ getGroupWithAudits(ref.slug, ref.plugin, plugins)
1608
+ ]
1609
+ } : {
1610
+ audits: [...acc.audits, getAuditByRef(ref, plugins)]
1611
+ }
1612
+ }),
1613
+ { groups: [], audits: [] }
1614
+ );
1615
+ const sortedAuditsAndGroups = [
1616
+ ...groups,
1617
+ ...audits.sort(compareCategoryAudits)
1618
+ ];
1619
+ const sortedRefs = category.refs.slice().sort((a, b) => {
1620
+ const aIndex = sortedAuditsAndGroups.findIndex(
1621
+ (ref) => ref.slug === a.slug
1622
+ );
1623
+ const bIndex = sortedAuditsAndGroups.findIndex(
1624
+ (ref) => ref.slug === b.slug
1625
+ );
1626
+ return aIndex - bIndex;
1627
+ });
1628
+ return { ...category, refs: sortedRefs };
1629
+ });
1630
+ const sortedPlugins = plugins.map((plugin) => ({
1631
+ ...plugin,
1632
+ audits: plugin.audits.sort(compareAudits).map((audit) => ({
1633
+ ...audit,
1634
+ details: {
1635
+ ...audit.details,
1636
+ issues: audit?.details?.issues.slice().sort(compareIssues) || []
1637
+ }
1638
+ }))
1639
+ }));
1640
+ return {
1641
+ ...report,
1642
+ categories: sortedCategories,
1643
+ plugins: sortedPlugins
1644
+ };
1645
+ }
1576
1646
  export {
1577
1647
  CODE_PUSHUP_DOMAIN,
1578
1648
  FOOTER_PREFIX,
@@ -1613,6 +1683,7 @@ export {
1613
1683
  reportToStdout,
1614
1684
  scoreReport,
1615
1685
  slugify,
1686
+ sortReport,
1616
1687
  toArray,
1617
1688
  toUnixPath,
1618
1689
  truncateDescription,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-pushup/utils",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "dependencies": {
5
5
  "@code-pushup/models": "*",
6
6
  "bundle-require": "^4.0.1",
package/src/index.d.ts CHANGED
@@ -13,3 +13,4 @@ export { reportToStdout } from './lib/report-to-stdout';
13
13
  export { ScoredReport, scoreReport } from './lib/scoring';
14
14
  export { CliArgsObject, countOccurrences, distinct, factorOf, objectToCliArgs, objectToEntries, objectToKeys, toArray, toUnixPath, } from './lib/transform';
15
15
  export { verboseUtils } from './lib/verbose-utils';
16
+ export { sortReport } from './lib/sort-report';
@@ -18,8 +18,8 @@ export declare function countWeightedRefs(refs: CategoryRef[]): number;
18
18
  export declare function countCategoryAudits(refs: CategoryRef[], plugins: ScoredReport['plugins']): number;
19
19
  export declare function getAuditByRef({ slug, weight, plugin }: CategoryRef, plugins: ScoredReport['plugins']): WeighedAuditReport;
20
20
  export declare function getGroupWithAudits(refSlug: string, refPlugin: string, plugins: ScoredReport['plugins']): EnrichedScoredGroupWithAudits;
21
- export declare function sortCategoryAudits(a: WeighedAuditReport, b: WeighedAuditReport): number;
22
- export declare function sortAudits(a: EnrichedAuditReport, b: EnrichedAuditReport): number;
21
+ export declare function compareCategoryAudits(a: WeighedAuditReport, b: WeighedAuditReport): number;
22
+ export declare function compareAudits(a: EnrichedAuditReport, b: EnrichedAuditReport): number;
23
23
  export declare function compareIssueSeverity(severity1: CliIssueSeverity, severity2: CliIssueSeverity): number;
24
24
  type LoadedReportFormat<T extends Format> = T extends 'json' ? Report : string;
25
25
  export declare function loadReport<T extends Format>(options: Required<Pick<PersistConfig, 'outputDir' | 'filename'>> & {
@@ -8,7 +8,7 @@ export type WeighedAuditReport = EnrichedAuditReport & {
8
8
  export type EnrichedScoredGroupWithAudits = EnrichedScoredGroup & {
9
9
  audits: AuditReport[];
10
10
  };
11
- type ScoredCategoryConfig = CategoryConfig & {
11
+ export type ScoredCategoryConfig = CategoryConfig & {
12
12
  score: number;
13
13
  };
14
14
  export type EnrichedScoredGroup = Group & {
@@ -26,4 +26,3 @@ export declare function calculateScore<T extends {
26
26
  weight: number;
27
27
  }>(refs: T[], scoreFn: (ref: T) => number): number;
28
28
  export declare function scoreReport(report: Report): ScoredReport;
29
- export {};
@@ -0,0 +1,2 @@
1
+ import { ScoredReport } from './scoring';
2
+ export declare function sortReport(report: ScoredReport): ScoredReport;