@code-pushup/utils 0.49.0 → 0.50.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
@@ -666,6 +666,8 @@ var auditResultSchema = scorableWithPluginMetaSchema.merge(
666
666
  );
667
667
  var reportsDiffSchema = z15.object({
668
668
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
669
+ portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
670
+ label: z15.string().optional().describe("Label (e.g. project name)"),
669
671
  categories: makeArraysComparisonSchema(
670
672
  categoryDiffSchema,
671
673
  categoryResultSchema,
@@ -735,8 +737,24 @@ function comparePairs(pairs, equalsFn) {
735
737
  );
736
738
  }
737
739
 
740
+ // packages/utils/src/lib/errors.ts
741
+ function stringifyError(error) {
742
+ if (error instanceof Error) {
743
+ if (error.name === "Error" || error.message.startsWith(error.name)) {
744
+ return error.message;
745
+ }
746
+ return `${error.name}: ${error.message}`;
747
+ }
748
+ if (typeof error === "string") {
749
+ return error;
750
+ }
751
+ return JSON.stringify(error);
752
+ }
753
+
738
754
  // packages/utils/src/lib/execute-process.ts
739
- import { spawn } from "node:child_process";
755
+ import {
756
+ spawn
757
+ } from "node:child_process";
740
758
 
741
759
  // packages/utils/src/lib/reports/utils.ts
742
760
  import ansis from "ansis";
@@ -863,12 +881,12 @@ function countCategoryAudits(refs, plugins) {
863
881
  }, 0);
864
882
  }
865
883
  function compareCategoryAuditsAndGroups(a, b) {
866
- if (a.weight !== b.weight) {
867
- return b.weight - a.weight;
868
- }
869
884
  if (a.score !== b.score) {
870
885
  return a.score - b.score;
871
886
  }
887
+ if (a.weight !== b.weight) {
888
+ return b.weight - a.weight;
889
+ }
872
890
  if ("value" in a && "value" in b && a.value !== b.value) {
873
891
  return b.value - a.value;
874
892
  }
@@ -960,25 +978,29 @@ var ProcessError = class extends Error {
960
978
  }
961
979
  };
