@code-pushup/coverage-plugin 0.45.0 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -2,1041 +2,1045 @@
2
2
  import { dirname as dirname2, join as join3 } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
- // packages/utils/src/lib/text-formats/constants.ts
6
- var NEW_LINE = "\n";
7
- var TAB = " ";
5
+ // packages/models/src/lib/implementation/schemas.ts
6
+ import { MATERIAL_ICONS } from "vscode-material-icons";
7
+ import { z } from "zod";
8
8
 
9
- // packages/utils/src/lib/text-formats/html/details.ts
10
- function details(title, content, cfg = { open: false }) {
11
- return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
12
- NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
13
- // ⚠️ The blank line ensure Markdown in content is rendered correctly.
14
- NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
15
- NEW_LINE}`;
16
- }
9
+ // packages/models/src/lib/implementation/limits.ts
10
+ var MAX_SLUG_LENGTH = 128;
11
+ var MAX_TITLE_LENGTH = 256;
12
+ var MAX_DESCRIPTION_LENGTH = 65536;
13
+ var MAX_ISSUE_MESSAGE_LENGTH = 1024;
17
14
 
18
- // packages/utils/src/lib/text-formats/html/font-style.ts
19
- var boldElement = "b";
20
- function bold(text) {
21
- return `<${boldElement}>${text}</${boldElement}>`;
15
+ // packages/models/src/lib/implementation/utils.ts
16
+ var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
17
+ var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
18
+ function hasDuplicateStrings(strings) {
19
+ const sortedStrings = [...strings].sort();
20
+ const duplStrings = sortedStrings.filter(
21
+ (item, index) => index !== 0 && item === sortedStrings[index - 1]
22
+ );
23
+ return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
22
24
  }
23
- var italicElement = "i";
24
- function italic(text) {
25
- return `<${italicElement}>${text}</${italicElement}>`;
25
+ function hasMissingStrings(toCheck, existing) {
26
+ const nonExisting = toCheck.filter((s) => !existing.includes(s));
27
+ return nonExisting.length === 0 ? false : nonExisting;
26
28
  }
27
- var codeElement = "code";
28
- function code(text) {
29
- return `<${codeElement}>${text}</${codeElement}>`;
29
+ function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
30
+ return transform(items || []);
30
31
  }
31
-
32
- // packages/utils/src/lib/text-formats/html/link.ts
33
- function link(href, text) {
34
- return `<a href="${href}">${text || href}"</a>`;
32
+ function exists(value) {
33
+ return value != null;
35
34
  }
