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