@code-pushup/lighthouse-plugin 0.42.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 ADDED
@@ -0,0 +1,1530 @@
1
+ // packages/plugin-lighthouse/src/lib/constants.ts
2
+ import { join } from "node:path";
3
+
4
+ // packages/models/src/lib/implementation/schemas.ts
5
+ import { MATERIAL_ICONS } from "vscode-material-icons";
6
+ import { z } from "zod";
7
+
8
+ // packages/models/src/lib/implementation/limits.ts
9
+ var MAX_SLUG_LENGTH = 128;
10
+ var MAX_TITLE_LENGTH = 256;
11
+ var MAX_DESCRIPTION_LENGTH = 65536;
12
+ var MAX_ISSUE_MESSAGE_LENGTH = 1024;
13
+
14
+ // packages/models/src/lib/implementation/utils.ts
15
+ var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
16
+ var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
17
+ function hasDuplicateStrings(strings) {
18
+ const sortedStrings = [...strings].sort();
19
+ const duplStrings = sortedStrings.filter(
20
+ (item, index) => index !== 0 && item === sortedStrings[index - 1]
21
+ );
22
+ return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
23
+ }
24
+ function hasMissingStrings(toCheck, existing) {
25
+ const nonExisting = toCheck.filter((s) => !existing.includes(s));
26
+ return nonExisting.length === 0 ? false : nonExisting;
27
+ }
28
+ function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
29
+ return transform(items || []);
30
+ }
31
+ function exists(value) {
32
+ return value != null;
33
+ }
34
+ function getMissingRefsForCategories(categories2, plugins) {
35
+ if (categories2.length === 0) {
36
+ return false;
37
+ }
38
+ const auditRefsFromCategory = categories2.flatMap(
39
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
40
+ );
41
+ const auditRefsFromPlugins = plugins.flatMap(
42
+ ({ audits: audits2, slug: pluginSlug }) => audits2.map(({ slug }) => `${pluginSlug}/${slug}`)
43
+ );
44
+ const missingAuditRefs = hasMissingStrings(
45
+ auditRefsFromCategory,
46
+ auditRefsFromPlugins
47
+ );
48
+ const groupRefsFromCategory = categories2.flatMap(
49
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
50
+ );
51
+ const groupRefsFromPlugins = plugins.flatMap(
52
+ ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
53
+ );
54
+ const missingGroupRefs = hasMissingStrings(
55
+ groupRefsFromCategory,
56
+ groupRefsFromPlugins
57
+ );
58
+ const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
59
+ return missingRefs.length > 0 ? missingRefs : false;
60
+ }
61
+ function missingRefsForCategoriesErrorMsg(categories2, plugins) {
62
+ const missingRefs = getMissingRefsForCategories(categories2, plugins);
63
+ return `The following category references need to point to an audit or group: ${errorItems(
64
+ missingRefs
65
+ )}`;
66
+ }
67
+
68
+ // packages/models/src/lib/implementation/schemas.ts
69
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
70
+ function executionMetaSchema(options = {
71
+ descriptionDate: "Execution start date and time",
72
+ descriptionDuration: "Execution duration in ms"
73
+ }) {
74
+ return z.object({
75
+ date: z.string({ description: options.descriptionDate }),
76
+ duration: z.number({ description: options.descriptionDuration })
77
+ });
78
+ }
79
+ var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
80
+ message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
81
+ }).max(MAX_SLUG_LENGTH, {
82
+ message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
83
+ });
84
+ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
85
+ var urlSchema = z.string().url();
86
+ var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
87
+ var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
88
+ var scoreSchema = z.number({
89
+ description: "Value between 0 and 1"
90
+ }).min(0).max(1);
91
+ function metaSchema(options) {
92
+ const {
93
+ descriptionDescription,
94
+ titleDescription,
95
+ docsUrlDescription,
96
+ description
97
+ } = options ?? {};
98
+ return z.object(
99
+ {
100
+ title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
101
+ description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
102
+ docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
103
+ },
104
+ { description }
105
+ );
106
+ }
107
+ var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
108
+ var fileNameSchema = z.string().trim().regex(filenameRegex, {
109
+ message: `The filename has to be valid`
110
+ }).min(1, { message: "file name is invalid" });
111
+ var positiveIntSchema = z.number().int().positive();
112
+ var nonnegativeIntSchema = z.number().int().nonnegative();
113
+ function packageVersionSchema(options) {
114
+ const { versionDescription = "NPM version of the package", required } = options ?? {};
115
+ const packageSchema = z.string({ description: "NPM package name" });
116
+ const versionSchema = z.string({ description: versionDescription });
117
+ return z.object(
118
+ {
119
+ packageName: required ? packageSchema : packageSchema.optional(),
120
+ version: required ? versionSchema : versionSchema.optional()
121
+ },
122
+ { description: "NPM package name and version of a published package" }
123
+ );
124
+ }
125
+ var weightSchema = nonnegativeIntSchema.describe(
126
+ "Coefficient for the given score (use weight 0 if only for display)"
127
+ );
128
+ function weightedRefSchema(description, slugDescription) {
129
+ return z.object(
130
+ {
131
+ slug: slugSchema.describe(slugDescription),
132
+ weight: weightSchema.describe("Weight used to calculate score")
133
+ },
134
+ { description }
135
+ );
136
+ }
137
+ function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
138
+ return z.object(
139
+ {
140
+ slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
141
+ refs: z.array(refSchema).min(1).refine(
142
+ (refs) => !duplicateCheckFn(refs),
143
+ (refs) => ({
144
+ message: duplicateMessageFn(refs)
145
+ })
146
+ ).refine(hasNonZeroWeightedRef, () => ({
147
+ message: "In a category there has to be at least one ref with weight > 0"
148
+ }))
149
+ },
150
+ { description }
151
+ );
152
+ }
153
+ var materialIconSchema = z.enum(MATERIAL_ICONS, {
154
+ description: "Icon from VSCode Material Icons extension"
155
+ });
156
+ function hasNonZeroWeightedRef(refs) {
157
+ return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
158
+ }
159
+
160
+ // packages/models/src/lib/audit.ts
161
+ import { z as z2 } from "zod";
162
+ var auditSchema = z2.object({
163
+ slug: slugSchema.describe("ID (unique within plugin)")
164
+ }).merge(
165
+ metaSchema({
166
+ titleDescription: "Descriptive name",
167
+ descriptionDescription: "Description (markdown)",
168
+ docsUrlDescription: "Link to documentation (rationale)",
169
+ description: "List of scorable metrics for the given plugin"
170
+ })
171
+ );
172
+ var pluginAuditsSchema = z2.array(auditSchema, {
173
+ description: "List of audits maintained in a plugin"
174
+ }).min(1).refine(
175
+ (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
176
+ (auditMetadata) => ({
177
+ message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
178
+ })
179
+ );
180
+ function duplicateSlugsInAuditsErrorMsg(audits2) {
181
+ const duplicateRefs = getDuplicateSlugsInAudits(audits2);
182
+ return `In plugin audits the following slugs are not unique: ${errorItems(
183
+ duplicateRefs
184
+ )}`;
185
+ }
186
+ function getDuplicateSlugsInAudits(audits2) {
187
+ return hasDuplicateStrings(audits2.map(({ slug }) => slug));
188
+ }
189
+
190
+ // packages/models/src/lib/audit-output.ts
191
+ import { z as z5 } from "zod";
192
+
193
+ // packages/models/src/lib/issue.ts
194
+ import { z as z3 } from "zod";
195
+ var sourceFileLocationSchema = z3.object(
196
+ {
197
+ file: filePathSchema.describe("Relative path to source file in Git repo"),
198
+ position: z3.object(
199
+ {
200
+ startLine: positiveIntSchema.describe("Start line"),
201
+ startColumn: positiveIntSchema.describe("Start column").optional(),
202
+ endLine: positiveIntSchema.describe("End line").optional(),
203
+ endColumn: positiveIntSchema.describe("End column").optional()
204
+ },
205
+ { description: "Location in file" }
206
+ ).optional()
207
+ },
208
+ { description: "Source file location" }
209
+ );
210
+ var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
211
+ description: "Severity level"
212
+ });
213
+ var issueSchema = z3.object(
214
+ {
215
+ message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
216
+ severity: issueSeveritySchema,
217
+ source: sourceFileLocationSchema.optional()
218
+ },
219
+ { description: "Issue information" }
220
+ );
221
+
222
+ // packages/models/src/lib/table.ts
223
+ import { z as z4 } from "zod";
224
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
225
+ description: "Cell alignment"
226
+ });
227
+ var tableColumnObjectSchema = z4.object({
228
+ key: z4.string(),
229
+ label: z4.string().optional(),
230
+ align: tableAlignmentSchema.optional()
231
+ });
232
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
233
+ description: "Object row"
234
+ });
235
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
236
+ description: "Primitive row"
237
+ });
238
+ var tableSharedSchema = z4.object({
239
+ title: z4.string().optional().describe("Display title for table")
240
+ });
241
+ var tablePrimitiveSchema = tableSharedSchema.merge(
242
+ z4.object(
243
+ {
244
+ columns: z4.array(tableAlignmentSchema).optional(),
245
+ rows: z4.array(tableRowPrimitiveSchema)
246
+ },
247
+ { description: "Table with primitive rows and optional alignment columns" }
248
+ )
249
+ );
250
+ var tableObjectSchema = tableSharedSchema.merge(
251
+ z4.object(
252
+ {
253
+ columns: z4.union([
254
+ z4.array(tableAlignmentSchema),
255
+ z4.array(tableColumnObjectSchema)
256
+ ]).optional(),
257
+ rows: z4.array(tableRowObjectSchema)
258
+ },
259
+ {
260
+ description: "Table with object rows and optional alignment or object columns"
261
+ }
262
+ )
263
+ );
264
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
265
+
266
+ // packages/models/src/lib/audit-output.ts
267
+ var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
268
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
269
+ var auditDetailsSchema = z5.object(
270
+ {
271
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
272
+ table: tableSchema("Table of related findings").optional()
273
+ },
274
+ { description: "Detailed information" }
275
+ );
276
+ var auditOutputSchema = z5.object(
277
+ {
278
+ slug: slugSchema.describe("Reference to audit"),
279
+ displayValue: auditDisplayValueSchema,
280
+ value: auditValueSchema,
281
+ score: scoreSchema,
282
+ details: auditDetailsSchema.optional()
283
+ },
284
+ { description: "Audit information" }
285
+ );
286
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
287
+ description: "List of JSON formatted audit output emitted by the runner process of a plugin"
288
+ }).refine(
289
+ (audits2) => !getDuplicateSlugsInAudits2(audits2),
290
+ (audits2) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits2) })
291
+ );
292
+ function duplicateSlugsInAuditsErrorMsg2(audits2) {
293
+ const duplicateRefs = getDuplicateSlugsInAudits2(audits2);
294
+ return `In plugin audits the slugs are not unique: ${errorItems(
295
+ duplicateRefs
296
+ )}`;
297
+ }
298
+ function getDuplicateSlugsInAudits2(audits2) {
299
+ return hasDuplicateStrings(audits2.map(({ slug }) => slug));
300
+ }
301
+
302
+ // packages/models/src/lib/category-config.ts
303
+ import { z as z6 } from "zod";
304
+ var categoryRefSchema = weightedRefSchema(
305
+ "Weighted references to audits and/or groups for the category",
306
+ "Slug of an audit or group (depending on `type`)"
307
+ ).merge(
308
+ z6.object({
309
+ type: z6.enum(["audit", "group"], {
310
+ description: "Discriminant for reference kind, affects where `slug` is looked up"
311
+ }),
312
+ plugin: slugSchema.describe(
313
+ "Plugin slug (plugin should contain referenced audit or group)"
314
+ )
315
+ })
316
+ );
317
+ var categoryConfigSchema = scorableSchema(
318
+ "Category with a score calculated from audits and groups from various plugins",
319
+ categoryRefSchema,
320
+ getDuplicateRefsInCategoryMetrics,
321
+ duplicateRefsInCategoryMetricsErrorMsg
322
+ ).merge(
323
+ metaSchema({
324
+ titleDescription: "Category Title",
325
+ docsUrlDescription: "Category docs URL",
326
+ descriptionDescription: "Category description",
327
+ description: "Meta info for category"
328
+ })
329
+ ).merge(
330
+ z6.object({
331
+ isBinary: z6.boolean({
332
+ description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
333
+ }).optional()
334
+ })
335
+ );
336
+ function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
337
+ const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
338
+ return `In the categories, the following audit or group refs are duplicates: ${errorItems(
339
+ duplicateRefs
340
+ )}`;
341
+ }
342
+ function getDuplicateRefsInCategoryMetrics(metrics) {
343
+ return hasDuplicateStrings(
344
+ metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
345
+ );
346
+ }
347
+ var categoriesSchema = z6.array(categoryConfigSchema, {
348
+ description: "Categorization of individual audits"
349
+ }).refine(
350
+ (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
351
+ (categoryCfg) => ({
352
+ message: duplicateSlugCategoriesErrorMsg(categoryCfg)
353
+ })
354
+ );
355
+ function duplicateSlugCategoriesErrorMsg(categories2) {
356
+ const duplicateStringSlugs = getDuplicateSlugCategories(categories2);
357
+ return `In the categories, the following slugs are duplicated: ${errorItems(
358
+ duplicateStringSlugs
359
+ )}`;
360
+ }
361
+ function getDuplicateSlugCategories(categories2) {
362
+ return hasDuplicateStrings(categories2.map(({ slug }) => slug));
363
+ }
364
+
365
+ // packages/models/src/lib/commit.ts
366
+ import { z as z7 } from "zod";
367
+ var commitSchema = z7.object(
368
+ {
369
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
370
+ /^[\da-f]{40}$/,
371
+ "Commit SHA should be a 40-character hexadecimal string"
372
+ ),
373
+ message: z7.string({ description: "Commit message" }),
374
+ date: z7.coerce.date({
375
+ description: "Date and time when commit was authored"
376
+ }),
377
+ author: z7.string({
378
+ description: "Commit author name"
379
+ }).trim()
380
+ },
381
+ { description: "Git commit" }
382
+ );
383
+
384
+ // packages/models/src/lib/core-config.ts
385
+ import { z as z13 } from "zod";
386
+
387
+ // packages/models/src/lib/persist-config.ts
388
+ import { z as z8 } from "zod";
389
+ var formatSchema = z8.enum(["json", "md"]);
390
+ var persistConfigSchema = z8.object({
391
+ outputDir: filePathSchema.describe("Artifacts folder").optional(),
392
+ filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
393
+ format: z8.array(formatSchema).optional()
394
+ });
395
+
396
+ // packages/models/src/lib/plugin-config.ts
397
+ import { z as z11 } from "zod";
398
+
399
+ // packages/models/src/lib/group.ts
400
+ import { z as z9 } from "zod";
401
+ var groupRefSchema = weightedRefSchema(
402
+ "Weighted reference to a group",
403
+ "Reference slug to a group within this plugin (e.g. 'max-lines')"
404
+ );
405
+ var groupMetaSchema = metaSchema({
406
+ titleDescription: "Descriptive name for the group",
407
+ descriptionDescription: "Description of the group (markdown)",
408
+ docsUrlDescription: "Group documentation site",
409
+ description: "Group metadata"
410
+ });
411
+ var groupSchema = scorableSchema(
412
+ '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',
413
+ groupRefSchema,
414
+ getDuplicateRefsInGroups,
415
+ duplicateRefsInGroupsErrorMsg
416
+ ).merge(groupMetaSchema);
417
+ var groupsSchema = z9.array(groupSchema, {
418
+ description: "List of groups"
419
+ }).optional().refine(
420
+ (groups) => !getDuplicateSlugsInGroups(groups),
421
+ (groups) => ({
422
+ message: duplicateSlugsInGroupsErrorMsg(groups)
423
+ })
424
+ );
425
+ function duplicateRefsInGroupsErrorMsg(groups) {
426
+ const duplicateRefs = getDuplicateRefsInGroups(groups);
427
+ return `In plugin groups the following references are not unique: ${errorItems(
428
+ duplicateRefs
429
+ )}`;
430
+ }
431
+ function getDuplicateRefsInGroups(groups) {
432
+ return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
433
+ }
434
+ function duplicateSlugsInGroupsErrorMsg(groups) {
435
+ const duplicateRefs = getDuplicateSlugsInGroups(groups);
436
+ return `In groups the following slugs are not unique: ${errorItems(
437
+ duplicateRefs
438
+ )}`;
439
+ }
440
+ function getDuplicateSlugsInGroups(groups) {
441
+ return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
442
+ }
443
+
444
+ // packages/models/src/lib/runner-config.ts
445
+ import { z as z10 } from "zod";
446
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
447
+ var runnerConfigSchema = z10.object(
448
+ {
449
+ command: z10.string({
450
+ description: "Shell command to execute"
451
+ }),
452
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
453
+ outputFile: filePathSchema.describe("Output path"),
454
+ outputTransform: outputTransformSchema.optional()
455
+ },
456
+ {
457
+ description: "How to execute runner"
458
+ }
459
+ );
460
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
461
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
462
+
463
+ // packages/models/src/lib/plugin-config.ts
464
+ var pluginMetaSchema = packageVersionSchema().merge(
465
+ metaSchema({
466
+ titleDescription: "Descriptive name",
467
+ descriptionDescription: "Description (markdown)",
468
+ docsUrlDescription: "Plugin documentation site",
469
+ description: "Plugin metadata"
470
+ })
471
+ ).merge(
472
+ z11.object({
473
+ slug: slugSchema.describe("Unique plugin slug within core config"),
474
+ icon: materialIconSchema
475
+ })
476
+ );
477
+ var pluginDataSchema = z11.object({
478
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
479
+ audits: pluginAuditsSchema,
480
+ groups: groupsSchema
481
+ });
482
+ var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
483
+ (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
484
+ (pluginCfg) => ({
485
+ message: missingRefsFromGroupsErrorMsg(pluginCfg)
486
+ })
487
+ );
488
+ function missingRefsFromGroupsErrorMsg(pluginCfg) {
489
+ const missingRefs = getMissingRefsFromGroups(pluginCfg);
490
+ return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
491
+ missingRefs
492
+ )}`;
493
+ }
494
+ function getMissingRefsFromGroups(pluginCfg) {
495
+ return hasMissingStrings(
496
+ pluginCfg.groups?.flatMap(
497
+ ({ refs: audits2 }) => audits2.map(({ slug: ref }) => ref)
498
+ ) ?? [],
499
+ pluginCfg.audits.map(({ slug }) => slug)
500
+ );
501
+ }
502
+
503
+ // packages/models/src/lib/upload-config.ts
504
+ import { z as z12 } from "zod";
505
+ var uploadConfigSchema = z12.object({
506
+ server: urlSchema.describe("URL of deployed portal API"),
507
+ apiKey: z12.string({
508
+ description: "API key with write access to portal (use `process.env` for security)"
509
+ }),
510
+ organization: slugSchema.describe(
511
+ "Organization slug from Code PushUp portal"
512
+ ),
513
+ project: slugSchema.describe("Project slug from Code PushUp portal"),
514
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
515
+ });
516
+
517
+ // packages/models/src/lib/core-config.ts
518
+ var unrefinedCoreConfigSchema = z13.object({
519
+ plugins: z13.array(pluginConfigSchema, {
520
+ description: "List of plugins to be used (official, community-provided, or custom)"
521
+ }).min(1),
522
+ /** portal configuration for persisting results */
523
+ persist: persistConfigSchema.optional(),
524
+ /** portal configuration for uploading results */
525
+ upload: uploadConfigSchema.optional(),
526
+ categories: categoriesSchema.optional()
527
+ });
528
+ var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
529
+ function refineCoreConfig(schema) {
530
+ return schema.refine(
531
+ (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
532
+ (coreCfg) => ({
533
+ message: missingRefsForCategoriesErrorMsg(
534
+ coreCfg.categories ?? [],
535
+ coreCfg.plugins
536
+ )
537
+ })
538
+ );
539
+ }
540
+
541
+ // packages/models/src/lib/implementation/constants.ts
542
+ var DEFAULT_PERSIST_OUTPUT_DIR = ".code-pushup";
543
+
544
+ // packages/models/src/lib/report.ts
545
+ import { z as z14 } from "zod";
546
+ var auditReportSchema = auditSchema.merge(auditOutputSchema);
547
+ var pluginReportSchema = pluginMetaSchema.merge(
548
+ executionMetaSchema({
549
+ descriptionDate: "Start date and time of plugin run",
550
+ descriptionDuration: "Duration of the plugin run in ms"
551
+ })
552
+ ).merge(
553
+ z14.object({
554
+ audits: z14.array(auditReportSchema).min(1),
555
+ groups: z14.array(groupSchema).optional()
556
+ })
557
+ ).refine(
558
+ (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
559
+ (pluginReport) => ({
560
+ message: missingRefsFromGroupsErrorMsg2(
561
+ pluginReport.audits,
562
+ pluginReport.groups ?? []
563
+ )
564
+ })
565
+ );
566
+ function missingRefsFromGroupsErrorMsg2(audits2, groups) {
567
+ const missingRefs = getMissingRefsFromGroups2(audits2, groups);
568
+ return `group references need to point to an existing audit in this plugin report: ${errorItems(
569
+ missingRefs
570
+ )}`;
571
+ }
572
+ function getMissingRefsFromGroups2(audits2, groups) {
573
+ return hasMissingStrings(
574
+ groups.flatMap(
575
+ ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
576
+ ),
577
+ audits2.map(({ slug }) => slug)
578
+ );
579
+ }
580
+ var reportSchema = packageVersionSchema({
581
+ versionDescription: "NPM version of the CLI",
582
+ required: true
583
+ }).merge(
584
+ executionMetaSchema({
585
+ descriptionDate: "Start date and time of the collect run",
586
+ descriptionDuration: "Duration of the collect run in ms"
587
+ })
588
+ ).merge(
589
+ z14.object(
590
+ {
591
+ categories: z14.array(categoryConfigSchema),
592
+ plugins: z14.array(pluginReportSchema).min(1),
593
+ commit: commitSchema.describe("Git commit for which report was collected").nullable()
594
+ },
595
+ { description: "Collect output data" }
596
+ )
597
+ ).refine(
598
+ (report) => !getMissingRefsForCategories(report.categories, report.plugins),
599
+ (report) => ({
600
+ message: missingRefsForCategoriesErrorMsg(
601
+ report.categories,
602
+ report.plugins
603
+ )
604
+ })
605
+ );
606
+
607
+ // packages/models/src/lib/reports-diff.ts
608
+ import { z as z15 } from "zod";
609
+ function makeComparisonSchema(schema) {
610
+ const sharedDescription = schema.description || "Result";
611
+ return z15.object({
612
+ before: schema.describe(`${sharedDescription} (source commit)`),
613
+ after: schema.describe(`${sharedDescription} (target commit)`)
614
+ });
615
+ }
616
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
617
+ return z15.object(
618
+ {
619
+ changed: z15.array(diffSchema),
620
+ unchanged: z15.array(resultSchema),
621
+ added: z15.array(resultSchema),
622
+ removed: z15.array(resultSchema)
623
+ },
624
+ { description }
625
+ );
626
+ }
627
+ var scorableMetaSchema = z15.object({
628
+ slug: slugSchema,
629
+ title: titleSchema,
630
+ docsUrl: docsUrlSchema
631
+ });
632
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
633
+ z15.object({
634
+ plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
635
+ })
636
+ );
637
+ var scorableDiffSchema = scorableMetaSchema.merge(
638
+ z15.object({
639
+ scores: makeComparisonSchema(scoreSchema).merge(
640
+ z15.object({
641
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
642
+ })
643
+ ).describe("Score comparison")
644
+ })
645
+ );
646
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
647
+ scorableWithPluginMetaSchema
648
+ );
649
+ var categoryDiffSchema = scorableDiffSchema;
650
+ var groupDiffSchema = scorableWithPluginDiffSchema;
651
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
652
+ z15.object({
653
+ values: makeComparisonSchema(auditValueSchema).merge(
654
+ z15.object({
655
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
656
+ })
657
+ ).describe("Audit `value` comparison"),
658
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
659
+ "Audit `displayValue` comparison"
660
+ )
661
+ })
662
+ );
663
+ var categoryResultSchema = scorableMetaSchema.merge(
664
+ z15.object({ score: scoreSchema })
665
+ );
666
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
667
+ z15.object({ score: scoreSchema })
668
+ );
669
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
670
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
671
+ );
672
+ var reportsDiffSchema = z15.object({
673
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
674
+ categories: makeArraysComparisonSchema(
675
+ categoryDiffSchema,
676
+ categoryResultSchema,
677
+ "Changes affecting categories"
678
+ ),
679
+ groups: makeArraysComparisonSchema(
680
+ groupDiffSchema,
681
+ groupResultSchema,
682
+ "Changes affecting groups"
683
+ ),
684
+ audits: makeArraysComparisonSchema(
685
+ auditDiffSchema,
686
+ auditResultSchema,
687
+ "Changes affecting audits"
688
+ )
689
+ }).merge(
690
+ packageVersionSchema({
691
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
692
+ required: true
693
+ })
694
+ ).merge(
695
+ executionMetaSchema({
696
+ descriptionDate: "Start date and time of the compare run",
697
+ descriptionDuration: "Duration of the compare run in ms"
698
+ })
699
+ );
700
+
701
+ // packages/plugin-lighthouse/src/lib/constants.ts
702
+ var LIGHTHOUSE_PLUGIN_SLUG = "lighthouse";
703
+ var LIGHTHOUSE_OUTPUT_PATH = join(
704
+ DEFAULT_PERSIST_OUTPUT_DIR,
705
+ LIGHTHOUSE_PLUGIN_SLUG
706
+ );
707
+
708
+ // packages/plugin-lighthouse/src/lib/normalize-flags.ts
709
+ import chalk6 from "chalk";
710
+
711
+ // packages/utils/src/lib/text-formats/constants.ts
712
+ var NEW_LINE = "\n";
713
+ var TAB = " ";
714
+
715
+ // packages/utils/src/lib/text-formats/html/details.ts
716
+ function details(title, content, cfg = { open: false }) {
717
+ 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.
718
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
719
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
720
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
721
+ NEW_LINE}`;
722
+ }
723
+
724
+ // packages/utils/src/lib/text-formats/html/font-style.ts
725
+ var boldElement = "b";
726
+ function bold(text) {
727
+ return `<${boldElement}>${text}</${boldElement}>`;
728
+ }
729
+ var italicElement = "i";
730
+ function italic(text) {
731
+ return `<${italicElement}>${text}</${italicElement}>`;
732
+ }
733
+ var codeElement = "code";
734
+ function code(text) {
735
+ return `<${codeElement}>${text}</${codeElement}>`;
736
+ }
737
+
738
+ // packages/utils/src/lib/text-formats/html/link.ts
739
+ function link(href, text) {
740
+ return `<a href="${href}">${text || href}"</a>`;
741
+ }
742
+
743
+ // packages/utils/src/lib/transform.ts
744
+ function toArray(val) {
745
+ return Array.isArray(val) ? val : [val];
746
+ }
747
+ function capitalize(text) {
748
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
749
+ 1
750
+ )}`;
751
+ }
752
+
753
+ // packages/utils/src/lib/table.ts
754
+ function rowToStringArray({ rows, columns = [] }) {
755
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
756
+ throw new TypeError(
757
+ "Column can`t be object when rows are primitive values"
758
+ );
759
+ }
760
+ return rows.map((row) => {
761
+ if (Array.isArray(row)) {
762
+ return row.map(String);
763
+ }
764
+ const objectRow = row;
765
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
766
+ return Object.values(objectRow).map(String);
767
+ }
768
+ return columns.map(
769
+ ({ key }) => String(objectRow[key])
770
+ );
771
+ });
772
+ }
773
+ function columnsToStringArray({ rows, columns = [] }) {
774
+ const firstRow = rows.at(0);
775
+ const primitiveRows = Array.isArray(firstRow);
776
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
777
+ throw new Error("invalid union type. Caught by model parsing.");
778
+ }
779
+ if (columns.length === 0) {
780
+ if (Array.isArray(firstRow)) {
781
+ return firstRow.map((_, idx) => String(idx));
782
+ }
783
+ return Object.keys(firstRow);
784
+ }
785
+ if (typeof columns.at(0) === "string") {
786
+ return columns.map(String);
787
+ }
788
+ const cols = columns;
789
+ return cols.map(({ label, key }) => label ?? capitalize(key));
790
+ }
791
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
792
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
793
+ if (typeof column === "string") {
794
+ return column;
795
+ } else if (typeof column === "object") {
796
+ return column.align ?? "center";
797
+ } else {
798
+ return "center";
799
+ }
800
+ }
801
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
802
+ const column = columns.at(targetIdx);
803
+ if (column == null) {
804
+ return "center";
805
+ } else if (typeof column === "string") {
806
+ return column;
807
+ } else if (typeof column === "object") {
808
+ return column.align ?? "center";
809
+ } else {
810
+ return "center";
811
+ }
812
+ }
813
+ function getColumnAlignments({
814
+ rows,
815
+ columns = []
816
+ }) {
817
+ if (rows.at(0) == null) {
818
+ throw new Error("first row can`t be undefined.");
819
+ }
820
+ if (Array.isArray(rows.at(0))) {
821
+ const firstPrimitiveRow = rows.at(0);
822
+ return Array.from({ length: firstPrimitiveRow.length }).map(
823
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
824
+ );
825
+ }
826
+ const firstObject = rows.at(0);
827
+ return Object.keys(firstObject).map(
828
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
829
+ );
830
+ }
831
+
832
+ // packages/utils/src/lib/text-formats/html/table.ts
833
+ function wrap(elem, content) {
834
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
835
+ }
836
+ function wrapRow(content) {
837
+ const elem = "tr";
838
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
839
+ }
840
+ function table(tableData) {
841
+ if (tableData.rows.length === 0) {
842
+ throw new Error("Data can't be empty");
843
+ }
844
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
845
+ const tableHeaderRow = wrapRow(tableHeaderCols);
846
+ const tableBody = rowToStringArray(tableData).map((arr) => {
847
+ const columns = arr.map((s) => wrap("td", s)).join("");
848
+ return wrapRow(columns);
849
+ }).join("");
850
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
851
+ }
852
+
853
+ // packages/utils/src/lib/text-formats/md/font-style.ts
854
+ var boldWrap = "**";
855
+ function bold2(text) {
856
+ return `${boldWrap}${text}${boldWrap}`;
857
+ }
858
+ var italicWrap = "_";
859
+ function italic2(text) {
860
+ return `${italicWrap}${text}${italicWrap}`;
861
+ }
862
+ var strikeThroughWrap = "~";
863
+ function strikeThrough(text) {
864
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
865
+ }
866
+ var codeWrap = "`";
867
+ function code2(text) {
868
+ return `${codeWrap}${text}${codeWrap}`;
869
+ }
870
+
871
+ // packages/utils/src/lib/text-formats/md/headline.ts
872
+ function headline(text, hierarchy = 1) {
873
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
874
+ }
875
+ function h(text, hierarchy = 1) {
876
+ return headline(text, hierarchy);
877
+ }
878
+ function h1(text) {
879
+ return headline(text, 1);
880
+ }
881
+ function h2(text) {
882
+ return headline(text, 2);
883
+ }
884
+ function h3(text) {
885
+ return headline(text, 3);
886
+ }
887
+ function h4(text) {
888
+ return headline(text, 4);
889
+ }
890
+ function h5(text) {
891
+ return headline(text, 5);
892
+ }
893
+ function h6(text) {
894
+ return headline(text, 6);
895
+ }
896
+
897
+ // packages/utils/src/lib/text-formats/md/image.ts
898
+ function image(src, alt) {
899
+ return `![${alt}](${src})`;
900
+ }
901
+
902
+ // packages/utils/src/lib/text-formats/md/link.ts
903
+ function link2(href, text) {
904
+ return `[${text || href}](${href})`;
905
+ }
906
+
907
+ // packages/utils/src/lib/text-formats/md/list.ts
908
+ function li(text, order = "unordered") {
909
+ const style = order === "unordered" ? "-" : "- [ ]";
910
+ return `${style} ${text}`;
911
+ }
912
+ function indentation(text, level = 1) {
913
+ return `${TAB.repeat(level)}${text}`;
914
+ }
915
+
916
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
917
+ function paragraphs(...sections) {
918
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
919
+ }
920
+
921
+ // packages/utils/src/lib/text-formats/md/section.ts
922
+ function section(...contents) {
923
+ return `${lines(...contents)}${NEW_LINE}`;
924
+ }
925
+ function lines(...contents) {
926
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
927
+ }
928
+
929
+ // packages/utils/src/lib/text-formats/md/table.ts
930
+ var alignString = /* @__PURE__ */ new Map([
931
+ ["left", ":--"],
932
+ ["center", ":--:"],
933
+ ["right", "--:"]
934
+ ]);
935
+ function tableRow(rows) {
936
+ return `|${rows.join("|")}|`;
937
+ }
938
+ function table2(data) {
939
+ if (data.rows.length === 0) {
940
+ throw new Error("Data can't be empty");
941
+ }
942
+ const alignmentRow = getColumnAlignments(data).map(
943
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
944
+ );
945
+ return section(
946
+ `${lines(
947
+ tableRow(columnsToStringArray(data)),
948
+ tableRow(alignmentRow),
949
+ ...rowToStringArray(data).map(tableRow)
950
+ )}`
951
+ );
952
+ }
953
+
954
+ // packages/utils/src/lib/text-formats/index.ts
955
+ var md = {
956
+ bold: bold2,
957
+ italic: italic2,
958
+ strikeThrough,
959
+ code: code2,
960
+ link: link2,
961
+ image,
962
+ headline,
963
+ h,
964
+ h1,
965
+ h2,
966
+ h3,
967
+ h4,
968
+ h5,
969
+ h6,
970
+ indentation,
971
+ lines,
972
+ li,
973
+ section,
974
+ paragraphs,
975
+ table: table2
976
+ };
977
+ var html = {
978
+ bold,
979
+ italic,
980
+ code,
981
+ link,
982
+ details,
983
+ table
984
+ };
985
+
986
+ // packages/utils/src/lib/file-system.ts
987
+ import { bundleRequire } from "bundle-require";
988
+ import chalk2 from "chalk";
989
+ import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
990
+
991
+ // packages/utils/src/lib/logging.ts
992
+ import isaacs_cliui from "@isaacs/cliui";
993
+ import { cliui } from "@poppinss/cliui";
994
+ import chalk from "chalk";
995
+
996
+ // packages/utils/src/lib/reports/constants.ts
997
+ var TERMINAL_WIDTH = 80;
998
+
999
+ // packages/utils/src/lib/logging.ts
1000
+ var singletonUiInstance;
1001
+ function ui() {
1002
+ if (singletonUiInstance === void 0) {
1003
+ singletonUiInstance = cliui();
1004
+ }
1005
+ return {
1006
+ ...singletonUiInstance,
1007
+ row: (args) => {
1008
+ logListItem(args);
1009
+ }
1010
+ };
1011
+ }
1012
+ var singletonisaacUi;
1013
+ function logListItem(args) {
1014
+ if (singletonisaacUi === void 0) {
1015
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
1016
+ }
1017
+ singletonisaacUi.div(...args);
1018
+ const content = singletonisaacUi.toString();
1019
+ singletonisaacUi.rows = [];
1020
+ singletonUiInstance?.logger.log(content);
1021
+ }
1022
+
1023
+ // packages/utils/src/lib/file-system.ts
1024
+ async function readTextFile(path2) {
1025
+ const buffer = await readFile(path2);
1026
+ return buffer.toString();
1027
+ }
1028
+ async function readJsonFile(path2) {
1029
+ const text = await readTextFile(path2);
1030
+ return JSON.parse(text);
1031
+ }
1032
+ async function ensureDirectoryExists(baseDir) {
1033
+ try {
1034
+ await mkdir(baseDir, { recursive: true });
1035
+ return;
1036
+ } catch (error) {
1037
+ ui().logger.info(error.message);
1038
+ if (error.code !== "EEXIST") {
1039
+ throw error;
1040
+ }
1041
+ }
1042
+ }
1043
+ var NoExportError = class extends Error {
1044
+ constructor(filepath) {
1045
+ super(`No default export found in ${filepath}`);
1046
+ }
1047
+ };
1048
+ async function importEsmModule(options) {
1049
+ const { mod } = await bundleRequire({
1050
+ format: "esm",
1051
+ ...options
1052
+ });
1053
+ if (!("default" in mod)) {
1054
+ throw new NoExportError(options.filepath);
1055
+ }
1056
+ return mod.default;
1057
+ }
1058
+
1059
+ // packages/utils/src/lib/reports/utils.ts
1060
+ var { image: image2, bold: boldMd } = md;
1061
+
1062
+ // packages/utils/src/lib/filter.ts
1063
+ function filterItemRefsBy(items, refFilterFn) {
1064
+ return items.map((item) => ({
1065
+ ...item,
1066
+ refs: item.refs.filter(refFilterFn)
1067
+ })).filter((item) => item.refs.length);
1068
+ }
1069
+
1070
+ // packages/utils/src/lib/git/git.ts
1071
+ import { simpleGit } from "simple-git";
1072
+
1073
+ // packages/utils/src/lib/git/git.commits-and-tags.ts
1074
+ import { simpleGit as simpleGit2 } from "simple-git";
1075
+
1076
+ // packages/utils/src/lib/semver.ts
1077
+ import { rcompare, valid } from "semver";
1078
+
1079
+ // packages/utils/src/lib/progress.ts
1080
+ import chalk3 from "chalk";
1081
+ import { MultiProgressBars } from "multi-progress-bars";
1082
+
1083
+ // packages/utils/src/lib/reports/formatting.ts
1084
+ var { headline: headline2, lines: lines2, link: link3, section: section2, table: table3 } = md;
1085
+
1086
+ // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
1087
+ var { link: link4, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
1088
+
1089
+ // packages/utils/src/lib/reports/generate-md-report.ts
1090
+ var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link5, section: section4, code: codeMd } = md;
1091
+ var { bold: boldHtml, details: details2 } = html;
1092
+
1093
+ // packages/utils/src/lib/reports/generate-md-reports-diff.ts
1094
+ var {
1095
+ h1: h13,
1096
+ h2: h24,
1097
+ lines: lines5,
1098
+ link: link6,
1099
+ bold: boldMd3,
1100
+ italic: italicMd,
1101
+ table: table4,
1102
+ section: section5
1103
+ } = md;
1104
+ var { details: details3 } = html;
1105
+
1106
+ // packages/utils/src/lib/reports/log-stdout-summary.ts
1107
+ import chalk4 from "chalk";
1108
+
1109
+ // packages/plugin-lighthouse/src/lib/runner/runner.ts
1110
+ import { runLighthouse } from "lighthouse/cli/run.js";
1111
+ import { dirname } from "node:path";
1112
+
1113
+ // packages/plugin-lighthouse/src/lib/runner/constants.ts
1114
+ import {
1115
+ defaultConfig
1116
+ } from "lighthouse";
1117
+ import { join as join2 } from "node:path";
1118
+ var { audits, categories } = defaultConfig;
1119
+ var allRawLighthouseAudits = await Promise.all(
1120
+ (audits ?? []).map(loadLighthouseAudit)
1121
+ );
1122
+ var PLUGIN_SLUG = "lighthouse";
1123
+ var LIGHTHOUSE_NAVIGATION_AUDITS = allRawLighthouseAudits.filter(
1124
+ (audit) => audit.meta.supportedModes == null || Array.isArray(audit.meta.supportedModes) && audit.meta.supportedModes.includes("navigation")
1125
+ ).map((audit) => ({
1126
+ slug: audit.meta.id,
1127
+ title: getMetaString(audit.meta.title),
1128
+ description: getMetaString(audit.meta.description)
1129
+ }));
1130
+ var navigationAuditSlugs = new Set(
1131
+ LIGHTHOUSE_NAVIGATION_AUDITS.map(({ slug }) => slug)
1132
+ );
1133
+ var LIGHTHOUSE_GROUPS = Object.entries(categories ?? {}).map(
1134
+ ([id, category]) => ({
1135
+ slug: id,
1136
+ title: getMetaString(category.title),
1137
+ ...category.description && {
1138
+ description: getMetaString(category.description)
1139
+ },
1140
+ refs: category.auditRefs.filter(({ id: auditSlug }) => navigationAuditSlugs.has(auditSlug)).map((ref) => ({
1141
+ slug: ref.id,
1142
+ weight: ref.weight
1143
+ }))
1144
+ })
1145
+ );
1146
+ function getMetaString(value) {
1147
+ if (typeof value === "string") {
1148
+ return value;
1149
+ }
1150
+ return value.formattedDefault;
1151
+ }
1152
+ async function loadLighthouseAudit(value) {
1153
+ if (typeof value === "object" && "implementation" in value) {
1154
+ return value.implementation;
1155
+ }
1156
+ if (typeof value === "function") {
1157
+ return value;
1158
+ }
1159
+ const path2 = typeof value === "string" ? value : value.path;
1160
+ const module = await import(`lighthouse/core/audits/${path2}.js`);
1161
+ return module.default;
1162
+ }
1163
+ var LIGHTHOUSE_REPORT_NAME = "lighthouse-report.json";
1164
+ var DEFAULT_CLI_FLAGS = {
1165
+ // default values extracted from
1166
+ // https://github.com/GoogleChrome/lighthouse/blob/7d80178c37a1b600ea8f092fc0b098029799a659/cli/cli-flags.js#L80
1167
+ verbose: false,
1168
+ quiet: false,
1169
+ saveAssets: false,
1170
+ // needed to pass CI on linux and windows (locally it works without headless too)
1171
+ chromeFlags: ["--headless=shell"],
1172
+ port: 0,
1173
+ hostname: "127.0.0.1",
1174
+ view: false,
1175
+ channel: "cli",
1176
+ // custom overwrites in favour of the plugin
1177
+ onlyAudits: [],
1178
+ skipAudits: [],
1179
+ onlyCategories: [],
1180
+ budgets: [],
1181
+ output: ["json"],
1182
+ outputPath: join2(LIGHTHOUSE_OUTPUT_PATH, LIGHTHOUSE_REPORT_NAME)
1183
+ };
1184
+
1185
+ // packages/plugin-lighthouse/src/lib/runner/utils.ts
1186
+ import chalk5 from "chalk";
1187
+ import log from "lighthouse-logger";
1188
+ import desktopConfig from "lighthouse/core/config/desktop-config.js";
1189
+ import experimentalConfig from "lighthouse/core/config/experimental-config.js";
1190
+ import perfConfig from "lighthouse/core/config/perf-config.js";
1191
+ import path from "node:path";
1192
+ function normalizeAuditOutputs(auditOutputs, flags = { skipAudits: [] }) {
1193
+ const toSkip = new Set(flags.skipAudits ?? []);
1194
+ return auditOutputs.filter(({ slug }) => {
1195
+ const doSkip = toSkip.has(slug);
1196
+ if (doSkip) {
1197
+ ui().logger.info(
1198
+ `Audit ${chalk5.bold(
1199
+ slug
1200
+ )} was included in audit outputs of lighthouse but listed under ${chalk5.bold(
1201
+ "skipAudits"
1202
+ )}.`
1203
+ );
1204
+ }
1205
+ return !doSkip;
1206
+ });
1207
+ }
1208
+ function toAuditOutputs(lhrAudits, { verbose = false } = {}) {
1209
+ if (verbose) {
1210
+ logUnsupportedDetails(lhrAudits);
1211
+ }
1212
+ return lhrAudits.map(
1213
+ ({
1214
+ id: slug,
1215
+ score,
1216
+ numericValue: value = 0,
1217
+ // not every audit has a numericValue
1218
+ details: details4,
1219
+ displayValue
1220
+ }) => {
1221
+ const auditOutput = {
1222
+ slug,
1223
+ score: score ?? 1,
1224
+ // score can be null
1225
+ value: Number.parseInt(value.toString(), 10),
1226
+ displayValue
1227
+ };
1228
+ if (details4 == null) {
1229
+ return auditOutput;
1230
+ }
1231
+ return auditOutput;
1232
+ }
1233
+ );
1234
+ }
1235
+ var unsupportedDetailTypes = /* @__PURE__ */ new Set([
1236
+ "opportunity",
1237
+ "table",
1238
+ "treemap-data",
1239
+ "screenshot",
1240
+ "filmstrip",
1241
+ "debugdata",
1242
+ "criticalrequestchain"
1243
+ ]);
1244
+ function logUnsupportedDetails(lhrAudits, { displayCount = 3 } = {}) {
1245
+ const slugsWithDetailParsingErrors = [
1246
+ ...new Set(
1247
+ lhrAudits.filter(
1248
+ ({ details: details4 }) => unsupportedDetailTypes.has(details4?.type)
1249
+ ).map(({ details: details4 }) => details4?.type)
1250
+ )
1251
+ ];
1252
+ if (slugsWithDetailParsingErrors.length > 0) {
1253
+ const postFix = (count) => count > displayCount ? ` and ${count - displayCount} more.` : "";
1254
+ ui().logger.debug(
1255
+ `${chalk5.yellow("\u26A0")} Plugin ${chalk5.bold(
1256
+ PLUGIN_SLUG
1257
+ )} skipped parsing of unsupported audit details: ${chalk5.bold(
1258
+ slugsWithDetailParsingErrors.slice(0, displayCount).join(", ")
1259
+ )}${postFix(slugsWithDetailParsingErrors.length)}`
1260
+ );
1261
+ }
1262
+ }
1263
+ function setLogLevel({
1264
+ verbose,
1265
+ quiet
1266
+ } = {}) {
1267
+ if (verbose) {
1268
+ log.setLevel("verbose");
1269
+ } else if (quiet) {
1270
+ log.setLevel("silent");
1271
+ } else {
1272
+ log.setLevel("info");
1273
+ }
1274
+ }
1275
+ async function getConfig(options = {}) {
1276
+ const { configPath: filepath, preset } = options;
1277
+ if (typeof filepath === "string") {
1278
+ if (filepath.endsWith(".json")) {
1279
+ return readJsonFile(filepath);
1280
+ } else if (/\.(ts|js|mjs)$/.test(filepath)) {
1281
+ return importEsmModule({ filepath });
1282
+ } else {
1283
+ ui().logger.info(`Format of file ${filepath} not supported`);
1284
+ }
1285
+ } else if (preset != null) {
1286
+ switch (preset) {
1287
+ case "desktop":
1288
+ return desktopConfig;
1289
+ case "perf":
1290
+ return perfConfig;
1291
+ case "experimental":
1292
+ return experimentalConfig;
1293
+ default:
1294
+ ui().logger.info(`Preset "${preset}" is not supported`);
1295
+ }
1296
+ }
1297
+ return void 0;
1298
+ }
1299
+ async function getBudgets(budgetPath) {
1300
+ if (budgetPath) {
1301
+ return await readJsonFile(
1302
+ path.resolve(process.cwd(), budgetPath)
1303
+ );
1304
+ }
1305
+ return [];
1306
+ }
1307
+
1308
+ // packages/plugin-lighthouse/src/lib/runner/runner.ts
1309
+ function createRunnerFunction(urlUnderTest, flags = DEFAULT_CLI_FLAGS) {
1310
+ return async () => {
1311
+ const {
1312
+ configPath,
1313
+ preset,
1314
+ budgetPath,
1315
+ budgets,
1316
+ outputPath,
1317
+ ...parsedFlags
1318
+ } = flags;
1319
+ setLogLevel(parsedFlags);
1320
+ const config = await getConfig({ configPath, preset });
1321
+ const budgetsJson = budgetPath ? await getBudgets(budgetPath) : budgets;
1322
+ if (typeof outputPath === "string") {
1323
+ await ensureDirectoryExists(dirname(outputPath));
1324
+ }
1325
+ const enrichedFlags = {
1326
+ ...parsedFlags,
1327
+ outputPath,
1328
+ budgets: budgetsJson
1329
+ };
1330
+ const runnerResult = await runLighthouse(
1331
+ urlUnderTest,
1332
+ enrichedFlags,
1333
+ config
1334
+ );
1335
+ if (runnerResult == null) {
1336
+ throw new Error("Lighthouse did not produce a result.");
1337
+ }
1338
+ const { lhr } = runnerResult;
1339
+ const auditOutputs = toAuditOutputs(Object.values(lhr.audits), flags);
1340
+ return normalizeAuditOutputs(auditOutputs, enrichedFlags);
1341
+ };
1342
+ }
1343
+
1344
+ // packages/plugin-lighthouse/src/lib/normalize-flags.ts
1345
+ var { onlyCategories, ...originalDefaultCliFlags } = DEFAULT_CLI_FLAGS;
1346
+ var DEFAULT_LIGHTHOUSE_OPTIONS = {
1347
+ ...originalDefaultCliFlags,
1348
+ onlyGroups: onlyCategories
1349
+ };
1350
+ var lighthouseUnsupportedCliFlags = [
1351
+ "precomputedLanternDataPath",
1352
+ // Path to the file where precomputed lantern data should be read from.
1353
+ "chromeIgnoreDefaultFlags",
1354
+ // ignore default flags from Lighthouse CLI
1355
+ // No error reporting implemented as in the source Sentry was involved
1356
+ // See: https://github.com/GoogleChrome/lighthouse/blob/d8ccf70692216b7fa047a4eaa2d1277b0b7fe947/cli/bin.js#L124
1357
+ "enableErrorReporting",
1358
+ // enable error reporting
1359
+ // lighthouse CLI specific debug logs
1360
+ "list-all-audits",
1361
+ // Prints a list of all available audits and exits.
1362
+ "list-locales",
1363
+ // Prints a list of all supported locales and exits.
1364
+ "list-trace-categories"
1365
+ // Prints a list of all required trace categories and exits.
1366
+ ];
1367
+ var LIGHTHOUSE_UNSUPPORTED_CLI_FLAGS = new Set(lighthouseUnsupportedCliFlags);
1368
+ var REFINED_STRING_OR_STRING_ARRAY = /* @__PURE__ */ new Set([
1369
+ "onlyAudits",
1370
+ "onlyCategories",
1371
+ "skipAudits",
1372
+ "budgets",
1373
+ "chromeFlags"
1374
+ ]);
1375
+ function normalizeFlags(flags) {
1376
+ const prefilledFlags = { ...DEFAULT_LIGHTHOUSE_OPTIONS, ...flags };
1377
+ logUnsupportedFlagsInUse(prefilledFlags);
1378
+ return Object.fromEntries(
1379
+ Object.entries(prefilledFlags).filter(
1380
+ ([flagName]) => !LIGHTHOUSE_UNSUPPORTED_CLI_FLAGS.has(
1381
+ flagName
1382
+ )
1383
+ ).map(([key, v]) => [key === "onlyGroups" ? "onlyCategories" : key, v]).map(([key, v]) => {
1384
+ if (!REFINED_STRING_OR_STRING_ARRAY.has(key)) {
1385
+ return [key, v];
1386
+ }
1387
+ return [key, Array.isArray(v) ? v : v == null ? [] : [v]];
1388
+ })
1389
+ );
1390
+ }
1391
+ function logUnsupportedFlagsInUse(flags, displayCount = 3) {
1392
+ const unsupportedFlagsInUse = Object.keys(flags).filter(
1393
+ (flag) => LIGHTHOUSE_UNSUPPORTED_CLI_FLAGS.has(flag)
1394
+ );
1395
+ if (unsupportedFlagsInUse.length > 0) {
1396
+ const postFix = (count) => count > displayCount ? ` and ${count - displayCount} more.` : "";
1397
+ ui().logger.debug(
1398
+ `${chalk6.yellow("\u26A0")} Plugin ${chalk6.bold(
1399
+ LIGHTHOUSE_PLUGIN_SLUG
1400
+ )} used unsupported flags: ${chalk6.bold(
1401
+ unsupportedFlagsInUse.slice(0, displayCount).join(", ")
1402
+ )}${postFix(unsupportedFlagsInUse.length)}`
1403
+ );
1404
+ }
1405
+ }
1406
+
1407
+ // packages/plugin-lighthouse/src/lib/utils.ts
1408
+ function lighthouseGroupRef(groupSlug, weight = 1) {
1409
+ return {
1410
+ plugin: LIGHTHOUSE_PLUGIN_SLUG,
1411
+ slug: groupSlug,
1412
+ type: "group",
1413
+ weight
1414
+ };
1415
+ }
1416
+ function lighthouseAuditRef(auditSlug, weight = 1) {
1417
+ return {
1418
+ plugin: LIGHTHOUSE_PLUGIN_SLUG,
1419
+ slug: auditSlug,
1420
+ type: "audit",
1421
+ weight
1422
+ };
1423
+ }
1424
+ var AuditsNotImplementedError = class extends Error {
1425
+ constructor(auditSlugs) {
1426
+ super(`audits: "${auditSlugs.join(", ")}" not implemented`);
1427
+ }
1428
+ };
1429
+ function validateAudits(audits2, onlyAudits) {
1430
+ const missingAudtis = toArray(onlyAudits).filter(
1431
+ (slug) => !audits2.some((audit) => audit.slug === slug)
1432
+ );
1433
+ if (missingAudtis.length > 0) {
1434
+ throw new AuditsNotImplementedError(missingAudtis);
1435
+ }
1436
+ return true;
1437
+ }
1438
+ var CategoriesNotImplementedError = class extends Error {
1439
+ constructor(categorySlugs) {
1440
+ super(`categories: "${categorySlugs.join(", ")}" not implemented`);
1441
+ }
1442
+ };
1443
+ function validateOnlyCategories(groups, onlyCategories2) {
1444
+ const missingCategories = toArray(onlyCategories2).filter(
1445
+ (slug) => groups.every((group) => group.slug !== slug)
1446
+ );
1447
+ if (missingCategories.length > 0) {
1448
+ throw new CategoriesNotImplementedError(missingCategories);
1449
+ }
1450
+ return true;
1451
+ }
1452
+ function filterAuditsAndGroupsByOnlyOptions(audits2, groups, options) {
1453
+ const {
1454
+ onlyAudits = [],
1455
+ skipAudits = [],
1456
+ onlyCategories: onlyCategories2 = []
1457
+ } = options ?? {};
1458
+ if (onlyCategories2.length > 0) {
1459
+ validateOnlyCategories(groups, onlyCategories2);
1460
+ const categorySlugs = new Set(onlyCategories2);
1461
+ const filteredGroups = groups.filter(
1462
+ ({ slug }) => categorySlugs.has(slug)
1463
+ );
1464
+ const auditSlugsFromRemainingGroups = new Set(
1465
+ filteredGroups.flatMap(({ refs }) => refs.map(({ slug }) => slug))
1466
+ );
1467
+ return {
1468
+ audits: audits2.filter(
1469
+ ({ slug }) => auditSlugsFromRemainingGroups.has(slug)
1470
+ ),
1471
+ groups: filteredGroups
1472
+ };
1473
+ } else if (onlyAudits.length > 0 || skipAudits.length > 0) {
1474
+ validateAudits(audits2, onlyAudits);
1475
+ validateAudits(audits2, skipAudits);
1476
+ const onlyAuditSlugs = new Set(onlyAudits);
1477
+ const skipAuditSlugs = new Set(skipAudits);
1478
+ const filterAudits = ({ slug }) => !// audit is NOT in given onlyAuditSlugs
1479
+ (onlyAudits.length > 0 && !onlyAuditSlugs.has(slug) || // audit IS in given skipAuditSlugs
1480
+ skipAudits.length > 0 && skipAuditSlugs.has(slug));
1481
+ return {
1482
+ audits: audits2.filter(filterAudits),
1483
+ groups: filterItemRefsBy(groups, filterAudits)
1484
+ };
1485
+ }
1486
+ return {
1487
+ audits: audits2,
1488
+ groups
1489
+ };
1490
+ }
1491
+
1492
+ // packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts
1493
+ function lighthousePlugin(url, flags) {
1494
+ const {
1495
+ skipAudits = [],
1496
+ onlyAudits = [],
1497
+ onlyCategories: onlyCategories2 = [],
1498
+ ...unparsedFlags
1499
+ } = normalizeFlags(flags ?? {});
1500
+ const { audits: audits2, groups } = filterAuditsAndGroupsByOnlyOptions(
1501
+ LIGHTHOUSE_NAVIGATION_AUDITS,
1502
+ LIGHTHOUSE_GROUPS,
1503
+ { skipAudits, onlyAudits, onlyCategories: onlyCategories2 }
1504
+ );
1505
+ return {
1506
+ slug: LIGHTHOUSE_PLUGIN_SLUG,
1507
+ title: "Lighthouse",
1508
+ icon: "lighthouse",
1509
+ audits: audits2,
1510
+ groups,
1511
+ runner: createRunnerFunction(url, {
1512
+ skipAudits,
1513
+ onlyAudits,
1514
+ onlyCategories: onlyCategories2,
1515
+ ...unparsedFlags
1516
+ })
1517
+ };
1518
+ }
1519
+
1520
+ // packages/plugin-lighthouse/src/index.ts
1521
+ var src_default = lighthousePlugin;
1522
+ export {
1523
+ LIGHTHOUSE_OUTPUT_PATH,
1524
+ LIGHTHOUSE_PLUGIN_SLUG,
1525
+ LIGHTHOUSE_REPORT_NAME,
1526
+ src_default as default,
1527
+ lighthouseAuditRef,
1528
+ lighthouseGroupRef,
1529
+ lighthousePlugin
1530
+ };