36
-
37
- // packages/utils/src/lib/transform.ts
38
- function capitalize(text) {
39
- return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
40
- 1
35
+ function getMissingRefsForCategories(categories, plugins) {
36
+ if (categories.length === 0) {
37
+ return false;
38
+ }
39
+ const auditRefsFromCategory = categories.flatMap(
40
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
41
+ );
42
+ const auditRefsFromPlugins = plugins.flatMap(
43
+ ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
44
+ );
45
+ const missingAuditRefs = hasMissingStrings(
46
+ auditRefsFromCategory,
47
+ auditRefsFromPlugins
48
+ );
49
+ const groupRefsFromCategory = categories.flatMap(
50
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
51
+ );
52
+ const groupRefsFromPlugins = plugins.flatMap(
53
+ ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
54
+ );
55
+ const missingGroupRefs = hasMissingStrings(
56
+ groupRefsFromCategory,
57
+ groupRefsFromPlugins
58
+ );
59
+ const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
60
+ return missingRefs.length > 0 ? missingRefs : false;
61
+ }
62
+ function missingRefsForCategoriesErrorMsg(categories, plugins) {
63
+ const missingRefs = getMissingRefsForCategories(categories, plugins);
64
+ return `The following category references need to point to an audit or group: ${errorItems(
65
+ missingRefs
41
66
  )}`;
42
67
  }
43
68
 
44
- // packages/utils/src/lib/table.ts
45
- function rowToStringArray({ rows, columns = [] }) {
46
- if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
47
- throw new TypeError(
48
- "Column can`t be object when rows are primitive values"
49
- );
50
- }
51
- return rows.map((row) => {
52
- if (Array.isArray(row)) {
53
- return row.map(String);
54
- }
55
- const objectRow = row;
56
- if (columns.length === 0 || typeof columns.at(0) === "string") {
57
- return Object.values(objectRow).map(String);
58
- }
59
- return columns.map(
60
- ({ key }) => String(objectRow[key])
61
- );
69
+ // packages/models/src/lib/implementation/schemas.ts
70
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
71
+ function executionMetaSchema(options = {
72
+ descriptionDate: "Execution start date and time",
73
+ descriptionDuration: "Execution duration in ms"
74
+ }) {
75
+ return z.object({
76
+ date: z.string({ description: options.descriptionDate }),
77
+ duration: z.number({ description: options.descriptionDuration })
62
78
  });
63
79
  }
64
- function columnsToStringArray({ rows, columns = [] }) {
65
- const firstRow = rows.at(0);
66
- const primitiveRows = Array.isArray(firstRow);
67
- if (typeof columns.at(0) === "string" && !primitiveRows) {
68
- throw new Error("invalid union type. Caught by model parsing.");
69
- }
70
- if (columns.length === 0) {
71
- if (Array.isArray(firstRow)) {
72
- return firstRow.map((_, idx) => String(idx));
73
- }
74
- return Object.keys(firstRow);
75
- }
76
- if (typeof columns.at(0) === "string") {
77
- return columns.map(String);
78
- }
79
- const cols = columns;
80
- return cols.map(({ label, key }) => label ?? capitalize(key));
81
- }
82
- function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
83
- const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
84
- if (typeof column === "string") {
85
- return column;
86
- } else if (typeof column === "object") {
87
- return column.align ?? "center";
88
- } else {
89
- return "center";
90
- }
80
+ var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
81
+ message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
82
+ }).max(MAX_SLUG_LENGTH, {
83
+ message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
84
+ });
85
+ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
86
+ var urlSchema = z.string().url();
87
+ var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
88
+ var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
89
+ var scoreSchema = z.number({
90
+ description: "Value between 0 and 1"
91
+ }).min(0).max(1);
92
+ function metaSchema(options) {
93
+ const {
94
+ descriptionDescription,
95
+ titleDescription,
96
+ docsUrlDescription,
97
+ description
98
+ } = options ?? {};
99
+ return z.object(
100
+ {
101
+ title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
102
+ description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
103
+ docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
104
+ },
105
+ { description }
106
+ );
91
107
  }
92
- function getColumnAlignmentForIndex(targetIdx, columns = []) {
93
- const column = columns.at(targetIdx);
94
- if (column == null) {
95
- return "center";
96
- } else if (typeof column === "string") {
97
- return column;
98
- } else if (typeof column === "object") {
99
- return column.align ?? "center";
100
- } else {
101
- return "center";
102
- }
108
+ var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
109
+ var fileNameSchema = z.string().trim().regex(filenameRegex, {
110
+ message: `The filename has to be valid`
111
+ }).min(1, { message: "file name is invalid" });
112
+ var positiveIntSchema = z.number().int().positive();
113
+ var nonnegativeIntSchema = z.number().int().nonnegative();
114
+ var nonnegativeNumberSchema = z.number().nonnegative();
115
+ function packageVersionSchema(options) {
116
+ const { versionDescription = "NPM version of the package", required } = options ?? {};
117
+ const packageSchema = z.string({ description: "NPM package name" });
118
+ const versionSchema = z.string({ description: versionDescription });
119
+ return z.object(
120
+ {
121
+ packageName: required ? packageSchema : packageSchema.optional(),
122
+ version: required ? versionSchema : versionSchema.optional()
123
+ },
124
+ { description: "NPM package name and version of a published package" }
125
+ );
103
126
  }
104
- function getColumnAlignments({
105
- rows,
106
- columns = []
107
- }) {
108
- if (rows.at(0) == null) {
109
- throw new Error("first row can`t be undefined.");
110
- }
111
- if (Array.isArray(rows.at(0))) {
112
- const firstPrimitiveRow = rows.at(0);
113
- return Array.from({ length: firstPrimitiveRow.length }).map(
114
- (_, idx) => getColumnAlignmentForIndex(idx, columns)
115
- );
116
- }
117
- const firstObject = rows.at(0);
118
- return Object.keys(firstObject).map(
119
- (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
127
+ var weightSchema = nonnegativeNumberSchema.describe(
128
+ "Coefficient for the given score (use weight 0 if only for display)"
129
+ );
130
+ function weightedRefSchema(description, slugDescription) {
131
+ return z.object(
132
+ {
133
+ slug: slugSchema.describe(slugDescription),
134
+ weight: weightSchema.describe("Weight used to calculate score")
135
+ },
136
+ { description }
120
137
  );
121
138
  }
122
-
123
- // packages/utils/src/lib/text-formats/html/table.ts
124
- function wrap(elem, content) {
125
- return `<${elem}>${content}</${elem}>${NEW_LINE}`;
139
+ function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
140
+ return z.object(
141
+ {
142
+ slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
143
+ refs: z.array(refSchema).min(1).refine(
144
+ (refs) => !duplicateCheckFn(refs),
145
+ (refs) => ({
146
+ message: duplicateMessageFn(refs)
147
+ })
148
+ ).refine(hasNonZeroWeightedRef, () => ({
149
+ message: "In a category there has to be at least one ref with weight > 0"
150
+ }))
151
+ },
152
+ { description }
153
+ );
126
154
  }
127
- function wrapRow(content) {
128
- const elem = "tr";
129
- return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
130
- }
131
- function table(tableData) {
132
- if (tableData.rows.length === 0) {
133
- throw new Error("Data can't be empty");
134
- }
135
- const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
136
- const tableHeaderRow = wrapRow(tableHeaderCols);
137
- const tableBody = rowToStringArray(tableData).map((arr) => {
138
- const columns = arr.map((s) => wrap("td", s)).join("");
139
- return wrapRow(columns);
140
- }).join("");
141
- return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
155
+ var materialIconSchema = z.enum(MATERIAL_ICONS, {
156
+ description: "Icon from VSCode Material Icons extension"
157
+ });
158
+ function hasNonZeroWeightedRef(refs) {
159
+ return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
142
160
  }
143
161
 
144
- // packages/utils/src/lib/text-formats/md/font-style.ts
145
- var boldWrap = "**";
146
- function bold2(text) {
147
- return `${boldWrap}${text}${boldWrap}`;
148
- }
149
- var italicWrap = "_";
150
- function italic2(text) {
151
- return `${italicWrap}${text}${italicWrap}`;
152
- }
153
- var strikeThroughWrap = "~";
154
- function strikeThrough(text) {
155
- return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
162
+ // packages/models/src/lib/audit.ts
163
+ import { z as z2 } from "zod";
164
+ var auditSchema = z2.object({
165
+ slug: slugSchema.describe("ID (unique within plugin)")
166
+ }).merge(
167
+ metaSchema({
168
+ titleDescription: "Descriptive name",
169
+ descriptionDescription: "Description (markdown)",
170
+ docsUrlDescription: "Link to documentation (rationale)",
171
+ description: "List of scorable metrics for the given plugin"
172
+ })
173
+ );
174
+ var pluginAuditsSchema = z2.array(auditSchema, {
175
+ description: "List of audits maintained in a plugin"
176
+ }).min(1).refine(
177
+ (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
178
+ (auditMetadata) => ({
179
+ message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
180
+ })
181
+ );
182
+ function duplicateSlugsInAuditsErrorMsg(audits) {
183
+ const duplicateRefs = getDuplicateSlugsInAudits(audits);
184
+ return `In plugin audits the following slugs are not unique: ${errorItems(
185
+ duplicateRefs
186
+ )}`;
156
187
  }
157
- var codeWrap = "`";
158
- function code2(text) {
159
- return `${codeWrap}${text}${codeWrap}`;
188
+ function getDuplicateSlugsInAudits(audits) {
189
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
160
190
  }
161
191
 
162
- // packages/utils/src/lib/text-formats/md/headline.ts
163
- function headline(text, hierarchy = 1) {
164
- return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
165
- }
166
- function h(text, hierarchy = 1) {
167
- return headline(text, hierarchy);
168
- }
169
- function h1(text) {
170
- return headline(text, 1);
171
- }
172
- function h2(text) {
173
- return headline(text, 2);
174
- }
175
- function h3(text) {
176
- return headline(text, 3);
177
- }
178
- function h4(text) {
179
- return headline(text, 4);
180
- }
181
- function h5(text) {
182
- return headline(text, 5);
183
- }
184
- function h6(text) {
185
- return headline(text, 6);
186
- }
192
+ // packages/models/src/lib/audit-output.ts
193
+ import { z as z5 } from "zod";
187
194
 
188
- // packages/utils/src/lib/text-formats/md/image.ts
189
- function image(src, alt) {
190
- return `![${alt}](${src})`;
191
- }
195
+ // packages/models/src/lib/issue.ts
196
+ import { z as z3 } from "zod";
197
+ var sourceFileLocationSchema = z3.object(
198
+ {
199
+ file: filePathSchema.describe("Relative path to source file in Git repo"),
200
+ position: z3.object(
201
+ {
202
+ startLine: positiveIntSchema.describe("Start line"),
203
+ startColumn: positiveIntSchema.describe("Start column").optional(),
204
+ endLine: positiveIntSchema.describe("End line").optional(),
205
+ endColumn: positiveIntSchema.describe("End column").optional()
206
+ },
207
+ { description: "Location in file" }
208
+ ).optional()
209
+ },
210
+ { description: "Source file location" }
211
+ );
212
+ var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
213
+ description: "Severity level"
214
+ });
215
+ var issueSchema = z3.object(
216
+ {
217
+ message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
218
+ severity: issueSeveritySchema,
219
+ source: sourceFileLocationSchema.optional()
220
+ },
221
+ { description: "Issue information" }
222
+ );
192
223
 
193
- // packages/utils/src/lib/text-formats/md/link.ts
194
- function link2(href, text) {
195
- return `[${text || href}](${href})`;
196
- }
224
+ // packages/models/src/lib/table.ts
225
+ import { z as z4 } from "zod";
226
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
227
+ description: "Cell alignment"
228
+ });
229
+ var tableColumnObjectSchema = z4.object({
230
+ key: z4.string(),
231
+ label: z4.string().optional(),
232
+ align: tableAlignmentSchema.optional()
233
+ });
234
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
235
+ description: "Object row"
236
+ });
237
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
238
+ description: "Primitive row"
239
+ });
240
+ var tableSharedSchema = z4.object({
241
+ title: z4.string().optional().describe("Display title for table")
242
+ });
243
+ var tablePrimitiveSchema = tableSharedSchema.merge(
244
+ z4.object(
245
+ {
246
+ columns: z4.array(tableAlignmentSchema).optional(),
247
+ rows: z4.array(tableRowPrimitiveSchema)
248
+ },
249
+ { description: "Table with primitive rows and optional alignment columns" }
250
+ )
251
+ );
252
+ var tableObjectSchema = tableSharedSchema.merge(
253
+ z4.object(
254
+ {
255
+ columns: z4.union([
256
+ z4.array(tableAlignmentSchema),
257
+ z4.array(tableColumnObjectSchema)
258
+ ]).optional(),
259
+ rows: z4.array(tableRowObjectSchema)
260
+ },
261
+ {
262
+ description: "Table with object rows and optional alignment or object columns"
263
+ }
264
+ )
265
+ );
266
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
197
267
 
198
- // packages/utils/src/lib/text-formats/md/list.ts
199
- function li(text, order = "unordered") {
200
- const style = order === "unordered" ? "-" : "- [ ]";
201
- return `${style} ${text}`;
268
+ // packages/models/src/lib/audit-output.ts
269
+ var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
270
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
271
+ var auditDetailsSchema = z5.object(
272
+ {
273
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
274
+ table: tableSchema("Table of related findings").optional()
275
+ },
276
+ { description: "Detailed information" }
277
+ );
278
+ var auditOutputSchema = z5.object(
279
+ {
280
+ slug: slugSchema.describe("Reference to audit"),
281
+ displayValue: auditDisplayValueSchema,
282
+ value: auditValueSchema,
283
+ score: scoreSchema,
284
+ details: auditDetailsSchema.optional()
285
+ },
286
+ { description: "Audit information" }
287
+ );
288
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
289
+ description: "List of JSON formatted audit output emitted by the runner process of a plugin"
290
+ }).refine(
291
+ (audits) => !getDuplicateSlugsInAudits2(audits),
292
+ (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
293
+ );
294
+ function duplicateSlugsInAuditsErrorMsg2(audits) {
295
+ const duplicateRefs = getDuplicateSlugsInAudits2(audits);
296
+ return `In plugin audits the slugs are not unique: ${errorItems(
297
+ duplicateRefs
298
+ )}`;
202
299
  }
203
- function indentation(text, level = 1) {
204
- return `${TAB.repeat(level)}${text}`;
300
+ function getDuplicateSlugsInAudits2(audits) {
301
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
205
302
  }
206
303
 
207
- // packages/utils/src/lib/text-formats/md/paragraphs.ts
208
- function paragraphs(...sections) {
209
- return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
304
+ // packages/models/src/lib/category-config.ts
305
+ import { z as z6 } from "zod";
306
+ var categoryRefSchema = weightedRefSchema(
307
+ "Weighted references to audits and/or groups for the category",
308
+ "Slug of an audit or group (depending on `type`)"
309
+ ).merge(
310
+ z6.object({
311
+ type: z6.enum(["audit", "group"], {
312
+ description: "Discriminant for reference kind, affects where `slug` is looked up"
313
+ }),
314
+ plugin: slugSchema.describe(
315
+ "Plugin slug (plugin should contain referenced audit or group)"
316
+ )
317
+ })
318
+ );
319
+ var categoryConfigSchema = scorableSchema(
320
+ "Category with a score calculated from audits and groups from various plugins",
321
+ categoryRefSchema,
322
+ getDuplicateRefsInCategoryMetrics,
323
+ duplicateRefsInCategoryMetricsErrorMsg
324
+ ).merge(
325
+ metaSchema({
326
+ titleDescription: "Category Title",
327
+ docsUrlDescription: "Category docs URL",
328
+ descriptionDescription: "Category description",
329
+ description: "Meta info for category"
330
+ })
331
+ ).merge(
332
+ z6.object({
333
+ isBinary: z6.boolean({
334
+ description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
335
+ }).optional()
336
+ })
337
+ );
338
+ function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
339
+ const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
340
+ return `In the categories, the following audit or group refs are duplicates: ${errorItems(
341
+ duplicateRefs
342
+ )}`;
210
343
  }
211
-
212
- // packages/utils/src/lib/text-formats/md/section.ts
213
- function section(...contents) {
214
- return `${lines(...contents)}${NEW_LINE}`;
344
+ function getDuplicateRefsInCategoryMetrics(metrics) {
345
+ return hasDuplicateStrings(
346
+ metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
347
+ );
215
348
  }
216
- function lines(...contents) {
217
- return `${contents.filter(Boolean).join(NEW_LINE)}`;
349
+ var categoriesSchema = z6.array(categoryConfigSchema, {
350
+ description: "Categorization of individual audits"
351
+ }).refine(
352
+ (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
353
+ (categoryCfg) => ({
354
+ message: duplicateSlugCategoriesErrorMsg(categoryCfg)
355
+ })
356
+ );
357
+ function duplicateSlugCategoriesErrorMsg(categories) {
358
+ const duplicateStringSlugs = getDuplicateSlugCategories(categories);
359
+ return `In the categories, the following slugs are duplicated: ${errorItems(
360
+ duplicateStringSlugs
361
+ )}`;
362
+ }
363
+ function getDuplicateSlugCategories(categories) {
364
+ return hasDuplicateStrings(categories.map(({ slug }) => slug));
218
365
  }
219
366
 
220
- // packages/utils/src/lib/text-formats/md/table.ts
221
- var alignString = /* @__PURE__ */ new Map([
222
- ["left", ":--"],
223
- ["center", ":--:"],
224
- ["right", "--:"]
225
- ]);
226
- function tableRow(rows) {
227
- return `|${rows.join("|")}|`;
228
- }
229
- function table2(data) {
230
- if (data.rows.length === 0) {
231
- throw new Error("Data can't be empty");
232
- }
233
- const alignmentRow = getColumnAlignments(data).map(
234
- (s) => alignString.get(s) ?? String(alignString.get("center"))
235
- );
236
- return section(
237
- `${lines(
238
- tableRow(columnsToStringArray(data)),
239
- tableRow(alignmentRow),
240
- ...rowToStringArray(data).map(tableRow)
241
- )}`
242
- );
243
- }
367
+ // packages/models/src/lib/commit.ts
368
+ import { z as z7 } from "zod";
369
+ var commitSchema = z7.object(
370
+ {
371
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
372
+ /^[\da-f]{40}$/,
373
+ "Commit SHA should be a 40-character hexadecimal string"
374
+ ),
375
+ message: z7.string({ description: "Commit message" }),
376
+ date: z7.coerce.date({
377
+ description: "Date and time when commit was authored"
378
+ }),
379
+ author: z7.string({
380
+ description: "Commit author name"
381
+ }).trim()
382
+ },
383
+ { description: "Git commit" }
384
+ );
244
385
 
245
- // packages/utils/src/lib/text-formats/index.ts
246
- var md = {
247
- bold: bold2,
248
- italic: italic2,
249
- strikeThrough,
250
- code: code2,
251
- link: link2,
252
- image,
253
- headline,
254
- h,
255
- h1,
256
- h2,
257
- h3,
258
- h4,
259
- h5,
260
- h6,
261
- indentation,
262
- lines,
263
- li,
264
- section,
265
- paragraphs,
266
- table: table2
267
- };
268
- var html = {
269
- bold,
270
- italic,
271
- code,
272
- link,
273
- details,
274
- table
275
- };
386
+ // packages/models/src/lib/core-config.ts
387
+ import { z as z13 } from "zod";
276
388
 
277
- // packages/models/src/lib/implementation/schemas.ts
278
- import { MATERIAL_ICONS } from "vscode-material-icons";
279
- import { z } from "zod";
389
+ // packages/models/src/lib/persist-config.ts
390
+ import { z as z8 } from "zod";
391
+ var formatSchema = z8.enum(["json", "md"]);
392
+ var persistConfigSchema = z8.object({
393
+ outputDir: filePathSchema.describe("Artifacts folder").optional(),
394
+ filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
395
+ format: z8.array(formatSchema).optional()
396
+ });
280
397
 
281
- // packages/models/src/lib/implementation/limits.ts
282
- var MAX_SLUG_LENGTH = 128;
283
- var MAX_TITLE_LENGTH = 256;
284
- var MAX_DESCRIPTION_LENGTH = 65536;
285
- var MAX_ISSUE_MESSAGE_LENGTH = 1024;
398
+ // packages/models/src/lib/plugin-config.ts
399
+ import { z as z11 } from "zod";
286
400
 
287
- // packages/models/src/lib/implementation/utils.ts
288
- var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
289
- var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
290
- function hasDuplicateStrings(strings) {
291
- const sortedStrings = [...strings].sort();
292
- const duplStrings = sortedStrings.filter(
293
- (item, index) => index !== 0 && item === sortedStrings[index - 1]
294
- );
295
- return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
401
+ // packages/models/src/lib/group.ts
402
+ import { z as z9 } from "zod";
403
+ var groupRefSchema = weightedRefSchema(
404
+ "Weighted reference to a group",
405
+ "Reference slug to a group within this plugin (e.g. 'max-lines')"
406
+ );
407
+ var groupMetaSchema = metaSchema({
408
+ titleDescription: "Descriptive name for the group",
409
+ descriptionDescription: "Description of the group (markdown)",
410
+ docsUrlDescription: "Group documentation site",
411
+ description: "Group metadata"
412
+ });
413
+ var groupSchema = scorableSchema(
414
+ 'A group aggregates a set of audits into a single score which can be referenced from a category. E.g. the group slug "performance" groups audits and can be referenced in a category',
415
+ groupRefSchema,
416
+ getDuplicateRefsInGroups,
417
+ duplicateRefsInGroupsErrorMsg
418
+ ).merge(groupMetaSchema);
419
+ var groupsSchema = z9.array(groupSchema, {
420
+ description: "List of groups"
421
+ }).optional().refine(
422
+ (groups) => !getDuplicateSlugsInGroups(groups),
423
+ (groups) => ({
424
+ message: duplicateSlugsInGroupsErrorMsg(groups)
425
+ })
426
+ );
427
+ function duplicateRefsInGroupsErrorMsg(groups) {
428
+ const duplicateRefs = getDuplicateRefsInGroups(groups);
429
+ return `In plugin groups the following references are not unique: ${errorItems(
430
+ duplicateRefs
431
+ )}`;
296
432
  }
297
- function hasMissingStrings(toCheck, existing) {
298
- const nonExisting = toCheck.filter((s) => !existing.includes(s));
299
- return nonExisting.length === 0 ? false : nonExisting;
433
+ function getDuplicateRefsInGroups(groups) {
434
+ return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
300
435
  }
301
- function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
302
- return transform(items || []);
436
+ function duplicateSlugsInGroupsErrorMsg(groups) {
437
+ const duplicateRefs = getDuplicateSlugsInGroups(groups);
438
+ return `In groups the following slugs are not unique: ${errorItems(
439
+ duplicateRefs
440
+ )}`;
303
441
  }
304
- function exists(value) {
305
- return value != null;
442
+ function getDuplicateSlugsInGroups(groups) {
443
+ return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
306
444
  }
307
- function getMissingRefsForCategories(categories, plugins) {
308
- if (categories.length === 0) {
309
- return false;
445
+
446
+ // packages/models/src/lib/runner-config.ts
447
+ import { z as z10 } from "zod";
448
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
449
+ var runnerConfigSchema = z10.object(
450
+ {
451
+ command: z10.string({
452
+ description: "Shell command to execute"
453
+ }),
454
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
455
+ outputFile: filePathSchema.describe("Output path"),
456
+ outputTransform: outputTransformSchema.optional()
457
+ },
458
+ {
459
+ description: "How to execute runner"
310
460
  }
311
- const auditRefsFromCategory = categories.flatMap(
312
- ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
313
- );
314
- const auditRefsFromPlugins = plugins.flatMap(
315
- ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
316
- );
317
- const missingAuditRefs = hasMissingStrings(
318
- auditRefsFromCategory,
319
- auditRefsFromPlugins
320
- );
321
- const groupRefsFromCategory = categories.flatMap(
322
- ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
323
- );
324
- const groupRefsFromPlugins = plugins.flatMap(
325
- ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
326
- );
327
- const missingGroupRefs = hasMissingStrings(
328
- groupRefsFromCategory,
329
- groupRefsFromPlugins
330
- );
331
- const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
332
- return missingRefs.length > 0 ? missingRefs : false;
333
- }
334
- function missingRefsForCategoriesErrorMsg(categories, plugins) {
335
- const missingRefs = getMissingRefsForCategories(categories, plugins);
336
- return `The following category references need to point to an audit or group: ${errorItems(
337
- missingRefs
338
- )}`;
339
- }
461
+ );
462
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
463
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
340
464
 
341
- // packages/models/src/lib/implementation/schemas.ts
342
- var primitiveValueSchema = z.union([z.string(), z.number()]);
343
- function executionMetaSchema(options = {
344
- descriptionDate: "Execution start date and time",
345
- descriptionDuration: "Execution duration in ms"
346
- }) {
347
- return z.object({
348
- date: z.string({ description: options.descriptionDate }),
349
- duration: z.number({ description: options.descriptionDuration })
350
- });
351
- }
352
- var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
353
- message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
354
- }).max(MAX_SLUG_LENGTH, {
355
- message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
465
+ // packages/models/src/lib/plugin-config.ts
466
+ var pluginMetaSchema = packageVersionSchema().merge(
467
+ metaSchema({
468
+ titleDescription: "Descriptive name",
469
+ descriptionDescription: "Description (markdown)",
470
+ docsUrlDescription: "Plugin documentation site",
471
+ description: "Plugin metadata"
472
+ })
473
+ ).merge(
474
+ z11.object({
475
+ slug: slugSchema.describe("Unique plugin slug within core config"),
476
+ icon: materialIconSchema
477
+ })
478
+ );
479
+ var pluginDataSchema = z11.object({
480
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
481
+ audits: pluginAuditsSchema,
482
+ groups: groupsSchema
356
483
  });
357
- var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
358
- var urlSchema = z.string().url();
359
- var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
360
- var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
361
- var scoreSchema = z.number({
362
- description: "Value between 0 and 1"
363
- }).min(0).max(1);
364
- function metaSchema(options) {
365
- const {
366
- descriptionDescription,
367
- titleDescription,
368
- docsUrlDescription,
369
- description
370
- } = options ?? {};
371
- return z.object(
372
- {
373
- title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
374
- description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
375
- docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
376
- },
377
- { description }
378
- );
379
- }
380
- var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
381
- var fileNameSchema = z.string().trim().regex(filenameRegex, {
382
- message: `The filename has to be valid`
383
- }).min(1, { message: "file name is invalid" });
384
- var positiveIntSchema = z.number().int().positive();
385
- var nonnegativeIntSchema = z.number().int().nonnegative();
386
- function packageVersionSchema(options) {
387
- const { versionDescription = "NPM version of the package", required } = options ?? {};
388
- const packageSchema = z.string({ description: "NPM package name" });
389
- const versionSchema = z.string({ description: versionDescription });
390
- return z.object(
391
- {
392
- packageName: required ? packageSchema : packageSchema.optional(),
393
- version: required ? versionSchema : versionSchema.optional()
394
- },
395
- { description: "NPM package name and version of a published package" }
396
- );
397
- }
398
- var weightSchema = nonnegativeIntSchema.describe(
399
- "Coefficient for the given score (use weight 0 if only for display)"
484
+ var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
485
+ (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
486
+ (pluginCfg) => ({
487
+ message: missingRefsFromGroupsErrorMsg(pluginCfg)
488
+ })
400
489
  );
401
- function weightedRefSchema(description, slugDescription) {
402
- return z.object(
403
- {
404
- slug: slugSchema.describe(slugDescription),
405
- weight: weightSchema.describe("Weight used to calculate score")
406
- },
407
- { description }
408
- );
490
+ function missingRefsFromGroupsErrorMsg(pluginCfg) {
491
+ const missingRefs = getMissingRefsFromGroups(pluginCfg);
492
+ return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
493
+ missingRefs
494
+ )}`;
409
495
  }
410
- function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
411
- return z.object(
412
- {
413
- slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
414
- refs: z.array(refSchema).min(1).refine(
415
- (refs) => !duplicateCheckFn(refs),
416
- (refs) => ({
417
- message: duplicateMessageFn(refs)
418
- })
419
- ).refine(hasNonZeroWeightedRef, () => ({
420
- message: "In a category there has to be at least one ref with weight > 0"
421
- }))
422
- },
423
- { description }
496
+ function getMissingRefsFromGroups(pluginCfg) {
497
+ return hasMissingStrings(
498
+ pluginCfg.groups?.flatMap(
499
+ ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
500
+ ) ?? [],
501
+ pluginCfg.audits.map(({ slug }) => slug)
424
502
  );
425
503
  }
426
- var materialIconSchema = z.enum(MATERIAL_ICONS, {
427
- description: "Icon from VSCode Material Icons extension"
504
+
505
+ // packages/models/src/lib/upload-config.ts
506
+ import { z as z12 } from "zod";
507
+ var uploadConfigSchema = z12.object({
508
+ server: urlSchema.describe("URL of deployed portal API"),
509
+ apiKey: z12.string({
510
+ description: "API key with write access to portal (use `process.env` for security)"
511
+ }),
512
+ organization: slugSchema.describe(
513
+ "Organization slug from Code PushUp portal"
514
+ ),
515
+ project: slugSchema.describe("Project slug from Code PushUp portal"),
516
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
517
+ });
518
+
519
+ // packages/models/src/lib/core-config.ts
520
+ var unrefinedCoreConfigSchema = z13.object({
521
+ plugins: z13.array(pluginConfigSchema, {
522
+ description: "List of plugins to be used (official, community-provided, or custom)"
523
+ }).min(1),
524
+ /** portal configuration for persisting results */
525
+ persist: persistConfigSchema.optional(),
526
+ /** portal configuration for uploading results */
527
+ upload: uploadConfigSchema.optional(),
528
+ categories: categoriesSchema.optional()
428
529
  });
429
- function hasNonZeroWeightedRef(refs) {
430
- return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
530
+ var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
531
+ function refineCoreConfig(schema) {
532
+ return schema.refine(
533
+ (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
534
+ (coreCfg) => ({
535
+ message: missingRefsForCategoriesErrorMsg(
536
+ coreCfg.categories ?? [],
537
+ coreCfg.plugins
538
+ )
539
+ })
540
+ );
431
541
  }
432
542
 
433
- // packages/models/src/lib/audit.ts
434
- import { z as z2 } from "zod";
435
- var auditSchema = z2.object({
436
- slug: slugSchema.describe("ID (unique within plugin)")
437
- }).merge(
438
- metaSchema({
439
- titleDescription: "Descriptive name",
440
- descriptionDescription: "Description (markdown)",
441
- docsUrlDescription: "Link to documentation (rationale)",
442
- description: "List of scorable metrics for the given plugin"
543
+ // packages/models/src/lib/report.ts
544
+ import { z as z14 } from "zod";
545
+ var auditReportSchema = auditSchema.merge(auditOutputSchema);
546
+ var pluginReportSchema = pluginMetaSchema.merge(
547
+ executionMetaSchema({
548
+ descriptionDate: "Start date and time of plugin run",
549
+ descriptionDuration: "Duration of the plugin run in ms"
443
550
  })
444
- );
445
- var pluginAuditsSchema = z2.array(auditSchema, {
446
- description: "List of audits maintained in a plugin"
447
- }).min(1).refine(
448
- (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
449
- (auditMetadata) => ({
450
- message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
551
+ ).merge(
552
+ z14.object({
553
+ audits: z14.array(auditReportSchema).min(1),
554
+ groups: z14.array(groupSchema).optional()
555
+ })
556
+ ).refine(
557
+ (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
558
+ (pluginReport) => ({
559
+ message: missingRefsFromGroupsErrorMsg2(
560
+ pluginReport.audits,
561
+ pluginReport.groups ?? []
562
+ )
451
563
  })
452
564
  );
453
- function duplicateSlugsInAuditsErrorMsg(audits) {
454
- const duplicateRefs = getDuplicateSlugsInAudits(audits);
455
- return `In plugin audits the following slugs are not unique: ${errorItems(
456
- duplicateRefs
565
+ function missingRefsFromGroupsErrorMsg2(audits, groups) {
566
+ const missingRefs = getMissingRefsFromGroups2(audits, groups);
567
+ return `group references need to point to an existing audit in this plugin report: ${errorItems(
568
+ missingRefs
457
569
  )}`;
458
570
  }
459
- function getDuplicateSlugsInAudits(audits) {
460
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
571
+ function getMissingRefsFromGroups2(audits, groups) {
572
+ return hasMissingStrings(
573
+ groups.flatMap(
574
+ ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
575
+ ),
576
+ audits.map(({ slug }) => slug)
577
+ );
461
578
  }
462
-
463
- // packages/models/src/lib/audit-output.ts
464
- import { z as z5 } from "zod";
465
-
466
- // packages/models/src/lib/issue.ts
467
- import { z as z3 } from "zod";
468
- var sourceFileLocationSchema = z3.object(
469
- {
470
- file: filePathSchema.describe("Relative path to source file in Git repo"),
471
- position: z3.object(
472
- {
473
- startLine: positiveIntSchema.describe("Start line"),
474
- startColumn: positiveIntSchema.describe("Start column").optional(),
475
- endLine: positiveIntSchema.describe("End line").optional(),
476
- endColumn: positiveIntSchema.describe("End column").optional()
477
- },
478
- { description: "Location in file" }
479
- ).optional()
480
- },
481
- { description: "Source file location" }
482
- );
483
- var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
484
- description: "Severity level"
485
- });
486
- var issueSchema = z3.object(
487
- {
488
- message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
489
- severity: issueSeveritySchema,
490
- source: sourceFileLocationSchema.optional()
491
- },
492
- { description: "Issue information" }
493
- );
494
-
495
- // packages/models/src/lib/table.ts
496
- import { z as z4 } from "zod";
497
- var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
498
- description: "Cell alignment"
499
- });
500
- var tableColumnObjectSchema = z4.object({
501
- key: z4.string(),
502
- label: z4.string().optional(),
503
- align: tableAlignmentSchema.optional()
504
- });
505
- var tableRowObjectSchema = z4.record(primitiveValueSchema, {
506
- description: "Object row"
507
- });
508
- var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
509
- description: "Primitive row"
510
- });
511
- var tableSharedSchema = z4.object({
512
- title: z4.string().optional().describe("Display title for table")
513
- });
514
- var tablePrimitiveSchema = tableSharedSchema.merge(
515
- z4.object(
579
+ var reportSchema = packageVersionSchema({
580
+ versionDescription: "NPM version of the CLI",
581
+ required: true
582
+ }).merge(
583
+ executionMetaSchema({
584
+ descriptionDate: "Start date and time of the collect run",
585
+ descriptionDuration: "Duration of the collect run in ms"
586
+ })
587
+ ).merge(
588
+ z14.object(
516
589
  {
517
- columns: z4.array(tableAlignmentSchema).optional(),
518
- rows: z4.array(tableRowPrimitiveSchema)
590
+ categories: z14.array(categoryConfigSchema),
591
+ plugins: z14.array(pluginReportSchema).min(1),
592
+ commit: commitSchema.describe("Git commit for which report was collected").nullable()
519
593
  },
520
- { description: "Table with primitive rows and optional alignment columns" }
594
+ { description: "Collect output data" }
521
595
  )
596
+ ).refine(
597
+ (report) => !getMissingRefsForCategories(report.categories, report.plugins),
598
+ (report) => ({
599
+ message: missingRefsForCategoriesErrorMsg(
600
+ report.categories,
601
+ report.plugins
602
+ )
603
+ })
522
604
  );
523
- var tableObjectSchema = tableSharedSchema.merge(
524
- z4.object(
605
+
606
+ // packages/models/src/lib/reports-diff.ts
607
+ import { z as z15 } from "zod";
608
+ function makeComparisonSchema(schema) {
609
+ const sharedDescription = schema.description || "Result";
610
+ return z15.object({
611
+ before: schema.describe(`${sharedDescription} (source commit)`),
612
+ after: schema.describe(`${sharedDescription} (target commit)`)
613
+ });
614
+ }
615
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
616
+ return z15.object(
525
617
  {
526
- columns: z4.union([
527
- z4.array(tableAlignmentSchema),
528
- z4.array(tableColumnObjectSchema)
529
- ]).optional(),
530
- rows: z4.array(tableRowObjectSchema)
618
+ changed: z15.array(diffSchema),
619
+ unchanged: z15.array(resultSchema),
620
+ added: z15.array(resultSchema),
621
+ removed: z15.array(resultSchema)
531
622
  },
532
- {
533
- description: "Table with object rows and optional alignment or object columns"
534
- }
535
- )
536
- );
537
- var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
538
-
539
- // packages/models/src/lib/audit-output.ts
540
- var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
541
- var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
542
- var auditDetailsSchema = z5.object(
543
- {
544
- issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
545
- table: tableSchema("Table of related findings").optional()
546
- },
547
- { description: "Detailed information" }
623
+ { description }
624
+ );
625
+ }
626
+ var scorableMetaSchema = z15.object({
627
+ slug: slugSchema,
628
+ title: titleSchema,
629
+ docsUrl: docsUrlSchema
630
+ });
631
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
632
+ z15.object({
633
+ plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
634
+ })
548
635
  );
549
- var auditOutputSchema = z5.object(
550
- {
551
- slug: slugSchema.describe("Reference to audit"),
552
- displayValue: auditDisplayValueSchema,
553
- value: auditValueSchema,
554
- score: scoreSchema,
555
- details: auditDetailsSchema.optional()
556
- },
557
- { description: "Audit information" }
636
+ var scorableDiffSchema = scorableMetaSchema.merge(
637
+ z15.object({
638
+ scores: makeComparisonSchema(scoreSchema).merge(
639
+ z15.object({
640
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
641
+ })
642
+ ).describe("Score comparison")
643
+ })
558
644
  );
559
- var auditOutputsSchema = z5.array(auditOutputSchema, {
560
- description: "List of JSON formatted audit output emitted by the runner process of a plugin"
561
- }).refine(
562
- (audits) => !getDuplicateSlugsInAudits2(audits),
563
- (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
645
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
646
+ scorableWithPluginMetaSchema
564
647
  );
565
- function duplicateSlugsInAuditsErrorMsg2(audits) {
566
- const duplicateRefs = getDuplicateSlugsInAudits2(audits);
567
- return `In plugin audits the slugs are not unique: ${errorItems(
568
- duplicateRefs
569
- )}`;
570
- }
571
- function getDuplicateSlugsInAudits2(audits) {
572
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
573
- }
574
-
575
- // packages/models/src/lib/category-config.ts
576
- import { z as z6 } from "zod";
577
- var categoryRefSchema = weightedRefSchema(
578
- "Weighted references to audits and/or groups for the category",
579
- "Slug of an audit or group (depending on `type`)"
580
- ).merge(
581
- z6.object({
582
- type: z6.enum(["audit", "group"], {
583
- description: "Discriminant for reference kind, affects where `slug` is looked up"
584
- }),
585
- plugin: slugSchema.describe(
586
- "Plugin slug (plugin should contain referenced audit or group)"
648
+ var categoryDiffSchema = scorableDiffSchema;
649
+ var groupDiffSchema = scorableWithPluginDiffSchema;
650
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
651
+ z15.object({
652
+ values: makeComparisonSchema(auditValueSchema).merge(
653
+ z15.object({
654
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
655
+ })
656
+ ).describe("Audit `value` comparison"),
657
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
658
+ "Audit `displayValue` comparison"
587
659
  )
588
660
  })
589
661
  );
590
- var categoryConfigSchema = scorableSchema(
591
- "Category with a score calculated from audits and groups from various plugins",
592
- categoryRefSchema,
593
- getDuplicateRefsInCategoryMetrics,
594
- duplicateRefsInCategoryMetricsErrorMsg
595
- ).merge(
596
- metaSchema({
597
- titleDescription: "Category Title",
598
- docsUrlDescription: "Category docs URL",
599
- descriptionDescription: "Category description",
600
- description: "Meta info for category"
662
+ var categoryResultSchema = scorableMetaSchema.merge(
663
+ z15.object({ score: scoreSchema })
664
+ );
665
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
666
+ z15.object({ score: scoreSchema })
667
+ );
668
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
669
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
670
+ );
671
+ var reportsDiffSchema = z15.object({
672
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
673
+ categories: makeArraysComparisonSchema(
674
+ categoryDiffSchema,
675
+ categoryResultSchema,
676
+ "Changes affecting categories"
677
+ ),
678
+ groups: makeArraysComparisonSchema(
679
+ groupDiffSchema,
680
+ groupResultSchema,
681
+ "Changes affecting groups"
682
+ ),
683
+ audits: makeArraysComparisonSchema(
684
+ auditDiffSchema,
685
+ auditResultSchema,
686
+ "Changes affecting audits"
687
+ )
688
+ }).merge(
689
+ packageVersionSchema({
690
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
691
+ required: true
601
692
  })
602
693
  ).merge(
603
- z6.object({
604
- isBinary: z6.boolean({
605
- description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
606
- }).optional()
694
+ executionMetaSchema({
695
+ descriptionDate: "Start date and time of the compare run",
696
+ descriptionDuration: "Duration of the compare run in ms"
607
697
  })
608
698
  );
609
- function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
610
- const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
611
- return `In the categories, the following audit or group refs are duplicates: ${errorItems(
612
- duplicateRefs
613
- )}`;
699
+
700
+ // packages/utils/src/lib/file-system.ts
701
+ import { bundleRequire } from "bundle-require";
702
+ import chalk2 from "chalk";
703
+ import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
704
+ import { join } from "node:path";
705
+
706
+ // packages/utils/src/lib/logging.ts
707
+ import isaacs_cliui from "@isaacs/cliui";
708
+ import { cliui } from "@poppinss/cliui";
709
+ import chalk from "chalk";
710
+
711
+ // packages/utils/src/lib/reports/constants.ts
712
+ var TERMINAL_WIDTH = 80;
713
+
714
+ // packages/utils/src/lib/logging.ts
715
+ var singletonUiInstance;
716
+ function ui() {
717
+ if (singletonUiInstance === void 0) {
718
+ singletonUiInstance = cliui();
719
+ }
720
+ return {
721
+ ...singletonUiInstance,
722
+ row: (args) => {
723
+ logListItem(args);
724
+ }
725
+ };
614
726
  }
615
- function getDuplicateRefsInCategoryMetrics(metrics) {
616
- return hasDuplicateStrings(
617
- metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
618
- );
727
+ var singletonisaacUi;
728
+ function logListItem(args) {
729
+ if (singletonisaacUi === void 0) {
730
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
731
+ }
732
+ singletonisaacUi.div(...args);
733
+ const content = singletonisaacUi.toString();
734
+ singletonisaacUi.rows = [];
735
+ singletonUiInstance?.logger.log(content);
619
736
  }
620
- var categoriesSchema = z6.array(categoryConfigSchema, {
621
- description: "Categorization of individual audits"
622
- }).refine(
623
- (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
624
- (categoryCfg) => ({
625
- message: duplicateSlugCategoriesErrorMsg(categoryCfg)
626
- })
627
- );
628
- function duplicateSlugCategoriesErrorMsg(categories) {
629
- const duplicateStringSlugs = getDuplicateSlugCategories(categories);
630
- return `In the categories, the following slugs are duplicated: ${errorItems(
631
- duplicateStringSlugs
632
- )}`;
737
+
738
+ // packages/utils/src/lib/file-system.ts
739
+ async function ensureDirectoryExists(baseDir) {
740
+ try {
741
+ await mkdir(baseDir, { recursive: true });
742
+ return;
743
+ } catch (error) {
744
+ ui().logger.info(error.message);
745
+ if (error.code !== "EEXIST") {
746
+ throw error;
747
+ }
748
+ }
633
749
  }
634
- function getDuplicateSlugCategories(categories) {
635
- return hasDuplicateStrings(categories.map(({ slug }) => slug));
750
+ var NoExportError = class extends Error {
751
+ constructor(filepath) {
752
+ super(`No default export found in ${filepath}`);
753
+ }
754
+ };
755
+ async function importEsmModule(options) {
756
+ const { mod } = await bundleRequire({
757
+ format: "esm",
758
+ ...options
759
+ });
760
+ if (!("default" in mod)) {
761
+ throw new NoExportError(options.filepath);
762
+ }
763
+ return mod.default;
764
+ }
765
+ function pluginWorkDir(slug) {
766
+ return join("node_modules", ".code-pushup", slug);
767
+ }
768
+ function filePathToCliArg(path) {
769
+ return `"${path}"`;
636
770
  }
637
771
 
638
- // packages/models/src/lib/commit.ts
639
- import { z as z7 } from "zod";
640
- var commitSchema = z7.object(
641
- {
642
- hash: z7.string({ description: "Commit SHA (full)" }).regex(
643
- /^[\da-f]{40}$/,
644
- "Commit SHA should be a 40-character hexadecimal string"
645
- ),
646
- message: z7.string({ description: "Commit message" }),
647
- date: z7.coerce.date({
648
- description: "Date and time when commit was authored"
649
- }),
650
- author: z7.string({
651
- description: "Commit author name"
652
- }).trim()
653
- },
654
- { description: "Git commit" }
655
- );
772
+ // packages/utils/src/lib/text-formats/constants.ts
773
+ var NEW_LINE = "\n";
774
+ var TAB = " ";
656
775
 
657
- // packages/models/src/lib/core-config.ts
658
- import { z as z13 } from "zod";
776
+ // packages/utils/src/lib/text-formats/html/details.ts
777
+ function details(title, content, cfg = { open: false }) {
778
+ return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
779
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
780
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
781
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
782
+ NEW_LINE}`;
783
+ }
659
784
 
660
- // packages/models/src/lib/persist-config.ts
661
- import { z as z8 } from "zod";
662
- var formatSchema = z8.enum(["json", "md"]);
663
- var persistConfigSchema = z8.object({
664
- outputDir: filePathSchema.describe("Artifacts folder").optional(),
665
- filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
666
- format: z8.array(formatSchema).optional()
667
- });
785
+ // packages/utils/src/lib/text-formats/html/font-style.ts
786
+ var boldElement = "b";
787
+ function bold(text) {
788
+ return `<${boldElement}>${text}</${boldElement}>`;
789
+ }
790
+ var italicElement = "i";
791
+ function italic(text) {
792
+ return `<${italicElement}>${text}</${italicElement}>`;
793
+ }
794
+ var codeElement = "code";
795
+ function code(text) {
796
+ return `<${codeElement}>${text}</${codeElement}>`;
797
+ }
668
798
 
669
- // packages/models/src/lib/plugin-config.ts
670
- import { z as z11 } from "zod";
799
+ // packages/utils/src/lib/text-formats/html/link.ts
800
+ function link(href, text) {
801
+ return `<a href="${href}">${text || href}"</a>`;
802
+ }
671
803
 
672
- // packages/models/src/lib/group.ts
673
- import { z as z9 } from "zod";
674
- var groupRefSchema = weightedRefSchema(
675
- "Weighted reference to a group",
676
- "Reference slug to a group within this plugin (e.g. 'max-lines')"
677
- );
678
- var groupMetaSchema = metaSchema({
679
- titleDescription: "Descriptive name for the group",
680
- descriptionDescription: "Description of the group (markdown)",
681
- docsUrlDescription: "Group documentation site",
682
- description: "Group metadata"
683
- });
684
- var groupSchema = scorableSchema(
685
- 'A group aggregates a set of audits into a single score which can be referenced from a category. E.g. the group slug "performance" groups audits and can be referenced in a category',
686
- groupRefSchema,
687
- getDuplicateRefsInGroups,
688
- duplicateRefsInGroupsErrorMsg
689
- ).merge(groupMetaSchema);
690
- var groupsSchema = z9.array(groupSchema, {
691
- description: "List of groups"
692
- }).optional().refine(
693
- (groups) => !getDuplicateSlugsInGroups(groups),
694
- (groups) => ({
695
- message: duplicateSlugsInGroupsErrorMsg(groups)
696
- })
697
- );
698
- function duplicateRefsInGroupsErrorMsg(groups) {
699
- const duplicateRefs = getDuplicateRefsInGroups(groups);
700
- return `In plugin groups the following references are not unique: ${errorItems(
701
- duplicateRefs
804
+ // packages/utils/src/lib/transform.ts
805
+ function capitalize(text) {
806
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
807
+ 1
702
808
  )}`;
703
809
  }
704
- function getDuplicateRefsInGroups(groups) {
705
- return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
810
+
811
+ // packages/utils/src/lib/table.ts
812
+ function rowToStringArray({ rows, columns = [] }) {
813
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
814
+ throw new TypeError(
815
+ "Column can`t be object when rows are primitive values"
816
+ );
817
+ }
818
+ return rows.map((row) => {
819
+ if (Array.isArray(row)) {
820
+ return row.map(String);
821
+ }
822
+ const objectRow = row;
823
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
824
+ return Object.values(objectRow).map(String);
825
+ }
826
+ return columns.map(
827
+ ({ key }) => String(objectRow[key])
828
+ );
829
+ });
830
+ }
831
+ function columnsToStringArray({ rows, columns = [] }) {
832
+ const firstRow = rows.at(0);
833
+ const primitiveRows = Array.isArray(firstRow);
834
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
835
+ throw new Error("invalid union type. Caught by model parsing.");
836
+ }
837
+ if (columns.length === 0) {
838
+ if (Array.isArray(firstRow)) {
839
+ return firstRow.map((_, idx) => String(idx));
840
+ }
841
+ return Object.keys(firstRow);
842
+ }
843
+ if (typeof columns.at(0) === "string") {
844
+ return columns.map(String);
845
+ }
846
+ const cols = columns;
847
+ return cols.map(({ label, key }) => label ?? capitalize(key));
848
+ }
849
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
850
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
851
+ if (typeof column === "string") {
852
+ return column;
853
+ } else if (typeof column === "object") {
854
+ return column.align ?? "center";
855
+ } else {
856
+ return "center";
857
+ }
706
858
  }
707
- function duplicateSlugsInGroupsErrorMsg(groups) {
708
- const duplicateRefs = getDuplicateSlugsInGroups(groups);
709
- return `In groups the following slugs are not unique: ${errorItems(
710
- duplicateRefs
711
- )}`;
859
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
860
+ const column = columns.at(targetIdx);
861
+ if (column == null) {
862
+ return "center";
863
+ } else if (typeof column === "string") {
864
+ return column;
865
+ } else if (typeof column === "object") {
866
+ return column.align ?? "center";
867
+ } else {
868
+ return "center";
869
+ }
712
870
  }
713
- function getDuplicateSlugsInGroups(groups) {
714
- return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
871
+ function getColumnAlignments({
872
+ rows,
873
+ columns = []
874
+ }) {
875
+ if (rows.at(0) == null) {
876
+ throw new Error("first row can`t be undefined.");
877
+ }
878
+ if (Array.isArray(rows.at(0))) {
879
+ const firstPrimitiveRow = rows.at(0);
880
+ return Array.from({ length: firstPrimitiveRow.length }).map(
881
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
882
+ );
883
+ }
884
+ const firstObject = rows.at(0);
885
+ return Object.keys(firstObject).map(
886
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
887
+ );
715
888
  }
716
889
 
717
- // packages/models/src/lib/runner-config.ts
718
- import { z as z10 } from "zod";
719
- var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
720
- var runnerConfigSchema = z10.object(
721
- {
722
- command: z10.string({
723
- description: "Shell command to execute"
724
- }),
725
- args: z10.array(z10.string({ description: "Command arguments" })).optional(),
726
- outputFile: filePathSchema.describe("Output path"),
727
- outputTransform: outputTransformSchema.optional()
728
- },
729
- {
730
- description: "How to execute runner"
890
+ // packages/utils/src/lib/text-formats/html/table.ts
891
+ function wrap(elem, content) {
892
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
893
+ }
894
+ function wrapRow(content) {
895
+ const elem = "tr";
896
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
897
+ }
898
+ function table(tableData) {
899
+ if (tableData.rows.length === 0) {
900
+ throw new Error("Data can't be empty");
731
901
  }
732
- );
733
- var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
734
- var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
902
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
903
+ const tableHeaderRow = wrapRow(tableHeaderCols);
904
+ const tableBody = rowToStringArray(tableData).map((arr) => {
905
+ const columns = arr.map((s) => wrap("td", s)).join("");
906
+ return wrapRow(columns);
907
+ }).join("");
908
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
909
+ }
735
910
 
736
- // packages/models/src/lib/plugin-config.ts
737
- var pluginMetaSchema = packageVersionSchema().merge(
738
- metaSchema({
739
- titleDescription: "Descriptive name",
740
- descriptionDescription: "Description (markdown)",
741
- docsUrlDescription: "Plugin documentation site",
742
- description: "Plugin metadata"
743
- })
744
- ).merge(
745
- z11.object({
746
- slug: slugSchema.describe("Unique plugin slug within core config"),
747
- icon: materialIconSchema
748
- })
749
- );
750
- var pluginDataSchema = z11.object({
751
- runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
752
- audits: pluginAuditsSchema,
753
- groups: groupsSchema
754
- });
755
- var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
756
- (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
757
- (pluginCfg) => ({
758
- message: missingRefsFromGroupsErrorMsg(pluginCfg)
759
- })
760
- );
761
- function missingRefsFromGroupsErrorMsg(pluginCfg) {
762
- const missingRefs = getMissingRefsFromGroups(pluginCfg);
763
- return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
764
- missingRefs
765
- )}`;
911
+ // packages/utils/src/lib/text-formats/md/font-style.ts
912
+ var boldWrap = "**";
913
+ function bold2(text) {
914
+ return `${boldWrap}${text}${boldWrap}`;
766
915
  }
767
- function getMissingRefsFromGroups(pluginCfg) {
768
- return hasMissingStrings(
769
- pluginCfg.groups?.flatMap(
770
- ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
771
- ) ?? [],
772
- pluginCfg.audits.map(({ slug }) => slug)
773
- );
916
+ var italicWrap = "_";
917
+ function italic2(text) {
918
+ return `${italicWrap}${text}${italicWrap}`;
774
919
  }
775
-
776
- // packages/models/src/lib/upload-config.ts
777
- import { z as z12 } from "zod";
778
- var uploadConfigSchema = z12.object({
779
- server: urlSchema.describe("URL of deployed portal API"),
780
- apiKey: z12.string({
781
- description: "API key with write access to portal (use `process.env` for security)"
782
- }),
783
- organization: slugSchema.describe(
784
- "Organization slug from Code PushUp portal"
785
- ),
786
- project: slugSchema.describe("Project slug from Code PushUp portal"),
787
- timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
788
- });
789
-
790
- // packages/models/src/lib/core-config.ts
791
- var unrefinedCoreConfigSchema = z13.object({
792
- plugins: z13.array(pluginConfigSchema, {
793
- description: "List of plugins to be used (official, community-provided, or custom)"
794
- }).min(1),
795
- /** portal configuration for persisting results */
796
- persist: persistConfigSchema.optional(),
797
- /** portal configuration for uploading results */
798
- upload: uploadConfigSchema.optional(),
799
- categories: categoriesSchema.optional()
800
- });
801
- var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
802
- function refineCoreConfig(schema) {
803
- return schema.refine(
804
- (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
805
- (coreCfg) => ({
806
- message: missingRefsForCategoriesErrorMsg(
807
- coreCfg.categories ?? [],
808
- coreCfg.plugins
809
- )
810
- })
811
- );
920
+ var strikeThroughWrap = "~";
921
+ function strikeThrough(text) {
922
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
923
+ }
924
+ var codeWrap = "`";
925
+ function code2(text) {
926
+ return `${codeWrap}${text}${codeWrap}`;
812
927
  }
813
928
 
814
- // packages/models/src/lib/report.ts
815
- import { z as z14 } from "zod";
816
- var auditReportSchema = auditSchema.merge(auditOutputSchema);
817
- var pluginReportSchema = pluginMetaSchema.merge(
818
- executionMetaSchema({
819
- descriptionDate: "Start date and time of plugin run",
820
- descriptionDuration: "Duration of the plugin run in ms"
821
- })
822
- ).merge(
823
- z14.object({
824
- audits: z14.array(auditReportSchema).min(1),
825
- groups: z14.array(groupSchema).optional()
826
- })
827
- ).refine(
828
- (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
829
- (pluginReport) => ({
830
- message: missingRefsFromGroupsErrorMsg2(
831
- pluginReport.audits,
832
- pluginReport.groups ?? []
833
- )
834
- })
835
- );
836
- function missingRefsFromGroupsErrorMsg2(audits, groups) {
837
- const missingRefs = getMissingRefsFromGroups2(audits, groups);
838
- return `group references need to point to an existing audit in this plugin report: ${errorItems(
839
- missingRefs
840
- )}`;
929
+ // packages/utils/src/lib/text-formats/md/headline.ts
930
+ function headline(text, hierarchy = 1) {
931
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
841
932
  }
842
- function getMissingRefsFromGroups2(audits, groups) {
843
- return hasMissingStrings(
844
- groups.flatMap(
845
- ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
846
- ),
847
- audits.map(({ slug }) => slug)
848
- );
933
+ function h(text, hierarchy = 1) {
934
+ return headline(text, hierarchy);
849
935
  }
850
- var reportSchema = packageVersionSchema({
851
- versionDescription: "NPM version of the CLI",
852
- required: true
853
- }).merge(
854
- executionMetaSchema({
855
- descriptionDate: "Start date and time of the collect run",
856
- descriptionDuration: "Duration of the collect run in ms"
857
- })
858
- ).merge(
859
- z14.object(
860
- {
861
- categories: z14.array(categoryConfigSchema),
862
- plugins: z14.array(pluginReportSchema).min(1),
863
- commit: commitSchema.describe("Git commit for which report was collected").nullable()
864
- },
865
- { description: "Collect output data" }
866
- )
867
- ).refine(
868
- (report) => !getMissingRefsForCategories(report.categories, report.plugins),
869
- (report) => ({
870
- message: missingRefsForCategoriesErrorMsg(
871
- report.categories,
872
- report.plugins
873
- )
874
- })
875
- );
876
-
877
- // packages/models/src/lib/reports-diff.ts
878
- import { z as z15 } from "zod";
879
- function makeComparisonSchema(schema) {
880
- const sharedDescription = schema.description || "Result";
881
- return z15.object({
882
- before: schema.describe(`${sharedDescription} (source commit)`),
883
- after: schema.describe(`${sharedDescription} (target commit)`)
884
- });
936
+ function h1(text) {
937
+ return headline(text, 1);
938
+ }
939
+ function h2(text) {
940
+ return headline(text, 2);
885
941
  }
886
- function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
887
- return z15.object(
888
- {
889
- changed: z15.array(diffSchema),
890
- unchanged: z15.array(resultSchema),
891
- added: z15.array(resultSchema),
892
- removed: z15.array(resultSchema)
893
- },
894
- { description }
895
- );
942
+ function h3(text) {
943
+ return headline(text, 3);
944
+ }
945
+ function h4(text) {
946
+ return headline(text, 4);
947
+ }
948
+ function h5(text) {
949
+ return headline(text, 5);
950
+ }
951
+ function h6(text) {
952
+ return headline(text, 6);
896
953
  }
897
- var scorableMetaSchema = z15.object({
898
- slug: slugSchema,
899
- title: titleSchema,
900
- docsUrl: docsUrlSchema
901
- });
902
- var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
903
- z15.object({
904
- plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
905
- })
906
- );
907
- var scorableDiffSchema = scorableMetaSchema.merge(
908
- z15.object({
909
- scores: makeComparisonSchema(scoreSchema).merge(
910
- z15.object({
911
- diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
912
- })
913
- ).describe("Score comparison")
914
- })
915
- );
916
- var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
917
- scorableWithPluginMetaSchema
918
- );
919
- var categoryDiffSchema = scorableDiffSchema;
920
- var groupDiffSchema = scorableWithPluginDiffSchema;
921
- var auditDiffSchema = scorableWithPluginDiffSchema.merge(
922
- z15.object({
923
- values: makeComparisonSchema(auditValueSchema).merge(
924
- z15.object({
925
- diff: z15.number().int().describe("Value change (`values.after - values.before`)")
926
- })
927
- ).describe("Audit `value` comparison"),
928
- displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
929
- "Audit `displayValue` comparison"
930
- )
931
- })
932
- );
933
- var categoryResultSchema = scorableMetaSchema.merge(
934
- z15.object({ score: scoreSchema })
935
- );
936
- var groupResultSchema = scorableWithPluginMetaSchema.merge(
937
- z15.object({ score: scoreSchema })
938
- );
939
- var auditResultSchema = scorableWithPluginMetaSchema.merge(
940
- auditOutputSchema.pick({ score: true, value: true, displayValue: true })
941
- );
942
- var reportsDiffSchema = z15.object({
943
- commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
944
- categories: makeArraysComparisonSchema(
945
- categoryDiffSchema,
946
- categoryResultSchema,
947
- "Changes affecting categories"
948
- ),
949
- groups: makeArraysComparisonSchema(
950
- groupDiffSchema,
951
- groupResultSchema,
952
- "Changes affecting groups"
953
- ),
954
- audits: makeArraysComparisonSchema(
955
- auditDiffSchema,
956
- auditResultSchema,
957
- "Changes affecting audits"
958
- )
959
- }).merge(
960
- packageVersionSchema({
961
- versionDescription: "NPM version of the CLI (when `compare` was run)",
962
- required: true
963
- })
964
- ).merge(
965
- executionMetaSchema({
966
- descriptionDate: "Start date and time of the compare run",
967
- descriptionDuration: "Duration of the compare run in ms"
968
- })
969
- );
970
954
 
971
- // packages/utils/src/lib/file-system.ts
972
- import { bundleRequire } from "bundle-require";
973
- import chalk2 from "chalk";
974
- import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
975
- import { join } from "node:path";
955
+ // packages/utils/src/lib/text-formats/md/image.ts
956
+ function image(src, alt) {
957
+ return `![${alt}](${src})`;
958
+ }
976
959
 
977
- // packages/utils/src/lib/logging.ts
978
- import isaacs_cliui from "@isaacs/cliui";
979
- import { cliui } from "@poppinss/cliui";
980
- import chalk from "chalk";
960
+ // packages/utils/src/lib/text-formats/md/link.ts
961
+ function link2(href, text) {
962
+ return `[${text || href}](${href})`;
963
+ }
981
964
 
982
- // packages/utils/src/lib/reports/constants.ts
983
- var TERMINAL_WIDTH = 80;
965
+ // packages/utils/src/lib/text-formats/md/list.ts
966
+ function li(text, order = "unordered") {
967
+ const style = order === "unordered" ? "-" : "- [ ]";
968
+ return `${style} ${text}`;
969
+ }
970
+ function indentation(text, level = 1) {
971
+ return `${TAB.repeat(level)}${text}`;
972
+ }
984
973
 
985
- // packages/utils/src/lib/logging.ts
986
- var singletonUiInstance;
987
- function ui() {
988
- if (singletonUiInstance === void 0) {
989
- singletonUiInstance = cliui();
990
- }
991
- return {
992
- ...singletonUiInstance,
993
- row: (args) => {
994
- logListItem(args);
995
- }
996
- };
974
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
975
+ function paragraphs(...sections) {
976
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
997
977
  }
998
- var singletonisaacUi;
999
- function logListItem(args) {
1000
- if (singletonisaacUi === void 0) {
1001
- singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
1002
- }
1003
- singletonisaacUi.div(...args);
1004
- const content = singletonisaacUi.toString();
1005
- singletonisaacUi.rows = [];
1006
- singletonUiInstance?.logger.log(content);
978
+
979
+ // packages/utils/src/lib/text-formats/md/section.ts
980
+ function section(...contents) {
981
+ return `${lines(...contents)}${NEW_LINE}`;
982
+ }
983
+ function lines(...contents) {
984
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
1007
985
  }
1008
986
 
1009
- // packages/utils/src/lib/file-system.ts
1010
- async function ensureDirectoryExists(baseDir) {
1011
- try {
1012
- await mkdir(baseDir, { recursive: true });
1013
- return;
1014
- } catch (error) {
1015
- ui().logger.info(error.message);
1016
- if (error.code !== "EEXIST") {
1017
- throw error;
1018
- }
1019
- }
987
+ // packages/utils/src/lib/text-formats/md/table.ts
988
+ var alignString = /* @__PURE__ */ new Map([
989
+ ["left", ":--"],
990
+ ["center", ":--:"],
991
+ ["right", "--:"]
992
+ ]);
993
+ function tableRow(rows) {
994
+ return `|${rows.join("|")}|`;
1020
995
  }
1021
- var NoExportError = class extends Error {
1022
- constructor(filepath) {
1023
- super(`No default export found in ${filepath}`);
1024
- }
1025
- };
1026
- async function importEsmModule(options) {
1027
- const { mod } = await bundleRequire({
1028
- format: "esm",
1029
- ...options
1030
- });
1031
- if (!("default" in mod)) {
1032
- throw new NoExportError(options.filepath);
996
+ function table2(data) {
997
+ if (data.rows.length === 0) {
998
+ throw new Error("Data can't be empty");
1033
999
  }
1034
- return mod.default;
1035
- }
1036
- function pluginWorkDir(slug) {
1037
- return join("node_modules", ".code-pushup", slug);
1000
+ const alignmentRow = getColumnAlignments(data).map(
1001
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
1002
+ );
1003
+ return section(
1004
+ `${lines(
1005
+ tableRow(columnsToStringArray(data)),
1006
+ tableRow(alignmentRow),
1007
+ ...rowToStringArray(data).map(tableRow)
1008
+ )}`
1009
+ );
1038
1010
  }
1039
1011
 
1012
+ // packages/utils/src/lib/text-formats/index.ts
1013
+ var md = {
1014
+ bold: bold2,
1015
+ italic: italic2,
1016
+ strikeThrough,
1017
+ code: code2,
1018
+ link: link2,
1019
+ image,
1020
+ headline,
1021
+ h,
1022
+ h1,
1023
+ h2,
1024
+ h3,
1025
+ h4,
1026
+ h5,
1027
+ h6,
1028
+ indentation,
1029
+ lines,
1030
+ li,
1031
+ section,
1032
+ paragraphs,
1033
+ table: table2
1034
+ };
1035
+ var html = {
1036
+ bold,
1037
+ italic,
1038
+ code,
1039
+ link,
1040
+ details,
1041
+ table
1042
+ };
1043
+
1040
1044
  // packages/utils/src/lib/reports/utils.ts
1041
1045
  var { image: image2, bold: boldMd } = md;
1042
1046
 
@@ -1081,7 +1085,7 @@ import chalk4 from "chalk";
1081
1085
 
1082
1086
  // packages/plugin-coverage/package.json
1083
1087
  var name = "@code-pushup/coverage-plugin";
1084
- var version = "0.45.0";
1088
+ var version = "0.45.1";
1085
1089
 
1086
1090
  // packages/plugin-coverage/src/lib/config.ts
1087
1091
  import { z as z16 } from "zod";
@@ -1161,7 +1165,7 @@ async function createRunnerConfig(scriptPath, config) {
1161
1165
  const threshold = config.perfectScoreThreshold;
1162
1166
  return {
1163
1167
  command: "node",
1164
- args: [scriptPath],
1168
+ args: [filePathToCliArg(scriptPath)],
1165
1169
  outputFile: RUNNER_OUTPUT_PATH,
1166
1170
  ...threshold != null && {
1167
1171
  outputTransform: (outputs) => applyMaxScoreAboveThreshold(outputs, threshold)
@@ -1225,19 +1229,15 @@ async function getNxCoveragePaths(targets = ["test"], verbose) {
1225
1229
  return await Promise.all(
1226
1230
  relevantNodes.map(async ({ name: name2, data }) => {
1227
1231
  const targetConfig = data.targets?.[target];
1228
- const coveragePath = await getCoveragePathForTarget(
1229
- target,
1232
+ const coveragePaths = await getCoveragePathsForTarget(
1230
1233
  targetConfig,
1231
- name2
1234
+ data.root,
1235
+ `${target} in ${name2}`
1232
1236
  );
1233
- const rootToReportsDir = join4(data.root, coveragePath);
1234
1237
  if (verbose) {
1235
1238
  ui().logger.info(`- ${name2}: ${target}`);
1236
1239
  }
1237
- return {
1238
- pathToProject: data.root,
1239
- resultsPath: join4(rootToReportsDir, "lcov.info")
1240
- };
1240
+ return coveragePaths;
1241
1241
  })
1242
1242
  );
1243
1243
  })
@@ -1250,47 +1250,58 @@ async function getNxCoveragePaths(targets = ["test"], verbose) {
1250
1250
  function hasNxTarget(project, target) {
1251
1251
  return project.data.targets != null && target in project.data.targets;
1252
1252
  }
1253
- async function getCoveragePathForTarget(target, targetConfig, projectName) {
1253
+ async function getCoveragePathsForTarget(targetConfig, pathToRoot, targetContext) {
1254
1254
  const { config } = targetConfig.options;
1255
1255
  if (targetConfig.executor?.includes("@nx/vite")) {
1256
- const testConfig = await importEsmModule({
1257
- filepath: config
1258
- });
1259
- const reportsDirectory = testConfig.test.coverage?.reportsDirectory;
1260
- const reporter = testConfig.test.coverage?.reporter;
1261
- if (reportsDirectory == null) {
1262
- throw new Error(
1263
- `Vitest coverage configuration at ${config} does not include coverage path for target ${target} in ${projectName}. Add the path under coverage > reportsDirectory.`
1264
- );
1265
- }
1266
- if (!reporter?.includes("lcov")) {
1267
- throw new Error(
1268
- `Vitest coverage configuration at ${config} does not include LCOV report format for target ${target} in ${projectName}. Add 'lcov' format under coverage > reporter.`
1269
- );
1270
- }
1271
- return reportsDirectory;
1256
+ return await getCoveragePathForVitest(config, pathToRoot, targetContext);
1272
1257
  }
1273
1258
  if (targetConfig.executor?.includes("@nx/jest")) {
1274
- const testConfig = await importEsmModule({
1275
- filepath: config
1276
- });
1277
- const coverageDirectory = testConfig.coverageDirectory;
1278
- if (coverageDirectory == null) {
1279
- throw new Error(
1280
- `Jest coverage configuration at ${config} does not include coverage path for target ${target} in ${projectName}. Add the path under coverageDirectory.`
1281
- );
1282
- }
1283
- if (!testConfig.coverageReporters?.includes("lcov")) {
1284
- throw new Error(
1285
- `Jest coverage configuration at ${config} does not include LCOV report format for target ${target} in ${projectName}. Add 'lcov' format under coverageReporters.`
1286
- );
1287
- }
1288
- return coverageDirectory;
1259
+ return await getCoveragePathForJest(config, pathToRoot, targetContext);
1289
1260
  }
1290
1261
  throw new Error(
1291
1262
  `Unsupported executor ${targetConfig.executor}. @nx/vite and @nx/jest are currently supported.`
1292
1263
  );
1293
1264
  }
1265
+ async function getCoveragePathForVitest(pathToConfig, pathToRoot, context) {
1266
+ const vitestConfig = await importEsmModule({
1267
+ filepath: pathToConfig,
1268
+ format: "esm"
1269
+ });
1270
+ const reportsDirectory = vitestConfig.test.coverage?.reportsDirectory;
1271
+ const reporter = vitestConfig.test.coverage?.reporter;
1272
+ if (reportsDirectory == null) {
1273
+ throw new Error(
1274
+ `Vitest coverage configuration at ${pathToConfig} does not include coverage path for target ${context}. Add the path under coverage > reportsDirectory.`
1275
+ );
1276
+ }
1277
+ if (!reporter?.includes("lcov")) {
1278
+ throw new Error(
1279
+ `Vitest coverage configuration at ${pathToConfig} does not include LCOV report format for target ${context}. Add 'lcov' format under coverage > reporter.`
1280
+ );
1281
+ }
1282
+ return {
1283
+ pathToProject: pathToRoot,
1284
+ resultsPath: join4(pathToRoot, reportsDirectory, "lcov.info")
1285
+ };
1286
+ }
1287
+ async function getCoveragePathForJest(pathToConfig, pathToRoot, context) {
1288
+ const testConfig = await importEsmModule({
1289
+ filepath: pathToConfig,
1290
+ format: "cjs"
1291
+ });
1292
+ const coverageDirectory = testConfig.coverageDirectory;
1293
+ if (coverageDirectory == null) {
1294
+ throw new Error(
1295
+ `Jest coverage configuration at ${pathToConfig} does not include coverage path for target ${context}. Add the path under coverageDirectory.`
1296
+ );
1297
+ }
1298
+ if (!testConfig.coverageReporters?.includes("lcov")) {
1299
+ throw new Error(
1300
+ `Jest coverage configuration at ${pathToConfig} does not include LCOV report format for target ${context}. Add 'lcov' format under coverageReporters.`
1301
+ );
1302
+ }
1303
+ return join4(pathToRoot, coverageDirectory, "lcov.info");
1304
+ }
1294
1305
 
1295
1306
  // packages/plugin-coverage/src/index.ts
1296
1307
  var src_default = coveragePlugin;