962
980
  function executeProcess(cfg) {
963
- const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
964
- const { onStdout, onError, onComplete } = observer ?? {};
981
+ const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
982
+ const { onStdout, onStderr, onError, onComplete } = observer ?? {};
965
983
  const date = (/* @__PURE__ */ new Date()).toISOString();
966
984
  const start = performance.now();
967
985
  return new Promise((resolve, reject) => {
968
- const process2 = spawn(command, args, { cwd, shell: true });
986
+ const spawnedProcess = spawn(command, args ?? [], {
987
+ shell: true,
988
+ ...options
989
+ });
969
990
  let stdout = "";
970
991
  let stderr = "";
971
- process2.stdout.on("data", (data) => {
992
+ spawnedProcess.stdout.on("data", (data) => {
972
993
  stdout += String(data);
973
- onStdout?.(String(data));
994
+ onStdout?.(String(data), spawnedProcess);
974
995
  });
975
- process2.stderr.on("data", (data) => {
996
+ spawnedProcess.stderr.on("data", (data) => {
976
997
  stderr += String(data);
998
+ onStderr?.(String(data), spawnedProcess);
977
999
  });
978
- process2.on("error", (err) => {
1000
+ spawnedProcess.on("error", (err) => {
979
1001
  stderr += err.toString();
980
1002
  });
981
- process2.on("close", (code2) => {
1003
+ spawnedProcess.on("close", (code2) => {
982
1004
  const timings = { date, duration: calcDuration(start) };
983
1005
  if (code2 === 0 || ignoreExitCode) {
984
1006
  onComplete?.();
@@ -1881,7 +1903,10 @@ var html = {
1881
1903
  };
1882
1904
 
1883
1905
  // packages/utils/src/lib/reports/formatting.ts
1884
- import { MarkdownDocument, md as md2 } from "build-md";
1906
+ import {
1907
+ MarkdownDocument,
1908
+ md as md2
1909
+ } from "build-md";
1885
1910
  function tableSection(tableData, options) {
1886
1911
  if (tableData.rows.length === 0) {
1887
1912
  return null;
@@ -2241,19 +2266,111 @@ function reportMetaTable({
2241
2266
 
2242
2267
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
2243
2268
  import {
2244
- MarkdownDocument as MarkdownDocument4,
2245
- md as md5
2269
+ MarkdownDocument as MarkdownDocument5,
2270
+ md as md6
2246
2271
  } from "build-md";
2272
+
2273
+ // packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
2274
+ import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
2247
2275
  var MAX_ROWS = 100;
2248
- function generateMdReportsDiff(diff, portalUrl) {
2249
- return new MarkdownDocument4().$concat(
2250
- createDiffHeaderSection(diff, portalUrl),
2251
- createDiffCategoriesSection(diff),
2252
- createDiffGroupsSection(diff),
2253
- createDiffAuditsSection(diff)
2254
- ).toString();
2276
+ function summarizeUnchanged(token, { changed, unchanged }) {
2277
+ const pluralizedCount = changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`;
2278
+ const pluralizedVerb = unchanged.length === 1 ? "is" : "are";
2279
+ return `${pluralizedCount} ${pluralizedVerb} unchanged.`;
2255
2280
  }
2256
- function createDiffHeaderSection(diff, portalUrl) {
2281
+ function summarizeDiffOutcomes(outcomes, token) {
2282
+ return objectToEntries(countDiffOutcomes(outcomes)).filter(
2283
+ (entry) => entry[0] !== "unchanged" && entry[1] > 0
2284
+ ).map(([outcome, count]) => {
2285
+ const formattedCount = `<strong>${count}</strong> ${pluralize(
2286
+ token,
2287
+ count
2288
+ )}`;
2289
+ switch (outcome) {
2290
+ case "positive":
2291
+ return `\u{1F44D} ${formattedCount} improved`;
2292
+ case "negative":
2293
+ return `\u{1F44E} ${formattedCount} regressed`;
2294
+ case "mixed":
2295
+ return `${formattedCount} changed without impacting score`;
2296
+ }
2297
+ }).join(", ");
2298
+ }
2299
+ function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
2300
+ if (changed.length === 0) {
2301
+ return new MarkdownDocument4().paragraph(
2302
+ summarizeUnchanged(token, { changed, unchanged })
2303
+ );
2304
+ }
2305
+ return new MarkdownDocument4().table(columns, rows.slice(0, MAX_ROWS)).paragraph(
2306
+ changed.length > MAX_ROWS && md5.italic(
2307
+ `Only the ${MAX_ROWS} most affected ${pluralize(
2308
+ token
2309
+ )} are listed above for brevity.`
2310
+ )
2311
+ ).paragraph(
2312
+ unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
2313
+ );
2314
+ }
2315
+ function formatTitle({
2316
+ title,
2317
+ docsUrl
2318
+ }) {
2319
+ if (docsUrl) {
2320
+ return md5.link(docsUrl, title);
2321
+ }
2322
+ return title;
2323
+ }
2324
+ function formatPortalLink(portalUrl) {
2325
+ return portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}");
2326
+ }
2327
+ function sortChanges(changes) {
2328
+ return [...changes].sort(
2329
+ (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
2330
+ );
2331
+ }
2332
+ function getDiffChanges(diff) {
2333
+ return [
2334
+ ...diff.categories.changed,
2335
+ ...diff.groups.changed,
2336
+ ...diff.audits.changed
2337
+ ];
2338
+ }
2339
+ function changesToDiffOutcomes(changes) {
2340
+ return changes.map((change) => {
2341
+ if (change.scores.diff > 0) {
2342
+ return "positive";
2343
+ }
2344
+ if (change.scores.diff < 0) {
2345
+ return "negative";
2346
+ }
2347
+ if (change.values != null && change.values.diff !== 0) {
2348
+ return "mixed";
2349
+ }
2350
+ return "unchanged";
2351
+ });
2352
+ }
2353
+ function mergeDiffOutcomes(outcomes) {
2354
+ if (outcomes.every((outcome) => outcome === "unchanged")) {
2355
+ return "unchanged";
2356
+ }
2357
+ if (outcomes.includes("positive") && !outcomes.includes("negative")) {
2358
+ return "positive";
2359
+ }
2360
+ if (outcomes.includes("negative") && !outcomes.includes("positive")) {
2361
+ return "negative";
2362
+ }
2363
+ return "mixed";
2364
+ }
2365
+ function countDiffOutcomes(outcomes) {
2366
+ return {
2367
+ positive: outcomes.filter((outcome) => outcome === "positive").length,
2368
+ negative: outcomes.filter((outcome) => outcome === "negative").length,
2369
+ mixed: outcomes.filter((outcome) => outcome === "mixed").length,
2370
+ unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
2371
+ };
2372
+ }
2373
+ function formatReportOutcome(outcome, commits) {
2257
2374
  const outcomeTexts = {
2258
2375
  positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
2259
2376
  negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
@@ -2262,27 +2379,91 @@ function createDiffHeaderSection(diff, portalUrl) {
2262
2379
  )}`,
2263
2380
  unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
2264
2381
  };
2382
+ if (commits) {
2383
+ const commitsText = `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
2384
+ return md5`${outcomeTexts[outcome]} – ${commitsText}.`;
2385
+ }
2386
+ return md5`${outcomeTexts[outcome]}.`;
2387
+ }
2388
+ function compareDiffsBy(type, a, b) {
2389
+ return sumScoreChanges(b[type].changed) - sumScoreChanges(a[type].changed) || sumConfigChanges(b[type]) - sumConfigChanges(a[type]);
2390
+ }
2391
+ function sumScoreChanges(changes) {
2392
+ return changes.reduce(
2393
+ (acc, { scores }) => acc + Math.abs(scores.diff),
2394
+ 0
2395
+ );
2396
+ }
2397
+ function sumConfigChanges({
2398
+ added,
2399
+ removed
2400
+ }) {
2401
+ return added.length + removed.length;
2402
+ }
2403
+
2404
+ // packages/utils/src/lib/reports/generate-md-reports-diff.ts
2405
+ function generateMdReportsDiff(diff) {
2406
+ return new MarkdownDocument5().$concat(
2407
+ createDiffHeaderSection(diff),
2408
+ createDiffCategoriesSection(diff),
2409
+ createDiffDetailsSection(diff)
2410
+ ).toString();
2411
+ }
2412
+ function generateMdReportsDiffForMonorepo(diffs) {
2413
+ const diffsWithOutcomes = diffs.map((diff) => ({
2414
+ ...diff,
2415
+ outcome: mergeDiffOutcomes(changesToDiffOutcomes(getDiffChanges(diff)))
2416
+ })).sort(
2417
+ (a, b) => compareDiffsBy("categories", a, b) || compareDiffsBy("groups", a, b) || compareDiffsBy("audits", a, b) || a.label.localeCompare(b.label)
2418
+ );
2419
+ const unchanged = diffsWithOutcomes.filter(
2420
+ ({ outcome }) => outcome === "unchanged"
2421
+ );
2422
+ const changed = diffsWithOutcomes.filter((diff) => !unchanged.includes(diff));
2423
+ return new MarkdownDocument5().$concat(
2424
+ createDiffHeaderSection(diffs),
2425
+ ...changed.map(createDiffProjectSection)
2426
+ ).$if(
2427
+ unchanged.length > 0,
2428
+ (doc) => doc.rule().paragraph(summarizeUnchanged("project", { unchanged, changed }))
2429
+ ).toString();
2430
+ }
2431
+ function createDiffHeaderSection(diff) {
2265
2432
  const outcome = mergeDiffOutcomes(
2266
- changesToDiffOutcomes([
2267
- ...diff.categories.changed,
2268
- ...diff.groups.changed,
2269
- ...diff.audits.changed
2270
- ])
2433
+ changesToDiffOutcomes(toArray(diff).flatMap(getDiffChanges))
2271
2434
  );
2272
- const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
2273
- return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
2274
- diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
2275
- ).paragraph(
2276
- portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
2435
+ const commits = Array.isArray(diff) ? diff[0]?.commits : diff.commits;
2436
+ const portalUrl = Array.isArray(diff) ? void 0 : diff.portalUrl;
2437
+ return new MarkdownDocument5().heading(HIERARCHY.level_1, "Code PushUp").paragraph(formatReportOutcome(outcome, commits)).paragraph(formatPortalLink(portalUrl));
2438
+ }
2439
+ function createDiffProjectSection(diff) {
2440
+ return new MarkdownDocument5().heading(HIERARCHY.level_2, md6`💼 Project ${md6.code(diff.label)}`).paragraph(formatReportOutcome(diff.outcome)).paragraph(formatPortalLink(diff.portalUrl)).$concat(
2441
+ createDiffCategoriesSection(diff, {
2442
+ skipHeading: true,
2443
+ skipUnchanged: true
2444
+ }),
2445
+ createDiffDetailsSection(diff, HIERARCHY.level_3)
2277
2446
  );
2278
2447
  }
2279
- function createDiffCategoriesSection(diff) {
2448
+ function createDiffCategoriesSection(diff, options) {
2280
2449
  const { changed, unchanged, added } = diff.categories;
2450
+ const { skipHeading, skipUnchanged } = options ?? {};
2281
2451
  const categoriesCount = changed.length + unchanged.length + added.length;
2282
2452
  const hasChanges = unchanged.length < categoriesCount;
2283
2453
  if (categoriesCount === 0) {
2284
2454
  return null;
2285
2455
  }
2456
+ const [columns, rows] = createCategoriesTable(diff, {
2457
+ hasChanges,
2458
+ skipUnchanged
2459
+ });
2460
+ return new MarkdownDocument5().heading(HIERARCHY.level_2, !skipHeading && "\u{1F3F7}\uFE0F Categories").table(columns, rows).paragraph(added.length > 0 && md6.italic("(\\*) New category.")).paragraph(
2461
+ skipUnchanged && unchanged.length > 0 && summarizeUnchanged("category", { changed, unchanged })
2462
+ );
2463
+ }
2464
+ function createCategoriesTable(diff, options) {
2465
+ const { changed, unchanged, added } = diff.categories;
2466
+ const { hasChanges, skipUnchanged } = options;
2286
2467
  const columns = [
2287
2468
  { heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
2288
2469
  {
@@ -2303,27 +2484,43 @@ function createDiffCategoriesSection(diff) {
2303
2484
  ]),
2304
2485
  ...added.map((category) => [
2305
2486
  formatTitle(category),
2306
- md5.italic("n/a (\\*)"),
2487
+ md6.italic("n/a (\\*)"),
2307
2488
  formatScoreWithColor(category.score),
2308
- md5.italic("n/a (\\*)")
2489
+ md6.italic("n/a (\\*)")
2309
2490
  ]),
2310
- ...unchanged.map((category) => [
2491
+ ...skipUnchanged ? [] : unchanged.map((category) => [
2311
2492
  formatTitle(category),
2312
2493
  formatScoreWithColor(category.score, { skipBold: true }),
2313
2494
  formatScoreWithColor(category.score),
2314
2495
  "\u2013"
2315
2496
  ])
2316
2497
  ];
2317
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
2498
+ return [
2318
2499
  hasChanges ? columns : columns.slice(0, 2),
2319
2500
  rows.map((row) => hasChanges ? row : row.slice(0, 2))
2320
- ).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
2501
+ ];
2321
2502
  }
2322
- function createDiffGroupsSection(diff) {
2503
+ function createDiffDetailsSection(diff, level = HIERARCHY.level_2) {
2504
+ if (diff.groups.changed.length + diff.audits.changed.length === 0) {
2505
+ return null;
2506
+ }
2507
+ const summary = ["group", "audit"].map(
2508
+ (token) => summarizeDiffOutcomes(
2509
+ changesToDiffOutcomes(diff[`${token}s`].changed),
2510
+ token
2511
+ )
2512
+ ).filter(Boolean).join(", ");
2513
+ const details2 = new MarkdownDocument5().$concat(
2514
+ createDiffGroupsSection(diff, level),
2515
+ createDiffAuditsSection(diff, level)
2516
+ );
2517
+ return new MarkdownDocument5().details(summary, details2);
2518
+ }
2519
+ function createDiffGroupsSection(diff, level) {
2323
2520
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
2324
2521
  return null;
2325
2522
  }
2326
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
2523
+ return new MarkdownDocument5().heading(level, "\u{1F5C3}\uFE0F Groups").$concat(
2327
2524
  createGroupsOrAuditsDetails(
2328
2525
  "group",
2329
2526
  diff.groups,
@@ -2344,8 +2541,8 @@ function createDiffGroupsSection(diff) {
2344
2541
  )
2345
2542
  );
2346
2543
  }
2347
- function createDiffAuditsSection(diff) {
2348
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
2544
+ function createDiffAuditsSection(diff, level) {
2545
+ return new MarkdownDocument5().heading(level, "\u{1F6E1}\uFE0F Audits").$concat(
2349
2546
  createGroupsOrAuditsDetails(
2350
2547
  "audit",
2351
2548
  diff.audits,
@@ -2360,7 +2557,7 @@ function createDiffAuditsSection(diff) {
2360
2557
  formatTitle(audit.plugin),
2361
2558
  formatTitle(audit),
2362
2559
  `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
2363
- md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
2560
+ md6`${scoreMarker(audit.scores.after, "square")} ${md6.bold(
2364
2561
  audit.displayValues.after || audit.values.after.toString()
2365
2562
  )}`,
2366
2563
  formatValueChange(audit)
@@ -2368,96 +2565,6 @@ function createDiffAuditsSection(diff) {
2368
2565
  )
2369
2566
  );
2370
2567
  }
2371
- function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
2372
- if (changed.length === 0) {
2373
- return new MarkdownDocument4().paragraph(
2374
- summarizeUnchanged(token, { changed, unchanged })
2375
- );
2376
- }
2377
- return new MarkdownDocument4().details(
2378
- summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
2379
- md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
2380
- md5.italic(
2381
- `Only the ${MAX_ROWS} most affected ${pluralize(
2382
- token
2383
- )} are listed above for brevity.`
2384
- )
2385
- ) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
2386
- );
2387
- }
2388
- function summarizeUnchanged(token, { changed, unchanged }) {
2389
- return [
2390
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
2391
- unchanged.length === 1 ? "is" : "are",
2392
- "unchanged."
2393
- ].join(" ");
2394
- }
2395
- function summarizeDiffOutcomes(outcomes, token) {
2396
- return objectToEntries(countDiffOutcomes(outcomes)).filter(
2397
- (entry) => entry[0] !== "unchanged" && entry[1] > 0
2398
- ).map(([outcome, count]) => {
2399
- const formattedCount = `<strong>${count}</strong> ${pluralize(
2400
- token,
2401
- count
2402
- )}`;
2403
- switch (outcome) {
2404
- case "positive":
2405
- return `\u{1F44D} ${formattedCount} improved`;
2406
- case "negative":
2407
- return `\u{1F44E} ${formattedCount} regressed`;
2408
- case "mixed":
2409
- return `${formattedCount} changed without impacting score`;
2410
- }
2411
- }).join(", ");
2412
- }
2413
- function formatTitle({
2414
- title,
2415
- docsUrl
2416
- }) {
2417
- if (docsUrl) {
2418
- return md5.link(docsUrl, title);
2419
- }
2420
- return title;
2421
- }
2422
- function sortChanges(changes) {
2423
- return [...changes].sort(
2424
- (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
2425
- );
2426
- }
2427
- function changesToDiffOutcomes(changes) {
2428
- return changes.map((change) => {
2429
- if (change.scores.diff > 0) {
2430
- return "positive";
2431
- }
2432
- if (change.scores.diff < 0) {
2433
- return "negative";
2434
- }
2435
- if (change.values != null && change.values.diff !== 0) {
2436
- return "mixed";
2437
- }
2438
- return "unchanged";
2439
- });
2440
- }
2441
- function mergeDiffOutcomes(outcomes) {
2442
- if (outcomes.every((outcome) => outcome === "unchanged")) {
2443
- return "unchanged";
2444
- }
2445
- if (outcomes.includes("positive") && !outcomes.includes("negative")) {
2446
- return "positive";
2447
- }
2448
- if (outcomes.includes("negative") && !outcomes.includes("positive")) {
2449
- return "negative";
2450
- }
2451
- return "mixed";
2452
- }
2453
- function countDiffOutcomes(outcomes) {
2454
- return {
2455
- positive: outcomes.filter((outcome) => outcome === "positive").length,
2456
- negative: outcomes.filter((outcome) => outcome === "negative").length,
2457
- mixed: outcomes.filter((outcome) => outcome === "mixed").length,
2458
- unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
2459
- };
2460
- }
2461
2568
 
2462
2569
  // packages/utils/src/lib/reports/load-report.ts
2463
2570
  import { join as join3 } from "node:path";
@@ -2514,7 +2621,8 @@ function logPlugins(report) {
2514
2621
  },
2515
2622
  {
2516
2623
  text: cyanBright(audit.displayValue || `${audit.value}`),
2517
- width: 10,
2624
+ // eslint-disable-next-line no-magic-numbers
2625
+ width: 20,
2518
2626
  padding: [0, 0, 0, 0]
2519
2627
  }
2520
2628
  ]);
@@ -2693,9 +2801,11 @@ export {
2693
2801
  formatBytes,
2694
2802
  formatDuration,
2695
2803
  formatGitPath,
2804
+ formatReportScore,
2696
2805
  fromJsonLines,
2697
2806
  generateMdReport,
2698
2807
  generateMdReportsDiff,
2808
+ generateMdReportsDiffForMonorepo,
2699
2809
  getCurrentBranchOrTag,
2700
2810
  getGitRoot,
2701
2811
  getHashFromTag,
@@ -2735,6 +2845,7 @@ export {
2735
2845
  slugify,
2736
2846
  sortReport,
2737
2847
  sortSemvers,
2848
+ stringifyError,
2738
2849
  toArray,
2739
2850
  toGitPath,
2740
2851
  toJsonLines,
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@code-pushup/utils",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
+ "description": "Low-level utilities (helper functions, etc.) used by Code PushUp CLI",
4
5
  "dependencies": {
5
- "@code-pushup/models": "0.49.0",
6
+ "@code-pushup/models": "0.50.0",
6
7
  "@isaacs/cliui": "^8.0.2",
7
8
  "@poppinss/cliui": "^6.4.0",
8
9
  "ansis": "^3.3.0",
9
- "build-md": "^0.4.1",
10
+ "build-md": "^0.4.2",
10
11
  "bundle-require": "^4.0.1",
11
12
  "esbuild": "^0.19.2",
12
13
  "multi-progress-bars": "^5.0.3",
@@ -14,7 +15,7 @@
14
15
  "simple-git": "^3.20.0"
15
16
  },
16
17
  "license": "MIT",
17
- "homepage": "https://github.com/code-pushup/cli#readme",
18
+ "homepage": "https://github.com/code-pushup/cli/tree/main/packages/utils#readme",
18
19
  "bugs": {
19
20
  "url": "https://github.com/code-pushup/cli/issues"
20
21
  },
@@ -23,33 +24,6 @@
23
24
  "url": "git+https://github.com/code-pushup/cli.git",
24
25
  "directory": "packages/utils"
25
26
  },
26
- "contributors": [
27
- {
28
- "name": "Igor Katsuba",
29
- "email": "igor@katsuba.dev",
30
- "url": "https://katsuba.dev"
31
- },
32
- {
33
- "name": "Kateřina Pilátová",
34
- "email": "katerina.pilatova@flowup.cz",
35
- "url": "https://github.com/Tlacenka"
36
- },
37
- {
38
- "name": "Matěj Chalk",
39
- "email": "matej.chalk@flowup.cz",
40
- "url": "https://github.com/matejchalk"
41
- },
42
- {
43
- "name": "Michael Hladky",
44
- "email": "michael.hladky@push-based.io",
45
- "url": "https://push-based.io"
46
- },
47
- {
48
- "name": "Michael Seredenko",
49
- "email": "misha.seredenko@push-based.io",
50
- "url": "https://github.com/MishaSeredenkoPushBased"
51
- }
52
- ],
53
27
  "type": "module",
54
28
  "main": "./index.js",
55
29
  "types": "./src/index.d.ts"
package/src/index.d.ts CHANGED
@@ -1,29 +1,30 @@
1
1
  export { exists } from '@code-pushup/models';
2
- export { Diff, comparePairs, matchArrayItemsByKey } from './lib/diff';
3
- export { ProcessConfig, ProcessError, ProcessObserver, ProcessResult, executeProcess, } from './lib/execute-process';
4
- export { CrawlFileSystemOptions, FileResult, MultipleFileResults, crawlFileSystem, directoryExists, ensureDirectoryExists, fileExists, filePathToCliArg, findLineNumberInText, importModule, logMultipleFileResults, pluginWorkDir, readJsonFile, readTextFile, removeDirectoryIfExists, } from './lib/file-system';
2
+ export { type Diff, comparePairs, matchArrayItemsByKey } from './lib/diff';
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';
5
6
  export { filterItemRefsBy } from './lib/filter';
6
7
  export { formatBytes, formatDuration, pluralize, pluralizeToken, slugify, truncateDescription, truncateIssueMessage, truncateText, truncateTitle, } from './lib/formatting';
7
8
  export { formatGitPath, getGitRoot, guardAgainstLocalChanges, safeCheckout, toGitPath, } from './lib/git/git';
8
- export { LogResult, getCurrentBranchOrTag, getHashFromTag, getHashes, getLatestCommit, getSemverTags, } from './lib/git/git.commits-and-tags';
9
+ export { type LogResult, getCurrentBranchOrTag, getHashFromTag, getHashes, getLatestCommit, getSemverTags, } from './lib/git/git.commits-and-tags';
9
10
  export { groupByStatus } from './lib/group-by-status';
10
11
  export { isPromiseFulfilledResult, isPromiseRejectedResult, } from './lib/guards';
11
12
  export { logMultipleResults } from './lib/log-results';
12
- export { CliUi, Column, link, ui } from './lib/logging';
13
+ export { type CliUi, type Column, link, ui } from './lib/logging';
13
14
  export { mergeConfigs } from './lib/merge-configs';
14
- export { ProgressBar, getProgressBar } from './lib/progress';
15
+ export { type ProgressBar, getProgressBar } from './lib/progress';
15
16
  export { CODE_PUSHUP_DOMAIN, FOOTER_PREFIX, README_LINK, TERMINAL_WIDTH, } from './lib/reports/constants';
16
17
  export { listAuditsFromAllPlugins, listGroupsFromAllPlugins, } from './lib/reports/flatten-plugins';
17
18
  export { generateMdReport } from './lib/reports/generate-md-report';
18
- export { generateMdReportsDiff } from './lib/reports/generate-md-reports-diff';
19
+ export { generateMdReportsDiff, generateMdReportsDiffForMonorepo, } from './lib/reports/generate-md-reports-diff';
19
20
  export { loadReport } from './lib/reports/load-report';
20
21
  export { logStdoutSummary } from './lib/reports/log-stdout-summary';
21
22
  export { scoreReport } from './lib/reports/scoring';
22
23
  export { sortReport } from './lib/reports/sorting';
23
- export { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/reports/types';
24
- export { calcDuration, compareIssueSeverity } from './lib/reports/utils';
24
+ export type { ScoredCategoryConfig, ScoredGroup, ScoredReport, } from './lib/reports/types';
25
+ export { calcDuration, compareIssueSeverity, formatReportScore, } from './lib/reports/utils';
25
26
  export { isSemver, normalizeSemver, sortSemvers } from './lib/semver';
26
27
  export * from './lib/text-formats';
27
- export { CliArgsObject, capitalize, countOccurrences, distinct, factorOf, fromJsonLines, objectFromEntries, objectToCliArgs, objectToEntries, objectToKeys, toArray, toJsonLines, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
28
- export { ExcludeNullFromPropertyTypes } from './lib/types';
28
+ export { type CliArgsObject, capitalize, countOccurrences, distinct, factorOf, fromJsonLines, objectFromEntries, objectToCliArgs, objectToEntries, objectToKeys, toArray, toJsonLines, toNumberPrecision, toOrdinal, toUnixNewlines, toUnixPath, } from './lib/transform';
29
+ export type { ExcludeNullFromPropertyTypes, ExtractArray, ExtractArrays, ItemOrArray, Prettify, WithRequired, } from './lib/types';
29
30
  export { verboseUtils } from './lib/verbose-utils';
@@ -0,0 +1 @@
1
+ export declare function stringifyError(error: unknown): string;
@@ -1,3 +1,5 @@
1
+ /// <reference types="node" />
2
+ import { type ChildProcess, type SpawnOptionsWithStdioTuple, type StdioPipe } from 'node:child_process';
1
3
  /**
2
4
  * Represents the process result.
3
5
  * @category Types
@@ -66,10 +68,9 @@ export declare class ProcessError extends Error {
66
68
  * args: ['--version']
67
69
  *
68
70
  */
69
- export type ProcessConfig = {
71
+ export type ProcessConfig = Omit<SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>, 'stdio'> & {
70
72
  command: string;
71
73
  args?: string[];
72
- cwd?: string;
73
74
  observer?: ProcessObserver;
74
75
  ignoreExitCode?: boolean;
75
76
  };
@@ -87,7 +88,8 @@ export type ProcessConfig = {
87
88
  * }
88
89
  */
89
90
  export type ProcessObserver = {
90
- onStdout?: (stdout: string) => void;
91
+ onStdout?: (stdout: string, sourceProcess?: ChildProcess) => void;
92
+ onStderr?: (stderr: string, sourceProcess?: ChildProcess) => void;
91
93
  onError?: (error: ProcessError) => void;
92
94
  onComplete?: () => void;
93
95
  };
@@ -1,5 +1,5 @@
1
- import { LogOptions as SimpleGitLogOptions } from 'simple-git';
2
- import { Commit } from '@code-pushup/models';
1
+ import { type LogOptions as SimpleGitLogOptions } from 'simple-git';
2
+ import { type Commit } from '@code-pushup/models';
3
3
  export declare function getLatestCommit(git?: import("simple-git").SimpleGit): Promise<Commit | null>;
4
4
  export declare function getCurrentBranchOrTag(git?: import("simple-git").SimpleGit): Promise<string>;
5
5
  export type LogResult = {
@@ -1,4 +1,4 @@
1
- import { StatusResult } from 'simple-git';
1
+ import { type StatusResult } from 'simple-git';
2
2
  export declare function getGitRoot(git?: import("simple-git").SimpleGit): Promise<string>;
3
3
  export declare function formatGitPath(path: string, gitRoot: string): string;
4
4
  export declare function toGitPath(path: string, git?: import("simple-git").SimpleGit): Promise<string>;
@@ -1,2 +1,2 @@
1
- import { CoreConfig } from '@code-pushup/models';
1
+ import type { CoreConfig } from '@code-pushup/models';
2
2
  export declare function mergeConfigs(config: CoreConfig, ...configs: Partial<CoreConfig>[]): Partial<CoreConfig>;
@@ -1,4 +1,4 @@
1
- import { CtorOptions, MultiProgressBars } from 'multi-progress-bars';
1
+ import { type CtorOptions, MultiProgressBars } from 'multi-progress-bars';
2
2
  type BarStyles = 'active' | 'done' | 'idle';
3
3
  type StatusStyles = Record<BarStyles, (s: string) => string>;
4
4
  export declare const barStyles: StatusStyles;
@@ -1,4 +1,4 @@
1
- import { Report } from '@code-pushup/models';
1
+ import type { Report } from '@code-pushup/models';
2
2
  export declare function listGroupsFromAllPlugins<T extends Report>(report: T): {
3
3
  plugin: T['plugins'][number];
4
4
  group: NonNullable<T['plugins'][number]['groups']>[number];
@@ -1,5 +1,5 @@
1
- import { HeadingLevel, InlineText, MarkdownDocument } from 'build-md';
2
- import { AuditReport, Table } from '@code-pushup/models';
1
+ import { type HeadingLevel, type InlineText, MarkdownDocument } from 'build-md';
2
+ import type { AuditReport, Table } from '@code-pushup/models';
3
3
  export declare function tableSection(tableData: Table, options?: {
4
4
  level?: HeadingLevel;
5
5
  }): MarkdownDocument | null;
@@ -1,6 +1,6 @@
1
- import { InlineText, MarkdownDocument } from 'build-md';
2
- import { AuditReport } from '@code-pushup/models';
3
- import { ScoredGroup, ScoredReport } from './types';
1
+ import { type InlineText, MarkdownDocument } from 'build-md';
2
+ import type { AuditReport } from '@code-pushup/models';
3
+ import type { ScoredGroup, ScoredReport } from './types';
4
4
  export declare function categoriesOverviewSection(report: Pick<ScoredReport, 'categories' | 'plugins'>): MarkdownDocument;
5
5
  export declare function categoriesDetailsSection(report: Pick<ScoredReport, 'categories' | 'plugins'>): MarkdownDocument;
6
6
  export declare function categoryRef({ title, score, value, displayValue }: AuditReport, pluginTitle: string): InlineText;
@@ -1,6 +1,6 @@
1
- import { InlineText, MarkdownDocument } from 'build-md';
2
- import { AuditReport, Issue, Report } from '@code-pushup/models';
3
- import { ScoredReport } from './types';
1
+ import { type InlineText, MarkdownDocument } from 'build-md';
2
+ import type { AuditReport, Issue, Report } from '@code-pushup/models';
3
+ import type { ScoredReport } from './types';
4
4
  export declare function auditDetailsAuditValue({ score, value, displayValue, }: AuditReport): InlineText;
5
5
  export declare function generateMdReport(report: ScoredReport): string;
6
6
  export declare function auditDetailsIssues(issues?: Issue[]): MarkdownDocument | null;
@@ -0,0 +1,29 @@
1
+ import { type InlineText, MarkdownDocument } from 'build-md';
2
+ import type { ReportsDiff } from '@code-pushup/models';
3
+ import type { DiffOutcome } from './types';
4
+ export declare function summarizeUnchanged(token: string, { changed, unchanged }: {
5
+ changed: unknown[];
6
+ unchanged: unknown[];
7
+ }): string;
8
+ export declare function summarizeDiffOutcomes(outcomes: DiffOutcome[], token: string): string;
9
+ export declare function createGroupsOrAuditsDetails<T extends 'group' | 'audit'>(token: T, { changed, unchanged }: ReportsDiff[`${T}s`], ...[columns, rows]: Parameters<MarkdownDocument['table']>): MarkdownDocument;
10
+ export declare function formatTitle({ title, docsUrl, }: {
11
+ title: string;
12
+ docsUrl?: string;
13
+ }): InlineText;
14
+ export declare function formatPortalLink(portalUrl: string | undefined): InlineText | undefined;
15
+ type Change = {
16
+ scores: {
17
+ diff: number;
18
+ };
19
+ values?: {
20
+ diff: number;
21
+ };
22
+ };
23
+ export declare function sortChanges<T extends Change>(changes: T[]): T[];
24
+ export declare function getDiffChanges(diff: ReportsDiff): Change[];
25
+ export declare function changesToDiffOutcomes(changes: Change[]): DiffOutcome[];
26
+ export declare function mergeDiffOutcomes(outcomes: DiffOutcome[]): DiffOutcome;
27
+ export declare function formatReportOutcome(outcome: DiffOutcome, commits?: ReportsDiff['commits']): InlineText;
28
+ export declare function compareDiffsBy<T extends 'categories' | 'groups' | 'audits'>(type: T, a: ReportsDiff, b: ReportsDiff): number;
29
+ export {};
@@ -1,2 +1,5 @@
1
- import { ReportsDiff } from '@code-pushup/models';
2
- export declare function generateMdReportsDiff(diff: ReportsDiff, portalUrl?: string): string;
1
+ import type { ReportsDiff } from '@code-pushup/models';
2
+ import type { WithRequired } from '../types';
3
+ export declare function generateMdReportsDiff(diff: ReportsDiff): string;
4
+ export type LabeledDiff = WithRequired<ReportsDiff, 'label'>;
5
+ export declare function generateMdReportsDiffForMonorepo(diffs: LabeledDiff[]): string;
@@ -1,4 +1,4 @@
1
- import { Format, PersistConfig, Report } from '@code-pushup/models';
1
+ import { type Format, type PersistConfig, type Report } from '@code-pushup/models';
2
2
  type LoadedReportFormat<T extends Format> = T extends 'json' ? Report : string;
3
3
  export declare function loadReport<T extends Format>(options: Required<Omit<PersistConfig, 'format'>> & {
4
4
  format: T;
@@ -1,4 +1,4 @@
1
- import { ScoredReport } from './types';
1
+ import type { ScoredReport } from './types';
2
2
  export declare function logStdoutSummary(report: ScoredReport): void;
3
3
  export declare function logCategories({ categories, plugins }: ScoredReport): void;
4
4
  export declare function binaryIconPrefix(score: number, isBinary: boolean | undefined): string;
@@ -1,5 +1,5 @@
1
- import { Report } from '@code-pushup/models';
2
- import { ScoredReport } from './types';
1
+ import type { Report } from '@code-pushup/models';
2
+ import type { ScoredReport } from './types';
3
3
  export declare class GroupRefInvalidError extends Error {
4
4
  constructor(auditSlug: string, pluginSlug: string);
5
5
  }
@@ -1,5 +1,5 @@
1
- import { CategoryRef, Group } from '@code-pushup/models';
2
- import { ScoredReport, SortableAuditReport, SortableGroup } from './types';
1
+ import type { CategoryRef, Group } from '@code-pushup/models';
2
+ import type { ScoredReport, SortableAuditReport, SortableGroup } from './types';
3
3
  export declare function getSortableAuditByRef({ slug, weight, plugin }: CategoryRef, plugins: ScoredReport['plugins']): SortableAuditReport;
4
4
  export declare function getSortedGroupAudits(group: Group, plugin: string, plugins: ScoredReport['plugins']): SortableAuditReport[];
5
5
  export declare function getSortableGroupByRef({ plugin, slug, weight }: CategoryRef, plugins: ScoredReport['plugins']): SortableGroup;
@@ -1,4 +1,4 @@
1
- import { AuditReport, CategoryConfig, Group, PluginReport, Report } from '@code-pushup/models';
1
+ import type { AuditReport, CategoryConfig, Group, PluginReport, Report } from '@code-pushup/models';
2
2
  export type ScoredCategoryConfig = CategoryConfig & {
3
3
  score: number;
4
4
  };
@@ -1,7 +1,7 @@
1
- import { Ansis } from 'ansis';
2
- import { InlineText } from 'build-md';
3
- import { AuditDiff, AuditReport, CategoryRef, IssueSeverity as CliIssueSeverity, Issue } from '@code-pushup/models';
4
- import { ScoredReport, SortableAuditReport, SortableGroup } from './types';
1
+ import { type Ansis } from 'ansis';
2
+ import { type InlineText } from 'build-md';
3
+ import type { AuditDiff, AuditReport, CategoryRef, IssueSeverity as CliIssueSeverity, Issue } from '@code-pushup/models';
4
+ import type { ScoredReport, SortableAuditReport, SortableGroup } from './types';
5
5
  export declare function formatReportScore(score: number): string;
6
6
  export declare function formatScoreWithColor(score: number, options?: {
7
7
  skipBold?: boolean;
@@ -1,2 +1,2 @@
1
- import { Table } from '@code-pushup/models';
1
+ import type { Table } from '@code-pushup/models';
2
2
  export declare function table(tableData: Table): string;
@@ -1,4 +1,4 @@
1
- import { Table, TableAlignment, TableColumnObject, TableColumnPrimitive } from '@code-pushup/models';
1
+ import type { Table, TableAlignment, TableColumnObject, TableColumnPrimitive } from '@code-pushup/models';
2
2
  export declare function rowToStringArray({ rows, columns }: Table): string[][];
3
3
  export declare function columnsToStringArray({ rows, columns, }: Pick<Table, 'columns' | 'rows'>): string[];
4
4
  export declare function getColumnAlignmentForKeyAndIndex(targetKey: string, targetIdx: number, columns?: TableColumnObject[]): TableAlignment;
@@ -1,3 +1,12 @@
1
1
  export type ExcludeNullFromPropertyTypes<T> = {
2
2
  [P in keyof T]: Exclude<T[P], null>;
3
3
  };
4
+ export type ItemOrArray<T> = T | T[];
5
+ export type ExtractArray<T> = T extends Array<unknown> ? T : never;
6
+ export type ExtractArrays<T extends Record<string, unknown>> = {
7
+ [K in keyof T]: ExtractArray<T[K]>;
8
+ };
9
+ export type WithRequired<T, K extends keyof T> = Prettify<Omit<T, K> & Required<Pick<T, K>>>;
10
+ export type Prettify<T> = {
11
+ [K in keyof T]: T[K];
12
+ };