@code-pushup/utils 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,527 +1,158 @@
1
- // packages/utils/src/lib/text-formats/constants.ts
2
- var NEW_LINE = "\n";
3
- var TAB = " ";
4
- var SPACE = " ";
5
-
6
- // packages/utils/src/lib/text-formats/html/details.ts
7
- function details(title, content, cfg = { open: false }) {
8
- 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.
9
- NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
10
- // ⚠️ The blank line ensure Markdown in content is rendered correctly.
11
- NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
12
- NEW_LINE}`;
13
- }
14
-
15
- // packages/utils/src/lib/text-formats/html/font-style.ts
16
- var boldElement = "b";
17
- function bold(text) {
18
- return `<${boldElement}>${text}</${boldElement}>`;
19
- }
20
- var italicElement = "i";
21
- function italic(text) {
22
- return `<${italicElement}>${text}</${italicElement}>`;
23
- }
24
- var codeElement = "code";
25
- function code(text) {
26
- return `<${codeElement}>${text}</${codeElement}>`;
27
- }
1
+ // packages/models/src/lib/implementation/schemas.ts
2
+ import { MATERIAL_ICONS } from "vscode-material-icons";
3
+ import { z } from "zod";
28
4
 
29
- // packages/utils/src/lib/text-formats/html/link.ts
30
- function link(href, text) {
31
- return `<a href="${href}">${text || href}"</a>`;
32
- }
5
+ // packages/models/src/lib/implementation/limits.ts
6
+ var MAX_SLUG_LENGTH = 128;
7
+ var MAX_TITLE_LENGTH = 256;
8
+ var MAX_DESCRIPTION_LENGTH = 65536;
9
+ var MAX_ISSUE_MESSAGE_LENGTH = 1024;
33
10
 
