@code-pushup/cli 0.1.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,2077 @@
1
+ #! /usr/bin/env node
2
+
3
+ // packages/cli/src/index.ts
4
+ import { hideBin } from "yargs/helpers";
5
+
6
+ // packages/cli/src/lib/autorun/autorun-command.ts
7
+ import chalk5 from "chalk";
8
+
9
+ // packages/core/src/lib/implementation/persist.ts
10
+ import { existsSync, mkdirSync } from "fs";
11
+ import { stat as stat2, writeFile } from "fs/promises";
12
+ import { join as join2 } from "path";
13
+
14
+ // packages/utils/src/lib/execute-process.ts
15
+ import { spawn } from "child_process";
16
+
17
+ // packages/utils/src/lib/report.ts
18
+ import { join } from "path";
19
+
20
+ // packages/models/src/lib/category-config.ts
21
+ import { z as z2 } from "zod";
22
+
23
+ // packages/models/src/lib/implementation/schemas.ts
24
+ import { z } from "zod";
25
+ import { MATERIAL_ICONS } from "@code-pushup/portal-client";
26
+
27
+ // packages/models/src/lib/implementation/utils.ts
28
+ var slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
29
+ var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
30
+ function hasDuplicateStrings(strings) {
31
+ const uniqueStrings = Array.from(new Set(strings));
32
+ const duplicatedStrings = strings.filter(
33
+ /* @__PURE__ */ ((i) => (v) => uniqueStrings[i] !== v || !++i)(0)
34
+ );
35
+ return duplicatedStrings.length === 0 ? false : duplicatedStrings;
36
+ }
37
+ function hasMissingStrings(toCheck, existing) {
38
+ const nonExisting = toCheck.filter((s) => !existing.includes(s));
39
+ return nonExisting.length === 0 ? false : nonExisting;
40
+ }
41
+ function errorItems(items, transform = (items2) => items2.join(", ")) {
42
+ const paredItems = items ? items : [];
43
+ return transform(paredItems);
44
+ }
45
+ function exists(value) {
46
+ return value != null;
47
+ }
48
+
49
+ // packages/models/src/lib/implementation/schemas.ts
50
+ function executionMetaSchema(options2 = {
51
+ descriptionDate: "Execution start date and time",
52
+ descriptionDuration: "Execution duration in ms"
53
+ }) {
54
+ return z.object({
55
+ date: z.string({ description: options2.descriptionDate }),
56
+ duration: z.number({ description: options2.descriptionDuration })
57
+ });
58
+ }
59
+ function slugSchema(description = "Unique ID (human-readable, URL-safe)") {
60
+ return z.string({ description }).regex(slugRegex, {
61
+ message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
62
+ }).max(128, {
63
+ message: "slug can be max 128 characters long"
64
+ });
65
+ }
66
+ function descriptionSchema(description = "Description (markdown)") {
67
+ return z.string({ description }).max(65536).optional();
68
+ }
69
+ function docsUrlSchema(description = "Documentation site") {
70
+ return urlSchema(description).optional().or(z.string().max(0));
71
+ }
72
+ function urlSchema(description) {
73
+ return z.string({ description }).url();
74
+ }
75
+ function titleSchema(description = "Descriptive name") {
76
+ return z.string({ description }).max(256);
77
+ }
78
+ function metaSchema(options2) {
79
+ const {
80
+ descriptionDescription,
81
+ titleDescription,
82
+ docsUrlDescription,
83
+ description
84
+ } = options2 || {};
85
+ return z.object(
86
+ {
87
+ title: titleSchema(titleDescription),
88
+ description: descriptionSchema(descriptionDescription),
89
+ docsUrl: docsUrlSchema(docsUrlDescription)
90
+ },
91
+ { description }
92
+ );
93
+ }
94
+ function filePathSchema(description) {
95
+ return z.string({ description }).trim().min(1, { message: "path is invalid" });
96
+ }
97
+ function fileNameSchema(description) {
98
+ return z.string({ description }).trim().regex(filenameRegex, {
99
+ message: `The filename has to be valid`
100
+ }).min(1, { message: "file name is invalid" });
101
+ }
102
+ function positiveIntSchema(description) {
103
+ return z.number({ description }).int().nonnegative();
104
+ }
105
+ function packageVersionSchema(options2) {
106
+ let { versionDescription, optional } = options2 || {};
107
+ versionDescription = versionDescription || "NPM version of the package";
108
+ optional = !!optional;
109
+ const packageSchema = z.string({ description: "NPM package name" });
110
+ const versionSchema = z.string({ description: versionDescription });
111
+ return z.object(
112
+ {
113
+ packageName: optional ? packageSchema.optional() : packageSchema,
114
+ version: optional ? versionSchema.optional() : versionSchema
115
+ },
116
+ { description: "NPM package name and version of a published package" }
117
+ );
118
+ }
119
+ function weightSchema(description = "Coefficient for the given score (use weight 0 if only for display)") {
120
+ return positiveIntSchema(description);
121
+ }
122
+ function weightedRefSchema(description, slugDescription) {
123
+ return z.object(
124
+ {
125
+ slug: slugSchema(slugDescription),
126
+ weight: weightSchema("Weight used to calculate score")
127
+ },
128
+ { description }
129
+ );
130
+ }
131
+ function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
132
+ return z.object(
133
+ {
134
+ slug: slugSchema('Human-readable unique ID, e.g. "performance"'),
135
+ refs: z.array(refSchema).refine(
136
+ (refs) => !duplicateCheckFn(refs),
137
+ (refs) => ({
138
+ message: duplicateMessageFn(refs)
139
+ })
140
+ )
141
+ },
142
+ { description }
143
+ );
144
+ }
145
+ var materialIconSchema = z.enum(
146
+ MATERIAL_ICONS,
147
+ { description: "Icon from VSCode Material Icons extension" }
148
+ );
149
+
150
+ // packages/models/src/lib/category-config.ts
151
+ var categoryRefSchema = weightedRefSchema(
152
+ "Weighted references to audits and/or groups for the category",
153
+ "Slug of an audit or group (depending on `type`)"
154
+ ).merge(
155
+ z2.object({
156
+ type: z2.enum(["audit", "group"], {
157
+ description: "Discriminant for reference kind, affects where `slug` is looked up"
158
+ }),
159
+ plugin: slugSchema(
160
+ "Plugin slug (plugin should contain referenced audit or group)"
161
+ )
162
+ })
163
+ );
164
+ var categoryConfigSchema = scorableSchema(
165
+ "Category with a score calculated from audits and groups from various plugins",
166
+ categoryRefSchema,
167
+ getDuplicateRefsInCategoryMetrics,
168
+ duplicateRefsInCategoryMetricsErrorMsg
169
+ ).merge(
170
+ metaSchema({
171
+ titleDescription: "Category Title",
172
+ docsUrlDescription: "Category docs URL",
173
+ descriptionDescription: "Category description",
174
+ description: "Meta info for category"
175
+ })
176
+ ).merge(
177
+ z2.object({
178
+ isBinary: z2.boolean({
179
+ description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
180
+ }).optional()
181
+ })
182
+ );
183
+ function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
184
+ const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
185
+ return `In the categories, the following audit or group refs are duplicates: ${errorItems(
186
+ duplicateRefs
187
+ )}`;
188
+ }
189
+ function getDuplicateRefsInCategoryMetrics(metrics) {
190
+ return hasDuplicateStrings(
191
+ metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
192
+ );
193
+ }
194
+
195
+ // packages/models/src/lib/core-config.ts
196
+ import { z as z11 } from "zod";
197
+
198
+ // packages/models/src/lib/persist-config.ts
199
+ import { z as z3 } from "zod";
200
+ var formatSchema = z3.enum(["json", "md"]);
201
+ var persistConfigSchema = z3.object({
202
+ outputDir: filePathSchema("Artifacts folder"),
203
+ filename: fileNameSchema("Artifacts file name (without extension)").default(
204
+ "report"
205
+ ),
206
+ format: z3.array(formatSchema).default(["json"]).optional()
207
+ // @TODO remove default or optional value and otherwise it will not set defaults.
208
+ });
209
+
210
+ // packages/models/src/lib/plugin-config.ts
211
+ import { z as z9 } from "zod";
212
+
213
+ // packages/models/src/lib/plugin-config-audits.ts
214
+ import { z as z4 } from "zod";
215
+ var auditSchema = z4.object({
216
+ slug: slugSchema("ID (unique within plugin)")
217
+ }).merge(
218
+ metaSchema({
219
+ titleDescription: "Descriptive name",
220
+ descriptionDescription: "Description (markdown)",
221
+ docsUrlDescription: "Link to documentation (rationale)",
222
+ description: "List of scorable metrics for the given plugin"
223
+ })
224
+ );
225
+ var pluginAuditsSchema = z4.array(auditSchema, {
226
+ description: "List of audits maintained in a plugin"
227
+ }).refine(
228
+ (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
229
+ (auditMetadata) => ({
230
+ message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
231
+ })
232
+ );
233
+ function duplicateSlugsInAuditsErrorMsg(audits) {
234
+ const duplicateRefs = getDuplicateSlugsInAudits(audits);
235
+ return `In plugin audits the slugs are not unique: ${errorItems(
236
+ duplicateRefs
237
+ )}`;
238
+ }
239
+ function getDuplicateSlugsInAudits(audits) {
240
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
241
+ }
242
+
243
+ // packages/models/src/lib/plugin-config-groups.ts
244
+ import { z as z5 } from "zod";
245
+ var auditGroupRefSchema = weightedRefSchema(
246
+ "Weighted references to audits",
247
+ "Reference slug to an audit within this plugin (e.g. 'max-lines')"
248
+ );
249
+ var auditGroupSchema = scorableSchema(
250
+ 'An audit 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',
251
+ auditGroupRefSchema,
252
+ getDuplicateRefsInGroups,
253
+ duplicateRefsInGroupsErrorMsg
254
+ ).merge(
255
+ metaSchema({
256
+ titleDescription: "Descriptive name for the group",
257
+ descriptionDescription: "Description of the group (markdown)",
258
+ docsUrlDescription: "Group documentation site",
259
+ description: "Group metadata"
260
+ })
261
+ );
262
+ var auditGroupsSchema = z5.array(auditGroupSchema, {
263
+ description: "List of groups"
264
+ }).optional().refine(
265
+ (groups) => !getDuplicateSlugsInGroups(groups),
266
+ (groups) => ({
267
+ message: duplicateSlugsInGroupsErrorMsg(groups)
268
+ })
269
+ );
270
+ function duplicateRefsInGroupsErrorMsg(groupAudits) {
271
+ const duplicateRefs = getDuplicateRefsInGroups(groupAudits);
272
+ return `In plugin groups the audit refs are not unique: ${errorItems(
273
+ duplicateRefs
274
+ )}`;
275
+ }
276
+ function getDuplicateRefsInGroups(groupAudits) {
277
+ return hasDuplicateStrings(
278
+ groupAudits.map(({ slug: ref }) => ref).filter(exists)
279
+ );
280
+ }
281
+ function duplicateSlugsInGroupsErrorMsg(groups) {
282
+ const duplicateRefs = getDuplicateSlugsInGroups(groups);
283
+ return `In groups the slugs are not unique: ${errorItems(duplicateRefs)}`;
284
+ }
285
+ function getDuplicateSlugsInGroups(groups) {
286
+ return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
287
+ }
288
+
289
+ // packages/models/src/lib/plugin-config-runner.ts
290
+ import { z as z8 } from "zod";
291
+
292
+ // packages/models/src/lib/plugin-process-output.ts
293
+ import { z as z7 } from "zod";
294
+
295
+ // packages/models/src/lib/plugin-process-output-audit-issue.ts
296
+ import { z as z6 } from "zod";
297
+ var sourceFileLocationSchema = z6.object(
298
+ {
299
+ file: filePathSchema("Relative path to source file in Git repo"),
300
+ position: z6.object(
301
+ {
302
+ startLine: positiveIntSchema("Start line"),
303
+ startColumn: positiveIntSchema("Start column").optional(),
304
+ endLine: positiveIntSchema("End line").optional(),
305
+ endColumn: positiveIntSchema("End column").optional()
306
+ },
307
+ { description: "Location in file" }
308
+ ).optional()
309
+ },
310
+ { description: "Source file location" }
311
+ );
312
+ var issueSeveritySchema = z6.enum(["info", "warning", "error"], {
313
+ description: "Severity level"
314
+ });
315
+ var issueSchema = z6.object(
316
+ {
317
+ message: z6.string({ description: "Descriptive error message" }).max(512),
318
+ severity: issueSeveritySchema,
319
+ source: sourceFileLocationSchema.optional()
320
+ },
321
+ { description: "Issue information" }
322
+ );
323
+
324
+ // packages/models/src/lib/plugin-process-output.ts
325
+ var auditOutputSchema = z7.object(
326
+ {
327
+ slug: slugSchema("Reference to audit"),
328
+ displayValue: z7.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional(),
329
+ value: positiveIntSchema("Raw numeric value"),
330
+ score: z7.number({
331
+ description: "Value between 0 and 1"
332
+ }).min(0).max(1),
333
+ details: z7.object(
334
+ {
335
+ issues: z7.array(issueSchema, { description: "List of findings" })
336
+ },
337
+ { description: "Detailed information" }
338
+ ).optional()
339
+ },
340
+ { description: "Audit information" }
341
+ );
342
+ var auditOutputsSchema = z7.array(auditOutputSchema, {
343
+ description: "List of JSON formatted audit output emitted by the runner process of a plugin"
344
+ }).refine(
345
+ (audits) => !getDuplicateSlugsInAudits2(audits),
346
+ (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
347
+ );
348
+ function duplicateSlugsInAuditsErrorMsg2(audits) {
349
+ const duplicateRefs = getDuplicateSlugsInAudits2(audits);
350
+ return `In plugin audits the slugs are not unique: ${errorItems(
351
+ duplicateRefs
352
+ )}`;
353
+ }
354
+ function getDuplicateSlugsInAudits2(audits) {
355
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
356
+ }
357
+
358
+ // packages/models/src/lib/plugin-config-runner.ts
359
+ var outputTransformSchema = z8.function().args(z8.unknown()).returns(z8.union([auditOutputsSchema, z8.promise(auditOutputsSchema)]));
360
+ var runnerConfigSchema = z8.object(
361
+ {
362
+ command: z8.string({
363
+ description: "Shell command to execute"
364
+ }),
365
+ args: z8.array(z8.string({ description: "Command arguments" })).optional(),
366
+ outputFile: filePathSchema("Output path"),
367
+ outputTransform: outputTransformSchema.optional()
368
+ },
369
+ {
370
+ description: "How to execute runner"
371
+ }
372
+ );
373
+ var onProgressSchema = z8.function().args(z8.unknown()).returns(z8.void());
374
+ var runnerFunctionSchema = z8.function().args(onProgressSchema.optional()).returns(z8.union([auditOutputsSchema, z8.promise(auditOutputsSchema)]));
375
+
376
+ // packages/models/src/lib/plugin-config.ts
377
+ var pluginMetaSchema = packageVersionSchema({
378
+ optional: true
379
+ }).merge(
380
+ metaSchema({
381
+ titleDescription: "Descriptive name",
382
+ descriptionDescription: "Description (markdown)",
383
+ docsUrlDescription: "Plugin documentation site",
384
+ description: "Plugin metadata"
385
+ })
386
+ ).merge(
387
+ z9.object({
388
+ slug: slugSchema("References plugin. ID (unique within core config)"),
389
+ icon: materialIconSchema
390
+ })
391
+ );
392
+ var pluginDataSchema = z9.object({
393
+ runner: z9.union([runnerConfigSchema, runnerFunctionSchema]),
394
+ audits: pluginAuditsSchema,
395
+ groups: auditGroupsSchema
396
+ });
397
+ var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
398
+ (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
399
+ (pluginCfg) => ({
400
+ message: missingRefsFromGroupsErrorMsg(pluginCfg)
401
+ })
402
+ );
403
+ function missingRefsFromGroupsErrorMsg(pluginCfg) {
404
+ const missingRefs = getMissingRefsFromGroups(pluginCfg);
405
+ return `In the groups, the following audit ref's needs to point to a audit in this plugin config: ${errorItems(
406
+ missingRefs
407
+ )}`;
408
+ }
409
+ function getMissingRefsFromGroups(pluginCfg) {
410
+ if (pluginCfg?.groups?.length && pluginCfg?.audits?.length) {
411
+ const groups = pluginCfg?.groups || [];
412
+ const audits = pluginCfg?.audits || [];
413
+ return hasMissingStrings(
414
+ groups.flatMap(({ refs: audits2 }) => audits2.map(({ slug: ref }) => ref)),
415
+ audits.map(({ slug }) => slug)
416
+ );
417
+ }
418
+ return false;
419
+ }
420
+
421
+ // packages/models/src/lib/upload-config.ts
422
+ import { z as z10 } from "zod";
423
+ var uploadConfigSchema = z10.object({
424
+ server: urlSchema("URL of deployed portal API"),
425
+ apiKey: z10.string({
426
+ description: "API key with write access to portal (use `process.env` for security)"
427
+ }),
428
+ organization: z10.string({
429
+ description: "Organization in code versioning system"
430
+ }),
431
+ project: z10.string({
432
+ description: "Project in code versioning system"
433
+ })
434
+ });
435
+
436
+ // packages/models/src/lib/core-config.ts
437
+ var unrefinedCoreConfigSchema = z11.object({
438
+ plugins: z11.array(pluginConfigSchema, {
439
+ description: "List of plugins to be used (official, community-provided, or custom)"
440
+ }),
441
+ /** portal configuration for persisting results */
442
+ persist: persistConfigSchema,
443
+ /** portal configuration for uploading results */
444
+ upload: uploadConfigSchema.optional(),
445
+ categories: z11.array(categoryConfigSchema, {
446
+ description: "Categorization of individual audits"
447
+ }).refine(
448
+ (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
449
+ (categoryCfg) => ({
450
+ message: duplicateSlugCategoriesErrorMsg(categoryCfg)
451
+ })
452
+ )
453
+ });
454
+ var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
455
+ function refineCoreConfig(schema) {
456
+ return schema.refine(
457
+ (coreCfg) => !getMissingRefsForCategories(coreCfg),
458
+ (coreCfg) => ({
459
+ message: missingRefsForCategoriesErrorMsg(coreCfg)
460
+ })
461
+ );
462
+ }
463
+ function missingRefsForCategoriesErrorMsg(coreCfg) {
464
+ const missingRefs = getMissingRefsForCategories(coreCfg);
465
+ return `In the categories, the following plugin refs do not exist in the provided plugins: ${errorItems(
466
+ missingRefs
467
+ )}`;
468
+ }
469
+ function getMissingRefsForCategories(coreCfg) {
470
+ const missingRefs = [];
471
+ const auditRefsFromCategory = coreCfg.categories.flatMap(
472
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
473
+ );
474
+ const auditRefsFromPlugins = coreCfg.plugins.flatMap(
475
+ ({ audits, slug: pluginSlug }) => {
476
+ return audits.map(({ slug }) => `${pluginSlug}/${slug}`);
477
+ }
478
+ );
479
+ const missingAuditRefs = hasMissingStrings(
480
+ auditRefsFromCategory,
481
+ auditRefsFromPlugins
482
+ );
483
+ if (Array.isArray(missingAuditRefs) && missingAuditRefs.length > 0) {
484
+ missingRefs.push(...missingAuditRefs);
485
+ }
486
+ const groupRefsFromCategory = coreCfg.categories.flatMap(
487
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
488
+ );
489
+ const groupRefsFromPlugins = coreCfg.plugins.flatMap(
490
+ ({ groups, slug: pluginSlug }) => {
491
+ return Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : [];
492
+ }
493
+ );
494
+ const missingGroupRefs = hasMissingStrings(
495
+ groupRefsFromCategory,
496
+ groupRefsFromPlugins
497
+ );
498
+ if (Array.isArray(missingGroupRefs) && missingGroupRefs.length > 0) {
499
+ missingRefs.push(...missingGroupRefs);
500
+ }
501
+ return missingRefs.length ? missingRefs : false;
502
+ }
503
+ function duplicateSlugCategoriesErrorMsg(categories) {
504
+ const duplicateStringSlugs = getDuplicateSlugCategories(categories);
505
+ return `In the categories, the following slugs are duplicated: ${errorItems(
506
+ duplicateStringSlugs
507
+ )}`;
508
+ }
509
+ function getDuplicateSlugCategories(categories) {
510
+ return hasDuplicateStrings(categories.map(({ slug }) => slug));
511
+ }
512
+
513
+ // packages/models/src/lib/report.ts
514
+ import { z as z12 } from "zod";
515
+ var auditReportSchema = auditSchema.merge(auditOutputSchema);
516
+ var pluginReportSchema = pluginMetaSchema.merge(
517
+ executionMetaSchema({
518
+ descriptionDate: "Start date and time of plugin run",
519
+ descriptionDuration: "Duration of the plugin run in ms"
520
+ })
521
+ ).merge(
522
+ z12.object({
523
+ audits: z12.array(auditReportSchema),
524
+ groups: z12.array(auditGroupSchema).optional()
525
+ })
526
+ );
527
+ var reportSchema = packageVersionSchema({
528
+ versionDescription: "NPM version of the CLI"
529
+ }).merge(
530
+ executionMetaSchema({
531
+ descriptionDate: "Start date and time of the collect run",
532
+ descriptionDuration: "Duration of the collect run in ms"
533
+ })
534
+ ).merge(
535
+ z12.object(
536
+ {
537
+ categories: z12.array(categoryConfigSchema),
538
+ plugins: z12.array(pluginReportSchema)
539
+ },
540
+ { description: "Collect output data" }
541
+ )
542
+ );
543
+
544
+ // packages/utils/src/lib/file-system.ts
545
+ import { bundleRequire } from "bundle-require";
546
+ import chalk from "chalk";
547
+ import { mkdir, readFile, readdir, stat } from "fs/promises";
548
+
549
+ // packages/utils/src/lib/formatting.ts
550
+ function slugify(text) {
551
+ return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z0-9-]/g, "");
552
+ }
553
+ function formatBytes(bytes, decimals = 2) {
554
+ if (!+bytes)
555
+ return "0 B";
556
+ const k = 1024;
557
+ const dm = decimals < 0 ? 0 : decimals;
558
+ const sizes = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
559
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
560
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
561
+ }
562
+ function formatDuration(duration) {
563
+ if (duration < 1e3) {
564
+ return `${duration} ms`;
565
+ }
566
+ return `${(duration / 1e3).toFixed(2)} s`;
567
+ }
568
+
569
+ // packages/utils/src/lib/guards.ts
570
+ function isPromiseFulfilledResult(result) {
571
+ return result.status === "fulfilled";
572
+ }
573
+ function isPromiseRejectedResult(result) {
574
+ return result.status === "rejected";
575
+ }
576
+
577
+ // packages/utils/src/lib/log-results.ts
578
+ function logMultipleResults(results, messagePrefix, succeededCallback, failedCallback) {
579
+ if (succeededCallback) {
580
+ const succeededResults = results.filter(isPromiseFulfilledResult);
581
+ logPromiseResults(
582
+ succeededResults,
583
+ `${messagePrefix} successfully: `,
584
+ succeededCallback
585
+ );
586
+ }
587
+ if (failedCallback) {
588
+ const failedResults = results.filter(isPromiseRejectedResult);
589
+ logPromiseResults(
590
+ failedResults,
591
+ `${messagePrefix} failed: `,
592
+ failedCallback
593
+ );
594
+ }
595
+ }
596
+ function logPromiseResults(results, logMessage, callback) {
597
+ if (results.length) {
598
+ if (results[0]?.status === "fulfilled") {
599
+ console.info(logMessage);
600
+ } else {
601
+ console.warn(logMessage);
602
+ }
603
+ results.forEach(callback);
604
+ }
605
+ }
606
+
607
+ // packages/utils/src/lib/file-system.ts
608
+ async function ensureDirectoryExists(baseDir) {
609
+ try {
610
+ await mkdir(baseDir, { recursive: true });
611
+ return;
612
+ } catch (error) {
613
+ console.error(error.message);
614
+ if (error.code !== "EEXIST") {
615
+ throw error;
616
+ }
617
+ }
618
+ }
619
+ async function readTextFile(path) {
620
+ const buffer = await readFile(path);
621
+ return buffer.toString();
622
+ }
623
+ async function readJsonFile(path) {
624
+ const text = await readTextFile(path);
625
+ return JSON.parse(text);
626
+ }
627
+ function logMultipleFileResults(fileResults, messagePrefix) {
628
+ const succeededCallback = (result) => {
629
+ const [fileName, size] = result.value;
630
+ console.info(
631
+ `- ${chalk.bold(fileName)}` + (size ? ` (${chalk.gray(formatBytes(size))})` : "")
632
+ );
633
+ };
634
+ const failedCallback = (result) => {
635
+ console.warn(`- ${chalk.bold(result.reason)}`);
636
+ };
637
+ logMultipleResults(
638
+ fileResults,
639
+ messagePrefix,
640
+ succeededCallback,
641
+ failedCallback
642
+ );
643
+ }
644
+ var NoExportError = class extends Error {
645
+ constructor(filepath) {
646
+ super(`No export found in ${filepath}`);
647
+ }
648
+ };
649
+ async function importEsmModule(options2, parse) {
650
+ parse = parse || ((v) => v);
651
+ options2 = {
652
+ format: "esm",
653
+ ...options2
654
+ };
655
+ const { mod } = await bundleRequire(options2);
656
+ if (mod.default === void 0) {
657
+ throw new NoExportError(options2.filepath);
658
+ }
659
+ return parse(mod.default);
660
+ }
661
+
662
+ // packages/utils/src/lib/report.ts
663
+ var FOOTER_PREFIX = "Made with \u2764 by";
664
+ var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
665
+ var README_LINK = "https://github.com/flowup/quality-metrics-cli#readme";
666
+ var reportHeadlineText = "Code PushUp Report";
667
+ var reportOverviewTableHeaders = [
668
+ "\u{1F3F7} Category",
669
+ "\u2B50 Score",
670
+ "\u{1F6E1} Audits"
671
+ ];
672
+ var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
673
+ var reportMetaTableHeaders = [
674
+ "Commit",
675
+ "Version",
676
+ "Duration",
677
+ "Plugins",
678
+ "Categories",
679
+ "Audits"
680
+ ];
681
+ var pluginMetaTableHeaders = [
682
+ "Plugin",
683
+ "Audits",
684
+ "Version",
685
+ "Duration"
686
+ ];
687
+ var detailsTableHeaders = [
688
+ "Severity",
689
+ "Message",
690
+ "Source file",
691
+ "Line(s)"
692
+ ];
693
+ function formatReportScore(score) {
694
+ return Math.round(score * 100).toString();
695
+ }
696
+ function getRoundScoreMarker(score) {
697
+ if (score >= 0.9) {
698
+ return "\u{1F7E2}";
699
+ }
700
+ if (score >= 0.5) {
701
+ return "\u{1F7E1}";
702
+ }
703
+ return "\u{1F534}";
704
+ }
705
+ function getSquaredScoreMarker(score) {
706
+ if (score >= 0.9) {
707
+ return "\u{1F7E9}";
708
+ }
709
+ if (score >= 0.5) {
710
+ return "\u{1F7E8}";
711
+ }
712
+ return "\u{1F7E5}";
713
+ }
714
+ function getSeverityIcon(severity) {
715
+ if (severity === "error") {
716
+ return "\u{1F6A8}";
717
+ }
718
+ if (severity === "warning") {
719
+ return "\u26A0\uFE0F";
720
+ }
721
+ return "\u2139\uFE0F";
722
+ }
723
+ function calcDuration(start, stop) {
724
+ stop = stop !== void 0 ? stop : performance.now();
725
+ return Math.floor(stop - start);
726
+ }
727
+ function countCategoryAudits(refs, plugins) {
728
+ const groupLookup = plugins.reduce((lookup, plugin) => {
729
+ if (!plugin.groups.length) {
730
+ return lookup;
731
+ }
732
+ return {
733
+ ...lookup,
734
+ [plugin.slug]: {
735
+ ...plugin.groups.reduce(
736
+ (groupLookup2, group) => {
737
+ return {
738
+ ...groupLookup2,
739
+ [group.slug]: group
740
+ };
741
+ },
742
+ {}
743
+ )
744
+ }
745
+ };
746
+ }, {});
747
+ return refs.reduce((acc, ref) => {
748
+ if (ref.type === "group") {
749
+ const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
750
+ return acc + (groupRefs?.length || 0);
751
+ }
752
+ return acc + 1;
753
+ }, 0);
754
+ }
755
+ function getAuditByRef({ slug, weight, plugin }, plugins) {
756
+ const auditPlugin = plugins.find(({ slug: slug2 }) => slug2 === plugin);
757
+ if (!auditPlugin) {
758
+ throwIsNotPresentError(`Plugin ${plugin}`, "report");
759
+ }
760
+ const audit = auditPlugin?.audits.find(
761
+ ({ slug: auditSlug }) => auditSlug === slug
762
+ );
763
+ if (!audit) {
764
+ throwIsNotPresentError(`Audit ${slug}`, auditPlugin?.slug);
765
+ }
766
+ return {
767
+ ...audit,
768
+ weight,
769
+ plugin
770
+ };
771
+ }
772
+ function getGroupWithAudits(refSlug, refPlugin, plugins) {
773
+ const plugin = plugins.find(({ slug }) => slug === refPlugin);
774
+ if (!plugin) {
775
+ throwIsNotPresentError(`Plugin ${refPlugin}`, "report");
776
+ }
777
+ const groupWithAudits = plugin?.groups?.find(({ slug }) => slug === refSlug);
778
+ if (!groupWithAudits) {
779
+ throwIsNotPresentError(`Group ${refSlug}`, plugin?.slug);
780
+ }
781
+ const groupAudits = groupWithAudits.refs.reduce(
782
+ (acc, ref) => {
783
+ const audit = getAuditByRef(
784
+ { ...ref, plugin: refPlugin },
785
+ plugins
786
+ );
787
+ if (audit) {
788
+ return [...acc, audit];
789
+ }
790
+ return [...acc];
791
+ },
792
+ []
793
+ );
794
+ const audits = groupAudits.sort(sortCategoryAudits);
795
+ return {
796
+ ...groupWithAudits,
797
+ audits
798
+ };
799
+ }
800
+ function sortCategoryAudits(a, b) {
801
+ if (a.weight !== b.weight) {
802
+ return b.weight - a.weight;
803
+ }
804
+ if (a.score !== b.score) {
805
+ return a.score - b.score;
806
+ }
807
+ if (a.value !== b.value) {
808
+ return b.value - a.value;
809
+ }
810
+ return a.title.localeCompare(b.title);
811
+ }
812
+ function sortAudits(a, b) {
813
+ if (a.score !== b.score) {
814
+ return a.score - b.score;
815
+ }
816
+ if (a.value !== b.value) {
817
+ return b.value - a.value;
818
+ }
819
+ return a.title.localeCompare(b.title);
820
+ }
821
+ async function loadReport(options2) {
822
+ const { outputDir, filename, format } = options2;
823
+ await ensureDirectoryExists(outputDir);
824
+ const filePath = join(outputDir, `${filename}.${format}`);
825
+ if (format === "json") {
826
+ const content = await readJsonFile(filePath);
827
+ return reportSchema.parse(content);
828
+ }
829
+ return readTextFile(filePath);
830
+ }
831
+ function throwIsNotPresentError(itemName, presentPlace) {
832
+ throw new Error(`${itemName} is not present in ${presentPlace}`);
833
+ }
834
+ function getPluginNameFromSlug(slug, plugins) {
835
+ return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
836
+ }
837
+
838
+ // packages/utils/src/lib/execute-process.ts
839
+ var ProcessError = class extends Error {
840
+ code;
841
+ stderr;
842
+ stdout;
843
+ constructor(result) {
844
+ super(result.stderr);
845
+ this.code = result.code;
846
+ this.stderr = result.stderr;
847
+ this.stdout = result.stdout;
848
+ }
849
+ };
850
+ function executeProcess(cfg) {
851
+ const { observer, cwd } = cfg;
852
+ const { onStdout, onError, onComplete } = observer || {};
853
+ const date = (/* @__PURE__ */ new Date()).toISOString();
854
+ const start = performance.now();
855
+ return new Promise((resolve, reject) => {
856
+ const process2 = spawn(cfg.command, cfg.args, { cwd, shell: true });
857
+ let stdout = "";
858
+ let stderr = "";
859
+ process2.stdout.on("data", (data) => {
860
+ stdout += data.toString();
861
+ onStdout?.(data);
862
+ });
863
+ process2.stderr.on("data", (data) => {
864
+ stderr += data.toString();
865
+ });
866
+ process2.on("error", (err) => {
867
+ stderr += err.toString();
868
+ });
869
+ process2.on("close", (code) => {
870
+ const timings = { date, duration: calcDuration(start) };
871
+ if (code === 0) {
872
+ onComplete?.();
873
+ resolve({ code, stdout, stderr, ...timings });
874
+ } else {
875
+ const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
876
+ onError?.(errorMsg);
877
+ reject(errorMsg);
878
+ }
879
+ });
880
+ });
881
+ }
882
+ function objectToCliArgs(params) {
883
+ if (!params) {
884
+ return [];
885
+ }
886
+ return Object.entries(params).flatMap(([key, value]) => {
887
+ if (key === "_") {
888
+ if (Array.isArray(value)) {
889
+ return value;
890
+ } else {
891
+ return [value + ""];
892
+ }
893
+ }
894
+ const prefix = key.length === 1 ? "-" : "--";
895
+ if (Array.isArray(value)) {
896
+ return value.map((v) => `${prefix}${key}="${v}"`);
897
+ }
898
+ if (Array.isArray(value)) {
899
+ return value.map((v) => `${prefix}${key}="${v}"`);
900
+ }
901
+ if (typeof value === "string") {
902
+ return [`${prefix}${key}="${value}"`];
903
+ }
904
+ if (typeof value === "number") {
905
+ return [`${prefix}${key}=${value}`];
906
+ }
907
+ if (typeof value === "boolean") {
908
+ return [`${prefix}${value ? "" : "no-"}${key}`];
909
+ }
910
+ throw new Error(`Unsupported type ${typeof value} for key ${key}`);
911
+ });
912
+ }
913
+ objectToCliArgs({ z: 5 });
914
+
915
+ // packages/utils/src/lib/git.ts
916
+ import simpleGit from "simple-git";
917
+ var git = simpleGit();
918
+ async function getLatestCommit() {
919
+ const log = await git.log({
920
+ maxCount: 1,
921
+ format: { hash: "%H", message: "%s", author: "%an", date: "%ad" }
922
+ });
923
+ return log?.latest;
924
+ }
925
+
926
+ // packages/utils/src/lib/progress.ts
927
+ import chalk2 from "chalk";
928
+ import { MultiProgressBars } from "multi-progress-bars";
929
+ var barStyles = {
930
+ active: (s) => chalk2.green(s),
931
+ done: (s) => chalk2.gray(s),
932
+ idle: (s) => chalk2.gray(s)
933
+ };
934
+ var messageStyles = {
935
+ active: (s) => chalk2.black(s),
936
+ done: (s) => chalk2.green(chalk2.bold(s)),
937
+ idle: (s) => chalk2.gray(s)
938
+ };
939
+ var mpb;
940
+ function getSingletonProgressBars(options2) {
941
+ if (!mpb) {
942
+ mpb = new MultiProgressBars({
943
+ initMessage: "",
944
+ border: true,
945
+ ...options2
946
+ });
947
+ }
948
+ return mpb;
949
+ }
950
+ function getProgressBar(taskName) {
951
+ const tasks = getSingletonProgressBars();
952
+ tasks.addTask(taskName, {
953
+ type: "percentage",
954
+ percentage: 0,
955
+ message: "",
956
+ barTransformFn: barStyles.idle
957
+ });
958
+ return {
959
+ incrementInSteps: (numPlugins) => {
960
+ tasks.incrementTask(taskName, {
961
+ percentage: 1 / numPlugins,
962
+ barTransformFn: barStyles.active
963
+ });
964
+ },
965
+ updateTitle: (title) => {
966
+ tasks.updateTask(taskName, {
967
+ message: title,
968
+ barTransformFn: barStyles.active
969
+ });
970
+ },
971
+ endProgress: (message = "") => {
972
+ tasks.done(taskName, {
973
+ message: messageStyles.done(message),
974
+ barTransformFn: barStyles.done
975
+ });
976
+ }
977
+ };
978
+ }
979
+
980
+ // packages/utils/src/lib/md/details.ts
981
+ function details(title, content, cfg = { open: false }) {
982
+ return `<details${cfg.open ? " open" : ""}>
983
+ <summary>${title}</summary>
984
+ ${content}
985
+ </details>
986
+ `;
987
+ }
988
+
989
+ // packages/utils/src/lib/md/headline.ts
990
+ function headline(text, hierarchy = 1) {
991
+ return `${new Array(hierarchy).fill("#").join("")} ${text}`;
992
+ }
993
+ function h2(text) {
994
+ return headline(text, 2);
995
+ }
996
+ function h3(text) {
997
+ return headline(text, 3);
998
+ }
999
+
1000
+ // packages/utils/src/lib/md/constants.ts
1001
+ var NEW_LINE = "\n";
1002
+
1003
+ // packages/utils/src/lib/md/table.ts
1004
+ var alignString = /* @__PURE__ */ new Map([
1005
+ ["l", ":--"],
1006
+ ["c", ":--:"],
1007
+ ["r", "--:"]
1008
+ ]);
1009
+ function tableMd(data, align) {
1010
+ if (data.length === 0) {
1011
+ throw new Error("Data can't be empty");
1012
+ }
1013
+ align = align || data[0]?.map(() => "c");
1014
+ const _data = data.map((arr) => "|" + arr.join("|") + "|");
1015
+ const secondRow = "|" + align?.map((s) => alignString.get(s)).join("|") + "|";
1016
+ return _data.shift() + NEW_LINE + secondRow + NEW_LINE + _data.join(NEW_LINE);
1017
+ }
1018
+ function tableHtml(data) {
1019
+ if (data.length === 0) {
1020
+ throw new Error("Data can't be empty");
1021
+ }
1022
+ const _data = data.map((arr, index) => {
1023
+ if (index === 0) {
1024
+ return "<tr>" + arr.map((s) => `<th>${s}</th>`).join("") + "</tr>";
1025
+ }
1026
+ return "<tr>" + arr.map((s) => `<td>${s}</td>`).join("") + "</tr>";
1027
+ });
1028
+ return "<table>" + _data.join("") + "</table>";
1029
+ }
1030
+
1031
+ // packages/utils/src/lib/md/font-style.ts
1032
+ var stylesMap = {
1033
+ i: "_",
1034
+ // italic
1035
+ b: "**",
1036
+ // bold
1037
+ s: "~",
1038
+ // strike through
1039
+ c: "`"
1040
+ // code
1041
+ };
1042
+ function style(text, styles = ["b"]) {
1043
+ return styles.reduce((t, s) => `${stylesMap[s]}${t}${stylesMap[s]}`, text);
1044
+ }
1045
+
1046
+ // packages/utils/src/lib/md/link.ts
1047
+ function link(href, text) {
1048
+ return `[${text || href}](${href})`;
1049
+ }
1050
+
1051
+ // packages/utils/src/lib/md/list.ts
1052
+ function li(text, order = "unordered") {
1053
+ const style2 = order === "unordered" ? "-" : "- [ ]";
1054
+ return `${style2} ${text}`;
1055
+ }
1056
+
1057
+ // packages/utils/src/lib/report-to-md.ts
1058
+ function reportToMd(report, commitData) {
1059
+ let md = reportToHeaderSection() + NEW_LINE;
1060
+ md += reportToOverviewSection(report) + NEW_LINE + NEW_LINE;
1061
+ md += reportToCategoriesSection(report) + NEW_LINE + NEW_LINE;
1062
+ md += reportToAuditsSection(report) + NEW_LINE + NEW_LINE;
1063
+ md += reportToAboutSection(report, commitData) + NEW_LINE + NEW_LINE;
1064
+ md += `${FOOTER_PREFIX} ${link(README_LINK, "Code PushUp")}`;
1065
+ return md;
1066
+ }
1067
+ function reportToHeaderSection() {
1068
+ return headline(reportHeadlineText) + NEW_LINE;
1069
+ }
1070
+ function reportToOverviewSection(report) {
1071
+ const { categories } = report;
1072
+ const tableContent = [
1073
+ reportOverviewTableHeaders,
1074
+ ...categories.map(({ title, refs, score }) => [
1075
+ link(`#${slugify(title)}`, title),
1076
+ `${getRoundScoreMarker(score)} ${style(formatReportScore(score))}`,
1077
+ countCategoryAudits(refs, report.plugins).toString()
1078
+ ])
1079
+ ];
1080
+ return tableMd(tableContent, ["l", "c", "c"]);
1081
+ }
1082
+ function reportToCategoriesSection(report) {
1083
+ const { categories, plugins } = report;
1084
+ const categoryDetails = categories.reduce((acc, category) => {
1085
+ const categoryTitle = h3(category.title);
1086
+ const categoryScore = `${getRoundScoreMarker(
1087
+ category.score
1088
+ )} Score: ${style(formatReportScore(category.score))}`;
1089
+ const categoryDocs = getDocsAndDescription(category);
1090
+ const auditsAndGroups = category.refs.reduce(
1091
+ (acc2, ref) => ({
1092
+ ...acc2,
1093
+ ...ref.type === "group" ? {
1094
+ groups: [
1095
+ ...acc2.groups,
1096
+ getGroupWithAudits(ref.slug, ref.plugin, plugins)
1097
+ ]
1098
+ } : {
1099
+ audits: [...acc2.audits, getAuditByRef(ref, plugins)]
1100
+ }
1101
+ }),
1102
+ { groups: [], audits: [] }
1103
+ );
1104
+ const audits = auditsAndGroups.audits.sort(sortCategoryAudits).map((audit) => auditItemToCategorySection(audit, plugins)).join(NEW_LINE);
1105
+ const groups = auditsAndGroups.groups.map((group) => groupItemToCategorySection(group, plugins)).join("");
1106
+ return acc + NEW_LINE + categoryTitle + NEW_LINE + NEW_LINE + categoryDocs + categoryScore + NEW_LINE + groups + NEW_LINE + audits;
1107
+ }, "");
1108
+ return h2("\u{1F3F7} Categories") + NEW_LINE + categoryDetails;
1109
+ }
1110
+ function auditItemToCategorySection(audit, plugins) {
1111
+ const pluginTitle = getPluginNameFromSlug(audit.plugin, plugins);
1112
+ const auditTitle = link(
1113
+ `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1114
+ audit?.title
1115
+ );
1116
+ return li(
1117
+ `${getSquaredScoreMarker(
1118
+ audit.score
1119
+ )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`
1120
+ );
1121
+ }
1122
+ function groupItemToCategorySection(group, plugins) {
1123
+ const pluginTitle = getPluginNameFromSlug(group.plugin, plugins);
1124
+ const groupScore = Number(formatReportScore(group?.score || 0));
1125
+ const groupTitle = li(
1126
+ `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`
1127
+ );
1128
+ const groupAudits = group.audits.reduce((acc, audit) => {
1129
+ const auditTitle = link(
1130
+ `#${slugify(audit.title)}-${slugify(pluginTitle)}`,
1131
+ audit?.title
1132
+ );
1133
+ acc += ` ${li(
1134
+ `${getSquaredScoreMarker(audit.score)} ${auditTitle} - ${getAuditResult(
1135
+ audit
1136
+ )}`
1137
+ )}`;
1138
+ acc += NEW_LINE;
1139
+ return acc;
1140
+ }, "");
1141
+ return groupTitle + NEW_LINE + groupAudits;
1142
+ }
1143
+ function reportToAuditsSection(report) {
1144
+ const auditsSection = report.plugins.reduce((acc, plugin) => {
1145
+ const auditsData = plugin.audits.sort(sortAudits).reduce((acc2, audit) => {
1146
+ const auditTitle = `${audit.title} (${getPluginNameFromSlug(
1147
+ audit.plugin,
1148
+ report.plugins
1149
+ )})`;
1150
+ const detailsTitle = `${getSquaredScoreMarker(
1151
+ audit.score
1152
+ )} ${getAuditResult(audit, true)} (score: ${formatReportScore(
1153
+ audit.score
1154
+ )})`;
1155
+ const docsItem = getDocsAndDescription(audit);
1156
+ acc2 += h3(auditTitle);
1157
+ acc2 += NEW_LINE;
1158
+ acc2 += NEW_LINE;
1159
+ if (!audit.details?.issues?.length) {
1160
+ acc2 += detailsTitle;
1161
+ acc2 += NEW_LINE;
1162
+ acc2 += NEW_LINE;
1163
+ acc2 += docsItem;
1164
+ return acc2;
1165
+ }
1166
+ const detailsTableData = [
1167
+ detailsTableHeaders,
1168
+ ...audit.details.issues.map((issue) => {
1169
+ const severity = `${getSeverityIcon(issue.severity)} <i>${issue.severity}</i>`;
1170
+ const message = issue.message;
1171
+ if (!issue.source) {
1172
+ return [severity, message, "", ""];
1173
+ }
1174
+ const file = `<code>${issue.source?.file}</code>`;
1175
+ if (!issue.source.position) {
1176
+ return [severity, message, file, ""];
1177
+ }
1178
+ const { startLine, endLine } = issue.source.position;
1179
+ const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
1180
+ return [severity, message, file, line];
1181
+ })
1182
+ ];
1183
+ const detailsTable = `<h4>Issues</h4>${tableHtml(detailsTableData)}`;
1184
+ acc2 += details(detailsTitle, detailsTable);
1185
+ acc2 += NEW_LINE;
1186
+ acc2 += NEW_LINE;
1187
+ acc2 += docsItem;
1188
+ return acc2;
1189
+ }, "");
1190
+ return acc + auditsData;
1191
+ }, "");
1192
+ return h2("\u{1F6E1}\uFE0F Audits") + NEW_LINE + NEW_LINE + auditsSection;
1193
+ }
1194
+ function reportToAboutSection(report, commitData) {
1195
+ const date = (/* @__PURE__ */ new Date()).toString();
1196
+ const { duration, version: version2, plugins, categories } = report;
1197
+ const commitInfo = commitData ? `${commitData.message} (${commitData.hash.slice(0, 7)})` : "N/A";
1198
+ const reportMetaTable = [
1199
+ reportMetaTableHeaders,
1200
+ [
1201
+ commitInfo,
1202
+ style(version2 || "", ["c"]),
1203
+ formatDuration(duration),
1204
+ plugins.length.toString(),
1205
+ categories.length.toString(),
1206
+ plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
1207
+ ]
1208
+ ];
1209
+ const pluginMetaTable = [
1210
+ pluginMetaTableHeaders,
1211
+ ...plugins.map(({ title, version: version3, duration: duration2, audits }) => [
1212
+ title,
1213
+ audits.length.toString(),
1214
+ style(version3 || "", ["c"]),
1215
+ formatDuration(duration2)
1216
+ ])
1217
+ ];
1218
+ return h2("About") + NEW_LINE + NEW_LINE + `Report was created by [Code PushUp](${README_LINK}) on ${date}.` + NEW_LINE + NEW_LINE + tableMd(reportMetaTable, ["l", "c", "c", "c", "c", "c"]) + NEW_LINE + NEW_LINE + "The following plugins were run:" + NEW_LINE + NEW_LINE + tableMd(pluginMetaTable, ["l", "c", "c", "c"]);
1219
+ }
1220
+ function getDocsAndDescription({
1221
+ docsUrl,
1222
+ description
1223
+ }) {
1224
+ if (docsUrl) {
1225
+ const docsLink = link(docsUrl, "\u{1F4D6} Docs");
1226
+ if (!description) {
1227
+ return docsLink + NEW_LINE + NEW_LINE;
1228
+ }
1229
+ if (description.endsWith("```")) {
1230
+ return description + NEW_LINE + NEW_LINE + docsLink + NEW_LINE + NEW_LINE;
1231
+ }
1232
+ return `${description} ${docsLink}${NEW_LINE}${NEW_LINE}`;
1233
+ }
1234
+ if (description) {
1235
+ return description + NEW_LINE + NEW_LINE;
1236
+ }
1237
+ return "";
1238
+ }
1239
+ function getAuditResult(audit, isHtml = false) {
1240
+ const { displayValue, value } = audit;
1241
+ return isHtml ? `<b>${displayValue || value}</b>` : style(String(displayValue || value));
1242
+ }
1243
+
1244
+ // packages/utils/src/lib/report-to-stdout.ts
1245
+ import cliui from "@isaacs/cliui";
1246
+ import chalk3 from "chalk";
1247
+ import Table from "cli-table3";
1248
+ function addLine(line = "") {
1249
+ return line + NEW_LINE;
1250
+ }
1251
+ function reportToStdout(report) {
1252
+ let output = "";
1253
+ output += addLine(reportToHeaderSection2(report));
1254
+ output += addLine();
1255
+ output += addLine(reportToDetailSection(report));
1256
+ output += addLine(reportToOverviewSection2(report));
1257
+ output += addLine(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
1258
+ return output;
1259
+ }
1260
+ function reportToHeaderSection2(report) {
1261
+ const { packageName, version: version2 } = report;
1262
+ return `${chalk3.bold(reportHeadlineText)} - ${packageName}@${version2}`;
1263
+ }
1264
+ function reportToOverviewSection2({
1265
+ categories,
1266
+ plugins
1267
+ }) {
1268
+ let output = addLine(chalk3.magentaBright.bold("Categories"));
1269
+ output += addLine();
1270
+ const table = new Table({
1271
+ head: reportRawOverviewTableHeaders,
1272
+ colAligns: ["left", "right", "right"],
1273
+ style: {
1274
+ head: ["cyan"]
1275
+ }
1276
+ });
1277
+ table.push(
1278
+ ...categories.map(({ title, score, refs }) => [
1279
+ title,
1280
+ withColor({ score }),
1281
+ countCategoryAudits(refs, plugins)
1282
+ ])
1283
+ );
1284
+ output += addLine(table.toString());
1285
+ return output;
1286
+ }
1287
+ function reportToDetailSection(report) {
1288
+ const { plugins } = report;
1289
+ let output = "";
1290
+ plugins.forEach(({ title, audits }) => {
1291
+ output += addLine();
1292
+ output += addLine(chalk3.magentaBright.bold(`${title} audits`));
1293
+ output += addLine();
1294
+ const ui = cliui({ width: 80 });
1295
+ audits.sort(sortAudits).forEach(({ score, title: title2, displayValue, value }) => {
1296
+ ui.div(
1297
+ {
1298
+ text: withColor({ score, text: "\u25CF" }),
1299
+ width: 2,
1300
+ padding: [0, 1, 0, 0]
1301
+ },
1302
+ {
1303
+ text: title2,
1304
+ padding: [0, 3, 0, 0]
1305
+ },
1306
+ {
1307
+ text: chalk3.cyanBright(displayValue || `${value}`),
1308
+ width: 10,
1309
+ padding: [0, 0, 0, 0]
1310
+ }
1311
+ );
1312
+ });
1313
+ output += addLine(ui.toString());
1314
+ output += addLine();
1315
+ });
1316
+ return output;
1317
+ }
1318
+ function withColor({ score, text }) {
1319
+ let str = text ?? formatReportScore(score);
1320
+ const style2 = text ? chalk3 : chalk3.bold;
1321
+ if (score < 0.5) {
1322
+ str = style2.red(str);
1323
+ } else if (score < 0.9) {
1324
+ str = style2.yellow(str);
1325
+ } else {
1326
+ str = style2.green(str);
1327
+ }
1328
+ return str;
1329
+ }
1330
+
1331
+ // packages/utils/src/lib/transformation.ts
1332
+ function deepClone(obj) {
1333
+ if (obj == null || typeof obj !== "object") {
1334
+ return obj;
1335
+ }
1336
+ const cloned = Array.isArray(obj) ? [] : {};
1337
+ for (const key in obj) {
1338
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
1339
+ cloned[key] = deepClone(obj[key]);
1340
+ }
1341
+ }
1342
+ return cloned;
1343
+ }
1344
+
1345
+ // packages/utils/src/lib/scoring.ts
1346
+ function calculateScore(refs, scoreFn) {
1347
+ const { numerator, denominator } = refs.reduce(
1348
+ (acc, ref) => {
1349
+ const score = scoreFn(ref);
1350
+ return {
1351
+ numerator: acc.numerator + score * ref.weight,
1352
+ denominator: acc.denominator + ref.weight
1353
+ };
1354
+ },
1355
+ { numerator: 0, denominator: 0 }
1356
+ );
1357
+ return numerator / denominator;
1358
+ }
1359
+ function scoreReport(report) {
1360
+ const scoredReport = deepClone(report);
1361
+ const allScoredAuditsAndGroups = /* @__PURE__ */ new Map();
1362
+ scoredReport.plugins?.forEach((plugin) => {
1363
+ const { audits } = plugin;
1364
+ const groups = plugin.groups || [];
1365
+ audits.forEach((audit) => {
1366
+ const key = `${plugin.slug}-${audit.slug}-audit`;
1367
+ audit.plugin = plugin.slug;
1368
+ allScoredAuditsAndGroups.set(key, audit);
1369
+ });
1370
+ function groupScoreFn(ref) {
1371
+ const score = allScoredAuditsAndGroups.get(
1372
+ `${plugin.slug}-${ref.slug}-audit`
1373
+ )?.score;
1374
+ if (score == null) {
1375
+ throw new Error(
1376
+ `Group has invalid ref - audit with slug ${plugin.slug}-${ref.slug}-audit not found`
1377
+ );
1378
+ }
1379
+ return score;
1380
+ }
1381
+ groups.forEach((group) => {
1382
+ const key = `${plugin.slug}-${group.slug}-group`;
1383
+ group.score = calculateScore(group.refs, groupScoreFn);
1384
+ group.plugin = plugin.slug;
1385
+ allScoredAuditsAndGroups.set(key, group);
1386
+ });
1387
+ plugin.groups = groups;
1388
+ });
1389
+ function catScoreFn(ref) {
1390
+ const key = `${ref.plugin}-${ref.slug}-${ref.type}`;
1391
+ const item = allScoredAuditsAndGroups.get(key);
1392
+ if (!item) {
1393
+ throw new Error(
1394
+ `Category has invalid ref - ${ref.type} with slug ${key} not found in ${ref.plugin} plugin`
1395
+ );
1396
+ }
1397
+ return item.score;
1398
+ }
1399
+ const scoredCategoriesMap = /* @__PURE__ */ new Map();
1400
+ for (const category of scoredReport.categories) {
1401
+ category.score = calculateScore(category.refs, catScoreFn);
1402
+ scoredCategoriesMap.set(category.slug, category);
1403
+ }
1404
+ scoredReport.categories = Array.from(scoredCategoriesMap.values());
1405
+ return scoredReport;
1406
+ }
1407
+
1408
+ // packages/utils/src/lib/verbose-utils.ts
1409
+ function getLogVerbose(verbose) {
1410
+ return (...args) => {
1411
+ if (verbose) {
1412
+ console.info(...args);
1413
+ }
1414
+ };
1415
+ }
1416
+ function getExecVerbose(verbose) {
1417
+ return (fn) => {
1418
+ if (verbose) {
1419
+ fn();
1420
+ }
1421
+ };
1422
+ }
1423
+ var verboseUtils = (verbose) => ({
1424
+ log: getLogVerbose(verbose),
1425
+ exec: getExecVerbose(verbose)
1426
+ });
1427
+
1428
+ // packages/core/src/lib/implementation/persist.ts
1429
+ var PersistDirError = class extends Error {
1430
+ constructor(outputDir) {
1431
+ super(`outPath: ${outputDir} is no directory.`);
1432
+ }
1433
+ };
1434
+ var PersistError = class extends Error {
1435
+ constructor(reportPath) {
1436
+ super(`fileName: ${reportPath} could not be saved.`);
1437
+ }
1438
+ };
1439
+ async function persistReport(report, config) {
1440
+ const { persist } = config;
1441
+ const outputDir = persist.outputDir;
1442
+ const filename = persist.filename;
1443
+ const format = persist.format ?? [];
1444
+ let scoredReport = scoreReport(report);
1445
+ console.info(reportToStdout(scoredReport));
1446
+ const results = [
1447
+ // JSON is always persisted
1448
+ { format: "json", content: JSON.stringify(report, null, 2) }
1449
+ ];
1450
+ if (format.includes("md")) {
1451
+ scoredReport = scoredReport || scoreReport(report);
1452
+ const commitData = await getLatestCommit();
1453
+ if (!commitData) {
1454
+ console.warn("no commit data available");
1455
+ }
1456
+ results.push({
1457
+ format: "md",
1458
+ content: reportToMd(scoredReport, commitData)
1459
+ });
1460
+ }
1461
+ if (!existsSync(outputDir)) {
1462
+ try {
1463
+ mkdirSync(outputDir, { recursive: true });
1464
+ } catch (e) {
1465
+ console.warn(e);
1466
+ throw new PersistDirError(outputDir);
1467
+ }
1468
+ }
1469
+ return Promise.allSettled(
1470
+ results.map(({ format: format2, content }) => {
1471
+ const reportPath = join2(outputDir, `${filename}.${format2}`);
1472
+ return writeFile(reportPath, content).then(() => stat2(reportPath)).then((stats) => [reportPath, stats.size]).catch((e) => {
1473
+ console.warn(e);
1474
+ throw new PersistError(reportPath);
1475
+ });
1476
+ })
1477
+ );
1478
+ }
1479
+ function logPersistedResults(persistResults) {
1480
+ logMultipleFileResults(persistResults, "Generated reports");
1481
+ }
1482
+
1483
+ // packages/core/src/lib/implementation/execute-plugin.ts
1484
+ import chalk4 from "chalk";
1485
+
1486
+ // packages/core/src/lib/implementation/runner.ts
1487
+ import { join as join3 } from "path";
1488
+ async function executeRunnerConfig(cfg, onProgress) {
1489
+ const { args, command, outputFile, outputTransform } = cfg;
1490
+ const { duration, date } = await executeProcess({
1491
+ command,
1492
+ args,
1493
+ observer: { onStdout: onProgress }
1494
+ });
1495
+ let audits = await readJsonFile(
1496
+ join3(process.cwd(), outputFile)
1497
+ );
1498
+ if (outputTransform) {
1499
+ audits = await outputTransform(audits);
1500
+ }
1501
+ return {
1502
+ duration,
1503
+ date,
1504
+ audits
1505
+ };
1506
+ }
1507
+ async function executeRunnerFunction(runner, onProgress) {
1508
+ const date = (/* @__PURE__ */ new Date()).toISOString();
1509
+ const start = performance.now();
1510
+ const audits = await runner(onProgress);
1511
+ return {
1512
+ date,
1513
+ duration: calcDuration(start),
1514
+ audits
1515
+ };
1516
+ }
1517
+
1518
+ // packages/core/src/lib/implementation/execute-plugin.ts
1519
+ var PluginOutputMissingAuditError = class extends Error {
1520
+ constructor(auditSlug) {
1521
+ super(`Audit metadata not found for slug ${auditSlug}`);
1522
+ }
1523
+ };
1524
+ async function executePlugin(pluginConfig, onProgress) {
1525
+ const {
1526
+ runner,
1527
+ audits: pluginConfigAudits,
1528
+ description,
1529
+ docsUrl,
1530
+ groups,
1531
+ ...pluginMeta
1532
+ } = pluginConfig;
1533
+ const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
1534
+ const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
1535
+ const auditOutputs = auditOutputsSchema.parse(unvalidatedAuditOutputs);
1536
+ auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
1537
+ const auditReports = auditOutputs.map(
1538
+ (auditOutput) => ({
1539
+ ...auditOutput,
1540
+ ...pluginConfigAudits.find(
1541
+ (audit) => audit.slug === auditOutput.slug
1542
+ )
1543
+ })
1544
+ );
1545
+ return {
1546
+ ...pluginMeta,
1547
+ ...executionMeta,
1548
+ audits: auditReports,
1549
+ ...description && { description },
1550
+ ...docsUrl && { docsUrl },
1551
+ ...groups && { groups }
1552
+ };
1553
+ }
1554
+ async function executePlugins(plugins, options2) {
1555
+ const { progress = false } = options2 || {};
1556
+ const progressName = "Run Plugins";
1557
+ const progressBar = progress ? getProgressBar(progressName) : null;
1558
+ const pluginsResult = await plugins.reduce(async (acc, pluginCfg) => {
1559
+ const outputs = await acc;
1560
+ progressBar?.updateTitle(`Executing ${chalk4.bold(pluginCfg.title)}`);
1561
+ try {
1562
+ const pluginReport = await executePlugin(pluginCfg);
1563
+ progressBar?.incrementInSteps(plugins.length);
1564
+ return outputs.concat(Promise.resolve(pluginReport));
1565
+ } catch (e) {
1566
+ progressBar?.incrementInSteps(plugins.length);
1567
+ return outputs.concat(
1568
+ Promise.reject(e instanceof Error ? e.message : String(e))
1569
+ );
1570
+ }
1571
+ }, Promise.resolve([]));
1572
+ progressBar?.endProgress("Done running plugins");
1573
+ const errorsCallback = ({ reason }) => console.error(reason);
1574
+ const results = await Promise.allSettled(pluginsResult);
1575
+ logMultipleResults(results, "Plugins", void 0, errorsCallback);
1576
+ const failedResults = results.filter(isPromiseRejectedResult);
1577
+ if (failedResults.length) {
1578
+ const errorMessages = failedResults.map(({ reason }) => reason).join(", ");
1579
+ throw new Error(
1580
+ `Plugins failed: ${failedResults.length} errors: ${errorMessages}`
1581
+ );
1582
+ }
1583
+ return results.filter(isPromiseFulfilledResult).map((result) => result.value);
1584
+ }
1585
+ function auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits) {
1586
+ auditOutputs.forEach((auditOutput) => {
1587
+ const auditMetadata = pluginConfigAudits.find(
1588
+ (audit) => audit.slug === auditOutput.slug
1589
+ );
1590
+ if (!auditMetadata) {
1591
+ throw new PluginOutputMissingAuditError(auditOutput.slug);
1592
+ }
1593
+ });
1594
+ }
1595
+
1596
+ // packages/core/package.json
1597
+ var name = "@code-pushup/core";
1598
+ var version = "0.1.0";
1599
+
1600
+ // packages/core/src/lib/implementation/collect.ts
1601
+ async function collect(options2) {
1602
+ const { plugins, categories } = options2;
1603
+ if (!plugins?.length) {
1604
+ throw new Error("No plugins registered");
1605
+ }
1606
+ const date = (/* @__PURE__ */ new Date()).toISOString();
1607
+ const start = performance.now();
1608
+ const pluginOutputs = await executePlugins(plugins, options2);
1609
+ return {
1610
+ packageName: name,
1611
+ version,
1612
+ date,
1613
+ duration: calcDuration(start),
1614
+ categories,
1615
+ plugins: pluginOutputs
1616
+ };
1617
+ }
1618
+
1619
+ // packages/core/src/lib/upload.ts
1620
+ import { uploadToPortal } from "@code-pushup/portal-client";
1621
+
1622
+ // packages/core/src/lib/implementation/json-to-gql.ts
1623
+ import {
1624
+ CategoryConfigRefType,
1625
+ IssueSourceType,
1626
+ IssueSeverity as PortalIssueSeverity
1627
+ } from "@code-pushup/portal-client";
1628
+ function jsonToGql(report) {
1629
+ return {
1630
+ packageName: report.packageName,
1631
+ packageVersion: report.version,
1632
+ commandStartDate: report.date,
1633
+ commandDuration: report.duration,
1634
+ plugins: report.plugins.map((plugin) => ({
1635
+ audits: plugin.audits.map((audit) => ({
1636
+ description: audit.description,
1637
+ details: {
1638
+ issues: audit.details?.issues.map((issue) => ({
1639
+ message: issue.message,
1640
+ severity: transformSeverity(issue.severity),
1641
+ sourceEndColumn: issue.source?.position?.endColumn,
1642
+ sourceEndLine: issue.source?.position?.endLine,
1643
+ sourceFilePath: issue.source?.file,
1644
+ sourceStartColumn: issue.source?.position?.startColumn,
1645
+ sourceStartLine: issue.source?.position?.startLine,
1646
+ sourceType: IssueSourceType.SourceCode
1647
+ })) || []
1648
+ },
1649
+ docsUrl: audit.docsUrl,
1650
+ formattedValue: audit.displayValue,
1651
+ score: audit.score,
1652
+ slug: audit.slug,
1653
+ title: audit.title,
1654
+ value: audit.value
1655
+ })),
1656
+ description: plugin.description,
1657
+ docsUrl: plugin.docsUrl,
1658
+ groups: plugin.groups?.map((group) => ({
1659
+ slug: group.slug,
1660
+ title: group.title,
1661
+ description: group.description,
1662
+ refs: group.refs.map((ref) => ({ slug: ref.slug, weight: ref.weight }))
1663
+ })),
1664
+ icon: plugin.icon,
1665
+ slug: plugin.slug,
1666
+ title: plugin.title,
1667
+ packageName: plugin.packageName,
1668
+ packageVersion: plugin.version,
1669
+ runnerDuration: plugin.duration,
1670
+ runnerStartDate: plugin.date
1671
+ })),
1672
+ categories: report.categories.map((category) => ({
1673
+ slug: category.slug,
1674
+ title: category.title,
1675
+ description: category.description,
1676
+ refs: category.refs.map((ref) => ({
1677
+ plugin: ref.plugin,
1678
+ type: ref.type === "audit" ? CategoryConfigRefType.Audit : CategoryConfigRefType.Group,
1679
+ weight: ref.weight,
1680
+ slug: ref.slug
1681
+ }))
1682
+ }))
1683
+ };
1684
+ }
1685
+ function transformSeverity(severity) {
1686
+ switch (severity) {
1687
+ case "info":
1688
+ return PortalIssueSeverity.Info;
1689
+ case "error":
1690
+ return PortalIssueSeverity.Error;
1691
+ case "warning":
1692
+ return PortalIssueSeverity.Warning;
1693
+ }
1694
+ }
1695
+
1696
+ // packages/core/src/lib/upload.ts
1697
+ async function upload(options2, uploadFn = uploadToPortal) {
1698
+ if (options2?.upload === void 0) {
1699
+ throw new Error("upload config needs to be set");
1700
+ }
1701
+ const { apiKey, server, organization, project } = options2.upload;
1702
+ const { outputDir, filename } = options2.persist;
1703
+ const report = await loadReport({
1704
+ outputDir,
1705
+ filename,
1706
+ format: "json"
1707
+ });
1708
+ const commitData = await getLatestCommit();
1709
+ if (!commitData) {
1710
+ throw new Error("no commit data available");
1711
+ }
1712
+ const data = {
1713
+ organization,
1714
+ project,
1715
+ commit: commitData.hash,
1716
+ ...jsonToGql(report)
1717
+ };
1718
+ return uploadFn({ apiKey, server, data });
1719
+ }
1720
+
1721
+ // packages/core/src/lib/collect-and-persist.ts
1722
+ async function collectAndPersistReports(options2) {
1723
+ const { exec } = verboseUtils(options2.verbose);
1724
+ const report = await collect(options2);
1725
+ const persistResults = await persistReport(report, options2);
1726
+ exec(() => logPersistedResults(persistResults));
1727
+ report.plugins.forEach((plugin) => {
1728
+ pluginReportSchema.parse(plugin);
1729
+ });
1730
+ }
1731
+
1732
+ // packages/core/src/lib/implementation/read-code-pushup-config.ts
1733
+ import { stat as stat3 } from "fs/promises";
1734
+ var ConfigPathError = class extends Error {
1735
+ constructor(configPath) {
1736
+ super(`Config path ${configPath} is not a file.`);
1737
+ }
1738
+ };
1739
+ async function readCodePushupConfig(filepath) {
1740
+ if (!filepath.length) {
1741
+ throw new Error("No filepath provided");
1742
+ }
1743
+ const isFile = (await stat3(filepath)).isFile();
1744
+ if (!isFile) {
1745
+ throw new ConfigPathError(filepath);
1746
+ }
1747
+ return importEsmModule(
1748
+ {
1749
+ filepath
1750
+ },
1751
+ coreConfigSchema.parse
1752
+ );
1753
+ }
1754
+
1755
+ // packages/cli/src/lib/implementation/only-plugins-options.ts
1756
+ var onlyPluginsOption = {
1757
+ describe: "List of plugins to run. If not set all plugins are run.",
1758
+ type: "array",
1759
+ default: [],
1760
+ coerce: (arg) => arg.flatMap((v) => v.split(","))
1761
+ };
1762
+
1763
+ // packages/cli/src/lib/autorun/autorun-command.ts
1764
+ function yargsAutorunCommandObject() {
1765
+ const command = "autorun";
1766
+ return {
1767
+ command,
1768
+ describe: "Shortcut for running collect followed by upload",
1769
+ builder: {
1770
+ onlyPlugins: onlyPluginsOption
1771
+ },
1772
+ handler: async (args) => {
1773
+ console.info(chalk5.bold(CLI_NAME));
1774
+ console.info(chalk5.gray(`Run ${command}...`));
1775
+ const options2 = args;
1776
+ await collectAndPersistReports(options2);
1777
+ if (!options2.upload) {
1778
+ console.warn("Upload skipped because configuration is not set.");
1779
+ } else {
1780
+ await upload(options2);
1781
+ }
1782
+ }
1783
+ };
1784
+ }
1785
+
1786
+ // packages/cli/src/lib/collect/collect-command.ts
1787
+ import chalk6 from "chalk";
1788
+ function yargsCollectCommandObject() {
1789
+ const command = "collect";
1790
+ return {
1791
+ command,
1792
+ describe: "Run Plugins and collect results",
1793
+ builder: {
1794
+ onlyPlugins: onlyPluginsOption
1795
+ },
1796
+ handler: async (args) => {
1797
+ const options2 = args;
1798
+ console.info(chalk6.bold(CLI_NAME));
1799
+ console.info(chalk6.gray(`Run ${command}...`));
1800
+ await collectAndPersistReports(options2);
1801
+ }
1802
+ };
1803
+ }
1804
+
1805
+ // packages/cli/src/lib/implementation/filter-kebab-case-keys.ts
1806
+ function filterKebabCaseKeys(obj) {
1807
+ const newObj = {};
1808
+ Object.keys(obj).forEach((key) => {
1809
+ if (key.includes("-")) {
1810
+ return;
1811
+ }
1812
+ if (typeof obj[key] === "string" || typeof obj[key] === "object" && Array.isArray(obj[key])) {
1813
+ newObj[key] = obj[key];
1814
+ }
1815
+ if (typeof obj[key] === "object" && !Array.isArray(obj[key]) && obj[key] != null) {
1816
+ newObj[key] = filterKebabCaseKeys(obj[key]);
1817
+ }
1818
+ });
1819
+ return newObj;
1820
+ }
1821
+
1822
+ // packages/cli/src/lib/print-config/print-config-command.ts
1823
+ function yargsConfigCommandObject() {
1824
+ const command = "print-config";
1825
+ return {
1826
+ command,
1827
+ describe: "Print config",
1828
+ builder: {
1829
+ onlyPlugins: onlyPluginsOption
1830
+ },
1831
+ handler: (yargsArgs) => {
1832
+ const { _, $0, ...args } = yargsArgs;
1833
+ const cleanArgs = filterKebabCaseKeys(args);
1834
+ console.info(JSON.stringify(cleanArgs, null, 2));
1835
+ }
1836
+ };
1837
+ }
1838
+
1839
+ // packages/cli/src/lib/upload/upload-command.ts
1840
+ import chalk7 from "chalk";
1841
+ function yargsUploadCommandObject() {
1842
+ const command = "upload";
1843
+ return {
1844
+ command,
1845
+ describe: "Upload report results to the portal",
1846
+ handler: async (args) => {
1847
+ console.info(chalk7.bold(CLI_NAME));
1848
+ console.info(chalk7.gray(`Run ${command}...`));
1849
+ const options2 = args;
1850
+ if (!options2.upload) {
1851
+ throw new Error("Upload configuration not set");
1852
+ }
1853
+ await upload(options2);
1854
+ }
1855
+ };
1856
+ }
1857
+
1858
+ // packages/cli/src/lib/commands.ts
1859
+ var commands = [
1860
+ {
1861
+ ...yargsAutorunCommandObject(),
1862
+ command: "*"
1863
+ },
1864
+ yargsAutorunCommandObject(),
1865
+ yargsCollectCommandObject(),
1866
+ yargsUploadCommandObject(),
1867
+ yargsConfigCommandObject()
1868
+ ];
1869
+
1870
+ // packages/cli/src/lib/implementation/only-plugins-utils.ts
1871
+ import chalk8 from "chalk";
1872
+ function filterPluginsByOnlyPluginsOption(plugins, { onlyPlugins }) {
1873
+ if (!onlyPlugins?.length) {
1874
+ return plugins;
1875
+ }
1876
+ return plugins.filter((plugin) => onlyPlugins.includes(plugin.slug));
1877
+ }
1878
+ function filterCategoryByOnlyPluginsOption(categories, { onlyPlugins }) {
1879
+ if (!onlyPlugins?.length) {
1880
+ return categories;
1881
+ }
1882
+ return categories.filter(
1883
+ (category) => category.refs.every((ref) => {
1884
+ const isNotSkipped = onlyPlugins.includes(ref.slug);
1885
+ if (!isNotSkipped) {
1886
+ console.info(
1887
+ `${chalk8.yellow("\u26A0")} Category "${category.title}" is ignored because it references audits from skipped plugin "${ref.slug}"`
1888
+ );
1889
+ }
1890
+ return isNotSkipped;
1891
+ })
1892
+ );
1893
+ }
1894
+ function validateOnlyPluginsOption(plugins, { onlyPlugins }) {
1895
+ const missingPlugins = onlyPlugins?.length ? onlyPlugins.filter((plugin) => !plugins.some(({ slug }) => slug === plugin)) : [];
1896
+ if (missingPlugins.length > 0) {
1897
+ console.warn(
1898
+ `${chalk8.yellow(
1899
+ "\u26A0"
1900
+ )} The --onlyPlugin argument references plugins with "${missingPlugins.join(
1901
+ '", "'
1902
+ )}" slugs, but no such plugins are present in the configuration. Expected one of the following plugin slugs: "${plugins.map(({ slug }) => slug).join('", "')}".`
1903
+ );
1904
+ }
1905
+ }
1906
+
1907
+ // packages/cli/src/lib/implementation/config-middleware.ts
1908
+ async function configMiddleware(processArgs) {
1909
+ const args = processArgs;
1910
+ const { config, ...cliOptions } = args;
1911
+ const importedRc = await readCodePushupConfig(config);
1912
+ validateOnlyPluginsOption(importedRc.plugins, cliOptions);
1913
+ const parsedProcessArgs = {
1914
+ config,
1915
+ progress: cliOptions.progress,
1916
+ verbose: cliOptions.verbose,
1917
+ upload: {
1918
+ ...importedRc.upload,
1919
+ ...cliOptions.upload
1920
+ },
1921
+ persist: {
1922
+ ...importedRc.persist,
1923
+ ...cliOptions.persist
1924
+ },
1925
+ plugins: filterPluginsByOnlyPluginsOption(importedRc.plugins, cliOptions),
1926
+ categories: filterCategoryByOnlyPluginsOption(
1927
+ importedRc.categories,
1928
+ cliOptions
1929
+ ),
1930
+ onlyPlugins: cliOptions.onlyPlugins
1931
+ };
1932
+ return parsedProcessArgs;
1933
+ }
1934
+
1935
+ // packages/cli/src/lib/middlewares.ts
1936
+ var middlewares = [
1937
+ { middlewareFunction: configMiddleware }
1938
+ ];
1939
+
1940
+ // packages/cli/src/lib/implementation/core-config-options.ts
1941
+ function yargsCoreConfigOptionsDefinition() {
1942
+ return {
1943
+ // persist
1944
+ "persist.outputDir": {
1945
+ describe: "Directory for the produced reports",
1946
+ type: "string"
1947
+ },
1948
+ "persist.filename": {
1949
+ describe: "Filename for the produced reports.",
1950
+ type: "string"
1951
+ },
1952
+ "persist.format": {
1953
+ describe: "Format of the report output. e.g. `md`, `json`",
1954
+ type: "array"
1955
+ },
1956
+ // upload
1957
+ "upload.organization": {
1958
+ describe: "Organization slug from portal",
1959
+ type: "string"
1960
+ },
1961
+ "upload.project": {
1962
+ describe: "Project slug from portal",
1963
+ type: "string"
1964
+ },
1965
+ "upload.server": {
1966
+ describe: "URL to your portal server",
1967
+ type: "string"
1968
+ },
1969
+ "upload.apiKey": {
1970
+ describe: "API key for the portal server",
1971
+ type: "string"
1972
+ }
1973
+ };
1974
+ }
1975
+
1976
+ // packages/cli/src/lib/implementation/global-options.ts
1977
+ function yargsGlobalOptionsDefinition() {
1978
+ return {
1979
+ progress: {
1980
+ describe: "Show progress bar in stdout.",
1981
+ type: "boolean",
1982
+ default: true
1983
+ },
1984
+ verbose: {
1985
+ describe: "When true creates more verbose output. This is helpful when debugging.",
1986
+ type: "boolean",
1987
+ default: false
1988
+ },
1989
+ config: {
1990
+ describe: "Path the the config file, e.g. code-pushup.config.js",
1991
+ type: "string",
1992
+ default: "code-pushup.config.js"
1993
+ }
1994
+ };
1995
+ }
1996
+
1997
+ // packages/cli/src/lib/options.ts
1998
+ var options = {
1999
+ ...yargsGlobalOptionsDefinition(),
2000
+ ...yargsCoreConfigOptionsDefinition()
2001
+ };
2002
+
2003
+ // packages/cli/src/lib/yargs-cli.ts
2004
+ import chalk9 from "chalk";
2005
+ import yargs from "yargs";
2006
+
2007
+ // packages/cli/src/lib/implementation/utils.ts
2008
+ function logErrorBeforeThrow(fn) {
2009
+ return async (...args) => {
2010
+ try {
2011
+ return await fn(...args);
2012
+ } catch (err) {
2013
+ console.error(err);
2014
+ await new Promise((resolve) => process.stdout.write("", resolve));
2015
+ throw err;
2016
+ }
2017
+ };
2018
+ }
2019
+
2020
+ // packages/cli/src/lib/yargs-cli.ts
2021
+ function yargsCli(argv, cfg) {
2022
+ const { usageMessage, scriptName, noExitProcess } = cfg;
2023
+ let { commands: commands2, options: options2, middlewares: middlewares2 } = cfg;
2024
+ commands2 = Array.isArray(commands2) ? commands2 : [];
2025
+ middlewares2 = Array.isArray(middlewares2) ? middlewares2 : [];
2026
+ options2 = options2 || {};
2027
+ const cli2 = yargs(argv);
2028
+ cli2.help().version(false).alias("h", "help").parserConfiguration({
2029
+ "strip-dashed": true
2030
+ }).array("persist.format").coerce("config", (config) => {
2031
+ if (Array.isArray(config)) {
2032
+ return config[config.length - 1];
2033
+ }
2034
+ return config;
2035
+ }).options(options2);
2036
+ if (usageMessage) {
2037
+ cli2.usage(chalk9.bold(usageMessage));
2038
+ }
2039
+ if (scriptName) {
2040
+ cli2.scriptName(scriptName);
2041
+ }
2042
+ middlewares2.forEach(({ middlewareFunction, applyBeforeValidation }) => {
2043
+ cli2.middleware(
2044
+ logErrorBeforeThrow(middlewareFunction),
2045
+ applyBeforeValidation
2046
+ );
2047
+ });
2048
+ commands2.forEach((commandObj) => {
2049
+ cli2.command({
2050
+ ...commandObj,
2051
+ ...commandObj.handler && {
2052
+ handler: logErrorBeforeThrow(commandObj.handler)
2053
+ },
2054
+ ...typeof commandObj.builder === "function" && {
2055
+ builder: logErrorBeforeThrow(commandObj.builder)
2056
+ }
2057
+ });
2058
+ });
2059
+ if (noExitProcess) {
2060
+ cli2.exitProcess(false);
2061
+ }
2062
+ return cli2;
2063
+ }
2064
+
2065
+ // packages/cli/src/lib/cli.ts
2066
+ var CLI_NAME = "Code PushUp CLI";
2067
+ var CLI_SCRIPT_NAME = "code-pushup";
2068
+ var cli = (args) => yargsCli(args, {
2069
+ usageMessage: CLI_NAME,
2070
+ scriptName: CLI_SCRIPT_NAME,
2071
+ options,
2072
+ middlewares,
2073
+ commands
2074
+ });
2075
+
2076
+ // packages/cli/src/index.ts
2077
+ cli(hideBin(process.argv)).argv;