@code-pushup/core 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
@@ -670,6 +670,8 @@ var auditResultSchema = scorableWithPluginMetaSchema.merge(
670
670
  );
671
671
  var reportsDiffSchema = z15.object({
672
672
  commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
673
+ portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
674
+ label: z15.string().optional().describe("Label (e.g. project name)"),
673
675
  categories: makeArraysComparisonSchema(
674
676
  categoryDiffSchema,
675
677
  categoryResultSchema,
@@ -739,8 +741,24 @@ function comparePairs(pairs, equalsFn) {
739
741
  );
740
742
  }
741
743
 
744
+ // packages/utils/src/lib/errors.ts
745
+ function stringifyError(error) {
746
+ if (error instanceof Error) {
747
+ if (error.name === "Error" || error.message.startsWith(error.name)) {
748
+ return error.message;
749
+ }
750
+ return `${error.name}: ${error.message}`;
751
+ }
752
+ if (typeof error === "string") {
753
+ return error;
754
+ }
755
+ return JSON.stringify(error);
756
+ }
757
+
742
758
  // packages/utils/src/lib/execute-process.ts
743
- import { spawn } from "node:child_process";
759
+ import {
760
+ spawn
761
+ } from "node:child_process";
744
762
 
745
763
  // packages/utils/src/lib/reports/utils.ts
746
764
  import ansis from "ansis";
@@ -867,12 +885,12 @@ function countCategoryAudits(refs, plugins) {
867
885
  }, 0);
868
886
  }
869
887
  function compareCategoryAuditsAndGroups(a, b) {
870
- if (a.weight !== b.weight) {
871
- return b.weight - a.weight;
872
- }
873
888
  if (a.score !== b.score) {
874
889
  return a.score - b.score;
875
890
  }
891
+ if (a.weight !== b.weight) {
892
+ return b.weight - a.weight;
893
+ }
876
894
  if ("value" in a && "value" in b && a.value !== b.value) {
877
895
  return b.value - a.value;
878
896
  }
@@ -964,25 +982,29 @@ var ProcessError = class extends Error {
964
982
  }
965
983
  };