34
- // packages/utils/src/lib/transform.ts
35
- import { platform } from "node:os";
36
- function toArray(val) {
37
- return Array.isArray(val) ? val : [val];
38
- }
39
- function objectToKeys(obj) {
40
- return Object.keys(obj);
41
- }
42
- function objectToEntries(obj) {
43
- return Object.entries(obj);
44
- }
45
- function objectFromEntries(entries) {
46
- return Object.fromEntries(entries);
47
- }
48
- function countOccurrences(values) {
49
- return values.reduce(
50
- (acc, value) => ({ ...acc, [value]: (acc[value] ?? 0) + 1 }),
51
- {}
11
+ // packages/models/src/lib/implementation/utils.ts
12
+ var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
13
+ var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
14
+ function hasDuplicateStrings(strings) {
15
+ const sortedStrings = [...strings].sort();
16
+ const duplStrings = sortedStrings.filter(
17
+ (item, index) => index !== 0 && item === sortedStrings[index - 1]
52
18
  );
19
+ return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
53
20
  }
54
- function distinct(array) {
55
- return [...new Set(array)];
21
+ function hasMissingStrings(toCheck, existing) {
22
+ const nonExisting = toCheck.filter((s) => !existing.includes(s));
23
+ return nonExisting.length === 0 ? false : nonExisting;
56
24
  }
57
- function deepClone(obj) {
58
- return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
25
+ function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
26
+ return transform(items || []);
59
27
  }
60
- function factorOf(items, filterFn) {
61
- const itemCount = items.length;
62
- if (!itemCount) {
63
- return 1;
64
- }
65
- const filterCount = items.filter(filterFn).length;
66
- return filterCount === 0 ? 1 : (itemCount - filterCount) / itemCount;
28
+ function exists(value) {
29
+ return value != null;
67
30
  }
68
- function objectToCliArgs(params) {
69
- if (!params) {
70
- return [];
31
+ function getMissingRefsForCategories(categories, plugins) {
32
+ if (categories.length === 0) {
33
+ return false;
71
34
  }
72
- return Object.entries(params).flatMap(([key, value]) => {
73
- if (key === "_") {
74
- return Array.isArray(value) ? value : [`${value}`];
75
- }
76
- const prefix = key.length === 1 ? "-" : "--";
77
- if (Array.isArray(value)) {
78
- return value.map((v) => `${prefix}${key}="${v}"`);
79
- }
80
- if (Array.isArray(value)) {
81
- return value.map((v) => `${prefix}${key}="${v}"`);
82
- }
83
- if (typeof value === "string") {
84
- return [`${prefix}${key}="${value}"`];
85
- }
86
- if (typeof value === "number") {
87
- return [`${prefix}${key}=${value}`];
88
- }
89
- if (typeof value === "boolean") {
90
- return [`${prefix}${value ? "" : "no-"}${key}`];
91
- }
92
- throw new Error(`Unsupported type ${typeof value} for key ${key}`);
93
- });
94
- }
95
- function toUnixPath(path) {
96
- return path.replace(/\\/g, "/");
35
+ const auditRefsFromCategory = categories.flatMap(
36
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
37
+ );
38
+ const auditRefsFromPlugins = plugins.flatMap(
39
+ ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
40
+ );
41
+ const missingAuditRefs = hasMissingStrings(
42
+ auditRefsFromCategory,
43
+ auditRefsFromPlugins
44
+ );
45
+ const groupRefsFromCategory = categories.flatMap(
46
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
47
+ );
48
+ const groupRefsFromPlugins = plugins.flatMap(
49
+ ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
50
+ );
51
+ const missingGroupRefs = hasMissingStrings(
52
+ groupRefsFromCategory,
53
+ groupRefsFromPlugins
54
+ );
55
+ const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
56
+ return missingRefs.length > 0 ? missingRefs : false;
97
57
  }
98
- function toUnixNewlines(text) {
99
- return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
58
+ function missingRefsForCategoriesErrorMsg(categories, plugins) {
59
+ const missingRefs = getMissingRefsForCategories(categories, plugins);
60
+ return `The following category references need to point to an audit or group: ${errorItems(
61
+ missingRefs
62
+ )}`;
100
63
  }
101
- function fromJsonLines(jsonLines) {
102
- const unifiedNewLines = toUnixNewlines(jsonLines).trim();
103
- return JSON.parse(`[${unifiedNewLines.split("\n").join(",")}]`);
64
+
65
+ // packages/models/src/lib/implementation/schemas.ts
66
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
67
+ function executionMetaSchema(options = {
68
+ descriptionDate: "Execution start date and time",
69
+ descriptionDuration: "Execution duration in ms"
70
+ }) {
71
+ return z.object({
72
+ date: z.string({ description: options.descriptionDate }),
73
+ duration: z.number({ description: options.descriptionDuration })
74
+ });
104
75
  }
105
- function toJsonLines(json) {
106
- return json.map((item) => JSON.stringify(item)).join("\n");
76
+ var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
77
+ message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
78
+ }).max(MAX_SLUG_LENGTH, {
79
+ message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
80
+ });
81
+ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
82
+ var urlSchema = z.string().url();
83
+ var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
84
+ var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
85
+ var scoreSchema = z.number({
86
+ description: "Value between 0 and 1"
87
+ }).min(0).max(1);
88
+ function metaSchema(options) {
89
+ const {
90
+ descriptionDescription,
91
+ titleDescription,
92
+ docsUrlDescription,
93
+ description
94
+ } = options ?? {};
95
+ return z.object(
96
+ {
97
+ title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
98
+ description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
99
+ docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
100
+ },
101
+ { description }
102
+ );
107
103
  }
108
- function capitalize(text) {
109
- return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
110
- 1
111
- )}`;
104
+ var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
105
+ var fileNameSchema = z.string().trim().regex(filenameRegex, {
106
+ message: `The filename has to be valid`
107
+ }).min(1, { message: "file name is invalid" });
108
+ var positiveIntSchema = z.number().int().positive();
109
+ var nonnegativeIntSchema = z.number().int().nonnegative();
110
+ var nonnegativeNumberSchema = z.number().nonnegative();
111
+ function packageVersionSchema(options) {
112
+ const { versionDescription = "NPM version of the package", required } = options ?? {};
113
+ const packageSchema = z.string({ description: "NPM package name" });
114
+ const versionSchema = z.string({ description: versionDescription });
115
+ return z.object(
116
+ {
117
+ packageName: required ? packageSchema : packageSchema.optional(),
118
+ version: required ? versionSchema : versionSchema.optional()
119
+ },
120
+ { description: "NPM package name and version of a published package" }
121
+ );
112
122
  }
113
- function apostrophize(text, upperCase) {
114
- const lastCharMatch = text.match(/(\w)\W*$/);
115
- const lastChar = lastCharMatch?.[1] ?? "";
116
- return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
123
+ var weightSchema = nonnegativeNumberSchema.describe(
124
+ "Coefficient for the given score (use weight 0 if only for display)"
125
+ );
126
+ function weightedRefSchema(description, slugDescription) {
127
+ return z.object(
128
+ {
129
+ slug: slugSchema.describe(slugDescription),
130
+ weight: weightSchema.describe("Weight used to calculate score")
131
+ },
132
+ { description }
133
+ );
117
134
  }
118
- function toNumberPrecision(value, decimalPlaces) {
119
- return Number(
120
- `${Math.round(
121
- Number.parseFloat(`${value}e${decimalPlaces}`)
122
- )}e-${decimalPlaces}`
135
+ function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
136
+ return z.object(
137
+ {
138
+ slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
139
+ refs: z.array(refSchema).min(1).refine(
140
+ (refs) => !duplicateCheckFn(refs),
141
+ (refs) => ({
142
+ message: duplicateMessageFn(refs)
143
+ })
144
+ ).refine(hasNonZeroWeightedRef, () => ({
145
+ message: "In a category there has to be at least one ref with weight > 0"
146
+ }))
147
+ },
148
+ { description }
123
149
  );
124
150
  }
125
- function toOrdinal(value) {
126
- if (value % 10 === 1 && value % 100 !== 11) {
127
- return `${value}st`;
128
- }
129
- if (value % 10 === 2 && value % 100 !== 12) {
130
- return `${value}nd`;
131
- }
132
- if (value % 10 === 3 && value % 100 !== 13) {
133
- return `${value}rd`;
134
- }
135
- return `${value}th`;
136
- }
137
-
138
- // packages/utils/src/lib/table.ts
139
- function rowToStringArray({ rows, columns = [] }) {
140
- if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
141
- throw new TypeError(
142
- "Column can`t be object when rows are primitive values"
143
- );
144
- }
145
- return rows.map((row) => {
146
- if (Array.isArray(row)) {
147
- return row.map(String);
148
- }
149
- const objectRow = row;
150
- if (columns.length === 0 || typeof columns.at(0) === "string") {
151
- return Object.values(objectRow).map(String);
152
- }
153
- return columns.map(
154
- ({ key }) => String(objectRow[key])
155
- );
156
- });
157
- }
158
- function columnsToStringArray({ rows, columns = [] }) {
159
- const firstRow = rows.at(0);
160
- const primitiveRows = Array.isArray(firstRow);
161
- if (typeof columns.at(0) === "string" && !primitiveRows) {
162
- throw new Error("invalid union type. Caught by model parsing.");
163
- }
164
- if (columns.length === 0) {
165
- if (Array.isArray(firstRow)) {
166
- return firstRow.map((_, idx) => String(idx));
167
- }
168
- return Object.keys(firstRow);
169
- }
170
- if (typeof columns.at(0) === "string") {
171
- return columns.map(String);
172
- }
173
- const cols = columns;
174
- return cols.map(({ label, key }) => label ?? capitalize(key));
175
- }
176
- function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
177
- const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
178
- if (typeof column === "string") {
179
- return column;
180
- } else if (typeof column === "object") {
181
- return column.align ?? "center";
182
- } else {
183
- return "center";
184
- }
185
- }
186
- function getColumnAlignmentForIndex(targetIdx, columns = []) {
187
- const column = columns.at(targetIdx);
188
- if (column == null) {
189
- return "center";
190
- } else if (typeof column === "string") {
191
- return column;
192
- } else if (typeof column === "object") {
193
- return column.align ?? "center";
194
- } else {
195
- return "center";
196
- }
197
- }
198
- function getColumnAlignments({
199
- rows,
200
- columns = []
201
- }) {
202
- if (rows.at(0) == null) {
203
- throw new Error("first row can`t be undefined.");
204
- }
205
- if (Array.isArray(rows.at(0))) {
206
- const firstPrimitiveRow = rows.at(0);
207
- return Array.from({ length: firstPrimitiveRow.length }).map(
208
- (_, idx) => getColumnAlignmentForIndex(idx, columns)
209
- );
210
- }
211
- const firstObject = rows.at(0);
212
- return Object.keys(firstObject).map(
213
- (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
214
- );
215
- }
216
-
217
- // packages/utils/src/lib/text-formats/html/table.ts
218
- function wrap(elem, content) {
219
- return `<${elem}>${content}</${elem}>${NEW_LINE}`;
220
- }
221
- function wrapRow(content) {
222
- const elem = "tr";
223
- return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
224
- }
225
- function table(tableData) {
226
- if (tableData.rows.length === 0) {
227
- throw new Error("Data can't be empty");
228
- }
229
- const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
230
- const tableHeaderRow = wrapRow(tableHeaderCols);
231
- const tableBody = rowToStringArray(tableData).map((arr) => {
232
- const columns = arr.map((s) => wrap("td", s)).join("");
233
- return wrapRow(columns);
234
- }).join("");
235
- return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
236
- }
237
-
238
- // packages/utils/src/lib/text-formats/md/font-style.ts
239
- var boldWrap = "**";
240
- function bold2(text) {
241
- return `${boldWrap}${text}${boldWrap}`;
242
- }
243
- var italicWrap = "_";
244
- function italic2(text) {
245
- return `${italicWrap}${text}${italicWrap}`;
246
- }
247
- var strikeThroughWrap = "~";
248
- function strikeThrough(text) {
249
- return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
250
- }
251
- var codeWrap = "`";
252
- function code2(text) {
253
- return `${codeWrap}${text}${codeWrap}`;
254
- }
255
-
256
- // packages/utils/src/lib/text-formats/md/headline.ts
257
- function headline(text, hierarchy = 1) {
258
- return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
259
- }
260
- function h(text, hierarchy = 1) {
261
- return headline(text, hierarchy);
262
- }
263
- function h1(text) {
264
- return headline(text, 1);
265
- }
266
- function h2(text) {
267
- return headline(text, 2);
268
- }
269
- function h3(text) {
270
- return headline(text, 3);
271
- }
272
- function h4(text) {
273
- return headline(text, 4);
274
- }
275
- function h5(text) {
276
- return headline(text, 5);
277
- }
278
- function h6(text) {
279
- return headline(text, 6);
280
- }
281
-
282
- // packages/utils/src/lib/text-formats/md/image.ts
283
- function image(src, alt) {
284
- return `![${alt}](${src})`;
285
- }
286
-
287
- // packages/utils/src/lib/text-formats/md/link.ts
288
- function link2(href, text) {
289
- return `[${text || href}](${href})`;
290
- }
291
-
292
- // packages/utils/src/lib/text-formats/md/list.ts
293
- function li(text, order = "unordered") {
294
- const style = order === "unordered" ? "-" : "- [ ]";
295
- return `${style} ${text}`;
296
- }
297
- function indentation(text, level = 1) {
298
- return `${TAB.repeat(level)}${text}`;
299
- }
300
-
301
- // packages/utils/src/lib/text-formats/md/paragraphs.ts
302
- function paragraphs(...sections) {
303
- return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
304
- }
305
-
306
- // packages/utils/src/lib/text-formats/md/section.ts
307
- function section(...contents) {
308
- return `${lines(...contents)}${NEW_LINE}`;
309
- }
310
- function lines(...contents) {
311
- return `${contents.filter(Boolean).join(NEW_LINE)}`;
312
- }
313
-
314
- // packages/utils/src/lib/text-formats/md/table.ts
315
- var alignString = /* @__PURE__ */ new Map([
316
- ["left", ":--"],
317
- ["center", ":--:"],
318
- ["right", "--:"]
319
- ]);
320
- function tableRow(rows) {
321
- return `|${rows.join("|")}|`;
322
- }
323
- function table2(data) {
324
- if (data.rows.length === 0) {
325
- throw new Error("Data can't be empty");
326
- }
327
- const alignmentRow = getColumnAlignments(data).map(
328
- (s) => alignString.get(s) ?? String(alignString.get("center"))
329
- );
330
- return section(
331
- `${lines(
332
- tableRow(columnsToStringArray(data)),
333
- tableRow(alignmentRow),
334
- ...rowToStringArray(data).map(tableRow)
335
- )}`
336
- );
337
- }
338
-
339
- // packages/utils/src/lib/text-formats/index.ts
340
- var md = {
341
- bold: bold2,
342
- italic: italic2,
343
- strikeThrough,
344
- code: code2,
345
- link: link2,
346
- image,
347
- headline,
348
- h,
349
- h1,
350
- h2,
351
- h3,
352
- h4,
353
- h5,
354
- h6,
355
- indentation,
356
- lines,
357
- li,
358
- section,
359
- paragraphs,
360
- table: table2
361
- };
362
- var html = {
363
- bold,
364
- italic,
365
- code,
366
- link,
367
- details,
368
- table
369
- };
370
-
371
- // packages/models/src/lib/implementation/schemas.ts
372
- import { MATERIAL_ICONS } from "vscode-material-icons";
373
- import { z } from "zod";
374
-
375
- // packages/models/src/lib/implementation/limits.ts
376
- var MAX_SLUG_LENGTH = 128;
377
- var MAX_TITLE_LENGTH = 256;
378
- var MAX_DESCRIPTION_LENGTH = 65536;
379
- var MAX_ISSUE_MESSAGE_LENGTH = 1024;
380
-
381
- // packages/models/src/lib/implementation/utils.ts
382
- var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
383
- var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
384
- function hasDuplicateStrings(strings) {
385
- const sortedStrings = [...strings].sort();
386
- const duplStrings = sortedStrings.filter(
387
- (item, index) => index !== 0 && item === sortedStrings[index - 1]
388
- );
389
- return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
390
- }
391
- function hasMissingStrings(toCheck, existing) {
392
- const nonExisting = toCheck.filter((s) => !existing.includes(s));
393
- return nonExisting.length === 0 ? false : nonExisting;
394
- }
395
- function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
396
- return transform(items || []);
397
- }
398
- function exists(value) {
399
- return value != null;
400
- }
401
- function getMissingRefsForCategories(categories, plugins) {
402
- if (categories.length === 0) {
403
- return false;
404
- }
405
- const auditRefsFromCategory = categories.flatMap(
406
- ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
407
- );
408
- const auditRefsFromPlugins = plugins.flatMap(
409
- ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
410
- );
411
- const missingAuditRefs = hasMissingStrings(
412
- auditRefsFromCategory,
413
- auditRefsFromPlugins
414
- );
415
- const groupRefsFromCategory = categories.flatMap(
416
- ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
417
- );
418
- const groupRefsFromPlugins = plugins.flatMap(
419
- ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
420
- );
421
- const missingGroupRefs = hasMissingStrings(
422
- groupRefsFromCategory,
423
- groupRefsFromPlugins
424
- );
425
- const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
426
- return missingRefs.length > 0 ? missingRefs : false;
427
- }
428
- function missingRefsForCategoriesErrorMsg(categories, plugins) {
429
- const missingRefs = getMissingRefsForCategories(categories, plugins);
430
- return `The following category references need to point to an audit or group: ${errorItems(
431
- missingRefs
432
- )}`;
433
- }
434
-
435
- // packages/models/src/lib/implementation/schemas.ts
436
- var primitiveValueSchema = z.union([z.string(), z.number()]);
437
- function executionMetaSchema(options = {
438
- descriptionDate: "Execution start date and time",
439
- descriptionDuration: "Execution duration in ms"
440
- }) {
441
- return z.object({
442
- date: z.string({ description: options.descriptionDate }),
443
- duration: z.number({ description: options.descriptionDuration })
444
- });
445
- }
446
- var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
447
- message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
448
- }).max(MAX_SLUG_LENGTH, {
449
- message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
450
- });
451
- var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
452
- var urlSchema = z.string().url();
453
- var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
454
- var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
455
- var scoreSchema = z.number({
456
- description: "Value between 0 and 1"
457
- }).min(0).max(1);
458
- function metaSchema(options) {
459
- const {
460
- descriptionDescription,
461
- titleDescription,
462
- docsUrlDescription,
463
- description
464
- } = options ?? {};
465
- return z.object(
466
- {
467
- title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
468
- description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
469
- docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
470
- },
471
- { description }
472
- );
473
- }
474
- var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
475
- var fileNameSchema = z.string().trim().regex(filenameRegex, {
476
- message: `The filename has to be valid`
477
- }).min(1, { message: "file name is invalid" });
478
- var positiveIntSchema = z.number().int().positive();
479
- var nonnegativeIntSchema = z.number().int().nonnegative();
480
- function packageVersionSchema(options) {
481
- const { versionDescription = "NPM version of the package", required } = options ?? {};
482
- const packageSchema = z.string({ description: "NPM package name" });
483
- const versionSchema = z.string({ description: versionDescription });
484
- return z.object(
485
- {
486
- packageName: required ? packageSchema : packageSchema.optional(),
487
- version: required ? versionSchema : versionSchema.optional()
488
- },
489
- { description: "NPM package name and version of a published package" }
490
- );
491
- }
492
- var weightSchema = nonnegativeIntSchema.describe(
493
- "Coefficient for the given score (use weight 0 if only for display)"
494
- );
495
- function weightedRefSchema(description, slugDescription) {
496
- return z.object(
497
- {
498
- slug: slugSchema.describe(slugDescription),
499
- weight: weightSchema.describe("Weight used to calculate score")
500
- },
501
- { description }
502
- );
503
- }
504
- function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
505
- return z.object(
506
- {
507
- slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
508
- refs: z.array(refSchema).min(1).refine(
509
- (refs) => !duplicateCheckFn(refs),
510
- (refs) => ({
511
- message: duplicateMessageFn(refs)
512
- })
513
- ).refine(hasNonZeroWeightedRef, () => ({
514
- message: "In a category there has to be at least one ref with weight > 0"
515
- }))
516
- },
517
- { description }
518
- );
519
- }
520
- var materialIconSchema = z.enum(MATERIAL_ICONS, {
521
- description: "Icon from VSCode Material Icons extension"
522
- });
523
- function hasNonZeroWeightedRef(refs) {
524
- return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
151
+ var materialIconSchema = z.enum(MATERIAL_ICONS, {
152
+ description: "Icon from VSCode Material Icons extension"
153
+ });
154
+ function hasNonZeroWeightedRef(refs) {
155
+ return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
525
156
  }
526
157
 
527
158
  // packages/models/src/lib/audit.ts
@@ -631,7 +262,7 @@ var tableObjectSchema = tableSharedSchema.merge(
631
262
  var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
632
263
 
633
264
  // packages/models/src/lib/audit-output.ts
634
- var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
265
+ var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
635
266
  var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
636
267
  var auditDetailsSchema = z5.object(
637
268
  {
@@ -1062,348 +693,723 @@ var reportsDiffSchema = z15.object({
1062
693
  })
1063
694
  );
1064
695
 
1065
- // packages/utils/src/lib/diff.ts
1066
- function matchArrayItemsByKey({
1067
- before,
1068
- after,
1069
- key
1070
- }) {
1071
- const pairs = [];
1072
- const added = [];
1073
- const afterKeys = /* @__PURE__ */ new Set();
1074
- const keyFn = typeof key === "function" ? key : (item) => item[key];
1075
- for (const afterItem of after) {
1076
- const afterKey = keyFn(afterItem);
1077
- afterKeys.add(afterKey);
1078
- const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
1079
- if (match) {
1080
- pairs.push({ before: match, after: afterItem });
1081
- } else {
1082
- added.push(afterItem);
1083
- }
696
+ // packages/utils/src/lib/diff.ts
697
+ function matchArrayItemsByKey({
698
+ before,
699
+ after,
700
+ key
701
+ }) {
702
+ const pairs = [];
703
+ const added = [];
704
+ const afterKeys = /* @__PURE__ */ new Set();
705
+ const keyFn = typeof key === "function" ? key : (item) => item[key];
706
+ for (const afterItem of after) {
707
+ const afterKey = keyFn(afterItem);
708
+ afterKeys.add(afterKey);
709
+ const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
710
+ if (match) {
711
+ pairs.push({ before: match, after: afterItem });
712
+ } else {
713
+ added.push(afterItem);
714
+ }
715
+ }
716
+ const removed = before.filter(
717
+ (beforeItem) => !afterKeys.has(keyFn(beforeItem))
718
+ );
719
+ return {
720
+ pairs,
721
+ added,
722
+ removed
723
+ };
724
+ }
725
+ function comparePairs(pairs, equalsFn) {
726
+ return pairs.reduce(
727
+ (acc, pair) => ({
728
+ ...acc,
729
+ ...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
730
+ }),
731
+ {
732
+ changed: [],
733
+ unchanged: []
734
+ }
735
+ );
736
+ }
737
+
738
+ // packages/utils/src/lib/execute-process.ts
739
+ import { spawn } from "node:child_process";
740
+
741
+ // packages/utils/src/lib/reports/utils.ts
742
+ import { join as join2 } from "node:path";
743
+
744
+ // packages/utils/src/lib/file-system.ts
745
+ import { bundleRequire } from "bundle-require";
746
+ import chalk2 from "chalk";
747
+ import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
748
+ import { join } from "node:path";
749
+
750
+ // packages/utils/src/lib/formatting.ts
751
+ function slugify(text) {
752
+ return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
753
+ }
754
+ function pluralize(text, amount) {
755
+ if (amount != null && Math.abs(amount) === 1) {
756
+ return text;
757
+ }
758
+ if (text.endsWith("y")) {
759
+ return `${text.slice(0, -1)}ies`;
760
+ }
761
+ if (text.endsWith("s")) {
762
+ return `${text}es`;
763
+ }
764
+ return `${text}s`;
765
+ }
766
+ function formatBytes(bytes, decimals = 2) {
767
+ const positiveBytes = Math.max(bytes, 0);
768
+ if (positiveBytes === 0) {
769
+ return "0 B";
770
+ }
771
+ const k = 1024;
772
+ const dm = decimals < 0 ? 0 : decimals;
773
+ const sizes = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
774
+ const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
775
+ return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
776
+ }
777
+ function pluralizeToken(token, times) {
778
+ return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
779
+ }
780
+ function formatDuration(duration) {
781
+ if (duration < 1e3) {
782
+ return `${duration} ms`;
783
+ }
784
+ return `${(duration / 1e3).toFixed(2)} s`;
785
+ }
786
+ function formatDate(date) {
787
+ const locale = "en-US";
788
+ return date.toLocaleString(locale, {
789
+ weekday: "short",
790
+ month: "short",
791
+ day: "numeric",
792
+ year: "numeric",
793
+ hour: "numeric",
794
+ minute: "2-digit",
795
+ timeZoneName: "short"
796
+ }).replace(/\u202F/g, " ");
797
+ }
798
+ function truncateText(text, maxChars) {
799
+ if (text.length <= maxChars) {
800
+ return text;
801
+ }
802
+ const ellipsis = "...";
803
+ return text.slice(0, maxChars - ellipsis.length) + ellipsis;
804
+ }
805
+ function truncateTitle(text) {
806
+ return truncateText(text, MAX_TITLE_LENGTH);
807
+ }
808
+ function truncateDescription(text) {
809
+ return truncateText(text, MAX_DESCRIPTION_LENGTH);
810
+ }
811
+ function truncateIssueMessage(text) {
812
+ return truncateText(text, MAX_ISSUE_MESSAGE_LENGTH);
813
+ }
814
+
815
+ // packages/utils/src/lib/guards.ts
816
+ function isPromiseFulfilledResult(result) {
817
+ return result.status === "fulfilled";
818
+ }
819
+ function isPromiseRejectedResult(result) {
820
+ return result.status === "rejected";
821
+ }
822
+
823
+ // packages/utils/src/lib/logging.ts
824
+ import isaacs_cliui from "@isaacs/cliui";
825
+ import { cliui } from "@poppinss/cliui";
826
+ import chalk from "chalk";
827
+
828
+ // packages/utils/src/lib/reports/constants.ts
829
+ var TERMINAL_WIDTH = 80;
830
+ var SCORE_COLOR_RANGE = {
831
+ GREEN_MIN: 0.9,
832
+ YELLOW_MIN: 0.5
833
+ };
834
+ var CATEGORIES_TITLE = "\u{1F3F7} Categories";
835
+ var FOOTER_PREFIX = "Made with \u2764 by";
836
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
837
+ var README_LINK = "https://github.com/code-pushup/cli#readme";
838
+ var reportHeadlineText = "Code PushUp Report";
839
+ var reportOverviewTableHeaders = [
840
+ {
841
+ key: "category",
842
+ label: "\u{1F3F7} Category",
843
+ align: "left"
844
+ },
845
+ {
846
+ key: "score",
847
+ label: "\u2B50 Score"
848
+ },
849
+ {
850
+ key: "audits",
851
+ label: "\u{1F6E1} Audits"
852
+ }
853
+ ];
854
+ var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
855
+ var issuesTableHeadings = [
856
+ {
857
+ key: "severity",
858
+ label: "Severity"
859
+ },
860
+ {
861
+ key: "message",
862
+ label: "Message"
863
+ },
864
+ {
865
+ key: "file",
866
+ label: "Source file"
867
+ },
868
+ {
869
+ key: "line",
870
+ label: "Line(s)"
871
+ }
872
+ ];
873
+
874
+ // packages/utils/src/lib/logging.ts
875
+ var singletonUiInstance;
876
+ function ui() {
877
+ if (singletonUiInstance === void 0) {
878
+ singletonUiInstance = cliui();
1084
879
  }
1085
- const removed = before.filter(
1086
- (beforeItem) => !afterKeys.has(keyFn(beforeItem))
1087
- );
1088
880
  return {
1089
- pairs,
1090
- added,
1091
- removed
881
+ ...singletonUiInstance,
882
+ row: (args) => {
883
+ logListItem(args);
884
+ }
1092
885
  };
1093
886
  }
1094
- function comparePairs(pairs, equalsFn) {
1095
- return pairs.reduce(
1096
- (acc, pair) => ({
1097
- ...acc,
1098
- ...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
1099
- }),
1100
- {
1101
- changed: [],
1102
- unchanged: []
887
+ var singletonisaacUi;
888
+ function logListItem(args) {
889
+ if (singletonisaacUi === void 0) {
890
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
891
+ }
892
+ singletonisaacUi.div(...args);
893
+ const content = singletonisaacUi.toString();
894
+ singletonisaacUi.rows = [];
895
+ singletonUiInstance?.logger.log(content);
896
+ }
897
+ function link(text) {
898
+ return chalk.underline(chalk.blueBright(text));
899
+ }
900
+
901
+ // packages/utils/src/lib/log-results.ts
902
+ function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
903
+ if (succeededTransform) {
904
+ const succeededResults = results.filter(isPromiseFulfilledResult);
905
+ logPromiseResults(
906
+ succeededResults,
907
+ `${messagePrefix} successfully: `,
908
+ succeededTransform
909
+ );
910
+ }
911
+ if (failedTransform) {
912
+ const failedResults = results.filter(isPromiseRejectedResult);
913
+ logPromiseResults(
914
+ failedResults,
915
+ `${messagePrefix} failed: `,
916
+ failedTransform
917
+ );
918
+ }
919
+ }
920
+ function logPromiseResults(results, logMessage, getMsg) {
921
+ if (results.length > 0) {
922
+ const log2 = results[0]?.status === "fulfilled" ? (m) => {
923
+ ui().logger.success(m);
924
+ } : (m) => {
925
+ ui().logger.warning(m);
926
+ };
927
+ log2(logMessage);
928
+ results.forEach((result) => {
929
+ log2(getMsg(result));
930
+ });
931
+ }
932
+ }
933
+
934
+ // packages/utils/src/lib/file-system.ts
935
+ async function readTextFile(path) {
936
+ const buffer = await readFile(path);
937
+ return buffer.toString();
938
+ }
939
+ async function readJsonFile(path) {
940
+ const text = await readTextFile(path);
941
+ return JSON.parse(text);
942
+ }
943
+ async function fileExists(path) {
944
+ try {
945
+ const stats = await stat(path);
946
+ return stats.isFile();
947
+ } catch {
948
+ return false;
949
+ }
950
+ }
951
+ async function directoryExists(path) {
952
+ try {
953
+ const stats = await stat(path);
954
+ return stats.isDirectory();
955
+ } catch {
956
+ return false;
957
+ }
958
+ }
959
+ async function ensureDirectoryExists(baseDir) {
960
+ try {
961
+ await mkdir(baseDir, { recursive: true });
962
+ return;
963
+ } catch (error) {
964
+ ui().logger.info(error.message);
965
+ if (error.code !== "EEXIST") {
966
+ throw error;
1103
967
  }
968
+ }
969
+ }
970
+ async function removeDirectoryIfExists(dir) {
971
+ if (await directoryExists(dir)) {
972
+ await rm(dir, { recursive: true, force: true });
973
+ }
974
+ }
975
+ function logMultipleFileResults(fileResults, messagePrefix) {
976
+ const succeededTransform = (result) => {
977
+ const [fileName, size] = result.value;
978
+ const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
979
+ return `- ${chalk2.bold(fileName)}${formattedSize}`;
980
+ };
981
+ const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
982
+ logMultipleResults(
983
+ fileResults,
984
+ messagePrefix,
985
+ succeededTransform,
986
+ failedTransform
1104
987
  );
1105
988
  }
989
+ var NoExportError = class extends Error {
990
+ constructor(filepath) {
991
+ super(`No default export found in ${filepath}`);
992
+ }
993
+ };
994
+ async function importEsmModule(options) {
995
+ const { mod } = await bundleRequire({
996
+ format: "esm",
997
+ ...options
998
+ });
999
+ if (!("default" in mod)) {
1000
+ throw new NoExportError(options.filepath);
1001
+ }
1002
+ return mod.default;
1003
+ }
1004
+ function pluginWorkDir(slug) {
1005
+ return join("node_modules", ".code-pushup", slug);
1006
+ }
1007
+ async function crawlFileSystem(options) {
1008
+ const {
1009
+ directory,
1010
+ pattern,
1011
+ fileTransform = (filePath) => filePath
1012
+ } = options;
1013
+ const files = await readdir(directory);
1014
+ const promises = files.map(async (file) => {
1015
+ const filePath = join(directory, file);
1016
+ const stats = await stat(filePath);
1017
+ if (stats.isDirectory()) {
1018
+ return crawlFileSystem({ directory: filePath, pattern, fileTransform });
1019
+ }
1020
+ if (stats.isFile() && (!pattern || new RegExp(pattern).test(file))) {
1021
+ return fileTransform(filePath);
1022
+ }
1023
+ return [];
1024
+ });
1025
+ const resultsNestedArray = await Promise.all(promises);
1026
+ return resultsNestedArray.flat();
1027
+ }
1028
+ function findLineNumberInText(content, pattern) {
1029
+ const lines6 = content.split(/\r?\n/);
1030
+ const lineNumber = lines6.findIndex((line) => line.includes(pattern)) + 1;
1031
+ return lineNumber === 0 ? null : lineNumber;
1032
+ }
1033
+ function filePathToCliArg(path) {
1034
+ return `"${path}"`;
1035
+ }
1106
1036
 
1107
- // packages/utils/src/lib/execute-process.ts
1108
- import { spawn } from "node:child_process";
1037
+ // packages/utils/src/lib/text-formats/constants.ts
1038
+ var NEW_LINE = "\n";
1039
+ var TAB = " ";
1040
+ var SPACE = " ";
1109
1041
 
1110
- // packages/utils/src/lib/reports/utils.ts
1111
- import { join as join2 } from "node:path";
1042
+ // packages/utils/src/lib/text-formats/html/details.ts
1043
+ function details(title, content, cfg = { open: false }) {
1044
+ 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.
1045
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
1046
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
1047
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
1048
+ NEW_LINE}`;
1049
+ }
1112
1050
 
1113
- // packages/utils/src/lib/file-system.ts
1114
- import { bundleRequire } from "bundle-require";
1115
- import chalk2 from "chalk";
1116
- import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
1117
- import { join } from "node:path";
1051
+ // packages/utils/src/lib/text-formats/html/font-style.ts
1052
+ var boldElement = "b";
1053
+ function bold(text) {
1054
+ return `<${boldElement}>${text}</${boldElement}>`;
1055
+ }
1056
+ var italicElement = "i";
1057
+ function italic(text) {
1058
+ return `<${italicElement}>${text}</${italicElement}>`;
1059
+ }
1060
+ var codeElement = "code";
1061
+ function code(text) {
1062
+ return `<${codeElement}>${text}</${codeElement}>`;
1063
+ }
1118
1064
 
1119
- // packages/utils/src/lib/formatting.ts
1120
- function slugify(text) {
1121
- return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
1065
+ // packages/utils/src/lib/text-formats/html/link.ts
1066
+ function link2(href, text) {
1067
+ return `<a href="${href}">${text || href}"</a>`;
1068
+ }
1069
+
1070
+ // packages/utils/src/lib/transform.ts
1071
+ import { platform } from "node:os";
1072
+ function toArray(val) {
1073
+ return Array.isArray(val) ? val : [val];
1074
+ }
1075
+ function objectToKeys(obj) {
1076
+ return Object.keys(obj);
1077
+ }
1078
+ function objectToEntries(obj) {
1079
+ return Object.entries(obj);
1080
+ }
1081
+ function objectFromEntries(entries) {
1082
+ return Object.fromEntries(entries);
1083
+ }
1084
+ function countOccurrences(values) {
1085
+ return values.reduce(
1086
+ (acc, value) => ({ ...acc, [value]: (acc[value] ?? 0) + 1 }),
1087
+ {}
1088
+ );
1089
+ }
1090
+ function distinct(array) {
1091
+ return [...new Set(array)];
1122
1092
  }
1123
- function pluralize(text, amount) {
1124
- if (amount != null && Math.abs(amount) === 1) {
1125
- return text;
1126
- }
1127
- if (text.endsWith("y")) {
1128
- return `${text.slice(0, -1)}ies`;
1129
- }
1130
- if (text.endsWith("s")) {
1131
- return `${text}es`;
1132
- }
1133
- return `${text}s`;
1093
+ function deepClone(obj) {
1094
+ return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
1134
1095
  }
1135
- function formatBytes(bytes, decimals = 2) {
1136
- const positiveBytes = Math.max(bytes, 0);
1137
- if (positiveBytes === 0) {
1138
- return "0 B";
1096
+ function factorOf(items, filterFn) {
1097
+ const itemCount = items.length;
1098
+ if (!itemCount) {
1099
+ return 1;
1139
1100
  }
1140
- const k = 1024;
1141
- const dm = decimals < 0 ? 0 : decimals;
1142
- const sizes = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
1143
- const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
1144
- return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
1145
- }
1146
- function pluralizeToken(token, times) {
1147
- return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
1101
+ const filterCount = items.filter(filterFn).length;
1102
+ return filterCount === 0 ? 1 : (itemCount - filterCount) / itemCount;
1148
1103
  }
1149
- function formatDuration(duration) {
1150
- if (duration < 1e3) {
1151
- return `${duration} ms`;
1104
+ function objectToCliArgs(params) {
1105
+ if (!params) {
1106
+ return [];
1152
1107
  }
1153
- return `${(duration / 1e3).toFixed(2)} s`;
1108
+ return Object.entries(params).flatMap(([key, value]) => {
1109
+ if (key === "_") {
1110
+ return Array.isArray(value) ? value : [`${value}`];
1111
+ }
1112
+ const prefix = key.length === 1 ? "-" : "--";
1113
+ if (Array.isArray(value)) {
1114
+ return value.map((v) => `${prefix}${key}="${v}"`);
1115
+ }
1116
+ if (Array.isArray(value)) {
1117
+ return value.map((v) => `${prefix}${key}="${v}"`);
1118
+ }
1119
+ if (typeof value === "string") {
1120
+ return [`${prefix}${key}="${value}"`];
1121
+ }
1122
+ if (typeof value === "number") {
1123
+ return [`${prefix}${key}=${value}`];
1124
+ }
1125
+ if (typeof value === "boolean") {
1126
+ return [`${prefix}${value ? "" : "no-"}${key}`];
1127
+ }
1128
+ throw new Error(`Unsupported type ${typeof value} for key ${key}`);
1129
+ });
1154
1130
  }
1155
- function formatDate(date) {
1156
- const locale = "en-US";
1157
- return date.toLocaleString(locale, {
1158
- weekday: "short",
1159
- month: "short",
1160
- day: "numeric",
1161
- year: "numeric",
1162
- hour: "numeric",
1163
- minute: "2-digit",
1164
- timeZoneName: "short"
1165
- }).replace(/\u202F/g, " ");
1131
+ function toUnixPath(path) {
1132
+ return path.replace(/\\/g, "/");
1166
1133
  }
1167
- function truncateText(text, maxChars) {
1168
- if (text.length <= maxChars) {
1169
- return text;
1170
- }
1171
- const ellipsis = "...";
1172
- return text.slice(0, maxChars - ellipsis.length) + ellipsis;
1134
+ function toUnixNewlines(text) {
1135
+ return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
1173
1136
  }
1174
- function truncateTitle(text) {
1175
- return truncateText(text, MAX_TITLE_LENGTH);
1137
+ function fromJsonLines(jsonLines) {
1138
+ const unifiedNewLines = toUnixNewlines(jsonLines).trim();
1139
+ return JSON.parse(`[${unifiedNewLines.split("\n").join(",")}]`);
1176
1140
  }
1177
- function truncateDescription(text) {
1178
- return truncateText(text, MAX_DESCRIPTION_LENGTH);
1141
+ function toJsonLines(json) {
1142
+ return json.map((item) => JSON.stringify(item)).join("\n");
1179
1143
  }
1180
- function truncateIssueMessage(text) {
1181
- return truncateText(text, MAX_ISSUE_MESSAGE_LENGTH);
1144
+ function capitalize(text) {
1145
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
1146
+ 1
1147
+ )}`;
1182
1148
  }
1183
-
1184
- // packages/utils/src/lib/guards.ts
1185
- function isPromiseFulfilledResult(result) {
1186
- return result.status === "fulfilled";
1149
+ function apostrophize(text, upperCase) {
1150
+ const lastCharMatch = text.match(/(\w)\W*$/);
1151
+ const lastChar = lastCharMatch?.[1] ?? "";
1152
+ return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
1187
1153
  }
1188
- function isPromiseRejectedResult(result) {
1189
- return result.status === "rejected";
1154
+ function toNumberPrecision(value, decimalPlaces) {
1155
+ return Number(
1156
+ `${Math.round(
1157
+ Number.parseFloat(`${value}e${decimalPlaces}`)
1158
+ )}e-${decimalPlaces}`
1159
+ );
1190
1160
  }
1191
-
1192
- // packages/utils/src/lib/logging.ts
1193
- import isaacs_cliui from "@isaacs/cliui";
1194
- import { cliui } from "@poppinss/cliui";
1195
- import chalk from "chalk";
1196
-
1197
- // packages/utils/src/lib/reports/constants.ts
1198
- var TERMINAL_WIDTH = 80;
1199
- var SCORE_COLOR_RANGE = {
1200
- GREEN_MIN: 0.9,
1201
- YELLOW_MIN: 0.5
1202
- };
1203
- var CATEGORIES_TITLE = "\u{1F3F7} Categories";
1204
- var FOOTER_PREFIX = "Made with \u2764 by";
1205
- var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
1206
- var README_LINK = "https://github.com/code-pushup/cli#readme";
1207
- var reportHeadlineText = "Code PushUp Report";
1208
- var reportOverviewTableHeaders = [
1209
- {
1210
- key: "category",
1211
- label: "\u{1F3F7} Category",
1212
- align: "left"
1213
- },
1214
- {
1215
- key: "score",
1216
- label: "\u2B50 Score"
1217
- },
1218
- {
1219
- key: "audits",
1220
- label: "\u{1F6E1} Audits"
1161
+ function toOrdinal(value) {
1162
+ if (value % 10 === 1 && value % 100 !== 11) {
1163
+ return `${value}st`;
1221
1164
  }
1222
- ];
1223
- var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
1224
- var issuesTableHeadings = [
1225
- {
1226
- key: "severity",
1227
- label: "Severity"
1228
- },
1229
- {
1230
- key: "message",
1231
- label: "Message"
1232
- },
1233
- {
1234
- key: "file",
1235
- label: "Source file"
1236
- },
1237
- {
1238
- key: "line",
1239
- label: "Line(s)"
1165
+ if (value % 10 === 2 && value % 100 !== 12) {
1166
+ return `${value}nd`;
1240
1167
  }
1241
- ];
1168
+ if (value % 10 === 3 && value % 100 !== 13) {
1169
+ return `${value}rd`;
1170
+ }
1171
+ return `${value}th`;
1172
+ }
1242
1173
 
1243
- // packages/utils/src/lib/logging.ts
1244
- var singletonUiInstance;
1245
- function ui() {
1246
- if (singletonUiInstance === void 0) {
1247
- singletonUiInstance = cliui();
1174
+ // packages/utils/src/lib/table.ts
1175
+ function rowToStringArray({ rows, columns = [] }) {
1176
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
1177
+ throw new TypeError(
1178
+ "Column can`t be object when rows are primitive values"
1179
+ );
1248
1180
  }
1249
- return {
1250
- ...singletonUiInstance,
1251
- row: (args) => {
1252
- logListItem(args);
1181
+ return rows.map((row) => {
1182
+ if (Array.isArray(row)) {
1183
+ return row.map(String);
1253
1184
  }
1254
- };
1185
+ const objectRow = row;
1186
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
1187
+ return Object.values(objectRow).map(String);
1188
+ }
1189
+ return columns.map(
1190
+ ({ key }) => String(objectRow[key])
1191
+ );
1192
+ });
1255
1193
  }
1256
- var singletonisaacUi;
1257
- function logListItem(args) {
1258
- if (singletonisaacUi === void 0) {
1259
- singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
1194
+ function columnsToStringArray({ rows, columns = [] }) {
1195
+ const firstRow = rows.at(0);
1196
+ const primitiveRows = Array.isArray(firstRow);
1197
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
1198
+ throw new Error("invalid union type. Caught by model parsing.");
1260
1199
  }
1261
- singletonisaacUi.div(...args);
1262
- const content = singletonisaacUi.toString();
1263
- singletonisaacUi.rows = [];
1264
- singletonUiInstance?.logger.log(content);
1200
+ if (columns.length === 0) {
1201
+ if (Array.isArray(firstRow)) {
1202
+ return firstRow.map((_, idx) => String(idx));
1203
+ }
1204
+ return Object.keys(firstRow);
1205
+ }
1206
+ if (typeof columns.at(0) === "string") {
1207
+ return columns.map(String);
1208
+ }
1209
+ const cols = columns;
1210
+ return cols.map(({ label, key }) => label ?? capitalize(key));
1265
1211
  }
1266
- function link3(text) {
1267
- return chalk.underline(chalk.blueBright(text));
1212
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
1213
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
1214
+ if (typeof column === "string") {
1215
+ return column;
1216
+ } else if (typeof column === "object") {
1217
+ return column.align ?? "center";
1218
+ } else {
1219
+ return "center";
1220
+ }
1268
1221
  }
1269
-
1270
- // packages/utils/src/lib/log-results.ts
1271
- function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
1272
- if (succeededTransform) {
1273
- const succeededResults = results.filter(isPromiseFulfilledResult);
1274
- logPromiseResults(
1275
- succeededResults,
1276
- `${messagePrefix} successfully: `,
1277
- succeededTransform
1278
- );
1222
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
1223
+ const column = columns.at(targetIdx);
1224
+ if (column == null) {
1225
+ return "center";
1226
+ } else if (typeof column === "string") {
1227
+ return column;
1228
+ } else if (typeof column === "object") {
1229
+ return column.align ?? "center";
1230
+ } else {
1231
+ return "center";
1232
+ }
1233
+ }
1234
+ function getColumnAlignments({
1235
+ rows,
1236
+ columns = []
1237
+ }) {
1238
+ if (rows.at(0) == null) {
1239
+ throw new Error("first row can`t be undefined.");
1279
1240
  }
1280
- if (failedTransform) {
1281
- const failedResults = results.filter(isPromiseRejectedResult);
1282
- logPromiseResults(
1283
- failedResults,
1284
- `${messagePrefix} failed: `,
1285
- failedTransform
1241
+ if (Array.isArray(rows.at(0))) {
1242
+ const firstPrimitiveRow = rows.at(0);
1243
+ return Array.from({ length: firstPrimitiveRow.length }).map(
1244
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
1286
1245
  );
1287
1246
  }
1247
+ const firstObject = rows.at(0);
1248
+ return Object.keys(firstObject).map(
1249
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
1250
+ );
1288
1251
  }
1289
- function logPromiseResults(results, logMessage, getMsg) {
1290
- if (results.length > 0) {
1291
- const log2 = results[0]?.status === "fulfilled" ? (m) => {
1292
- ui().logger.success(m);
1293
- } : (m) => {
1294
- ui().logger.warning(m);
1295
- };
1296
- log2(logMessage);
1297
- results.forEach((result) => {
1298
- log2(getMsg(result));
1299
- });
1252
+
1253
+ // packages/utils/src/lib/text-formats/html/table.ts
1254
+ function wrap(elem, content) {
1255
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
1256
+ }
1257
+ function wrapRow(content) {
1258
+ const elem = "tr";
1259
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
1260
+ }
1261
+ function table(tableData) {
1262
+ if (tableData.rows.length === 0) {
1263
+ throw new Error("Data can't be empty");
1300
1264
  }
1265
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
1266
+ const tableHeaderRow = wrapRow(tableHeaderCols);
1267
+ const tableBody = rowToStringArray(tableData).map((arr) => {
1268
+ const columns = arr.map((s) => wrap("td", s)).join("");
1269
+ return wrapRow(columns);
1270
+ }).join("");
1271
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
1301
1272
  }
1302
1273
 
1303
- // packages/utils/src/lib/file-system.ts
1304
- async function readTextFile(path) {
1305
- const buffer = await readFile(path);
1306
- return buffer.toString();
1274
+ // packages/utils/src/lib/text-formats/md/font-style.ts
1275
+ var boldWrap = "**";
1276
+ function bold2(text) {
1277
+ return `${boldWrap}${text}${boldWrap}`;
1307
1278
  }
1308
- async function readJsonFile(path) {
1309
- const text = await readTextFile(path);
1310
- return JSON.parse(text);
1279
+ var italicWrap = "_";
1280
+ function italic2(text) {
1281
+ return `${italicWrap}${text}${italicWrap}`;
1311
1282
  }
1312
- async function fileExists(path) {
1313
- try {
1314
- const stats = await stat(path);
1315
- return stats.isFile();
1316
- } catch {
1317
- return false;
1318
- }
1283
+ var strikeThroughWrap = "~";
1284
+ function strikeThrough(text) {
1285
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
1319
1286
  }
1320
- async function directoryExists(path) {
1321
- try {
1322
- const stats = await stat(path);
1323
- return stats.isDirectory();
1324
- } catch {
1325
- return false;
1326
- }
1287
+ var codeWrap = "`";
1288
+ function code2(text) {
1289
+ return `${codeWrap}${text}${codeWrap}`;
1327
1290
  }
1328
- async function ensureDirectoryExists(baseDir) {
1329
- try {
1330
- await mkdir(baseDir, { recursive: true });
1331
- return;
1332
- } catch (error) {
1333
- ui().logger.info(error.message);
1334
- if (error.code !== "EEXIST") {
1335
- throw error;
1336
- }
1337
- }
1291
+
1292
+ // packages/utils/src/lib/text-formats/md/headline.ts
1293
+ function headline(text, hierarchy = 1) {
1294
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
1338
1295
  }
1339
- async function removeDirectoryIfExists(dir) {
1340
- if (await directoryExists(dir)) {
1341
- await rm(dir, { recursive: true, force: true });
1342
- }
1296
+ function h(text, hierarchy = 1) {
1297
+ return headline(text, hierarchy);
1343
1298
  }
1344
- function logMultipleFileResults(fileResults, messagePrefix) {
1345
- const succeededTransform = (result) => {
1346
- const [fileName, size] = result.value;
1347
- const formattedSize = size ? ` (${chalk2.gray(formatBytes(size))})` : "";
1348
- return `- ${chalk2.bold(fileName)}${formattedSize}`;
1349
- };
1350
- const failedTransform = (result) => `- ${chalk2.bold(result.reason)}`;
1351
- logMultipleResults(
1352
- fileResults,
1353
- messagePrefix,
1354
- succeededTransform,
1355
- failedTransform
1356
- );
1299
+ function h1(text) {
1300
+ return headline(text, 1);
1357
1301
  }
1358
- var NoExportError = class extends Error {
1359
- constructor(filepath) {
1360
- super(`No default export found in ${filepath}`);
1361
- }
1362
- };
1363
- async function importEsmModule(options) {
1364
- const { mod } = await bundleRequire({
1365
- format: "esm",
1366
- ...options
1367
- });
1368
- if (!("default" in mod)) {
1369
- throw new NoExportError(options.filepath);
1370
- }
1371
- return mod.default;
1302
+ function h2(text) {
1303
+ return headline(text, 2);
1372
1304
  }
1373
- function pluginWorkDir(slug) {
1374
- return join("node_modules", ".code-pushup", slug);
1305
+ function h3(text) {
1306
+ return headline(text, 3);
1375
1307
  }
1376
- async function crawlFileSystem(options) {
1377
- const {
1378
- directory,
1379
- pattern,
1380
- fileTransform = (filePath) => filePath
1381
- } = options;
1382
- const files = await readdir(directory);
1383
- const promises = files.map(async (file) => {
1384
- const filePath = join(directory, file);
1385
- const stats = await stat(filePath);
1386
- if (stats.isDirectory()) {
1387
- return crawlFileSystem({ directory: filePath, pattern, fileTransform });
1388
- }
1389
- if (stats.isFile() && (!pattern || new RegExp(pattern).test(file))) {
1390
- return fileTransform(filePath);
1391
- }
1392
- return [];
1393
- });
1394
- const resultsNestedArray = await Promise.all(promises);
1395
- return resultsNestedArray.flat();
1308
+ function h4(text) {
1309
+ return headline(text, 4);
1396
1310
  }
1397
- function findLineNumberInText(content, pattern) {
1398
- const lines6 = content.split(/\r?\n/);
1399
- const lineNumber = lines6.findIndex((line) => line.includes(pattern)) + 1;
1400
- return lineNumber === 0 ? null : lineNumber;
1311
+ function h5(text) {
1312
+ return headline(text, 5);
1313
+ }
1314
+ function h6(text) {
1315
+ return headline(text, 6);
1316
+ }
1317
+
1318
+ // packages/utils/src/lib/text-formats/md/image.ts
1319
+ function image(src, alt) {
1320
+ return `![${alt}](${src})`;
1321
+ }
1322
+
1323
+ // packages/utils/src/lib/text-formats/md/link.ts
1324
+ function link3(href, text) {
1325
+ return `[${text || href}](${href})`;
1326
+ }
1327
+
1328
+ // packages/utils/src/lib/text-formats/md/list.ts
1329
+ function li(text, order = "unordered") {
1330
+ const style = order === "unordered" ? "-" : "- [ ]";
1331
+ return `${style} ${text}`;
1332
+ }
1333
+ function indentation(text, level = 1) {
1334
+ return `${TAB.repeat(level)}${text}`;
1335
+ }
1336
+
1337
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
1338
+ function paragraphs(...sections) {
1339
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
1340
+ }
1341
+
1342
+ // packages/utils/src/lib/text-formats/md/section.ts
1343
+ function section(...contents) {
1344
+ return `${lines(...contents)}${NEW_LINE}`;
1345
+ }
1346
+ function lines(...contents) {
1347
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
1348
+ }
1349
+
1350
+ // packages/utils/src/lib/text-formats/md/table.ts
1351
+ var alignString = /* @__PURE__ */ new Map([
1352
+ ["left", ":--"],
1353
+ ["center", ":--:"],
1354
+ ["right", "--:"]
1355
+ ]);
1356
+ function tableRow(rows) {
1357
+ return `|${rows.join("|")}|`;
1358
+ }
1359
+ function table2(data) {
1360
+ if (data.rows.length === 0) {
1361
+ throw new Error("Data can't be empty");
1362
+ }
1363
+ const alignmentRow = getColumnAlignments(data).map(
1364
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
1365
+ );
1366
+ return section(
1367
+ `${lines(
1368
+ tableRow(columnsToStringArray(data)),
1369
+ tableRow(alignmentRow),
1370
+ ...rowToStringArray(data).map(tableRow)
1371
+ )}`
1372
+ );
1401
1373
  }
1402
1374
 
1375
+ // packages/utils/src/lib/text-formats/index.ts
1376
+ var md = {
1377
+ bold: bold2,
1378
+ italic: italic2,
1379
+ strikeThrough,
1380
+ code: code2,
1381
+ link: link3,
1382
+ image,
1383
+ headline,
1384
+ h,
1385
+ h1,
1386
+ h2,
1387
+ h3,
1388
+ h4,
1389
+ h5,
1390
+ h6,
1391
+ indentation,
1392
+ lines,
1393
+ li,
1394
+ section,
1395
+ paragraphs,
1396
+ table: table2
1397
+ };
1398
+ var html = {
1399
+ bold,
1400
+ italic,
1401
+ code,
1402
+ link: link2,
1403
+ details,
1404
+ table
1405
+ };
1406
+
1403
1407
  // packages/utils/src/lib/reports/utils.ts
1404
1408
  var { image: image2, bold: boldMd } = md;
1405
1409
  function formatReportScore(score) {
1406
- return Math.round(score * 100).toString();
1410
+ const scaledScore = score * 100;
1411
+ const roundedScore = Math.round(scaledScore);
1412
+ return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
1407
1413
  }
1408
1414
  function formatScoreWithColor(score, options) {
1409
1415
  const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
@@ -2716,6 +2722,7 @@ export {
2716
2722
  exists,
2717
2723
  factorOf,
2718
2724
  fileExists,
2725
+ filePathToCliArg,
2719
2726
  filterItemRefsBy,
2720
2727
  findLineNumberInText,
2721
2728
  formatBytes,
@@ -2738,7 +2745,7 @@ export {
2738
2745
  isPromiseFulfilledResult,
2739
2746
  isPromiseRejectedResult,
2740
2747
  isSemver,
2741
- link3 as link,
2748
+ link,
2742
2749
  listAuditsFromAllPlugins,
2743
2750
  listGroupsFromAllPlugins,
2744
2751
  loadReport,