966
984
  function executeProcess(cfg) {
967
- const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
968
- const { onStdout, onError, onComplete } = observer ?? {};
985
+ const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
986
+ const { onStdout, onStderr, onError, onComplete } = observer ?? {};
969
987
  const date = (/* @__PURE__ */ new Date()).toISOString();
970
988
  const start = performance.now();
971
989
  return new Promise((resolve, reject) => {
972
- const process2 = spawn(command, args, { cwd, shell: true });
990
+ const spawnedProcess = spawn(command, args ?? [], {
991
+ shell: true,
992
+ ...options
993
+ });
973
994
  let stdout = "";
974
995
  let stderr = "";
975
- process2.stdout.on("data", (data) => {
996
+ spawnedProcess.stdout.on("data", (data) => {
976
997
  stdout += String(data);
977
- onStdout?.(String(data));
998
+ onStdout?.(String(data), spawnedProcess);
978
999
  });
979
- process2.stderr.on("data", (data) => {
1000
+ spawnedProcess.stderr.on("data", (data) => {
980
1001
  stderr += String(data);
1002
+ onStderr?.(String(data), spawnedProcess);
981
1003
  });
982
- process2.on("error", (err) => {
1004
+ spawnedProcess.on("error", (err) => {
983
1005
  stderr += err.toString();
984
1006
  });
985
- process2.on("close", (code2) => {
1007
+ spawnedProcess.on("close", (code2) => {
986
1008
  const timings = { date, duration: calcDuration(start) };
987
1009
  if (code2 === 0 || ignoreExitCode) {
988
1010
  onComplete?.();
@@ -1181,6 +1203,9 @@ import { isAbsolute, join, relative } from "node:path";
1181
1203
  import { simpleGit } from "simple-git";
1182
1204
 
1183
1205
  // packages/utils/src/lib/transform.ts
1206
+ function toArray(val) {
1207
+ return Array.isArray(val) ? val : [val];
1208
+ }
1184
1209
  function objectToEntries(obj) {
1185
1210
  return Object.entries(obj);
1186
1211
  }
@@ -1452,7 +1477,10 @@ function getColumnAlignments(tableData) {
1452
1477
  }
1453
1478
 
1454
1479
  // packages/utils/src/lib/reports/formatting.ts
1455
- import { MarkdownDocument, md as md2 } from "build-md";
1480
+ import {
1481
+ MarkdownDocument,
1482
+ md as md2
1483
+ } from "build-md";
1456
1484
  function tableSection(tableData, options) {
1457
1485
  if (tableData.rows.length === 0) {
1458
1486
  return null;
@@ -1812,19 +1840,111 @@ function reportMetaTable({
1812
1840
 
1813
1841
  // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1814
1842
  import {
1815
- MarkdownDocument as MarkdownDocument4,
1816
- md as md5
1843
+ MarkdownDocument as MarkdownDocument5,
1844
+ md as md6
1817
1845
  } from "build-md";
1846
+
1847
+ // packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
1848
+ import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
1818
1849
  var MAX_ROWS = 100;
1819
- function generateMdReportsDiff(diff, portalUrl) {
1820
- return new MarkdownDocument4().$concat(
1821
- createDiffHeaderSection(diff, portalUrl),
1822
- createDiffCategoriesSection(diff),
1823
- createDiffGroupsSection(diff),
1824
- createDiffAuditsSection(diff)
1825
- ).toString();
1850
+ function summarizeUnchanged(token, { changed, unchanged }) {
1851
+ const pluralizedCount = changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`;
1852
+ const pluralizedVerb = unchanged.length === 1 ? "is" : "are";
1853
+ return `${pluralizedCount} ${pluralizedVerb} unchanged.`;
1854
+ }
1855
+ function summarizeDiffOutcomes(outcomes, token) {
1856
+ return objectToEntries(countDiffOutcomes(outcomes)).filter(
1857
+ (entry) => entry[0] !== "unchanged" && entry[1] > 0
1858
+ ).map(([outcome, count]) => {
1859
+ const formattedCount = `<strong>${count}</strong> ${pluralize(
1860
+ token,
1861
+ count
1862
+ )}`;
1863
+ switch (outcome) {
1864
+ case "positive":
1865
+ return `\u{1F44D} ${formattedCount} improved`;
1866
+ case "negative":
1867
+ return `\u{1F44E} ${formattedCount} regressed`;
1868
+ case "mixed":
1869
+ return `${formattedCount} changed without impacting score`;
1870
+ }
1871
+ }).join(", ");
1872
+ }
1873
+ function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
1874
+ if (changed.length === 0) {
1875
+ return new MarkdownDocument4().paragraph(
1876
+ summarizeUnchanged(token, { changed, unchanged })
1877
+ );
1878
+ }
1879
+ return new MarkdownDocument4().table(columns, rows.slice(0, MAX_ROWS)).paragraph(
1880
+ changed.length > MAX_ROWS && md5.italic(
1881
+ `Only the ${MAX_ROWS} most affected ${pluralize(
1882
+ token
1883
+ )} are listed above for brevity.`
1884
+ )
1885
+ ).paragraph(
1886
+ unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
1887
+ );
1888
+ }
1889
+ function formatTitle({
1890
+ title,
1891
+ docsUrl
1892
+ }) {
1893
+ if (docsUrl) {
1894
+ return md5.link(docsUrl, title);
1895
+ }
1896
+ return title;
1897
+ }
1898
+ function formatPortalLink(portalUrl) {
1899
+ return portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}");
1900
+ }
1901
+ function sortChanges(changes) {
1902
+ return [...changes].sort(
1903
+ (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
1904
+ );
1905
+ }
1906
+ function getDiffChanges(diff) {
1907
+ return [
1908
+ ...diff.categories.changed,
1909
+ ...diff.groups.changed,
1910
+ ...diff.audits.changed
1911
+ ];
1912
+ }
1913
+ function changesToDiffOutcomes(changes) {
1914
+ return changes.map((change) => {
1915
+ if (change.scores.diff > 0) {
1916
+ return "positive";
1917
+ }
1918
+ if (change.scores.diff < 0) {
1919
+ return "negative";
1920
+ }
1921
+ if (change.values != null && change.values.diff !== 0) {
1922
+ return "mixed";
1923
+ }
1924
+ return "unchanged";
1925
+ });
1926
+ }
1927
+ function mergeDiffOutcomes(outcomes) {
1928
+ if (outcomes.every((outcome) => outcome === "unchanged")) {
1929
+ return "unchanged";
1930
+ }
1931
+ if (outcomes.includes("positive") && !outcomes.includes("negative")) {
1932
+ return "positive";
1933
+ }
1934
+ if (outcomes.includes("negative") && !outcomes.includes("positive")) {
1935
+ return "negative";
1936
+ }
1937
+ return "mixed";
1938
+ }
1939
+ function countDiffOutcomes(outcomes) {
1940
+ return {
1941
+ positive: outcomes.filter((outcome) => outcome === "positive").length,
1942
+ negative: outcomes.filter((outcome) => outcome === "negative").length,
1943
+ mixed: outcomes.filter((outcome) => outcome === "mixed").length,
1944
+ unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
1945
+ };
1826
1946
  }
1827
- function createDiffHeaderSection(diff, portalUrl) {
1947
+ function formatReportOutcome(outcome, commits) {
1828
1948
  const outcomeTexts = {
1829
1949
  positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
1830
1950
  negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
@@ -1833,27 +1953,91 @@ function createDiffHeaderSection(diff, portalUrl) {
1833
1953
  )}`,
1834
1954
  unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
1835
1955
  };
1956
+ if (commits) {
1957
+ const commitsText = `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1958
+ return md5`${outcomeTexts[outcome]} – ${commitsText}.`;
1959
+ }
1960
+ return md5`${outcomeTexts[outcome]}.`;
1961
+ }
1962
+ function compareDiffsBy(type, a, b) {
1963
+ return sumScoreChanges(b[type].changed) - sumScoreChanges(a[type].changed) || sumConfigChanges(b[type]) - sumConfigChanges(a[type]);
1964
+ }
1965
+ function sumScoreChanges(changes) {
1966
+ return changes.reduce(
1967
+ (acc, { scores }) => acc + Math.abs(scores.diff),
1968
+ 0
1969
+ );
1970
+ }
1971
+ function sumConfigChanges({
1972
+ added,
1973
+ removed
1974
+ }) {
1975
+ return added.length + removed.length;
1976
+ }
1977
+
1978
+ // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1979
+ function generateMdReportsDiff(diff) {
1980
+ return new MarkdownDocument5().$concat(
1981
+ createDiffHeaderSection(diff),
1982
+ createDiffCategoriesSection(diff),
1983
+ createDiffDetailsSection(diff)
1984
+ ).toString();
1985
+ }
1986
+ function generateMdReportsDiffForMonorepo(diffs) {
1987
+ const diffsWithOutcomes = diffs.map((diff) => ({
1988
+ ...diff,
1989
+ outcome: mergeDiffOutcomes(changesToDiffOutcomes(getDiffChanges(diff)))
1990
+ })).sort(
1991
+ (a, b) => compareDiffsBy("categories", a, b) || compareDiffsBy("groups", a, b) || compareDiffsBy("audits", a, b) || a.label.localeCompare(b.label)
1992
+ );
1993
+ const unchanged = diffsWithOutcomes.filter(
1994
+ ({ outcome }) => outcome === "unchanged"
1995
+ );
1996
+ const changed = diffsWithOutcomes.filter((diff) => !unchanged.includes(diff));
1997
+ return new MarkdownDocument5().$concat(
1998
+ createDiffHeaderSection(diffs),
1999
+ ...changed.map(createDiffProjectSection)
2000
+ ).$if(
2001
+ unchanged.length > 0,
2002
+ (doc) => doc.rule().paragraph(summarizeUnchanged("project", { unchanged, changed }))
2003
+ ).toString();
2004
+ }
2005
+ function createDiffHeaderSection(diff) {
1836
2006
  const outcome = mergeDiffOutcomes(
1837
- changesToDiffOutcomes([
1838
- ...diff.categories.changed,
1839
- ...diff.groups.changed,
1840
- ...diff.audits.changed
1841
- ])
2007
+ changesToDiffOutcomes(toArray(diff).flatMap(getDiffChanges))
1842
2008
  );
1843
- const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
1844
- return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
1845
- diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
1846
- ).paragraph(
1847
- portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
2009
+ const commits = Array.isArray(diff) ? diff[0]?.commits : diff.commits;
2010
+ const portalUrl = Array.isArray(diff) ? void 0 : diff.portalUrl;
2011
+ return new MarkdownDocument5().heading(HIERARCHY.level_1, "Code PushUp").paragraph(formatReportOutcome(outcome, commits)).paragraph(formatPortalLink(portalUrl));
2012
+ }
2013
+ function createDiffProjectSection(diff) {
2014
+ return new MarkdownDocument5().heading(HIERARCHY.level_2, md6`💼 Project ${md6.code(diff.label)}`).paragraph(formatReportOutcome(diff.outcome)).paragraph(formatPortalLink(diff.portalUrl)).$concat(
2015
+ createDiffCategoriesSection(diff, {
2016
+ skipHeading: true,
2017
+ skipUnchanged: true
2018
+ }),
2019
+ createDiffDetailsSection(diff, HIERARCHY.level_3)
1848
2020
  );
1849
2021
  }
1850
- function createDiffCategoriesSection(diff) {
2022
+ function createDiffCategoriesSection(diff, options) {
1851
2023
  const { changed, unchanged, added } = diff.categories;
2024
+ const { skipHeading, skipUnchanged } = options ?? {};
1852
2025
  const categoriesCount = changed.length + unchanged.length + added.length;
1853
2026
  const hasChanges = unchanged.length < categoriesCount;
1854
2027
  if (categoriesCount === 0) {
1855
2028
  return null;
1856
2029
  }
2030
+ const [columns, rows] = createCategoriesTable(diff, {
2031
+ hasChanges,
2032
+ skipUnchanged
2033
+ });
2034
+ return new MarkdownDocument5().heading(HIERARCHY.level_2, !skipHeading && "\u{1F3F7}\uFE0F Categories").table(columns, rows).paragraph(added.length > 0 && md6.italic("(\\*) New category.")).paragraph(
2035
+ skipUnchanged && unchanged.length > 0 && summarizeUnchanged("category", { changed, unchanged })
2036
+ );
2037
+ }
2038
+ function createCategoriesTable(diff, options) {
2039
+ const { changed, unchanged, added } = diff.categories;
2040
+ const { hasChanges, skipUnchanged } = options;
1857
2041
  const columns = [
1858
2042
  { heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
1859
2043
  {
@@ -1874,27 +2058,43 @@ function createDiffCategoriesSection(diff) {
1874
2058
  ]),
1875
2059
  ...added.map((category) => [
1876
2060
  formatTitle(category),
1877
- md5.italic("n/a (\\*)"),
2061
+ md6.italic("n/a (\\*)"),
1878
2062
  formatScoreWithColor(category.score),
1879
- md5.italic("n/a (\\*)")
2063
+ md6.italic("n/a (\\*)")
1880
2064
  ]),
1881
- ...unchanged.map((category) => [
2065
+ ...skipUnchanged ? [] : unchanged.map((category) => [
1882
2066
  formatTitle(category),
1883
2067
  formatScoreWithColor(category.score, { skipBold: true }),
1884
2068
  formatScoreWithColor(category.score),
1885
2069
  "\u2013"
1886
2070
  ])
1887
2071
  ];
1888
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
2072
+ return [
1889
2073
  hasChanges ? columns : columns.slice(0, 2),
1890
2074
  rows.map((row) => hasChanges ? row : row.slice(0, 2))
1891
- ).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
2075
+ ];
1892
2076
  }
1893
- function createDiffGroupsSection(diff) {
2077
+ function createDiffDetailsSection(diff, level = HIERARCHY.level_2) {
2078
+ if (diff.groups.changed.length + diff.audits.changed.length === 0) {
2079
+ return null;
2080
+ }
2081
+ const summary = ["group", "audit"].map(
2082
+ (token) => summarizeDiffOutcomes(
2083
+ changesToDiffOutcomes(diff[`${token}s`].changed),
2084
+ token
2085
+ )
2086
+ ).filter(Boolean).join(", ");
2087
+ const details2 = new MarkdownDocument5().$concat(
2088
+ createDiffGroupsSection(diff, level),
2089
+ createDiffAuditsSection(diff, level)
2090
+ );
2091
+ return new MarkdownDocument5().details(summary, details2);
2092
+ }
2093
+ function createDiffGroupsSection(diff, level) {
1894
2094
  if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
1895
2095
  return null;
1896
2096
  }
1897
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
2097
+ return new MarkdownDocument5().heading(level, "\u{1F5C3}\uFE0F Groups").$concat(
1898
2098
  createGroupsOrAuditsDetails(
1899
2099
  "group",
1900
2100
  diff.groups,
@@ -1915,8 +2115,8 @@ function createDiffGroupsSection(diff) {
1915
2115
  )
1916
2116
  );
1917
2117
  }
1918
- function createDiffAuditsSection(diff) {
1919
- return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
2118
+ function createDiffAuditsSection(diff, level) {
2119
+ return new MarkdownDocument5().heading(level, "\u{1F6E1}\uFE0F Audits").$concat(
1920
2120
  createGroupsOrAuditsDetails(
1921
2121
  "audit",
1922
2122
  diff.audits,
@@ -1931,7 +2131,7 @@ function createDiffAuditsSection(diff) {
1931
2131
  formatTitle(audit.plugin),
1932
2132
  formatTitle(audit),
1933
2133
  `${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
1934
- md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
2134
+ md6`${scoreMarker(audit.scores.after, "square")} ${md6.bold(
1935
2135
  audit.displayValues.after || audit.values.after.toString()
1936
2136
  )}`,
1937
2137
  formatValueChange(audit)
@@ -1939,96 +2139,6 @@ function createDiffAuditsSection(diff) {
1939
2139
  )
1940
2140
  );
1941
2141
  }
1942
- function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
1943
- if (changed.length === 0) {
1944
- return new MarkdownDocument4().paragraph(
1945
- summarizeUnchanged(token, { changed, unchanged })
1946
- );
1947
- }
1948
- return new MarkdownDocument4().details(
1949
- summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
1950
- md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
1951
- md5.italic(
1952
- `Only the ${MAX_ROWS} most affected ${pluralize(
1953
- token
1954
- )} are listed above for brevity.`
1955
- )
1956
- ) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
1957
- );
1958
- }
1959
- function summarizeUnchanged(token, { changed, unchanged }) {
1960
- return [
1961
- changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
1962
- unchanged.length === 1 ? "is" : "are",
1963
- "unchanged."
1964
- ].join(" ");
1965
- }
1966
- function summarizeDiffOutcomes(outcomes, token) {
1967
- return objectToEntries(countDiffOutcomes(outcomes)).filter(
1968
- (entry) => entry[0] !== "unchanged" && entry[1] > 0
1969
- ).map(([outcome, count]) => {
1970
- const formattedCount = `<strong>${count}</strong> ${pluralize(
1971
- token,
1972
- count
1973
- )}`;
1974
- switch (outcome) {
1975
- case "positive":
1976
- return `\u{1F44D} ${formattedCount} improved`;
1977
- case "negative":
1978
- return `\u{1F44E} ${formattedCount} regressed`;
1979
- case "mixed":
1980
- return `${formattedCount} changed without impacting score`;
1981
- }
1982
- }).join(", ");
1983
- }
1984
- function formatTitle({
1985
- title,
1986
- docsUrl
1987
- }) {
1988
- if (docsUrl) {
1989
- return md5.link(docsUrl, title);
1990
- }
1991
- return title;
1992
- }
1993
- function sortChanges(changes) {
1994
- return [...changes].sort(
1995
- (a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
1996
- );
1997
- }
1998
- function changesToDiffOutcomes(changes) {
1999
- return changes.map((change) => {
2000
- if (change.scores.diff > 0) {
2001
- return "positive";
2002
- }
2003
- if (change.scores.diff < 0) {
2004
- return "negative";
2005
- }
2006
- if (change.values != null && change.values.diff !== 0) {
2007
- return "mixed";
2008
- }
2009
- return "unchanged";
2010
- });
2011
- }
2012
- function mergeDiffOutcomes(outcomes) {
2013
- if (outcomes.every((outcome) => outcome === "unchanged")) {
2014
- return "unchanged";
2015
- }
2016
- if (outcomes.includes("positive") && !outcomes.includes("negative")) {
2017
- return "positive";
2018
- }
2019
- if (outcomes.includes("negative") && !outcomes.includes("positive")) {
2020
- return "negative";
2021
- }
2022
- return "mixed";
2023
- }
2024
- function countDiffOutcomes(outcomes) {
2025
- return {
2026
- positive: outcomes.filter((outcome) => outcome === "positive").length,
2027
- negative: outcomes.filter((outcome) => outcome === "negative").length,
2028
- mixed: outcomes.filter((outcome) => outcome === "mixed").length,
2029
- unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
2030
- };
2031
- }
2032
2142
 
2033
2143
  // packages/utils/src/lib/reports/load-report.ts
2034
2144
  import { join as join2 } from "node:path";
@@ -2085,7 +2195,8 @@ function logPlugins(report) {
2085
2195
  },
2086
2196
  {
2087
2197
  text: cyanBright(audit.displayValue || `${audit.value}`),
2088
- width: 10,
2198
+ // eslint-disable-next-line no-magic-numbers
2199
+ width: 20,
2089
2200
  padding: [0, 0, 0, 0]
2090
2201
  }
2091
2202
  ]);
@@ -2238,7 +2349,7 @@ var verboseUtils = (verbose = false) => ({
2238
2349
 
2239
2350
  // packages/core/package.json
2240
2351
  var name = "@code-pushup/core";
2241
- var version = "0.49.0";
2352
+ var version = "0.50.0";
2242
2353
 
2243
2354
  // packages/core/src/lib/implementation/execute-plugin.ts
2244
2355
  import { bold as bold5 } from "ansis";
@@ -2634,7 +2745,7 @@ function selectMeta(meta) {
2634
2745
  }
2635
2746
 
2636
2747
  // packages/core/src/lib/compare.ts
2637
- async function compareReportFiles(inputPaths, persistConfig, uploadConfig) {
2748
+ async function compareReportFiles(inputPaths, persistConfig, uploadConfig, label) {
2638
2749
  const { outputDir, filename, format } = persistConfig;
2639
2750
  const [reportBefore, reportAfter] = await Promise.all([
2640
2751
  readJsonFile(inputPaths.before),
@@ -2644,12 +2755,20 @@ async function compareReportFiles(inputPaths, persistConfig, uploadConfig) {
2644
2755
  before: reportSchema.parse(reportBefore),
2645
2756
  after: reportSchema.parse(reportAfter)
2646
2757
  };
2647
- const reportsDiff = compareReports(reports);
2648
- const portalUrl = uploadConfig && reportsDiff.commits && format.includes("md") ? await fetchPortalComparisonLink(uploadConfig, reportsDiff.commits) : void 0;
2758
+ const diff = compareReports(reports);
2759
+ if (label) {
2760
+ diff.label = label;
2761
+ }
2762
+ if (uploadConfig && diff.commits) {
2763
+ diff.portalUrl = await fetchPortalComparisonLink(
2764
+ uploadConfig,
2765
+ diff.commits
2766
+ );
2767
+ }
2649
2768
  return Promise.all(
2650
2769
  format.map(async (fmt) => {
2651
2770
  const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
2652
- const content = reportsDiffToFileContent(reportsDiff, fmt, portalUrl);
2771
+ const content = reportsDiffToFileContent(diff, fmt);
2653
2772
  await ensureDirectoryExists(outputDir);
2654
2773
  await writeFile2(outputPath, content);
2655
2774
  return outputPath;
@@ -2679,12 +2798,12 @@ function compareReports(reports) {
2679
2798
  duration
2680
2799
  };
2681
2800
  }
2682
- function reportsDiffToFileContent(reportsDiff, format, portalUrl) {
2801
+ function reportsDiffToFileContent(reportsDiff, format) {
2683
2802
  switch (format) {
2684
2803
  case "json":
2685
2804
  return JSON.stringify(reportsDiff, null, 2);
2686
2805
  case "md":
2687
- return generateMdReportsDiff(reportsDiff, portalUrl ?? void 0);
2806
+ return generateMdReportsDiff(reportsDiff);
2688
2807
  }
2689
2808
  }
2690
2809
  async function fetchPortalComparisonLink(uploadConfig, commits) {
@@ -2956,6 +3075,45 @@ async function autoloadRc(tsconfig) {
2956
3075
  tsconfig
2957
3076
  );
2958
3077
  }
3078
+
3079
+ // packages/core/src/lib/merge-diffs.ts
3080
+ import { writeFile as writeFile3 } from "node:fs/promises";
3081
+ import { basename, dirname, join as join7 } from "node:path";
3082
+ async function mergeDiffs(files, persistConfig) {
3083
+ const results = await Promise.allSettled(
3084
+ files.map(async (file) => {
3085
+ const json = await readJsonFile(file).catch((error) => {
3086
+ throw new Error(
3087
+ `Failed to read JSON file ${file} - ${stringifyError(error)}`
3088
+ );
3089
+ });
3090
+ const result = await reportsDiffSchema.safeParseAsync(json);
3091
+ if (!result.success) {
3092
+ throw new Error(
3093
+ `Invalid reports diff in ${file} - ${result.error.message}`
3094
+ );
3095
+ }
3096
+ return { ...result.data, file };
3097
+ })
3098
+ );
3099
+ results.filter(isPromiseRejectedResult).forEach(({ reason }) => {
3100
+ ui().logger.warning(
3101
+ `Skipped invalid report diff - ${stringifyError(reason)}`
3102
+ );
3103
+ });
3104
+ const diffs = results.filter(isPromiseFulfilledResult).map(({ value }) => value);
3105
+ const labeledDiffs = diffs.map((diff) => ({
3106
+ ...diff,
3107
+ label: diff.label || basename(dirname(diff.file))
3108
+ // fallback is parent folder name
3109
+ }));
3110
+ const markdown = generateMdReportsDiffForMonorepo(labeledDiffs);
3111
+ const { outputDir, filename } = persistConfig;
3112
+ const outputPath = join7(outputDir, `${filename}-diff.md`);
3113
+ await ensureDirectoryExists(outputDir);
3114
+ await writeFile3(outputPath, markdown);
3115
+ return outputPath;
3116
+ }
2959
3117
  export {
2960
3118
  ConfigPathError,
2961
3119
  PersistDirError,
@@ -2969,6 +3127,7 @@ export {
2969
3127
  executePlugin,
2970
3128
  executePlugins,
2971
3129
  history,
3130
+ mergeDiffs,
2972
3131
  persistReport,
2973
3132
  readRcByPath,
2974
3133
  upload
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@code-pushup/core",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "license": "MIT",
5
+ "description": "Core business logic for the used by the Code PushUp CLI",
5
6
  "dependencies": {
6
- "@code-pushup/models": "0.49.0",
7
- "@code-pushup/utils": "0.49.0",
7
+ "@code-pushup/models": "0.50.0",
8
+ "@code-pushup/utils": "0.50.0",
8
9
  "@code-pushup/portal-client": "^0.9.0",
9
10
  "ansis": "^3.3.0"
10
11
  },
11
- "type": "module",
12
- "main": "./index.js",
13
- "homepage": "https://github.com/code-pushup/cli#readme",
12
+ "homepage": "https://github.com/code-pushup/cli/tree/main/packages/core#readme",
14
13
  "bugs": {
15
14
  "url": "https://github.com/code-pushup/cli/issues"
16
15
  },
@@ -19,32 +18,7 @@
19
18
  "url": "git+https://github.com/code-pushup/cli.git",
20
19
  "directory": "packages/core"
21
20
  },
22
- "contributors": [
23
- {
24
- "name": "Igor Katsuba",
25
- "email": "igor@katsuba.dev",
26
- "url": "https://katsuba.dev"
27
- },
28
- {
29
- "name": "Kateřina Pilátová",
30
- "email": "katerina.pilatova@flowup.cz",
31
- "url": "https://github.com/Tlacenka"
32
- },
33
- {
34
- "name": "Matěj Chalk",
35
- "email": "matej.chalk@flowup.cz",
36
- "url": "https://github.com/matejchalk"
37
- },
38
- {
39
- "name": "Michael Hladky",
40
- "email": "michael.hladky@push-based.io",
41
- "url": "https://push-based.io"
42
- },
43
- {
44
- "name": "Michael Seredenko",
45
- "email": "misha.seredenko@push-based.io",
46
- "url": "https://github.com/MishaSeredenkoPushBased"
47
- }
48
- ],
21
+ "type": "module",
22
+ "main": "./index.js",
49
23
  "types": "./src/index.d.ts"
50
24
  }
package/src/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- export { CollectAndPersistReportsOptions, collectAndPersistReports, } from './lib/collect-and-persist';
1
+ export { type CollectAndPersistReportsOptions, collectAndPersistReports, } from './lib/collect-and-persist';
2
2
  export { compareReportFiles, compareReports } from './lib/compare';
3
- export { CollectOptions, collect } from './lib/implementation/collect';
4
- export { ReportsToCompare } from './lib/implementation/compare-scorables';
3
+ export { type CollectOptions, collect } from './lib/implementation/collect';
4
+ export type { ReportsToCompare } from './lib/implementation/compare-scorables';
5
5
  export { PluginOutputMissingAuditError, executePlugin, executePlugins, } from './lib/implementation/execute-plugin';
6
6
  export { PersistDirError, PersistError, persistReport, } from './lib/implementation/persist';
7
- export { history, HistoryOptions, HistoryOnlyOptions } from './lib/history';
7
+ export { history, type HistoryOptions, type HistoryOnlyOptions, } from './lib/history';
8
8
  export { ConfigPathError, autoloadRc, readRcByPath, } from './lib/implementation/read-rc-file';
9
- export { GlobalOptions } from './lib/types';
10
- export { UploadOptions, upload } from './lib/upload';
9
+ export type { GlobalOptions } from './lib/types';
10
+ export { type UploadOptions, upload } from './lib/upload';
11
+ export { mergeDiffs } from './lib/merge-diffs';
@@ -1,5 +1,5 @@
1
- import { CoreConfig, PersistConfig } from '@code-pushup/models';
2
- import { GlobalOptions } from './types';
1
+ import { type CoreConfig, type PersistConfig } from '@code-pushup/models';
2
+ import type { GlobalOptions } from './types';
3
3
  export type CollectAndPersistReportsOptions = Required<Pick<CoreConfig, 'plugins' | 'categories'>> & {
4
4
  persist: Required<PersistConfig>;
5
5
  } & Partial<GlobalOptions>;
@@ -1,4 +1,4 @@
1
- import { type PersistConfig, Report, ReportsDiff, type UploadConfig } from '@code-pushup/models';
2
- import { Diff } from '@code-pushup/utils';
3
- export declare function compareReportFiles(inputPaths: Diff<string>, persistConfig: Required<PersistConfig>, uploadConfig: UploadConfig | undefined): Promise<string[]>;
1
+ import { type PersistConfig, type Report, type ReportsDiff, type UploadConfig } from '@code-pushup/models';
2
+ import { type Diff } from '@code-pushup/utils';
3
+ export declare function compareReportFiles(inputPaths: Diff<string>, persistConfig: Required<PersistConfig>, uploadConfig: UploadConfig | undefined, label?: string): Promise<string[]>;
4
4
  export declare function compareReports(reports: Diff<Report>): ReportsDiff;
@@ -1,5 +1,5 @@
1
- import { CoreConfig, PersistConfig, UploadConfig } from '@code-pushup/models';
2
- import { GlobalOptions } from './types';
1
+ import type { CoreConfig, PersistConfig, UploadConfig } from '@code-pushup/models';
2
+ import type { GlobalOptions } from './types';
3
3
  export type HistoryOnlyOptions = {
4
4
  targetBranch?: string;
5
5
  skipUploads?: boolean;
@@ -1,5 +1,5 @@
1
- import { CoreConfig, Report } from '@code-pushup/models';
2
- import { GlobalOptions } from '../types';
1
+ import type { CoreConfig, Report } from '@code-pushup/models';
2
+ import type { GlobalOptions } from '../types';
3
3
  export type CollectOptions = Required<Pick<CoreConfig, 'plugins' | 'categories'>> & Partial<GlobalOptions>;
4
4
  /**
5
5
  * Run audits, collect plugin output and aggregate it into a JSON object
@@ -1,5 +1,5 @@
1
- import { ReportsDiff } from '@code-pushup/models';
2
- import { Diff, ScoredReport } from '@code-pushup/utils';
1
+ import type { ReportsDiff } from '@code-pushup/models';
2
+ import { type Diff, type ScoredReport } from '@code-pushup/utils';
3
3
  export type ReportsToCompare = Diff<ScoredReport>;
4
4
  export declare function compareCategories(reports: ReportsToCompare): ReportsDiff['categories'];
5
5
  export declare function compareGroups(reports: ReportsToCompare): ReportsDiff['groups'];
@@ -1,4 +1,4 @@
1
- import { OnProgress, PluginConfig, PluginReport } from '@code-pushup/models';
1
+ import { type OnProgress, type PluginConfig, type PluginReport } from '@code-pushup/models';
2
2
  /**
3
3
  * Error thrown when plugin output is invalid.
4
4
  */
@@ -1,5 +1,5 @@
1
- import { PersistConfig, Report } from '@code-pushup/models';
2
- import { MultipleFileResults } from '@code-pushup/utils';
1
+ import type { PersistConfig, Report } from '@code-pushup/models';
2
+ import { type MultipleFileResults } from '@code-pushup/utils';
3
3
  export declare class PersistDirError extends Error {
4
4
  constructor(outputDir: string);
5
5
  }
@@ -1,4 +1,4 @@
1
- import { CoreConfig } from '@code-pushup/models';
1
+ import { type CoreConfig } from '@code-pushup/models';
2
2
  export declare class ConfigPathError extends Error {
3
3
  constructor(configPath: string);
4
4
  }
@@ -1,4 +1,4 @@
1
- import { OnProgress, RunnerConfig, RunnerFunction } from '@code-pushup/models';
1
+ import type { OnProgress, RunnerConfig, RunnerFunction } from '@code-pushup/models';
2
2
  export type RunnerResult = {
3
3
  date: string;
4
4
  duration: number;
@@ -0,0 +1,2 @@
1
+ import { type PersistConfig } from '@code-pushup/models';
2
+ export declare function mergeDiffs(files: string[], persistConfig: Required<PersistConfig>): Promise<string>;
@@ -1,3 +1,3 @@
1
- import { type AuditOutputs, Issue } from '@code-pushup/models';
1
+ import type { AuditOutputs, Issue } from '@code-pushup/models';
2
2
  export declare function normalizeIssue(issue: Issue, gitRoot: string): Issue;
3
3
  export declare function normalizeAuditOutputs(audits: AuditOutputs): Promise<AuditOutputs>;
@@ -1,6 +1,6 @@
1
1
  import { uploadToPortal } from '@code-pushup/portal-client';
2
- import { PersistConfig, UploadConfig } from '@code-pushup/models';
3
- import { GlobalOptions } from './types';
2
+ import type { PersistConfig, UploadConfig } from '@code-pushup/models';
3
+ import type { GlobalOptions } from './types';
4
4
  export type UploadOptions = {
5
5
  upload?: UploadConfig;
6
6
  } & {