@code-pushup/cli 0.55.0 → 0.57.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/package.json +10 -9
- package/src/index.js +6 -0
- package/src/index.js.map +1 -0
- package/src/lib/autorun/autorun-command.js +43 -0
- package/src/lib/autorun/autorun-command.js.map +1 -0
- package/src/lib/cli.js +40 -0
- package/src/lib/cli.js.map +1 -0
- package/src/lib/collect/collect-command.js +38 -0
- package/src/lib/collect/collect-command.js.map +1 -0
- package/src/lib/commands.js +21 -0
- package/src/lib/commands.js.map +1 -0
- package/src/lib/compare/compare-command.d.ts +1 -1
- package/src/lib/compare/compare-command.js +24 -0
- package/src/lib/compare/compare-command.js.map +1 -0
- package/src/lib/constants.js +3 -0
- package/src/lib/constants.js.map +1 -0
- package/src/lib/history/history-command.js +50 -0
- package/src/lib/history/history-command.js.map +1 -0
- package/src/lib/history/history.model.js +2 -0
- package/src/lib/history/history.model.js.map +1 -0
- package/src/lib/history/history.options.d.ts +1 -1
- package/src/lib/history/history.options.js +41 -0
- package/src/lib/history/history.options.js.map +1 -0
- package/src/lib/history/utils.d.ts +1 -1
- package/src/lib/history/utils.js +27 -0
- package/src/lib/history/utils.js.map +1 -0
- package/src/lib/implementation/compare.model.js +2 -0
- package/src/lib/implementation/compare.model.js.map +1 -0
- package/src/lib/implementation/compare.options.d.ts +1 -1
- package/src/lib/implementation/compare.options.js +19 -0
- package/src/lib/implementation/compare.options.js.map +1 -0
- package/src/lib/implementation/core-config.middleware.d.ts +3 -3
- package/src/lib/implementation/core-config.middleware.js +31 -0
- package/src/lib/implementation/core-config.middleware.js.map +1 -0
- package/src/lib/implementation/core-config.model.js +2 -0
- package/src/lib/implementation/core-config.model.js.map +1 -0
- package/src/lib/implementation/core-config.options.d.ts +1 -1
- package/src/lib/implementation/core-config.options.js +43 -0
- package/src/lib/implementation/core-config.options.js.map +1 -0
- package/src/lib/implementation/filter.middleware.d.ts +1 -1
- package/src/lib/implementation/filter.middleware.js +61 -0
- package/src/lib/implementation/filter.middleware.js.map +1 -0
- package/src/lib/implementation/filter.model.js +2 -0
- package/src/lib/implementation/filter.model.js.map +1 -0
- package/src/lib/implementation/filter.options.d.ts +1 -1
- package/src/lib/implementation/filter.options.js +36 -0
- package/src/lib/implementation/filter.options.js.map +1 -0
- package/src/lib/implementation/formatting.js +26 -0
- package/src/lib/implementation/formatting.js.map +1 -0
- package/src/lib/implementation/global.model.d.ts +1 -1
- package/src/lib/implementation/global.model.js +2 -0
- package/src/lib/implementation/global.model.js.map +1 -0
- package/src/lib/implementation/global.options.d.ts +1 -1
- package/src/lib/implementation/global.options.js +23 -0
- package/src/lib/implementation/global.options.js.map +1 -0
- package/src/lib/implementation/global.utils.js +45 -0
- package/src/lib/implementation/global.utils.js.map +1 -0
- package/src/lib/implementation/logging.js +24 -0
- package/src/lib/implementation/logging.js.map +1 -0
- package/src/lib/implementation/merge-diffs.model.js +2 -0
- package/src/lib/implementation/merge-diffs.model.js.map +1 -0
- package/src/lib/implementation/merge-diffs.options.d.ts +1 -1
- package/src/lib/implementation/merge-diffs.options.js +10 -0
- package/src/lib/implementation/merge-diffs.options.js.map +1 -0
- package/src/lib/implementation/validate-filter-options.utils.d.ts +1 -1
- package/src/lib/implementation/validate-filter-options.utils.js +86 -0
- package/src/lib/implementation/validate-filter-options.utils.js.map +1 -0
- package/src/lib/merge-diffs/merge-diffs-command.js +22 -0
- package/src/lib/merge-diffs/merge-diffs-command.js.map +1 -0
- package/src/lib/middlewares.js +13 -0
- package/src/lib/middlewares.js.map +1 -0
- package/src/lib/options.d.ts +1 -1
- package/src/lib/options.js +17 -0
- package/src/lib/options.js.map +1 -0
- package/src/lib/print-config/print-config-command.js +17 -0
- package/src/lib/print-config/print-config-command.js.map +1 -0
- package/src/lib/upload/upload-command.js +26 -0
- package/src/lib/upload/upload-command.js.map +1 -0
- package/src/lib/yargs-cli.js +105 -0
- package/src/lib/yargs-cli.js.map +1 -0
- package/index.js +0 -4262
package/index.js
DELETED
|
@@ -1,4262 +0,0 @@
|
|
|
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 { bold as bold7, gray as gray4 } from "ansis";
|
|
8
|
-
|
|
9
|
-
// packages/models/src/lib/implementation/schemas.ts
|
|
10
|
-
import { MATERIAL_ICONS } from "vscode-material-icons";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
|
|
13
|
-
// packages/models/src/lib/implementation/limits.ts
|
|
14
|
-
var MAX_SLUG_LENGTH = 128;
|
|
15
|
-
var MAX_TITLE_LENGTH = 256;
|
|
16
|
-
var MAX_DESCRIPTION_LENGTH = 65536;
|
|
17
|
-
var MAX_ISSUE_MESSAGE_LENGTH = 1024;
|
|
18
|
-
|
|
19
|
-
// packages/models/src/lib/implementation/utils.ts
|
|
20
|
-
var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
|
|
21
|
-
var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
|
|
22
|
-
function hasDuplicateStrings(strings) {
|
|
23
|
-
const sortedStrings = strings.toSorted();
|
|
24
|
-
const duplStrings = sortedStrings.filter(
|
|
25
|
-
(item, index) => index !== 0 && item === sortedStrings[index - 1]
|
|
26
|
-
);
|
|
27
|
-
return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
|
|
28
|
-
}
|
|
29
|
-
function hasMissingStrings(toCheck, existing) {
|
|
30
|
-
const nonExisting = toCheck.filter((s) => !existing.includes(s));
|
|
31
|
-
return nonExisting.length === 0 ? false : nonExisting;
|
|
32
|
-
}
|
|
33
|
-
function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
|
|
34
|
-
return transform(items || []);
|
|
35
|
-
}
|
|
36
|
-
function exists(value) {
|
|
37
|
-
return value != null;
|
|
38
|
-
}
|
|
39
|
-
function getMissingRefsForCategories(categories, plugins) {
|
|
40
|
-
if (!categories || categories.length === 0) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const auditRefsFromCategory = categories.flatMap(
|
|
44
|
-
({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
|
|
45
|
-
);
|
|
46
|
-
const auditRefsFromPlugins = plugins.flatMap(
|
|
47
|
-
({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
|
|
48
|
-
);
|
|
49
|
-
const missingAuditRefs = hasMissingStrings(
|
|
50
|
-
auditRefsFromCategory,
|
|
51
|
-
auditRefsFromPlugins
|
|
52
|
-
);
|
|
53
|
-
const groupRefsFromCategory = categories.flatMap(
|
|
54
|
-
({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
|
|
55
|
-
);
|
|
56
|
-
const groupRefsFromPlugins = plugins.flatMap(
|
|
57
|
-
({ groups: groups2, slug: pluginSlug }) => Array.isArray(groups2) ? groups2.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
|
|
58
|
-
);
|
|
59
|
-
const missingGroupRefs = hasMissingStrings(
|
|
60
|
-
groupRefsFromCategory,
|
|
61
|
-
groupRefsFromPlugins
|
|
62
|
-
);
|
|
63
|
-
const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
|
|
64
|
-
return missingRefs.length > 0 ? missingRefs : false;
|
|
65
|
-
}
|
|
66
|
-
function missingRefsForCategoriesErrorMsg(categories, plugins) {
|
|
67
|
-
const missingRefs = getMissingRefsForCategories(categories, plugins);
|
|
68
|
-
return `The following category references need to point to an audit or group: ${errorItems(
|
|
69
|
-
missingRefs
|
|
70
|
-
)}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// packages/models/src/lib/implementation/schemas.ts
|
|
74
|
-
var tableCellValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]).default(null);
|
|
75
|
-
function executionMetaSchema(options2 = {
|
|
76
|
-
descriptionDate: "Execution start date and time",
|
|
77
|
-
descriptionDuration: "Execution duration in ms"
|
|
78
|
-
}) {
|
|
79
|
-
return z.object({
|
|
80
|
-
date: z.string({ description: options2.descriptionDate }),
|
|
81
|
-
duration: z.number({ description: options2.descriptionDuration })
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
|
|
85
|
-
message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
|
|
86
|
-
}).max(MAX_SLUG_LENGTH, {
|
|
87
|
-
message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
|
|
88
|
-
});
|
|
89
|
-
var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
|
|
90
|
-
var urlSchema = z.string().url();
|
|
91
|
-
var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
|
|
92
|
-
var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
|
|
93
|
-
var scoreSchema = z.number({
|
|
94
|
-
description: "Value between 0 and 1"
|
|
95
|
-
}).min(0).max(1);
|
|
96
|
-
function metaSchema(options2) {
|
|
97
|
-
const {
|
|
98
|
-
descriptionDescription,
|
|
99
|
-
titleDescription,
|
|
100
|
-
docsUrlDescription,
|
|
101
|
-
description
|
|
102
|
-
} = options2 ?? {};
|
|
103
|
-
return z.object(
|
|
104
|
-
{
|
|
105
|
-
title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
|
|
106
|
-
description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
|
|
107
|
-
docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
|
|
108
|
-
},
|
|
109
|
-
{ description }
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
|
|
113
|
-
var fileNameSchema = z.string().trim().regex(filenameRegex, {
|
|
114
|
-
message: `The filename has to be valid`
|
|
115
|
-
}).min(1, { message: "file name is invalid" });
|
|
116
|
-
var positiveIntSchema = z.number().int().positive();
|
|
117
|
-
var nonnegativeNumberSchema = z.number().nonnegative();
|
|
118
|
-
function packageVersionSchema(options2) {
|
|
119
|
-
const { versionDescription = "NPM version of the package", required } = options2 ?? {};
|
|
120
|
-
const packageSchema = z.string({ description: "NPM package name" });
|
|
121
|
-
const versionSchema = z.string({ description: versionDescription });
|
|
122
|
-
return z.object(
|
|
123
|
-
{
|
|
124
|
-
packageName: required ? packageSchema : packageSchema.optional(),
|
|
125
|
-
version: required ? versionSchema : versionSchema.optional()
|
|
126
|
-
},
|
|
127
|
-
{ description: "NPM package name and version of a published package" }
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
var weightSchema = nonnegativeNumberSchema.describe(
|
|
131
|
-
"Coefficient for the given score (use weight 0 if only for display)"
|
|
132
|
-
);
|
|
133
|
-
function weightedRefSchema(description, slugDescription) {
|
|
134
|
-
return z.object(
|
|
135
|
-
{
|
|
136
|
-
slug: slugSchema.describe(slugDescription),
|
|
137
|
-
weight: weightSchema.describe("Weight used to calculate score")
|
|
138
|
-
},
|
|
139
|
-
{ description }
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
|
|
143
|
-
return z.object(
|
|
144
|
-
{
|
|
145
|
-
slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
|
|
146
|
-
refs: z.array(refSchema).min(1).refine(
|
|
147
|
-
(refs) => !duplicateCheckFn(refs),
|
|
148
|
-
(refs) => ({
|
|
149
|
-
message: duplicateMessageFn(refs)
|
|
150
|
-
})
|
|
151
|
-
).refine(hasNonZeroWeightedRef, () => ({
|
|
152
|
-
message: "In a category there has to be at least one ref with weight > 0"
|
|
153
|
-
}))
|
|
154
|
-
},
|
|
155
|
-
{ description }
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
var materialIconSchema = z.enum(MATERIAL_ICONS, {
|
|
159
|
-
description: "Icon from VSCode Material Icons extension"
|
|
160
|
-
});
|
|
161
|
-
function hasNonZeroWeightedRef(refs) {
|
|
162
|
-
return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// packages/models/src/lib/source.ts
|
|
166
|
-
import { z as z2 } from "zod";
|
|
167
|
-
var sourceFileLocationSchema = z2.object(
|
|
168
|
-
{
|
|
169
|
-
file: filePathSchema.describe("Relative path to source file in Git repo"),
|
|
170
|
-
position: z2.object(
|
|
171
|
-
{
|
|
172
|
-
startLine: positiveIntSchema.describe("Start line"),
|
|
173
|
-
startColumn: positiveIntSchema.describe("Start column").optional(),
|
|
174
|
-
endLine: positiveIntSchema.describe("End line").optional(),
|
|
175
|
-
endColumn: positiveIntSchema.describe("End column").optional()
|
|
176
|
-
},
|
|
177
|
-
{ description: "Location in file" }
|
|
178
|
-
).optional()
|
|
179
|
-
},
|
|
180
|
-
{ description: "Source file location" }
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
// packages/models/src/lib/audit.ts
|
|
184
|
-
import { z as z3 } from "zod";
|
|
185
|
-
var auditSchema = z3.object({
|
|
186
|
-
slug: slugSchema.describe("ID (unique within plugin)")
|
|
187
|
-
}).merge(
|
|
188
|
-
metaSchema({
|
|
189
|
-
titleDescription: "Descriptive name",
|
|
190
|
-
descriptionDescription: "Description (markdown)",
|
|
191
|
-
docsUrlDescription: "Link to documentation (rationale)",
|
|
192
|
-
description: "List of scorable metrics for the given plugin"
|
|
193
|
-
})
|
|
194
|
-
);
|
|
195
|
-
var pluginAuditsSchema = z3.array(auditSchema, {
|
|
196
|
-
description: "List of audits maintained in a plugin"
|
|
197
|
-
}).min(1).refine(
|
|
198
|
-
(auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
|
|
199
|
-
(auditMetadata) => ({
|
|
200
|
-
message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
|
|
201
|
-
})
|
|
202
|
-
);
|
|
203
|
-
function duplicateSlugsInAuditsErrorMsg(audits) {
|
|
204
|
-
const duplicateRefs = getDuplicateSlugsInAudits(audits);
|
|
205
|
-
return `In plugin audits the following slugs are not unique: ${errorItems(
|
|
206
|
-
duplicateRefs
|
|
207
|
-
)}`;
|
|
208
|
-
}
|
|
209
|
-
function getDuplicateSlugsInAudits(audits) {
|
|
210
|
-
return hasDuplicateStrings(audits.map(({ slug }) => slug));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// packages/models/src/lib/audit-output.ts
|
|
214
|
-
import { z as z6 } from "zod";
|
|
215
|
-
|
|
216
|
-
// packages/models/src/lib/issue.ts
|
|
217
|
-
import { z as z4 } from "zod";
|
|
218
|
-
var issueSeveritySchema = z4.enum(["info", "warning", "error"], {
|
|
219
|
-
description: "Severity level"
|
|
220
|
-
});
|
|
221
|
-
var issueSchema = z4.object(
|
|
222
|
-
{
|
|
223
|
-
message: z4.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
|
|
224
|
-
severity: issueSeveritySchema,
|
|
225
|
-
source: sourceFileLocationSchema.optional()
|
|
226
|
-
},
|
|
227
|
-
{ description: "Issue information" }
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// packages/models/src/lib/table.ts
|
|
231
|
-
import { z as z5 } from "zod";
|
|
232
|
-
var tableAlignmentSchema = z5.enum(["left", "center", "right"], {
|
|
233
|
-
description: "Cell alignment"
|
|
234
|
-
});
|
|
235
|
-
var tableColumnObjectSchema = z5.object({
|
|
236
|
-
key: z5.string(),
|
|
237
|
-
label: z5.string().optional(),
|
|
238
|
-
align: tableAlignmentSchema.optional()
|
|
239
|
-
});
|
|
240
|
-
var tableRowObjectSchema = z5.record(tableCellValueSchema, {
|
|
241
|
-
description: "Object row"
|
|
242
|
-
});
|
|
243
|
-
var tableRowPrimitiveSchema = z5.array(tableCellValueSchema, {
|
|
244
|
-
description: "Primitive row"
|
|
245
|
-
});
|
|
246
|
-
var tableSharedSchema = z5.object({
|
|
247
|
-
title: z5.string().optional().describe("Display title for table")
|
|
248
|
-
});
|
|
249
|
-
var tablePrimitiveSchema = tableSharedSchema.merge(
|
|
250
|
-
z5.object(
|
|
251
|
-
{
|
|
252
|
-
columns: z5.array(tableAlignmentSchema).optional(),
|
|
253
|
-
rows: z5.array(tableRowPrimitiveSchema)
|
|
254
|
-
},
|
|
255
|
-
{ description: "Table with primitive rows and optional alignment columns" }
|
|
256
|
-
)
|
|
257
|
-
);
|
|
258
|
-
var tableObjectSchema = tableSharedSchema.merge(
|
|
259
|
-
z5.object(
|
|
260
|
-
{
|
|
261
|
-
columns: z5.union([
|
|
262
|
-
z5.array(tableAlignmentSchema),
|
|
263
|
-
z5.array(tableColumnObjectSchema)
|
|
264
|
-
]).optional(),
|
|
265
|
-
rows: z5.array(tableRowObjectSchema)
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
description: "Table with object rows and optional alignment or object columns"
|
|
269
|
-
}
|
|
270
|
-
)
|
|
271
|
-
);
|
|
272
|
-
var tableSchema = (description = "Table information") => z5.union([tablePrimitiveSchema, tableObjectSchema], { description });
|
|
273
|
-
|
|
274
|
-
// packages/models/src/lib/audit-output.ts
|
|
275
|
-
var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
|
|
276
|
-
var auditDisplayValueSchema = z6.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
|
|
277
|
-
var auditDetailsSchema = z6.object(
|
|
278
|
-
{
|
|
279
|
-
issues: z6.array(issueSchema, { description: "List of findings" }).optional(),
|
|
280
|
-
table: tableSchema("Table of related findings").optional()
|
|
281
|
-
},
|
|
282
|
-
{ description: "Detailed information" }
|
|
283
|
-
);
|
|
284
|
-
var auditOutputSchema = z6.object(
|
|
285
|
-
{
|
|
286
|
-
slug: slugSchema.describe("Reference to audit"),
|
|
287
|
-
displayValue: auditDisplayValueSchema,
|
|
288
|
-
value: auditValueSchema,
|
|
289
|
-
score: scoreSchema,
|
|
290
|
-
details: auditDetailsSchema.optional()
|
|
291
|
-
},
|
|
292
|
-
{ description: "Audit information" }
|
|
293
|
-
);
|
|
294
|
-
var auditOutputsSchema = z6.array(auditOutputSchema, {
|
|
295
|
-
description: "List of JSON formatted audit output emitted by the runner process of a plugin"
|
|
296
|
-
}).refine(
|
|
297
|
-
(audits) => !getDuplicateSlugsInAudits2(audits),
|
|
298
|
-
(audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
|
|
299
|
-
);
|
|
300
|
-
function duplicateSlugsInAuditsErrorMsg2(audits) {
|
|
301
|
-
const duplicateRefs = getDuplicateSlugsInAudits2(audits);
|
|
302
|
-
return `In plugin audits the slugs are not unique: ${errorItems(
|
|
303
|
-
duplicateRefs
|
|
304
|
-
)}`;
|
|
305
|
-
}
|
|
306
|
-
function getDuplicateSlugsInAudits2(audits) {
|
|
307
|
-
return hasDuplicateStrings(audits.map(({ slug }) => slug));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// packages/models/src/lib/category-config.ts
|
|
311
|
-
import { z as z7 } from "zod";
|
|
312
|
-
var categoryRefSchema = weightedRefSchema(
|
|
313
|
-
"Weighted references to audits and/or groups for the category",
|
|
314
|
-
"Slug of an audit or group (depending on `type`)"
|
|
315
|
-
).merge(
|
|
316
|
-
z7.object({
|
|
317
|
-
type: z7.enum(["audit", "group"], {
|
|
318
|
-
description: "Discriminant for reference kind, affects where `slug` is looked up"
|
|
319
|
-
}),
|
|
320
|
-
plugin: slugSchema.describe(
|
|
321
|
-
"Plugin slug (plugin should contain referenced audit or group)"
|
|
322
|
-
)
|
|
323
|
-
})
|
|
324
|
-
);
|
|
325
|
-
var categoryConfigSchema = scorableSchema(
|
|
326
|
-
"Category with a score calculated from audits and groups from various plugins",
|
|
327
|
-
categoryRefSchema,
|
|
328
|
-
getDuplicateRefsInCategoryMetrics,
|
|
329
|
-
duplicateRefsInCategoryMetricsErrorMsg
|
|
330
|
-
).merge(
|
|
331
|
-
metaSchema({
|
|
332
|
-
titleDescription: "Category Title",
|
|
333
|
-
docsUrlDescription: "Category docs URL",
|
|
334
|
-
descriptionDescription: "Category description",
|
|
335
|
-
description: "Meta info for category"
|
|
336
|
-
})
|
|
337
|
-
).merge(
|
|
338
|
-
z7.object({
|
|
339
|
-
isBinary: z7.boolean({
|
|
340
|
-
description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
|
|
341
|
-
}).optional()
|
|
342
|
-
})
|
|
343
|
-
);
|
|
344
|
-
function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
|
|
345
|
-
const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
|
|
346
|
-
return `In the categories, the following audit or group refs are duplicates: ${errorItems(
|
|
347
|
-
duplicateRefs
|
|
348
|
-
)}`;
|
|
349
|
-
}
|
|
350
|
-
function getDuplicateRefsInCategoryMetrics(metrics) {
|
|
351
|
-
return hasDuplicateStrings(
|
|
352
|
-
metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
var categoriesSchema = z7.array(categoryConfigSchema, {
|
|
356
|
-
description: "Categorization of individual audits"
|
|
357
|
-
}).refine(
|
|
358
|
-
(categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
|
|
359
|
-
(categoryCfg) => ({
|
|
360
|
-
message: duplicateSlugCategoriesErrorMsg(categoryCfg)
|
|
361
|
-
})
|
|
362
|
-
);
|
|
363
|
-
function duplicateSlugCategoriesErrorMsg(categories) {
|
|
364
|
-
const duplicateStringSlugs = getDuplicateSlugCategories(categories);
|
|
365
|
-
return `In the categories, the following slugs are duplicated: ${errorItems(
|
|
366
|
-
duplicateStringSlugs
|
|
367
|
-
)}`;
|
|
368
|
-
}
|
|
369
|
-
function getDuplicateSlugCategories(categories) {
|
|
370
|
-
return hasDuplicateStrings(categories.map(({ slug }) => slug));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// packages/models/src/lib/commit.ts
|
|
374
|
-
import { z as z8 } from "zod";
|
|
375
|
-
var commitSchema = z8.object(
|
|
376
|
-
{
|
|
377
|
-
hash: z8.string({ description: "Commit SHA (full)" }).regex(
|
|
378
|
-
/^[\da-f]{40}$/,
|
|
379
|
-
"Commit SHA should be a 40-character hexadecimal string"
|
|
380
|
-
),
|
|
381
|
-
message: z8.string({ description: "Commit message" }),
|
|
382
|
-
date: z8.coerce.date({
|
|
383
|
-
description: "Date and time when commit was authored"
|
|
384
|
-
}),
|
|
385
|
-
author: z8.string({
|
|
386
|
-
description: "Commit author name"
|
|
387
|
-
}).trim()
|
|
388
|
-
},
|
|
389
|
-
{ description: "Git commit" }
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
// packages/models/src/lib/core-config.ts
|
|
393
|
-
import { z as z14 } from "zod";
|
|
394
|
-
|
|
395
|
-
// packages/models/src/lib/persist-config.ts
|
|
396
|
-
import { z as z9 } from "zod";
|
|
397
|
-
var formatSchema = z9.enum(["json", "md"]);
|
|
398
|
-
var persistConfigSchema = z9.object({
|
|
399
|
-
outputDir: filePathSchema.describe("Artifacts folder").optional(),
|
|
400
|
-
filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
|
|
401
|
-
format: z9.array(formatSchema).optional()
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// packages/models/src/lib/plugin-config.ts
|
|
405
|
-
import { z as z12 } from "zod";
|
|
406
|
-
|
|
407
|
-
// packages/models/src/lib/group.ts
|
|
408
|
-
import { z as z10 } from "zod";
|
|
409
|
-
var groupRefSchema = weightedRefSchema(
|
|
410
|
-
"Weighted reference to a group",
|
|
411
|
-
"Reference slug to a group within this plugin (e.g. 'max-lines')"
|
|
412
|
-
);
|
|
413
|
-
var groupMetaSchema = metaSchema({
|
|
414
|
-
titleDescription: "Descriptive name for the group",
|
|
415
|
-
descriptionDescription: "Description of the group (markdown)",
|
|
416
|
-
docsUrlDescription: "Group documentation site",
|
|
417
|
-
description: "Group metadata"
|
|
418
|
-
});
|
|
419
|
-
var groupSchema = scorableSchema(
|
|
420
|
-
'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',
|
|
421
|
-
groupRefSchema,
|
|
422
|
-
getDuplicateRefsInGroups,
|
|
423
|
-
duplicateRefsInGroupsErrorMsg
|
|
424
|
-
).merge(groupMetaSchema);
|
|
425
|
-
var groupsSchema = z10.array(groupSchema, {
|
|
426
|
-
description: "List of groups"
|
|
427
|
-
}).optional().refine(
|
|
428
|
-
(groups2) => !getDuplicateSlugsInGroups(groups2),
|
|
429
|
-
(groups2) => ({
|
|
430
|
-
message: duplicateSlugsInGroupsErrorMsg(groups2)
|
|
431
|
-
})
|
|
432
|
-
);
|
|
433
|
-
function duplicateRefsInGroupsErrorMsg(groups2) {
|
|
434
|
-
const duplicateRefs = getDuplicateRefsInGroups(groups2);
|
|
435
|
-
return `In plugin groups the following references are not unique: ${errorItems(
|
|
436
|
-
duplicateRefs
|
|
437
|
-
)}`;
|
|
438
|
-
}
|
|
439
|
-
function getDuplicateRefsInGroups(groups2) {
|
|
440
|
-
return hasDuplicateStrings(groups2.map(({ slug: ref }) => ref).filter(exists));
|
|
441
|
-
}
|
|
442
|
-
function duplicateSlugsInGroupsErrorMsg(groups2) {
|
|
443
|
-
const duplicateRefs = getDuplicateSlugsInGroups(groups2);
|
|
444
|
-
return `In groups the following slugs are not unique: ${errorItems(
|
|
445
|
-
duplicateRefs
|
|
446
|
-
)}`;
|
|
447
|
-
}
|
|
448
|
-
function getDuplicateSlugsInGroups(groups2) {
|
|
449
|
-
return Array.isArray(groups2) ? hasDuplicateStrings(groups2.map(({ slug }) => slug)) : false;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// packages/models/src/lib/runner-config.ts
|
|
453
|
-
import { z as z11 } from "zod";
|
|
454
|
-
var outputTransformSchema = z11.function().args(z11.unknown()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
|
|
455
|
-
var runnerConfigSchema = z11.object(
|
|
456
|
-
{
|
|
457
|
-
command: z11.string({
|
|
458
|
-
description: "Shell command to execute"
|
|
459
|
-
}),
|
|
460
|
-
args: z11.array(z11.string({ description: "Command arguments" })).optional(),
|
|
461
|
-
outputFile: filePathSchema.describe("Output path"),
|
|
462
|
-
outputTransform: outputTransformSchema.optional()
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
description: "How to execute runner"
|
|
466
|
-
}
|
|
467
|
-
);
|
|
468
|
-
var onProgressSchema = z11.function().args(z11.unknown()).returns(z11.void());
|
|
469
|
-
var runnerFunctionSchema = z11.function().args(onProgressSchema.optional()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
|
|
470
|
-
|
|
471
|
-
// packages/models/src/lib/plugin-config.ts
|
|
472
|
-
var pluginMetaSchema = packageVersionSchema().merge(
|
|
473
|
-
metaSchema({
|
|
474
|
-
titleDescription: "Descriptive name",
|
|
475
|
-
descriptionDescription: "Description (markdown)",
|
|
476
|
-
docsUrlDescription: "Plugin documentation site",
|
|
477
|
-
description: "Plugin metadata"
|
|
478
|
-
})
|
|
479
|
-
).merge(
|
|
480
|
-
z12.object({
|
|
481
|
-
slug: slugSchema.describe("Unique plugin slug within core config"),
|
|
482
|
-
icon: materialIconSchema
|
|
483
|
-
})
|
|
484
|
-
);
|
|
485
|
-
var pluginDataSchema = z12.object({
|
|
486
|
-
runner: z12.union([runnerConfigSchema, runnerFunctionSchema]),
|
|
487
|
-
audits: pluginAuditsSchema,
|
|
488
|
-
groups: groupsSchema
|
|
489
|
-
});
|
|
490
|
-
var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
|
|
491
|
-
(pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
|
|
492
|
-
(pluginCfg) => ({
|
|
493
|
-
message: missingRefsFromGroupsErrorMsg(pluginCfg)
|
|
494
|
-
})
|
|
495
|
-
);
|
|
496
|
-
function missingRefsFromGroupsErrorMsg(pluginCfg) {
|
|
497
|
-
const missingRefs = getMissingRefsFromGroups(pluginCfg);
|
|
498
|
-
return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
|
|
499
|
-
missingRefs
|
|
500
|
-
)}`;
|
|
501
|
-
}
|
|
502
|
-
function getMissingRefsFromGroups(pluginCfg) {
|
|
503
|
-
return hasMissingStrings(
|
|
504
|
-
pluginCfg.groups?.flatMap(
|
|
505
|
-
({ refs: audits }) => audits.map(({ slug: ref }) => ref)
|
|
506
|
-
) ?? [],
|
|
507
|
-
pluginCfg.audits.map(({ slug }) => slug)
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// packages/models/src/lib/upload-config.ts
|
|
512
|
-
import { z as z13 } from "zod";
|
|
513
|
-
var uploadConfigSchema = z13.object({
|
|
514
|
-
server: urlSchema.describe("URL of deployed portal API"),
|
|
515
|
-
apiKey: z13.string({
|
|
516
|
-
description: "API key with write access to portal (use `process.env` for security)"
|
|
517
|
-
}),
|
|
518
|
-
organization: slugSchema.describe(
|
|
519
|
-
"Organization slug from Code PushUp portal"
|
|
520
|
-
),
|
|
521
|
-
project: slugSchema.describe("Project slug from Code PushUp portal"),
|
|
522
|
-
timeout: z13.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// packages/models/src/lib/core-config.ts
|
|
526
|
-
var unrefinedCoreConfigSchema = z14.object({
|
|
527
|
-
plugins: z14.array(pluginConfigSchema, {
|
|
528
|
-
description: "List of plugins to be used (official, community-provided, or custom)"
|
|
529
|
-
}).min(1),
|
|
530
|
-
/** portal configuration for persisting results */
|
|
531
|
-
persist: persistConfigSchema.optional(),
|
|
532
|
-
/** portal configuration for uploading results */
|
|
533
|
-
upload: uploadConfigSchema.optional(),
|
|
534
|
-
categories: categoriesSchema.optional()
|
|
535
|
-
});
|
|
536
|
-
var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
|
|
537
|
-
function refineCoreConfig(schema) {
|
|
538
|
-
return schema.refine(
|
|
539
|
-
({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
|
|
540
|
-
({ categories, plugins }) => ({
|
|
541
|
-
message: missingRefsForCategoriesErrorMsg(categories, plugins)
|
|
542
|
-
})
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// packages/models/src/lib/implementation/configuration.ts
|
|
547
|
-
var CONFIG_FILE_NAME = "code-pushup.config";
|
|
548
|
-
var SUPPORTED_CONFIG_FILE_FORMATS = ["ts", "mjs", "js"];
|
|
549
|
-
|
|
550
|
-
// packages/models/src/lib/implementation/constants.ts
|
|
551
|
-
var DEFAULT_PERSIST_OUTPUT_DIR = ".code-pushup";
|
|
552
|
-
var DEFAULT_PERSIST_FILENAME = "report";
|
|
553
|
-
var DEFAULT_PERSIST_FORMAT = ["json", "md"];
|
|
554
|
-
|
|
555
|
-
// packages/models/src/lib/report.ts
|
|
556
|
-
import { z as z15 } from "zod";
|
|
557
|
-
var auditReportSchema = auditSchema.merge(auditOutputSchema);
|
|
558
|
-
var pluginReportSchema = pluginMetaSchema.merge(
|
|
559
|
-
executionMetaSchema({
|
|
560
|
-
descriptionDate: "Start date and time of plugin run",
|
|
561
|
-
descriptionDuration: "Duration of the plugin run in ms"
|
|
562
|
-
})
|
|
563
|
-
).merge(
|
|
564
|
-
z15.object({
|
|
565
|
-
audits: z15.array(auditReportSchema).min(1),
|
|
566
|
-
groups: z15.array(groupSchema).optional()
|
|
567
|
-
})
|
|
568
|
-
).refine(
|
|
569
|
-
(pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
|
|
570
|
-
(pluginReport) => ({
|
|
571
|
-
message: missingRefsFromGroupsErrorMsg2(
|
|
572
|
-
pluginReport.audits,
|
|
573
|
-
pluginReport.groups ?? []
|
|
574
|
-
)
|
|
575
|
-
})
|
|
576
|
-
);
|
|
577
|
-
function missingRefsFromGroupsErrorMsg2(audits, groups2) {
|
|
578
|
-
const missingRefs = getMissingRefsFromGroups2(audits, groups2);
|
|
579
|
-
return `group references need to point to an existing audit in this plugin report: ${errorItems(
|
|
580
|
-
missingRefs
|
|
581
|
-
)}`;
|
|
582
|
-
}
|
|
583
|
-
function getMissingRefsFromGroups2(audits, groups2) {
|
|
584
|
-
return hasMissingStrings(
|
|
585
|
-
groups2.flatMap(
|
|
586
|
-
({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
|
|
587
|
-
),
|
|
588
|
-
audits.map(({ slug }) => slug)
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
var reportSchema = packageVersionSchema({
|
|
592
|
-
versionDescription: "NPM version of the CLI",
|
|
593
|
-
required: true
|
|
594
|
-
}).merge(
|
|
595
|
-
executionMetaSchema({
|
|
596
|
-
descriptionDate: "Start date and time of the collect run",
|
|
597
|
-
descriptionDuration: "Duration of the collect run in ms"
|
|
598
|
-
})
|
|
599
|
-
).merge(
|
|
600
|
-
z15.object(
|
|
601
|
-
{
|
|
602
|
-
plugins: z15.array(pluginReportSchema).min(1),
|
|
603
|
-
categories: z15.array(categoryConfigSchema).optional(),
|
|
604
|
-
commit: commitSchema.describe("Git commit for which report was collected").nullable()
|
|
605
|
-
},
|
|
606
|
-
{ description: "Collect output data" }
|
|
607
|
-
)
|
|
608
|
-
).refine(
|
|
609
|
-
({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
|
|
610
|
-
({ categories, plugins }) => ({
|
|
611
|
-
message: missingRefsForCategoriesErrorMsg(categories, plugins)
|
|
612
|
-
})
|
|
613
|
-
);
|
|
614
|
-
|
|
615
|
-
// packages/models/src/lib/reports-diff.ts
|
|
616
|
-
import { z as z16 } from "zod";
|
|
617
|
-
function makeComparisonSchema(schema) {
|
|
618
|
-
const sharedDescription = schema.description || "Result";
|
|
619
|
-
return z16.object({
|
|
620
|
-
before: schema.describe(`${sharedDescription} (source commit)`),
|
|
621
|
-
after: schema.describe(`${sharedDescription} (target commit)`)
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
|
|
625
|
-
return z16.object(
|
|
626
|
-
{
|
|
627
|
-
changed: z16.array(diffSchema),
|
|
628
|
-
unchanged: z16.array(resultSchema),
|
|
629
|
-
added: z16.array(resultSchema),
|
|
630
|
-
removed: z16.array(resultSchema)
|
|
631
|
-
},
|
|
632
|
-
{ description }
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
var scorableMetaSchema = z16.object({
|
|
636
|
-
slug: slugSchema,
|
|
637
|
-
title: titleSchema,
|
|
638
|
-
docsUrl: docsUrlSchema
|
|
639
|
-
});
|
|
640
|
-
var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
|
|
641
|
-
z16.object({
|
|
642
|
-
plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
|
|
643
|
-
})
|
|
644
|
-
);
|
|
645
|
-
var scorableDiffSchema = scorableMetaSchema.merge(
|
|
646
|
-
z16.object({
|
|
647
|
-
scores: makeComparisonSchema(scoreSchema).merge(
|
|
648
|
-
z16.object({
|
|
649
|
-
diff: z16.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
|
|
650
|
-
})
|
|
651
|
-
).describe("Score comparison")
|
|
652
|
-
})
|
|
653
|
-
);
|
|
654
|
-
var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
|
|
655
|
-
scorableWithPluginMetaSchema
|
|
656
|
-
);
|
|
657
|
-
var categoryDiffSchema = scorableDiffSchema;
|
|
658
|
-
var groupDiffSchema = scorableWithPluginDiffSchema;
|
|
659
|
-
var auditDiffSchema = scorableWithPluginDiffSchema.merge(
|
|
660
|
-
z16.object({
|
|
661
|
-
values: makeComparisonSchema(auditValueSchema).merge(
|
|
662
|
-
z16.object({
|
|
663
|
-
diff: z16.number().describe("Value change (`values.after - values.before`)")
|
|
664
|
-
})
|
|
665
|
-
).describe("Audit `value` comparison"),
|
|
666
|
-
displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
|
|
667
|
-
"Audit `displayValue` comparison"
|
|
668
|
-
)
|
|
669
|
-
})
|
|
670
|
-
);
|
|
671
|
-
var categoryResultSchema = scorableMetaSchema.merge(
|
|
672
|
-
z16.object({ score: scoreSchema })
|
|
673
|
-
);
|
|
674
|
-
var groupResultSchema = scorableWithPluginMetaSchema.merge(
|
|
675
|
-
z16.object({ score: scoreSchema })
|
|
676
|
-
);
|
|
677
|
-
var auditResultSchema = scorableWithPluginMetaSchema.merge(
|
|
678
|
-
auditOutputSchema.pick({ score: true, value: true, displayValue: true })
|
|
679
|
-
);
|
|
680
|
-
var reportsDiffSchema = z16.object({
|
|
681
|
-
commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
|
|
682
|
-
portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
|
|
683
|
-
label: z16.string().optional().describe("Label (e.g. project name)"),
|
|
684
|
-
categories: makeArraysComparisonSchema(
|
|
685
|
-
categoryDiffSchema,
|
|
686
|
-
categoryResultSchema,
|
|
687
|
-
"Changes affecting categories"
|
|
688
|
-
),
|
|
689
|
-
groups: makeArraysComparisonSchema(
|
|
690
|
-
groupDiffSchema,
|
|
691
|
-
groupResultSchema,
|
|
692
|
-
"Changes affecting groups"
|
|
693
|
-
),
|
|
694
|
-
audits: makeArraysComparisonSchema(
|
|
695
|
-
auditDiffSchema,
|
|
696
|
-
auditResultSchema,
|
|
697
|
-
"Changes affecting audits"
|
|
698
|
-
)
|
|
699
|
-
}).merge(
|
|
700
|
-
packageVersionSchema({
|
|
701
|
-
versionDescription: "NPM version of the CLI (when `compare` was run)",
|
|
702
|
-
required: true
|
|
703
|
-
})
|
|
704
|
-
).merge(
|
|
705
|
-
executionMetaSchema({
|
|
706
|
-
descriptionDate: "Start date and time of the compare run",
|
|
707
|
-
descriptionDuration: "Duration of the compare run in ms"
|
|
708
|
-
})
|
|
709
|
-
);
|
|
710
|
-
|
|
711
|
-
// packages/utils/src/lib/diff.ts
|
|
712
|
-
function matchArrayItemsByKey({
|
|
713
|
-
before,
|
|
714
|
-
after,
|
|
715
|
-
key
|
|
716
|
-
}) {
|
|
717
|
-
const pairs = [];
|
|
718
|
-
const added = [];
|
|
719
|
-
const afterKeys = /* @__PURE__ */ new Set();
|
|
720
|
-
const keyFn = typeof key === "function" ? key : (item) => item[key];
|
|
721
|
-
for (const afterItem of after) {
|
|
722
|
-
const afterKey = keyFn(afterItem);
|
|
723
|
-
afterKeys.add(afterKey);
|
|
724
|
-
const match = before.find((beforeItem) => keyFn(beforeItem) === afterKey);
|
|
725
|
-
if (match) {
|
|
726
|
-
pairs.push({ before: match, after: afterItem });
|
|
727
|
-
} else {
|
|
728
|
-
added.push(afterItem);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
const removed = before.filter(
|
|
732
|
-
(beforeItem) => !afterKeys.has(keyFn(beforeItem))
|
|
733
|
-
);
|
|
734
|
-
return {
|
|
735
|
-
pairs,
|
|
736
|
-
added,
|
|
737
|
-
removed
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
function comparePairs(pairs, equalsFn) {
|
|
741
|
-
return pairs.reduce(
|
|
742
|
-
(acc, pair) => ({
|
|
743
|
-
...acc,
|
|
744
|
-
...equalsFn(pair) ? { unchanged: [...acc.unchanged, pair.after] } : { changed: [...acc.changed, pair] }
|
|
745
|
-
}),
|
|
746
|
-
{
|
|
747
|
-
changed: [],
|
|
748
|
-
unchanged: []
|
|
749
|
-
}
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// packages/utils/src/lib/errors.ts
|
|
754
|
-
function stringifyError(error) {
|
|
755
|
-
if (error instanceof Error) {
|
|
756
|
-
if (error.name === "Error" || error.message.startsWith(error.name)) {
|
|
757
|
-
return error.message;
|
|
758
|
-
}
|
|
759
|
-
return `${error.name}: ${error.message}`;
|
|
760
|
-
}
|
|
761
|
-
if (typeof error === "string") {
|
|
762
|
-
return error;
|
|
763
|
-
}
|
|
764
|
-
return JSON.stringify(error);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// packages/utils/src/lib/execute-process.ts
|
|
768
|
-
import {
|
|
769
|
-
spawn
|
|
770
|
-
} from "node:child_process";
|
|
771
|
-
|
|
772
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
773
|
-
import ansis from "ansis";
|
|
774
|
-
import { md } from "build-md";
|
|
775
|
-
|
|
776
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
777
|
-
var TERMINAL_WIDTH = 80;
|
|
778
|
-
var SCORE_COLOR_RANGE = {
|
|
779
|
-
GREEN_MIN: 0.9,
|
|
780
|
-
YELLOW_MIN: 0.5
|
|
781
|
-
};
|
|
782
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
783
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
784
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
785
|
-
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
786
|
-
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
787
|
-
"Category",
|
|
788
|
-
"Score",
|
|
789
|
-
"Audits"
|
|
790
|
-
];
|
|
791
|
-
|
|
792
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
793
|
-
function formatReportScore(score) {
|
|
794
|
-
const scaledScore = score * 100;
|
|
795
|
-
const roundedScore = Math.round(scaledScore);
|
|
796
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
797
|
-
}
|
|
798
|
-
function formatScoreWithColor(score, options2) {
|
|
799
|
-
const styledNumber = options2?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
800
|
-
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
801
|
-
}
|
|
802
|
-
var MARKERS = {
|
|
803
|
-
circle: {
|
|
804
|
-
red: "\u{1F534}",
|
|
805
|
-
yellow: "\u{1F7E1}",
|
|
806
|
-
green: "\u{1F7E2}"
|
|
807
|
-
},
|
|
808
|
-
square: {
|
|
809
|
-
red: "\u{1F7E5}",
|
|
810
|
-
yellow: "\u{1F7E8}",
|
|
811
|
-
green: "\u{1F7E9}"
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
function scoreMarker(score, markerType = "circle") {
|
|
815
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
816
|
-
return MARKERS[markerType].green;
|
|
817
|
-
}
|
|
818
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
819
|
-
return MARKERS[markerType].yellow;
|
|
820
|
-
}
|
|
821
|
-
return MARKERS[markerType].red;
|
|
822
|
-
}
|
|
823
|
-
function getDiffMarker(diff) {
|
|
824
|
-
if (diff > 0) {
|
|
825
|
-
return "\u2191";
|
|
826
|
-
}
|
|
827
|
-
if (diff < 0) {
|
|
828
|
-
return "\u2193";
|
|
829
|
-
}
|
|
830
|
-
return "";
|
|
831
|
-
}
|
|
832
|
-
function colorByScoreDiff(text, diff) {
|
|
833
|
-
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
834
|
-
return shieldsBadge(text, color);
|
|
835
|
-
}
|
|
836
|
-
function shieldsBadge(text, color) {
|
|
837
|
-
return md.image(
|
|
838
|
-
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
839
|
-
text
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
function formatDiffNumber(diff) {
|
|
843
|
-
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
844
|
-
const sign = diff < 0 ? "\u2212" : "+";
|
|
845
|
-
return `${sign}${number}`;
|
|
846
|
-
}
|
|
847
|
-
function severityMarker(severity) {
|
|
848
|
-
if (severity === "error") {
|
|
849
|
-
return "\u{1F6A8}";
|
|
850
|
-
}
|
|
851
|
-
if (severity === "warning") {
|
|
852
|
-
return "\u26A0\uFE0F";
|
|
853
|
-
}
|
|
854
|
-
return "\u2139\uFE0F";
|
|
855
|
-
}
|
|
856
|
-
var MIN_NON_ZERO_RESULT = 0.1;
|
|
857
|
-
function roundValue(value) {
|
|
858
|
-
const roundedValue = Math.round(value * 10) / 10;
|
|
859
|
-
if (roundedValue === 0 && value !== 0) {
|
|
860
|
-
return MIN_NON_ZERO_RESULT * Math.sign(value);
|
|
861
|
-
}
|
|
862
|
-
return roundedValue;
|
|
863
|
-
}
|
|
864
|
-
function formatScoreChange(diff) {
|
|
865
|
-
const marker = getDiffMarker(diff);
|
|
866
|
-
const text = formatDiffNumber(roundValue(diff * 100));
|
|
867
|
-
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
868
|
-
}
|
|
869
|
-
function formatValueChange({
|
|
870
|
-
values,
|
|
871
|
-
scores
|
|
872
|
-
}) {
|
|
873
|
-
const marker = getDiffMarker(values.diff);
|
|
874
|
-
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : roundValue(values.diff / values.before * 100);
|
|
875
|
-
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
876
|
-
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
877
|
-
}
|
|
878
|
-
function calcDuration(start, stop) {
|
|
879
|
-
return Math.round((stop ?? performance.now()) - start);
|
|
880
|
-
}
|
|
881
|
-
function countCategoryAudits(refs, plugins) {
|
|
882
|
-
const groupLookup = plugins.reduce(
|
|
883
|
-
(lookup, plugin) => {
|
|
884
|
-
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
885
|
-
return lookup;
|
|
886
|
-
}
|
|
887
|
-
return {
|
|
888
|
-
...lookup,
|
|
889
|
-
[plugin.slug]: Object.fromEntries(
|
|
890
|
-
plugin.groups.map((group) => [group.slug, group])
|
|
891
|
-
)
|
|
892
|
-
};
|
|
893
|
-
},
|
|
894
|
-
{}
|
|
895
|
-
);
|
|
896
|
-
return refs.reduce((acc, ref) => {
|
|
897
|
-
if (ref.type === "group") {
|
|
898
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
899
|
-
return acc + (groupRefs?.length ?? 0);
|
|
900
|
-
}
|
|
901
|
-
return acc + 1;
|
|
902
|
-
}, 0);
|
|
903
|
-
}
|
|
904
|
-
function compareCategoryAuditsAndGroups(a, b) {
|
|
905
|
-
if (a.score !== b.score) {
|
|
906
|
-
return a.score - b.score;
|
|
907
|
-
}
|
|
908
|
-
if (a.weight !== b.weight) {
|
|
909
|
-
return b.weight - a.weight;
|
|
910
|
-
}
|
|
911
|
-
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
912
|
-
return b.value - a.value;
|
|
913
|
-
}
|
|
914
|
-
return a.title.localeCompare(b.title);
|
|
915
|
-
}
|
|
916
|
-
function compareAudits(a, b) {
|
|
917
|
-
if (a.score !== b.score) {
|
|
918
|
-
return a.score - b.score;
|
|
919
|
-
}
|
|
920
|
-
if (a.value !== b.value) {
|
|
921
|
-
return b.value - a.value;
|
|
922
|
-
}
|
|
923
|
-
return a.title.localeCompare(b.title);
|
|
924
|
-
}
|
|
925
|
-
function compareIssueSeverity(severity1, severity2) {
|
|
926
|
-
const levels = {
|
|
927
|
-
info: 0,
|
|
928
|
-
warning: 1,
|
|
929
|
-
error: 2
|
|
930
|
-
};
|
|
931
|
-
return levels[severity1] - levels[severity2];
|
|
932
|
-
}
|
|
933
|
-
function throwIsNotPresentError(itemName, presentPlace) {
|
|
934
|
-
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
935
|
-
}
|
|
936
|
-
function getPluginNameFromSlug(slug, plugins) {
|
|
937
|
-
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
938
|
-
}
|
|
939
|
-
function compareIssues(a, b) {
|
|
940
|
-
if (a.severity !== b.severity) {
|
|
941
|
-
return -compareIssueSeverity(a.severity, b.severity);
|
|
942
|
-
}
|
|
943
|
-
if (!a.source && b.source) {
|
|
944
|
-
return -1;
|
|
945
|
-
}
|
|
946
|
-
if (a.source && !b.source) {
|
|
947
|
-
return 1;
|
|
948
|
-
}
|
|
949
|
-
if (a.source?.file !== b.source?.file) {
|
|
950
|
-
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
951
|
-
}
|
|
952
|
-
if (!a.source?.position && b.source?.position) {
|
|
953
|
-
return -1;
|
|
954
|
-
}
|
|
955
|
-
if (a.source?.position && !b.source?.position) {
|
|
956
|
-
return 1;
|
|
957
|
-
}
|
|
958
|
-
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
959
|
-
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
960
|
-
}
|
|
961
|
-
return 0;
|
|
962
|
-
}
|
|
963
|
-
function applyScoreColor({ score, text }, style = ansis) {
|
|
964
|
-
const formattedScore = text ?? formatReportScore(score);
|
|
965
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
966
|
-
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
967
|
-
}
|
|
968
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
969
|
-
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
970
|
-
}
|
|
971
|
-
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
972
|
-
}
|
|
973
|
-
function targetScoreIcon(score, targetScore, options2 = {}) {
|
|
974
|
-
if (targetScore != null) {
|
|
975
|
-
const {
|
|
976
|
-
passIcon = "\u2705",
|
|
977
|
-
failIcon = "\u274C",
|
|
978
|
-
prefix = "",
|
|
979
|
-
postfix = ""
|
|
980
|
-
} = options2;
|
|
981
|
-
if (score >= targetScore) {
|
|
982
|
-
return `${prefix}${passIcon}${postfix}`;
|
|
983
|
-
}
|
|
984
|
-
return `${prefix}${failIcon}${postfix}`;
|
|
985
|
-
}
|
|
986
|
-
return "";
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// packages/utils/src/lib/execute-process.ts
|
|
990
|
-
var ProcessError = class extends Error {
|
|
991
|
-
code;
|
|
992
|
-
stderr;
|
|
993
|
-
stdout;
|
|
994
|
-
constructor(result) {
|
|
995
|
-
super(result.stderr);
|
|
996
|
-
this.code = result.code;
|
|
997
|
-
this.stderr = result.stderr;
|
|
998
|
-
this.stdout = result.stdout;
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
function executeProcess(cfg) {
|
|
1002
|
-
const { command: command2, args, observer, ignoreExitCode = false, ...options2 } = cfg;
|
|
1003
|
-
const { onStdout, onStderr, onError, onComplete } = observer ?? {};
|
|
1004
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1005
|
-
const start = performance.now();
|
|
1006
|
-
return new Promise((resolve, reject) => {
|
|
1007
|
-
const spawnedProcess = spawn(command2, args ?? [], {
|
|
1008
|
-
shell: true,
|
|
1009
|
-
windowsHide: true,
|
|
1010
|
-
...options2
|
|
1011
|
-
});
|
|
1012
|
-
let stdout = "";
|
|
1013
|
-
let stderr = "";
|
|
1014
|
-
spawnedProcess.stdout.on("data", (data) => {
|
|
1015
|
-
stdout += String(data);
|
|
1016
|
-
onStdout?.(String(data), spawnedProcess);
|
|
1017
|
-
});
|
|
1018
|
-
spawnedProcess.stderr.on("data", (data) => {
|
|
1019
|
-
stderr += String(data);
|
|
1020
|
-
onStderr?.(String(data), spawnedProcess);
|
|
1021
|
-
});
|
|
1022
|
-
spawnedProcess.on("error", (err) => {
|
|
1023
|
-
stderr += err.toString();
|
|
1024
|
-
});
|
|
1025
|
-
spawnedProcess.on("close", (code2) => {
|
|
1026
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1027
|
-
if (code2 === 0 || ignoreExitCode) {
|
|
1028
|
-
onComplete?.();
|
|
1029
|
-
resolve({ code: code2, stdout, stderr, ...timings });
|
|
1030
|
-
} else {
|
|
1031
|
-
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
1032
|
-
onError?.(errorMsg);
|
|
1033
|
-
reject(errorMsg);
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// packages/utils/src/lib/file-system.ts
|
|
1040
|
-
import { bold, gray } from "ansis";
|
|
1041
|
-
import { bundleRequire } from "bundle-require";
|
|
1042
|
-
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
1043
|
-
|
|
1044
|
-
// packages/utils/src/lib/formatting.ts
|
|
1045
|
-
function slugify(text) {
|
|
1046
|
-
return text.trim().toLowerCase().replace(/\s+|\//g, "-").replace(/[^a-z\d-]/g, "");
|
|
1047
|
-
}
|
|
1048
|
-
function pluralize(text, amount) {
|
|
1049
|
-
if (amount != null && Math.abs(amount) === 1) {
|
|
1050
|
-
return text;
|
|
1051
|
-
}
|
|
1052
|
-
if (text.endsWith("y")) {
|
|
1053
|
-
return `${text.slice(0, -1)}ies`;
|
|
1054
|
-
}
|
|
1055
|
-
if (text.endsWith("s")) {
|
|
1056
|
-
return `${text}es`;
|
|
1057
|
-
}
|
|
1058
|
-
return `${text}s`;
|
|
1059
|
-
}
|
|
1060
|
-
function formatBytes(bytes, decimals = 2) {
|
|
1061
|
-
const positiveBytes = Math.max(bytes, 0);
|
|
1062
|
-
if (positiveBytes === 0) {
|
|
1063
|
-
return "0 B";
|
|
1064
|
-
}
|
|
1065
|
-
const k = 1024;
|
|
1066
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
1067
|
-
const sizes = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
1068
|
-
const i = Math.floor(Math.log(positiveBytes) / Math.log(k));
|
|
1069
|
-
return `${Number.parseFloat((positiveBytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
1070
|
-
}
|
|
1071
|
-
function pluralizeToken(token, times) {
|
|
1072
|
-
return `${times} ${Math.abs(times) === 1 ? token : pluralize(token)}`;
|
|
1073
|
-
}
|
|
1074
|
-
function formatDuration(duration, granularity = 0) {
|
|
1075
|
-
if (duration < 1e3) {
|
|
1076
|
-
return `${granularity ? duration.toFixed(granularity) : duration} ms`;
|
|
1077
|
-
}
|
|
1078
|
-
return `${(duration / 1e3).toFixed(2)} s`;
|
|
1079
|
-
}
|
|
1080
|
-
function formatDate(date) {
|
|
1081
|
-
const locale = "en-US";
|
|
1082
|
-
return date.toLocaleString(locale, {
|
|
1083
|
-
weekday: "short",
|
|
1084
|
-
month: "short",
|
|
1085
|
-
day: "numeric",
|
|
1086
|
-
year: "numeric",
|
|
1087
|
-
hour: "numeric",
|
|
1088
|
-
minute: "2-digit",
|
|
1089
|
-
timeZoneName: "short"
|
|
1090
|
-
}).replace(/\u202F/g, " ");
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// packages/utils/src/lib/guards.ts
|
|
1094
|
-
function isPromiseFulfilledResult(result) {
|
|
1095
|
-
return result.status === "fulfilled";
|
|
1096
|
-
}
|
|
1097
|
-
function isPromiseRejectedResult(result) {
|
|
1098
|
-
return result.status === "rejected";
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// packages/utils/src/lib/logging.ts
|
|
1102
|
-
import isaacs_cliui from "@isaacs/cliui";
|
|
1103
|
-
import { cliui } from "@poppinss/cliui";
|
|
1104
|
-
import { underline } from "ansis";
|
|
1105
|
-
var singletonUiInstance;
|
|
1106
|
-
function ui() {
|
|
1107
|
-
if (singletonUiInstance === void 0) {
|
|
1108
|
-
singletonUiInstance = cliui();
|
|
1109
|
-
}
|
|
1110
|
-
return {
|
|
1111
|
-
...singletonUiInstance,
|
|
1112
|
-
row: (args) => {
|
|
1113
|
-
logListItem(args);
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
var singletonisaacUi;
|
|
1118
|
-
function logListItem(args) {
|
|
1119
|
-
if (singletonisaacUi === void 0) {
|
|
1120
|
-
singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
|
|
1121
|
-
}
|
|
1122
|
-
singletonisaacUi.div(...args);
|
|
1123
|
-
const content = singletonisaacUi.toString();
|
|
1124
|
-
singletonisaacUi.rows = [];
|
|
1125
|
-
singletonUiInstance?.logger.log(content);
|
|
1126
|
-
}
|
|
1127
|
-
function link(text) {
|
|
1128
|
-
return underline.blueBright(text);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// packages/utils/src/lib/log-results.ts
|
|
1132
|
-
function logMultipleResults(results, messagePrefix, succeededTransform, failedTransform) {
|
|
1133
|
-
if (succeededTransform) {
|
|
1134
|
-
const succeededResults = results.filter(isPromiseFulfilledResult);
|
|
1135
|
-
logPromiseResults(
|
|
1136
|
-
succeededResults,
|
|
1137
|
-
`${messagePrefix} successfully: `,
|
|
1138
|
-
succeededTransform
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1141
|
-
if (failedTransform) {
|
|
1142
|
-
const failedResults = results.filter(isPromiseRejectedResult);
|
|
1143
|
-
logPromiseResults(
|
|
1144
|
-
failedResults,
|
|
1145
|
-
`${messagePrefix} failed: `,
|
|
1146
|
-
failedTransform
|
|
1147
|
-
);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
function logPromiseResults(results, logMessage, getMsg) {
|
|
1151
|
-
if (results.length > 0) {
|
|
1152
|
-
const log2 = results[0]?.status === "fulfilled" ? (m) => {
|
|
1153
|
-
ui().logger.success(m);
|
|
1154
|
-
} : (m) => {
|
|
1155
|
-
ui().logger.warning(m);
|
|
1156
|
-
};
|
|
1157
|
-
log2(logMessage);
|
|
1158
|
-
results.forEach((result) => {
|
|
1159
|
-
log2(getMsg(result));
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// packages/utils/src/lib/file-system.ts
|
|
1165
|
-
async function readTextFile(path) {
|
|
1166
|
-
const buffer = await readFile(path);
|
|
1167
|
-
return buffer.toString();
|
|
1168
|
-
}
|
|
1169
|
-
async function readJsonFile(path) {
|
|
1170
|
-
const text = await readTextFile(path);
|
|
1171
|
-
return JSON.parse(text);
|
|
1172
|
-
}
|
|
1173
|
-
async function fileExists(path) {
|
|
1174
|
-
try {
|
|
1175
|
-
const stats = await stat(path);
|
|
1176
|
-
return stats.isFile();
|
|
1177
|
-
} catch {
|
|
1178
|
-
return false;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
async function directoryExists(path) {
|
|
1182
|
-
try {
|
|
1183
|
-
const stats = await stat(path);
|
|
1184
|
-
return stats.isDirectory();
|
|
1185
|
-
} catch {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
async function ensureDirectoryExists(baseDir) {
|
|
1190
|
-
try {
|
|
1191
|
-
await mkdir(baseDir, { recursive: true });
|
|
1192
|
-
return;
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
ui().logger.info(error.message);
|
|
1195
|
-
if (error.code !== "EEXIST") {
|
|
1196
|
-
throw error;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
1201
|
-
const succeededTransform = (result) => {
|
|
1202
|
-
const [fileName, size] = result.value;
|
|
1203
|
-
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1204
|
-
return `- ${bold(fileName)}${formattedSize}`;
|
|
1205
|
-
};
|
|
1206
|
-
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
1207
|
-
logMultipleResults(
|
|
1208
|
-
fileResults,
|
|
1209
|
-
messagePrefix,
|
|
1210
|
-
succeededTransform,
|
|
1211
|
-
failedTransform
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
async function importModule(options2) {
|
|
1215
|
-
const { mod } = await bundleRequire(options2);
|
|
1216
|
-
if (typeof mod === "object" && "default" in mod) {
|
|
1217
|
-
return mod.default;
|
|
1218
|
-
}
|
|
1219
|
-
return mod;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// packages/utils/src/lib/filter.ts
|
|
1223
|
-
function filterItemRefsBy(items, refFilterFn) {
|
|
1224
|
-
return items.map((item) => ({
|
|
1225
|
-
...item,
|
|
1226
|
-
refs: item.refs.filter(refFilterFn)
|
|
1227
|
-
})).filter((item) => item.refs.length);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// packages/utils/src/lib/git/git.ts
|
|
1231
|
-
import { isAbsolute, join, relative } from "node:path";
|
|
1232
|
-
import { simpleGit } from "simple-git";
|
|
1233
|
-
|
|
1234
|
-
// packages/utils/src/lib/transform.ts
|
|
1235
|
-
function toArray(val) {
|
|
1236
|
-
return Array.isArray(val) ? val : [val];
|
|
1237
|
-
}
|
|
1238
|
-
function objectToEntries(obj) {
|
|
1239
|
-
return Object.entries(obj);
|
|
1240
|
-
}
|
|
1241
|
-
function deepClone(obj) {
|
|
1242
|
-
return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
|
|
1243
|
-
}
|
|
1244
|
-
function toUnixPath(path) {
|
|
1245
|
-
return path.replace(/\\/g, "/");
|
|
1246
|
-
}
|
|
1247
|
-
function capitalize(text) {
|
|
1248
|
-
return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
|
|
1249
|
-
1
|
|
1250
|
-
)}`;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// packages/utils/src/lib/git/git.ts
|
|
1254
|
-
function getGitRoot(git = simpleGit()) {
|
|
1255
|
-
return git.revparse("--show-toplevel");
|
|
1256
|
-
}
|
|
1257
|
-
function formatGitPath(path, gitRoot) {
|
|
1258
|
-
const absolutePath = isAbsolute(path) ? path : join(process.cwd(), path);
|
|
1259
|
-
const relativePath = relative(gitRoot, absolutePath);
|
|
1260
|
-
return toUnixPath(relativePath);
|
|
1261
|
-
}
|
|
1262
|
-
var GitStatusError = class _GitStatusError extends Error {
|
|
1263
|
-
static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
|
|
1264
|
-
static getReducedStatus(status) {
|
|
1265
|
-
return Object.fromEntries(
|
|
1266
|
-
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1267
|
-
(entry) => {
|
|
1268
|
-
const value = entry[1];
|
|
1269
|
-
if (value == null) {
|
|
1270
|
-
return false;
|
|
1271
|
-
}
|
|
1272
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
1273
|
-
return false;
|
|
1274
|
-
}
|
|
1275
|
-
if (typeof value === "number" && value === 0) {
|
|
1276
|
-
return false;
|
|
1277
|
-
}
|
|
1278
|
-
return !(typeof value === "boolean" && !value);
|
|
1279
|
-
}
|
|
1280
|
-
)
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1283
|
-
constructor(status) {
|
|
1284
|
-
super(
|
|
1285
|
-
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1286
|
-
${JSON.stringify(
|
|
1287
|
-
_GitStatusError.getReducedStatus(status),
|
|
1288
|
-
null,
|
|
1289
|
-
2
|
|
1290
|
-
)}`
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
};
|
|
1294
|
-
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1295
|
-
const status = await git.status(["-s"]);
|
|
1296
|
-
if (status.files.length > 0) {
|
|
1297
|
-
throw new GitStatusError(status);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1301
|
-
if (forceCleanStatus) {
|
|
1302
|
-
await git.raw(["reset", "--hard"]);
|
|
1303
|
-
await git.clean(["f", "d"]);
|
|
1304
|
-
ui().logger.info(`git status cleaned`);
|
|
1305
|
-
}
|
|
1306
|
-
await guardAgainstLocalChanges(git);
|
|
1307
|
-
await git.checkout(branchOrHash);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1311
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1312
|
-
|
|
1313
|
-
// packages/utils/src/lib/semver.ts
|
|
1314
|
-
import { rcompare, valid } from "semver";
|
|
1315
|
-
function normalizeSemver(semverString) {
|
|
1316
|
-
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1317
|
-
return semverString.slice(1);
|
|
1318
|
-
}
|
|
1319
|
-
if (semverString.includes("@")) {
|
|
1320
|
-
return semverString.split("@").at(-1) ?? "";
|
|
1321
|
-
}
|
|
1322
|
-
return semverString;
|
|
1323
|
-
}
|
|
1324
|
-
function isSemver(semverString = "") {
|
|
1325
|
-
return valid(normalizeSemver(semverString)) != null;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1329
|
-
async function getLatestCommit(git = simpleGit2()) {
|
|
1330
|
-
const log2 = await git.log({
|
|
1331
|
-
maxCount: 1,
|
|
1332
|
-
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1333
|
-
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1334
|
-
});
|
|
1335
|
-
return commitSchema.parse(log2.latest);
|
|
1336
|
-
}
|
|
1337
|
-
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1338
|
-
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1339
|
-
// @TODO use simple git
|
|
1340
|
-
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1341
|
-
}
|
|
1342
|
-
function validateFilter({ from, to }) {
|
|
1343
|
-
if (to && !from) {
|
|
1344
|
-
throw new Error(
|
|
1345
|
-
`filter needs the "from" option defined to accept the "to" option.
|
|
1346
|
-
`
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
function filterLogs(allTags, opt) {
|
|
1351
|
-
if (!opt) {
|
|
1352
|
-
return allTags;
|
|
1353
|
-
}
|
|
1354
|
-
validateFilter(opt);
|
|
1355
|
-
const { from, to, maxCount } = opt;
|
|
1356
|
-
const finIndex = (tagName, fallback) => {
|
|
1357
|
-
const idx = allTags.indexOf(tagName ?? "");
|
|
1358
|
-
if (idx > -1) {
|
|
1359
|
-
return idx;
|
|
1360
|
-
}
|
|
1361
|
-
return fallback;
|
|
1362
|
-
};
|
|
1363
|
-
const fromIndex = finIndex(from, 0);
|
|
1364
|
-
const toIndex = finIndex(to, void 0);
|
|
1365
|
-
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1366
|
-
}
|
|
1367
|
-
async function getHashFromTag(tag, git = simpleGit2()) {
|
|
1368
|
-
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1369
|
-
const hash = tagDetails.trim();
|
|
1370
|
-
return {
|
|
1371
|
-
hash: hash.split("\n").at(-1) ?? "",
|
|
1372
|
-
message: tag
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
async function getSemverTags(opt = {}, git = simpleGit2()) {
|
|
1376
|
-
validateFilter(opt);
|
|
1377
|
-
const { targetBranch, ...options2 } = opt;
|
|
1378
|
-
let currentBranch;
|
|
1379
|
-
if (targetBranch) {
|
|
1380
|
-
currentBranch = await getCurrentBranchOrTag(git);
|
|
1381
|
-
await git.checkout(targetBranch);
|
|
1382
|
-
}
|
|
1383
|
-
const tagsRaw = await git.tag([
|
|
1384
|
-
"--merged",
|
|
1385
|
-
targetBranch ?? await getCurrentBranchOrTag(git)
|
|
1386
|
-
]);
|
|
1387
|
-
const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
|
|
1388
|
-
const relevantTags = filterLogs(allTags, options2);
|
|
1389
|
-
const tagsWithHashes = await Promise.all(
|
|
1390
|
-
relevantTags.map((tag) => getHashFromTag(tag, git))
|
|
1391
|
-
);
|
|
1392
|
-
if (currentBranch) {
|
|
1393
|
-
await git.checkout(currentBranch);
|
|
1394
|
-
}
|
|
1395
|
-
return tagsWithHashes;
|
|
1396
|
-
}
|
|
1397
|
-
async function getHashes(options2 = {}, git = simpleGit2()) {
|
|
1398
|
-
const { targetBranch, from, to, maxCount, ...opt } = options2;
|
|
1399
|
-
validateFilter({ from, to });
|
|
1400
|
-
let currentBranch;
|
|
1401
|
-
if (targetBranch) {
|
|
1402
|
-
currentBranch = await getCurrentBranchOrTag(git);
|
|
1403
|
-
await git.checkout(targetBranch);
|
|
1404
|
-
}
|
|
1405
|
-
const logs = await git.log({
|
|
1406
|
-
...opt,
|
|
1407
|
-
format: {
|
|
1408
|
-
hash: "%H",
|
|
1409
|
-
message: "%s"
|
|
1410
|
-
},
|
|
1411
|
-
from,
|
|
1412
|
-
to,
|
|
1413
|
-
maxCount
|
|
1414
|
-
});
|
|
1415
|
-
if (targetBranch) {
|
|
1416
|
-
await git.checkout(currentBranch);
|
|
1417
|
-
}
|
|
1418
|
-
return [...logs.all];
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
// packages/utils/src/lib/group-by-status.ts
|
|
1422
|
-
function groupByStatus(results) {
|
|
1423
|
-
return results.reduce(
|
|
1424
|
-
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1425
|
-
{ fulfilled: [], rejected: [] }
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// packages/utils/src/lib/progress.ts
|
|
1430
|
-
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1431
|
-
import { MultiProgressBars } from "multi-progress-bars";
|
|
1432
|
-
var barStyles = {
|
|
1433
|
-
active: (s) => green(s),
|
|
1434
|
-
done: (s) => gray2(s),
|
|
1435
|
-
idle: (s) => gray2(s)
|
|
1436
|
-
};
|
|
1437
|
-
var messageStyles = {
|
|
1438
|
-
active: (s) => black(s),
|
|
1439
|
-
done: (s) => bold2.green(s),
|
|
1440
|
-
idle: (s) => gray2(s)
|
|
1441
|
-
};
|
|
1442
|
-
var mpb;
|
|
1443
|
-
function getSingletonProgressBars(options2) {
|
|
1444
|
-
if (!mpb) {
|
|
1445
|
-
mpb = new MultiProgressBars({
|
|
1446
|
-
progressWidth: TERMINAL_WIDTH,
|
|
1447
|
-
initMessage: "",
|
|
1448
|
-
border: true,
|
|
1449
|
-
...options2
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
return mpb;
|
|
1453
|
-
}
|
|
1454
|
-
function getProgressBar(taskName) {
|
|
1455
|
-
const tasks = getSingletonProgressBars();
|
|
1456
|
-
tasks.addTask(taskName, {
|
|
1457
|
-
type: "percentage",
|
|
1458
|
-
percentage: 0,
|
|
1459
|
-
message: "",
|
|
1460
|
-
barTransformFn: barStyles.idle
|
|
1461
|
-
});
|
|
1462
|
-
return {
|
|
1463
|
-
incrementInSteps: (numPlugins) => {
|
|
1464
|
-
tasks.incrementTask(taskName, {
|
|
1465
|
-
percentage: 1 / numPlugins,
|
|
1466
|
-
barTransformFn: barStyles.active
|
|
1467
|
-
});
|
|
1468
|
-
},
|
|
1469
|
-
updateTitle: (title) => {
|
|
1470
|
-
tasks.updateTask(taskName, {
|
|
1471
|
-
message: title,
|
|
1472
|
-
barTransformFn: barStyles.active
|
|
1473
|
-
});
|
|
1474
|
-
},
|
|
1475
|
-
endProgress: (message = "") => {
|
|
1476
|
-
tasks.done(taskName, {
|
|
1477
|
-
message: messageStyles.done(message),
|
|
1478
|
-
barTransformFn: barStyles.done
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1485
|
-
function listGroupsFromAllPlugins(report) {
|
|
1486
|
-
return report.plugins.flatMap(
|
|
1487
|
-
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1488
|
-
);
|
|
1489
|
-
}
|
|
1490
|
-
function listAuditsFromAllPlugins(report) {
|
|
1491
|
-
return report.plugins.flatMap(
|
|
1492
|
-
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1497
|
-
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1498
|
-
|
|
1499
|
-
// packages/utils/src/lib/text-formats/constants.ts
|
|
1500
|
-
var HIERARCHY = {
|
|
1501
|
-
level_1: 1,
|
|
1502
|
-
level_2: 2,
|
|
1503
|
-
level_3: 3,
|
|
1504
|
-
level_4: 4,
|
|
1505
|
-
level_5: 5,
|
|
1506
|
-
level_6: 6
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
// packages/utils/src/lib/text-formats/table.ts
|
|
1510
|
-
function rowToStringArray({ rows, columns = [] }) {
|
|
1511
|
-
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1512
|
-
throw new TypeError(
|
|
1513
|
-
"Column can`t be object when rows are primitive values"
|
|
1514
|
-
);
|
|
1515
|
-
}
|
|
1516
|
-
return rows.map((row) => {
|
|
1517
|
-
if (Array.isArray(row)) {
|
|
1518
|
-
return row.map(String);
|
|
1519
|
-
}
|
|
1520
|
-
const objectRow = row;
|
|
1521
|
-
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1522
|
-
return Object.values(objectRow).map(
|
|
1523
|
-
(value) => value == null ? "" : String(value)
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
|
-
return columns.map(
|
|
1527
|
-
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1528
|
-
);
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
function columnsToStringArray({
|
|
1532
|
-
rows,
|
|
1533
|
-
columns = []
|
|
1534
|
-
}) {
|
|
1535
|
-
const firstRow = rows.at(0);
|
|
1536
|
-
const primitiveRows = Array.isArray(firstRow);
|
|
1537
|
-
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1538
|
-
throw new Error("invalid union type. Caught by model parsing.");
|
|
1539
|
-
}
|
|
1540
|
-
if (columns.length === 0) {
|
|
1541
|
-
if (Array.isArray(firstRow)) {
|
|
1542
|
-
return firstRow.map((_, idx) => String(idx));
|
|
1543
|
-
}
|
|
1544
|
-
return Object.keys(firstRow);
|
|
1545
|
-
}
|
|
1546
|
-
if (typeof columns.at(0) === "string") {
|
|
1547
|
-
return columns.map(String);
|
|
1548
|
-
}
|
|
1549
|
-
const cols = columns;
|
|
1550
|
-
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1551
|
-
}
|
|
1552
|
-
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1553
|
-
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1554
|
-
if (typeof column === "string") {
|
|
1555
|
-
return column;
|
|
1556
|
-
} else if (typeof column === "object") {
|
|
1557
|
-
return column.align ?? "center";
|
|
1558
|
-
} else {
|
|
1559
|
-
return "center";
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1563
|
-
const column = columns.at(targetIdx);
|
|
1564
|
-
if (column == null) {
|
|
1565
|
-
return "center";
|
|
1566
|
-
} else if (typeof column === "string") {
|
|
1567
|
-
return column;
|
|
1568
|
-
} else if (typeof column === "object") {
|
|
1569
|
-
return column.align ?? "center";
|
|
1570
|
-
} else {
|
|
1571
|
-
return "center";
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
function getColumnAlignments(tableData) {
|
|
1575
|
-
const { rows, columns = [] } = tableData;
|
|
1576
|
-
if (rows.at(0) == null) {
|
|
1577
|
-
throw new Error("first row can`t be undefined.");
|
|
1578
|
-
}
|
|
1579
|
-
if (Array.isArray(rows.at(0))) {
|
|
1580
|
-
const firstPrimitiveRow = rows.at(0);
|
|
1581
|
-
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1582
|
-
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1583
|
-
);
|
|
1584
|
-
}
|
|
1585
|
-
const biggestRow = rows.toSorted((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1586
|
-
if (columns.length > 0) {
|
|
1587
|
-
return columns.map(
|
|
1588
|
-
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1589
|
-
column.key,
|
|
1590
|
-
idx,
|
|
1591
|
-
columns
|
|
1592
|
-
)
|
|
1593
|
-
);
|
|
1594
|
-
}
|
|
1595
|
-
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// packages/utils/src/lib/reports/formatting.ts
|
|
1599
|
-
import {
|
|
1600
|
-
MarkdownDocument,
|
|
1601
|
-
md as md2
|
|
1602
|
-
} from "build-md";
|
|
1603
|
-
import { posix as pathPosix } from "node:path";
|
|
1604
|
-
|
|
1605
|
-
// packages/utils/src/lib/reports/types.ts
|
|
1606
|
-
var SUPPORTED_ENVIRONMENTS = [
|
|
1607
|
-
"vscode",
|
|
1608
|
-
"github",
|
|
1609
|
-
"gitlab",
|
|
1610
|
-
"other"
|
|
1611
|
-
];
|
|
1612
|
-
|
|
1613
|
-
// packages/utils/src/lib/reports/environment-type.ts
|
|
1614
|
-
var environmentChecks = {
|
|
1615
|
-
vscode: () => process.env["TERM_PROGRAM"] === "vscode",
|
|
1616
|
-
github: () => process.env["GITHUB_ACTIONS"] === "true",
|
|
1617
|
-
gitlab: () => process.env["GITLAB_CI"] === "true",
|
|
1618
|
-
other: () => true
|
|
1619
|
-
};
|
|
1620
|
-
function getEnvironmentType() {
|
|
1621
|
-
return SUPPORTED_ENVIRONMENTS.find((env) => environmentChecks[env]()) ?? "other";
|
|
1622
|
-
}
|
|
1623
|
-
function getGitHubBaseUrl() {
|
|
1624
|
-
return `${process.env["GITHUB_SERVER_URL"]}/${process.env["GITHUB_REPOSITORY"]}/blob/${process.env["GITHUB_SHA"]}`;
|
|
1625
|
-
}
|
|
1626
|
-
function getGitLabBaseUrl() {
|
|
1627
|
-
return `${process.env["CI_SERVER_URL"]}/${process.env["CI_PROJECT_PATH"]}/-/blob/${process.env["CI_COMMIT_SHA"]}`;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
// packages/utils/src/lib/reports/formatting.ts
|
|
1631
|
-
function tableSection(tableData, options2) {
|
|
1632
|
-
if (tableData.rows.length === 0) {
|
|
1633
|
-
return null;
|
|
1634
|
-
}
|
|
1635
|
-
const { level = HIERARCHY.level_4 } = options2 ?? {};
|
|
1636
|
-
const columns = columnsToStringArray(tableData);
|
|
1637
|
-
const alignments = getColumnAlignments(tableData);
|
|
1638
|
-
const rows = rowToStringArray(tableData);
|
|
1639
|
-
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1640
|
-
columns.map((heading, i) => {
|
|
1641
|
-
const alignment = alignments[i];
|
|
1642
|
-
if (alignment) {
|
|
1643
|
-
return { heading, alignment };
|
|
1644
|
-
}
|
|
1645
|
-
return heading;
|
|
1646
|
-
}),
|
|
1647
|
-
rows
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
function metaDescription(audit) {
|
|
1651
|
-
const docsUrl = audit.docsUrl;
|
|
1652
|
-
const description = audit.description?.trim();
|
|
1653
|
-
if (docsUrl) {
|
|
1654
|
-
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1655
|
-
if (!description) {
|
|
1656
|
-
return docsLink;
|
|
1657
|
-
}
|
|
1658
|
-
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1659
|
-
|
|
1660
|
-
` : `${description} `;
|
|
1661
|
-
return md2`${parsedDescription}${docsLink}`;
|
|
1662
|
-
}
|
|
1663
|
-
if (description && description.trim().length > 0) {
|
|
1664
|
-
return description;
|
|
1665
|
-
}
|
|
1666
|
-
return "";
|
|
1667
|
-
}
|
|
1668
|
-
function linkToLocalSourceForIde(source, options2) {
|
|
1669
|
-
const { file, position } = source;
|
|
1670
|
-
const { outputDir } = options2 ?? {};
|
|
1671
|
-
if (!outputDir) {
|
|
1672
|
-
return md2.code(file);
|
|
1673
|
-
}
|
|
1674
|
-
return md2.link(formatFileLink(file, position, outputDir), md2.code(file));
|
|
1675
|
-
}
|
|
1676
|
-
function formatSourceLine(position) {
|
|
1677
|
-
if (!position) {
|
|
1678
|
-
return "";
|
|
1679
|
-
}
|
|
1680
|
-
const { startLine, endLine } = position;
|
|
1681
|
-
return endLine && startLine !== endLine ? `${startLine}-${endLine}` : `${startLine}`;
|
|
1682
|
-
}
|
|
1683
|
-
function formatGitHubLink(file, position) {
|
|
1684
|
-
const baseUrl = getGitHubBaseUrl();
|
|
1685
|
-
if (!position) {
|
|
1686
|
-
return `${baseUrl}/${file}`;
|
|
1687
|
-
}
|
|
1688
|
-
const { startLine, endLine, startColumn, endColumn } = position;
|
|
1689
|
-
const start = startColumn ? `L${startLine}C${startColumn}` : `L${startLine}`;
|
|
1690
|
-
const end = endLine ? endColumn ? `L${endLine}C${endColumn}` : `L${endLine}` : "";
|
|
1691
|
-
const lineRange = end && start !== end ? `${start}-${end}` : start;
|
|
1692
|
-
return `${baseUrl}/${file}#${lineRange}`;
|
|
1693
|
-
}
|
|
1694
|
-
function formatGitLabLink(file, position) {
|
|
1695
|
-
const baseUrl = getGitLabBaseUrl();
|
|
1696
|
-
if (!position) {
|
|
1697
|
-
return `${baseUrl}/${file}`;
|
|
1698
|
-
}
|
|
1699
|
-
const { startLine, endLine } = position;
|
|
1700
|
-
const lineRange = endLine && startLine !== endLine ? `${startLine}-${endLine}` : startLine;
|
|
1701
|
-
return `${baseUrl}/${file}#L${lineRange}`;
|
|
1702
|
-
}
|
|
1703
|
-
function formatFileLink(file, position, outputDir) {
|
|
1704
|
-
const relativePath = pathPosix.relative(outputDir, file);
|
|
1705
|
-
const env = getEnvironmentType();
|
|
1706
|
-
switch (env) {
|
|
1707
|
-
case "vscode":
|
|
1708
|
-
return position ? `${relativePath}#L${position.startLine}` : relativePath;
|
|
1709
|
-
case "github":
|
|
1710
|
-
return formatGitHubLink(file, position);
|
|
1711
|
-
case "gitlab":
|
|
1712
|
-
return formatGitLabLink(file, position);
|
|
1713
|
-
default:
|
|
1714
|
-
return relativePath;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1719
|
-
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1720
|
-
|
|
1721
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
1722
|
-
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1723
|
-
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1724
|
-
if (!auditPlugin) {
|
|
1725
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1726
|
-
}
|
|
1727
|
-
const audit = auditPlugin.audits.find(
|
|
1728
|
-
({ slug: auditSlug }) => auditSlug === slug
|
|
1729
|
-
);
|
|
1730
|
-
if (!audit) {
|
|
1731
|
-
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1732
|
-
}
|
|
1733
|
-
return {
|
|
1734
|
-
...audit,
|
|
1735
|
-
weight,
|
|
1736
|
-
plugin
|
|
1737
|
-
};
|
|
1738
|
-
}
|
|
1739
|
-
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1740
|
-
return group.refs.map(
|
|
1741
|
-
(ref) => getSortableAuditByRef(
|
|
1742
|
-
{
|
|
1743
|
-
plugin,
|
|
1744
|
-
slug: ref.slug,
|
|
1745
|
-
weight: ref.weight,
|
|
1746
|
-
type: "audit"
|
|
1747
|
-
},
|
|
1748
|
-
plugins
|
|
1749
|
-
)
|
|
1750
|
-
).sort(compareCategoryAuditsAndGroups);
|
|
1751
|
-
}
|
|
1752
|
-
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1753
|
-
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1754
|
-
if (!groupPlugin) {
|
|
1755
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1756
|
-
}
|
|
1757
|
-
const group = groupPlugin.groups?.find(
|
|
1758
|
-
({ slug: groupSlug }) => groupSlug === slug
|
|
1759
|
-
);
|
|
1760
|
-
if (!group) {
|
|
1761
|
-
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1762
|
-
}
|
|
1763
|
-
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1764
|
-
const sortedAuditRefs = group.refs.toSorted((a, b) => {
|
|
1765
|
-
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1766
|
-
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1767
|
-
return aIndex - bIndex;
|
|
1768
|
-
});
|
|
1769
|
-
return {
|
|
1770
|
-
...group,
|
|
1771
|
-
refs: sortedAuditRefs,
|
|
1772
|
-
plugin,
|
|
1773
|
-
weight
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
1776
|
-
function sortReport(report) {
|
|
1777
|
-
const { categories, plugins } = report;
|
|
1778
|
-
const sortedCategories = categories?.map((category) => {
|
|
1779
|
-
const { audits, groups: groups2 } = category.refs.reduce(
|
|
1780
|
-
(acc, ref) => ({
|
|
1781
|
-
...acc,
|
|
1782
|
-
...ref.type === "group" ? {
|
|
1783
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1784
|
-
} : {
|
|
1785
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1786
|
-
}
|
|
1787
|
-
}),
|
|
1788
|
-
{ groups: [], audits: [] }
|
|
1789
|
-
);
|
|
1790
|
-
const sortedAuditsAndGroups = [...audits, ...groups2].sort(
|
|
1791
|
-
compareCategoryAuditsAndGroups
|
|
1792
|
-
);
|
|
1793
|
-
const sortedRefs = category.refs.toSorted((a, b) => {
|
|
1794
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
1795
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
1796
|
-
);
|
|
1797
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
1798
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
1799
|
-
);
|
|
1800
|
-
return aIndex - bIndex;
|
|
1801
|
-
});
|
|
1802
|
-
return { ...category, refs: sortedRefs };
|
|
1803
|
-
});
|
|
1804
|
-
return {
|
|
1805
|
-
...report,
|
|
1806
|
-
categories: sortedCategories,
|
|
1807
|
-
plugins: sortPlugins(plugins)
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
function sortPlugins(plugins) {
|
|
1811
|
-
return plugins.map((plugin) => ({
|
|
1812
|
-
...plugin,
|
|
1813
|
-
audits: plugin.audits.toSorted(compareAudits).map(
|
|
1814
|
-
(audit) => audit.details?.issues ? {
|
|
1815
|
-
...audit,
|
|
1816
|
-
details: {
|
|
1817
|
-
...audit.details,
|
|
1818
|
-
issues: audit.details.issues.toSorted(compareIssues)
|
|
1819
|
-
}
|
|
1820
|
-
} : audit
|
|
1821
|
-
)
|
|
1822
|
-
}));
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1826
|
-
function categoriesOverviewSection(report) {
|
|
1827
|
-
const { categories, plugins } = report;
|
|
1828
|
-
return new MarkdownDocument2().table(
|
|
1829
|
-
[
|
|
1830
|
-
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
1831
|
-
{ heading: "\u2B50 Score", alignment: "center" },
|
|
1832
|
-
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
1833
|
-
],
|
|
1834
|
-
categories.map(({ title, refs, score, isBinary }) => [
|
|
1835
|
-
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
1836
|
-
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1837
|
-
md3.link(`#${slugify(title)}`, title),
|
|
1838
|
-
md3`${scoreMarker(score)} ${md3.bold(
|
|
1839
|
-
formatReportScore(score)
|
|
1840
|
-
)}${binaryIconSuffix(score, isBinary)}`,
|
|
1841
|
-
countCategoryAudits(refs, plugins).toString()
|
|
1842
|
-
])
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
function categoriesDetailsSection(report) {
|
|
1846
|
-
const { categories, plugins } = report;
|
|
1847
|
-
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
1848
|
-
categories,
|
|
1849
|
-
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
1850
|
-
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
1851
|
-
formatReportScore(category.score)
|
|
1852
|
-
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
1853
|
-
).list(
|
|
1854
|
-
category.refs.map((ref) => {
|
|
1855
|
-
if (ref.type === "group") {
|
|
1856
|
-
const group = getSortableGroupByRef(ref, plugins);
|
|
1857
|
-
const groupAudits = group.refs.map(
|
|
1858
|
-
(groupRef) => getSortableAuditByRef(
|
|
1859
|
-
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
1860
|
-
plugins
|
|
1861
|
-
)
|
|
1862
|
-
);
|
|
1863
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1864
|
-
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1865
|
-
} else {
|
|
1866
|
-
const audit = getSortableAuditByRef(ref, plugins);
|
|
1867
|
-
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1868
|
-
return categoryRef(audit, pluginTitle);
|
|
1869
|
-
}
|
|
1870
|
-
})
|
|
1871
|
-
)
|
|
1872
|
-
);
|
|
1873
|
-
}
|
|
1874
|
-
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
1875
|
-
const auditTitleAsLink = md3.link(
|
|
1876
|
-
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
1877
|
-
title
|
|
1878
|
-
);
|
|
1879
|
-
const marker = scoreMarker(score, "square");
|
|
1880
|
-
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
1881
|
-
pluginTitle
|
|
1882
|
-
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
1883
|
-
}
|
|
1884
|
-
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
1885
|
-
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
1886
|
-
pluginTitle
|
|
1887
|
-
)})`;
|
|
1888
|
-
const auditsList = md3.list(
|
|
1889
|
-
groupAudits.map(
|
|
1890
|
-
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
1891
|
-
const auditTitleLink = md3.link(
|
|
1892
|
-
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
1893
|
-
auditTitle
|
|
1894
|
-
);
|
|
1895
|
-
const marker = scoreMarker(auditScore, "square");
|
|
1896
|
-
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
1897
|
-
String(displayValue ?? value)
|
|
1898
|
-
)}`;
|
|
1899
|
-
}
|
|
1900
|
-
)
|
|
1901
|
-
);
|
|
1902
|
-
return md3`${groupTitle}${auditsList}`;
|
|
1903
|
-
}
|
|
1904
|
-
function binaryIconSuffix(score, isBinary) {
|
|
1905
|
-
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1909
|
-
function auditDetailsAuditValue({
|
|
1910
|
-
score,
|
|
1911
|
-
value,
|
|
1912
|
-
displayValue
|
|
1913
|
-
}) {
|
|
1914
|
-
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
1915
|
-
String(displayValue ?? value)
|
|
1916
|
-
)} (score: ${formatReportScore(score)})`;
|
|
1917
|
-
}
|
|
1918
|
-
function hasCategories(report) {
|
|
1919
|
-
return !!report.categories && report.categories.length > 0;
|
|
1920
|
-
}
|
|
1921
|
-
function generateMdReport(report, options2) {
|
|
1922
|
-
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$concat(
|
|
1923
|
-
...hasCategories(report) ? [categoriesOverviewSection(report), categoriesDetailsSection(report)] : [],
|
|
1924
|
-
auditsSection(report, options2),
|
|
1925
|
-
aboutSection(report)
|
|
1926
|
-
).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
1927
|
-
}
|
|
1928
|
-
function auditDetailsIssues(issues = [], options2) {
|
|
1929
|
-
if (issues.length === 0) {
|
|
1930
|
-
return null;
|
|
1931
|
-
}
|
|
1932
|
-
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
1933
|
-
[
|
|
1934
|
-
{ heading: "Severity", alignment: "center" },
|
|
1935
|
-
{ heading: "Message", alignment: "left" },
|
|
1936
|
-
{ heading: "Source file", alignment: "left" },
|
|
1937
|
-
{ heading: "Line(s)", alignment: "center" }
|
|
1938
|
-
],
|
|
1939
|
-
issues.map(({ severity: level, message, source }) => {
|
|
1940
|
-
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
1941
|
-
if (!source) {
|
|
1942
|
-
return [severity, message];
|
|
1943
|
-
}
|
|
1944
|
-
const file = linkToLocalSourceForIde(source, options2);
|
|
1945
|
-
if (!source.position) {
|
|
1946
|
-
return [severity, message, file];
|
|
1947
|
-
}
|
|
1948
|
-
const line = formatSourceLine(source.position);
|
|
1949
|
-
return [severity, message, file, line];
|
|
1950
|
-
})
|
|
1951
|
-
);
|
|
1952
|
-
}
|
|
1953
|
-
function auditDetails(audit, options2) {
|
|
1954
|
-
const { table: table2, issues = [] } = audit.details ?? {};
|
|
1955
|
-
const detailsValue = auditDetailsAuditValue(audit);
|
|
1956
|
-
if (issues.length === 0 && !table2?.rows.length) {
|
|
1957
|
-
return new MarkdownDocument3().paragraph(detailsValue);
|
|
1958
|
-
}
|
|
1959
|
-
const tableSectionContent = table2 && tableSection(table2);
|
|
1960
|
-
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues, options2);
|
|
1961
|
-
return new MarkdownDocument3().details(
|
|
1962
|
-
detailsValue,
|
|
1963
|
-
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
1964
|
-
);
|
|
1965
|
-
}
|
|
1966
|
-
function auditsSection({ plugins }, options2) {
|
|
1967
|
-
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
1968
|
-
plugins.flatMap(
|
|
1969
|
-
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
1970
|
-
),
|
|
1971
|
-
(doc, { plugin, ...audit }) => {
|
|
1972
|
-
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
1973
|
-
const detailsContent = auditDetails(audit, options2);
|
|
1974
|
-
const descriptionContent = metaDescription(audit);
|
|
1975
|
-
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
1976
|
-
}
|
|
1977
|
-
);
|
|
1978
|
-
}
|
|
1979
|
-
function aboutSection(report) {
|
|
1980
|
-
const { date, plugins } = report;
|
|
1981
|
-
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
1982
|
-
md4`Report was created by ${md4.link(
|
|
1983
|
-
README_LINK,
|
|
1984
|
-
"Code PushUp"
|
|
1985
|
-
)} on ${formatDate(new Date(date))}.`
|
|
1986
|
-
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
1987
|
-
}
|
|
1988
|
-
function pluginMetaTable({
|
|
1989
|
-
plugins
|
|
1990
|
-
}) {
|
|
1991
|
-
return [
|
|
1992
|
-
[
|
|
1993
|
-
{ heading: "Plugin", alignment: "left" },
|
|
1994
|
-
{ heading: "Audits", alignment: "center" },
|
|
1995
|
-
{ heading: "Version", alignment: "center" },
|
|
1996
|
-
{ heading: "Duration", alignment: "right" }
|
|
1997
|
-
],
|
|
1998
|
-
plugins.map(({ title, audits, version: version3 = "", duration }) => [
|
|
1999
|
-
title,
|
|
2000
|
-
audits.length.toString(),
|
|
2001
|
-
version3 && md4.code(version3),
|
|
2002
|
-
formatDuration(duration)
|
|
2003
|
-
])
|
|
2004
|
-
];
|
|
2005
|
-
}
|
|
2006
|
-
function reportMetaTable({
|
|
2007
|
-
commit,
|
|
2008
|
-
version: version3,
|
|
2009
|
-
duration,
|
|
2010
|
-
plugins,
|
|
2011
|
-
categories
|
|
2012
|
-
}) {
|
|
2013
|
-
return [
|
|
2014
|
-
[
|
|
2015
|
-
{ heading: "Commit", alignment: "left" },
|
|
2016
|
-
{ heading: "Version", alignment: "center" },
|
|
2017
|
-
{ heading: "Duration", alignment: "right" },
|
|
2018
|
-
{ heading: "Plugins", alignment: "center" },
|
|
2019
|
-
{ heading: "Categories", alignment: "center" },
|
|
2020
|
-
{ heading: "Audits", alignment: "center" }
|
|
2021
|
-
],
|
|
2022
|
-
[
|
|
2023
|
-
[
|
|
2024
|
-
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
2025
|
-
md4.code(version3),
|
|
2026
|
-
formatDuration(duration),
|
|
2027
|
-
plugins.length.toString(),
|
|
2028
|
-
(categories?.length ?? 0).toString(),
|
|
2029
|
-
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
2030
|
-
]
|
|
2031
|
-
]
|
|
2032
|
-
];
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2036
|
-
import {
|
|
2037
|
-
MarkdownDocument as MarkdownDocument5,
|
|
2038
|
-
md as md6
|
|
2039
|
-
} from "build-md";
|
|
2040
|
-
|
|
2041
|
-
// packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
|
|
2042
|
-
import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
|
|
2043
|
-
var MAX_ROWS = 100;
|
|
2044
|
-
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2045
|
-
const pluralizedCount = changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`;
|
|
2046
|
-
const pluralizedVerb = unchanged.length === 1 ? "is" : "are";
|
|
2047
|
-
return `${pluralizedCount} ${pluralizedVerb} unchanged.`;
|
|
2048
|
-
}
|
|
2049
|
-
function summarizeDiffOutcomes(outcomes, token) {
|
|
2050
|
-
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
2051
|
-
(entry) => entry[0] !== "unchanged" && entry[1] > 0
|
|
2052
|
-
).map(([outcome, count]) => {
|
|
2053
|
-
const formattedCount = `<strong>${count}</strong> ${pluralize(
|
|
2054
|
-
token,
|
|
2055
|
-
count
|
|
2056
|
-
)}`;
|
|
2057
|
-
switch (outcome) {
|
|
2058
|
-
case "positive":
|
|
2059
|
-
return `\u{1F44D} ${formattedCount} improved`;
|
|
2060
|
-
case "negative":
|
|
2061
|
-
return `\u{1F44E} ${formattedCount} regressed`;
|
|
2062
|
-
case "mixed":
|
|
2063
|
-
return `${formattedCount} changed without impacting score`;
|
|
2064
|
-
}
|
|
2065
|
-
}).join(", ");
|
|
2066
|
-
}
|
|
2067
|
-
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
2068
|
-
if (changed.length === 0) {
|
|
2069
|
-
return new MarkdownDocument4().paragraph(
|
|
2070
|
-
summarizeUnchanged(token, { changed, unchanged })
|
|
2071
|
-
);
|
|
2072
|
-
}
|
|
2073
|
-
return new MarkdownDocument4().table(columns, rows.slice(0, MAX_ROWS)).paragraph(
|
|
2074
|
-
changed.length > MAX_ROWS && md5.italic(
|
|
2075
|
-
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
2076
|
-
token
|
|
2077
|
-
)} are listed above for brevity.`
|
|
2078
|
-
)
|
|
2079
|
-
).paragraph(
|
|
2080
|
-
unchanged.length > 0 && summarizeUnchanged(token, { changed, unchanged })
|
|
2081
|
-
);
|
|
2082
|
-
}
|
|
2083
|
-
function formatTitle({
|
|
2084
|
-
title,
|
|
2085
|
-
docsUrl
|
|
2086
|
-
}) {
|
|
2087
|
-
if (docsUrl) {
|
|
2088
|
-
return md5.link(docsUrl, title);
|
|
2089
|
-
}
|
|
2090
|
-
return title;
|
|
2091
|
-
}
|
|
2092
|
-
function formatPortalLink(portalUrl) {
|
|
2093
|
-
return portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}");
|
|
2094
|
-
}
|
|
2095
|
-
function sortChanges(changes) {
|
|
2096
|
-
return changes.toSorted(
|
|
2097
|
-
(a, b) => Math.abs(b.scores.diff) - Math.abs(a.scores.diff) || Math.abs(b.values?.diff ?? 0) - Math.abs(a.values?.diff ?? 0)
|
|
2098
|
-
);
|
|
2099
|
-
}
|
|
2100
|
-
function getDiffChanges(diff) {
|
|
2101
|
-
return [
|
|
2102
|
-
...diff.categories.changed,
|
|
2103
|
-
...diff.groups.changed,
|
|
2104
|
-
...diff.audits.changed
|
|
2105
|
-
];
|
|
2106
|
-
}
|
|
2107
|
-
function changesToDiffOutcomes(changes) {
|
|
2108
|
-
return changes.map((change) => {
|
|
2109
|
-
if (change.scores.diff > 0) {
|
|
2110
|
-
return "positive";
|
|
2111
|
-
}
|
|
2112
|
-
if (change.scores.diff < 0) {
|
|
2113
|
-
return "negative";
|
|
2114
|
-
}
|
|
2115
|
-
if (change.values != null && change.values.diff !== 0) {
|
|
2116
|
-
return "mixed";
|
|
2117
|
-
}
|
|
2118
|
-
return "unchanged";
|
|
2119
|
-
});
|
|
2120
|
-
}
|
|
2121
|
-
function mergeDiffOutcomes(outcomes) {
|
|
2122
|
-
if (outcomes.every((outcome) => outcome === "unchanged")) {
|
|
2123
|
-
return "unchanged";
|
|
2124
|
-
}
|
|
2125
|
-
if (outcomes.includes("positive") && !outcomes.includes("negative")) {
|
|
2126
|
-
return "positive";
|
|
2127
|
-
}
|
|
2128
|
-
if (outcomes.includes("negative") && !outcomes.includes("positive")) {
|
|
2129
|
-
return "negative";
|
|
2130
|
-
}
|
|
2131
|
-
return "mixed";
|
|
2132
|
-
}
|
|
2133
|
-
function countDiffOutcomes(outcomes) {
|
|
2134
|
-
return {
|
|
2135
|
-
positive: outcomes.filter((outcome) => outcome === "positive").length,
|
|
2136
|
-
negative: outcomes.filter((outcome) => outcome === "negative").length,
|
|
2137
|
-
mixed: outcomes.filter((outcome) => outcome === "mixed").length,
|
|
2138
|
-
unchanged: outcomes.filter((outcome) => outcome === "unchanged").length
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2141
|
-
function formatReportOutcome(outcome, commits) {
|
|
2142
|
-
const outcomeTexts = {
|
|
2143
|
-
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
2144
|
-
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
2145
|
-
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
2146
|
-
"improvements and regressions"
|
|
2147
|
-
)}`,
|
|
2148
|
-
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
2149
|
-
};
|
|
2150
|
-
if (commits) {
|
|
2151
|
-
const commitsText = `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2152
|
-
return md5`${outcomeTexts[outcome]} – ${commitsText}.`;
|
|
2153
|
-
}
|
|
2154
|
-
return md5`${outcomeTexts[outcome]}.`;
|
|
2155
|
-
}
|
|
2156
|
-
function compareDiffsBy(type, a, b) {
|
|
2157
|
-
return sumScoreChanges(b[type].changed) - sumScoreChanges(a[type].changed) || sumConfigChanges(b[type]) - sumConfigChanges(a[type]);
|
|
2158
|
-
}
|
|
2159
|
-
function sumScoreChanges(changes) {
|
|
2160
|
-
return changes.reduce(
|
|
2161
|
-
(acc, { scores }) => acc + Math.abs(scores.diff),
|
|
2162
|
-
0
|
|
2163
|
-
);
|
|
2164
|
-
}
|
|
2165
|
-
function sumConfigChanges({
|
|
2166
|
-
added,
|
|
2167
|
-
removed
|
|
2168
|
-
}) {
|
|
2169
|
-
return added.length + removed.length;
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2173
|
-
function generateMdReportsDiff(diff) {
|
|
2174
|
-
return new MarkdownDocument5().$concat(
|
|
2175
|
-
createDiffHeaderSection(diff),
|
|
2176
|
-
createDiffCategoriesSection(diff),
|
|
2177
|
-
createDiffDetailsSection(diff)
|
|
2178
|
-
).toString();
|
|
2179
|
-
}
|
|
2180
|
-
function generateMdReportsDiffForMonorepo(diffs) {
|
|
2181
|
-
const diffsWithOutcomes = diffs.map((diff) => ({
|
|
2182
|
-
...diff,
|
|
2183
|
-
outcome: mergeDiffOutcomes(changesToDiffOutcomes(getDiffChanges(diff)))
|
|
2184
|
-
})).sort(
|
|
2185
|
-
(a, b) => compareDiffsBy("categories", a, b) || compareDiffsBy("groups", a, b) || compareDiffsBy("audits", a, b) || a.label.localeCompare(b.label)
|
|
2186
|
-
);
|
|
2187
|
-
const unchanged = diffsWithOutcomes.filter(
|
|
2188
|
-
({ outcome }) => outcome === "unchanged"
|
|
2189
|
-
);
|
|
2190
|
-
const changed = diffsWithOutcomes.filter((diff) => !unchanged.includes(diff));
|
|
2191
|
-
return new MarkdownDocument5().$concat(
|
|
2192
|
-
createDiffHeaderSection(diffs),
|
|
2193
|
-
...changed.map(createDiffProjectSection)
|
|
2194
|
-
).$if(
|
|
2195
|
-
unchanged.length > 0,
|
|
2196
|
-
(doc) => doc.rule().paragraph(summarizeUnchanged("project", { unchanged, changed }))
|
|
2197
|
-
).toString();
|
|
2198
|
-
}
|
|
2199
|
-
function createDiffHeaderSection(diff) {
|
|
2200
|
-
const outcome = mergeDiffOutcomes(
|
|
2201
|
-
changesToDiffOutcomes(toArray(diff).flatMap(getDiffChanges))
|
|
2202
|
-
);
|
|
2203
|
-
const commits = Array.isArray(diff) ? diff[0]?.commits : diff.commits;
|
|
2204
|
-
const portalUrl = Array.isArray(diff) ? void 0 : diff.portalUrl;
|
|
2205
|
-
return new MarkdownDocument5().heading(HIERARCHY.level_1, "Code PushUp").paragraph(formatReportOutcome(outcome, commits)).paragraph(formatPortalLink(portalUrl));
|
|
2206
|
-
}
|
|
2207
|
-
function createDiffProjectSection(diff) {
|
|
2208
|
-
return new MarkdownDocument5().heading(HIERARCHY.level_2, md6`💼 Project ${md6.code(diff.label)}`).paragraph(formatReportOutcome(diff.outcome)).paragraph(formatPortalLink(diff.portalUrl)).$concat(
|
|
2209
|
-
createDiffCategoriesSection(diff, {
|
|
2210
|
-
skipHeading: true,
|
|
2211
|
-
skipUnchanged: true
|
|
2212
|
-
}),
|
|
2213
|
-
createDiffDetailsSection(diff, HIERARCHY.level_3)
|
|
2214
|
-
);
|
|
2215
|
-
}
|
|
2216
|
-
function createDiffCategoriesSection(diff, options2) {
|
|
2217
|
-
const { changed, unchanged, added } = diff.categories;
|
|
2218
|
-
const { skipHeading, skipUnchanged } = options2 ?? {};
|
|
2219
|
-
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2220
|
-
const hasChanges = unchanged.length < categoriesCount;
|
|
2221
|
-
if (categoriesCount === 0) {
|
|
2222
|
-
return null;
|
|
2223
|
-
}
|
|
2224
|
-
const [columns, rows] = createCategoriesTable(diff, {
|
|
2225
|
-
hasChanges,
|
|
2226
|
-
skipUnchanged
|
|
2227
|
-
});
|
|
2228
|
-
return new MarkdownDocument5().heading(HIERARCHY.level_2, !skipHeading && "\u{1F3F7}\uFE0F Categories").table(columns, rows).paragraph(added.length > 0 && md6.italic("(\\*) New category.")).paragraph(
|
|
2229
|
-
skipUnchanged && unchanged.length > 0 && summarizeUnchanged("category", { changed, unchanged })
|
|
2230
|
-
);
|
|
2231
|
-
}
|
|
2232
|
-
function createCategoriesTable(diff, options2) {
|
|
2233
|
-
const { changed, unchanged, added } = diff.categories;
|
|
2234
|
-
const { hasChanges, skipUnchanged } = options2;
|
|
2235
|
-
const rows = [
|
|
2236
|
-
...sortChanges(changed).map((category) => [
|
|
2237
|
-
formatTitle(category),
|
|
2238
|
-
formatScoreWithColor(category.scores.before, {
|
|
2239
|
-
skipBold: true
|
|
2240
|
-
}),
|
|
2241
|
-
formatScoreWithColor(category.scores.after),
|
|
2242
|
-
formatScoreChange(category.scores.diff)
|
|
2243
|
-
]),
|
|
2244
|
-
...added.map((category) => [
|
|
2245
|
-
formatTitle(category),
|
|
2246
|
-
md6.italic("n/a (\\*)"),
|
|
2247
|
-
formatScoreWithColor(category.score),
|
|
2248
|
-
md6.italic("n/a (\\*)")
|
|
2249
|
-
]),
|
|
2250
|
-
...skipUnchanged ? [] : unchanged.map((category) => [
|
|
2251
|
-
formatTitle(category),
|
|
2252
|
-
formatScoreWithColor(category.score, { skipBold: true }),
|
|
2253
|
-
formatScoreWithColor(category.score),
|
|
2254
|
-
"\u2013"
|
|
2255
|
-
])
|
|
2256
|
-
];
|
|
2257
|
-
if (rows.length === 0) {
|
|
2258
|
-
return [[], []];
|
|
2259
|
-
}
|
|
2260
|
-
const columns = [
|
|
2261
|
-
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
2262
|
-
{
|
|
2263
|
-
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
2264
|
-
alignment: "center"
|
|
2265
|
-
},
|
|
2266
|
-
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2267
|
-
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2268
|
-
];
|
|
2269
|
-
return [
|
|
2270
|
-
hasChanges ? columns : columns.slice(0, 2),
|
|
2271
|
-
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
2272
|
-
];
|
|
2273
|
-
}
|
|
2274
|
-
function createDiffDetailsSection(diff, level = HIERARCHY.level_2) {
|
|
2275
|
-
if (diff.groups.changed.length + diff.audits.changed.length === 0) {
|
|
2276
|
-
return null;
|
|
2277
|
-
}
|
|
2278
|
-
const summary = ["group", "audit"].map(
|
|
2279
|
-
(token) => summarizeDiffOutcomes(
|
|
2280
|
-
changesToDiffOutcomes(diff[`${token}s`].changed),
|
|
2281
|
-
token
|
|
2282
|
-
)
|
|
2283
|
-
).filter(Boolean).join(", ");
|
|
2284
|
-
const details2 = new MarkdownDocument5().$concat(
|
|
2285
|
-
createDiffGroupsSection(diff, level),
|
|
2286
|
-
createDiffAuditsSection(diff, level)
|
|
2287
|
-
);
|
|
2288
|
-
return new MarkdownDocument5().details(summary, details2);
|
|
2289
|
-
}
|
|
2290
|
-
function createDiffGroupsSection(diff, level) {
|
|
2291
|
-
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2292
|
-
return null;
|
|
2293
|
-
}
|
|
2294
|
-
return new MarkdownDocument5().heading(level, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
2295
|
-
createGroupsOrAuditsDetails(
|
|
2296
|
-
"group",
|
|
2297
|
-
diff.groups,
|
|
2298
|
-
[
|
|
2299
|
-
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2300
|
-
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
2301
|
-
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
2302
|
-
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2303
|
-
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2304
|
-
],
|
|
2305
|
-
sortChanges(diff.groups.changed).map((group) => [
|
|
2306
|
-
formatTitle(group.plugin),
|
|
2307
|
-
formatTitle(group),
|
|
2308
|
-
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2309
|
-
formatScoreWithColor(group.scores.after),
|
|
2310
|
-
formatScoreChange(group.scores.diff)
|
|
2311
|
-
])
|
|
2312
|
-
)
|
|
2313
|
-
);
|
|
2314
|
-
}
|
|
2315
|
-
function createDiffAuditsSection(diff, level) {
|
|
2316
|
-
return new MarkdownDocument5().heading(level, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
2317
|
-
createGroupsOrAuditsDetails(
|
|
2318
|
-
"audit",
|
|
2319
|
-
diff.audits,
|
|
2320
|
-
[
|
|
2321
|
-
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2322
|
-
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
2323
|
-
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
2324
|
-
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
2325
|
-
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2326
|
-
],
|
|
2327
|
-
sortChanges(diff.audits.changed).map((audit) => [
|
|
2328
|
-
formatTitle(audit.plugin),
|
|
2329
|
-
formatTitle(audit),
|
|
2330
|
-
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2331
|
-
md6`${scoreMarker(audit.scores.after, "square")} ${md6.bold(
|
|
2332
|
-
audit.displayValues.after || audit.values.after.toString()
|
|
2333
|
-
)}`,
|
|
2334
|
-
formatValueChange(audit)
|
|
2335
|
-
])
|
|
2336
|
-
)
|
|
2337
|
-
);
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
// packages/utils/src/lib/reports/load-report.ts
|
|
2341
|
-
import { join as join2 } from "node:path";
|
|
2342
|
-
async function loadReport(options2) {
|
|
2343
|
-
const { outputDir, filename, format } = options2;
|
|
2344
|
-
await ensureDirectoryExists(outputDir);
|
|
2345
|
-
const filePath = join2(outputDir, `${filename}.${format}`);
|
|
2346
|
-
if (format === "json") {
|
|
2347
|
-
const content = await readJsonFile(filePath);
|
|
2348
|
-
return reportSchema.parse(content);
|
|
2349
|
-
}
|
|
2350
|
-
const text = await readTextFile(filePath);
|
|
2351
|
-
return text;
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
|
-
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2355
|
-
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2356
|
-
function log(msg = "") {
|
|
2357
|
-
ui().logger.log(msg);
|
|
2358
|
-
}
|
|
2359
|
-
function logStdoutSummary(report, verbose = false) {
|
|
2360
|
-
const { plugins, categories, packageName, version: version3 } = report;
|
|
2361
|
-
log(reportToHeaderSection({ packageName, version: version3 }));
|
|
2362
|
-
log();
|
|
2363
|
-
logPlugins(plugins, verbose);
|
|
2364
|
-
if (categories && categories.length > 0) {
|
|
2365
|
-
logCategories({ plugins, categories });
|
|
2366
|
-
}
|
|
2367
|
-
log(`${FOOTER_PREFIX} ${CODE_PUSHUP_DOMAIN}`);
|
|
2368
|
-
log();
|
|
2369
|
-
}
|
|
2370
|
-
function reportToHeaderSection({
|
|
2371
|
-
packageName,
|
|
2372
|
-
version: version3
|
|
2373
|
-
}) {
|
|
2374
|
-
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version3}`;
|
|
2375
|
-
}
|
|
2376
|
-
function logPlugins(plugins, verbose) {
|
|
2377
|
-
plugins.forEach((plugin) => {
|
|
2378
|
-
const { title, audits } = plugin;
|
|
2379
|
-
const filteredAudits = verbose || audits.length === 1 ? audits : audits.filter(({ score }) => score !== 1);
|
|
2380
|
-
const diff = audits.length - filteredAudits.length;
|
|
2381
|
-
logAudits(title, filteredAudits);
|
|
2382
|
-
if (diff > 0) {
|
|
2383
|
-
const notice = filteredAudits.length === 0 ? `... All ${diff} audits have perfect scores ...` : `... ${diff} audits with perfect scores omitted for brevity ...`;
|
|
2384
|
-
logRow(1, notice);
|
|
2385
|
-
}
|
|
2386
|
-
log();
|
|
2387
|
-
});
|
|
2388
|
-
}
|
|
2389
|
-
function logAudits(pluginTitle, audits) {
|
|
2390
|
-
log();
|
|
2391
|
-
log(bold4.magentaBright(`${pluginTitle} audits`));
|
|
2392
|
-
log();
|
|
2393
|
-
audits.forEach(({ score, title, displayValue, value }) => {
|
|
2394
|
-
logRow(score, title, displayValue || `${value}`);
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
function logRow(score, title, value) {
|
|
2398
|
-
ui().row([
|
|
2399
|
-
{
|
|
2400
|
-
text: applyScoreColor({ score, text: "\u25CF" }),
|
|
2401
|
-
width: 2,
|
|
2402
|
-
padding: [0, 1, 0, 0]
|
|
2403
|
-
},
|
|
2404
|
-
{
|
|
2405
|
-
text: title,
|
|
2406
|
-
// eslint-disable-next-line no-magic-numbers
|
|
2407
|
-
padding: [0, 3, 0, 0]
|
|
2408
|
-
},
|
|
2409
|
-
...value ? [
|
|
2410
|
-
{
|
|
2411
|
-
text: cyanBright(value),
|
|
2412
|
-
// eslint-disable-next-line no-magic-numbers
|
|
2413
|
-
width: 20,
|
|
2414
|
-
padding: [0, 0, 0, 0]
|
|
2415
|
-
}
|
|
2416
|
-
] : []
|
|
2417
|
-
]);
|
|
2418
|
-
}
|
|
2419
|
-
function logCategories({
|
|
2420
|
-
plugins,
|
|
2421
|
-
categories
|
|
2422
|
-
}) {
|
|
2423
|
-
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2424
|
-
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2425
|
-
title,
|
|
2426
|
-
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2427
|
-
countCategoryAudits(refs, plugins)
|
|
2428
|
-
]);
|
|
2429
|
-
const table2 = ui().table();
|
|
2430
|
-
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2431
|
-
table2.head(
|
|
2432
|
-
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2433
|
-
content: cyan(heading),
|
|
2434
|
-
hAlign: hAlign(idx)
|
|
2435
|
-
}))
|
|
2436
|
-
);
|
|
2437
|
-
rows.forEach(
|
|
2438
|
-
(row) => table2.row(
|
|
2439
|
-
row.map((content, idx) => ({
|
|
2440
|
-
content: content.toString(),
|
|
2441
|
-
hAlign: hAlign(idx)
|
|
2442
|
-
}))
|
|
2443
|
-
)
|
|
2444
|
-
);
|
|
2445
|
-
log(bold4.magentaBright("Categories"));
|
|
2446
|
-
log();
|
|
2447
|
-
table2.render();
|
|
2448
|
-
log();
|
|
2449
|
-
}
|
|
2450
|
-
function binaryIconPrefix(score, isBinary) {
|
|
2451
|
-
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2452
|
-
passIcon: bold4(green2("\u2713")),
|
|
2453
|
-
failIcon: bold4(red("\u2717")),
|
|
2454
|
-
postfix: " "
|
|
2455
|
-
});
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
// packages/utils/src/lib/reports/scoring.ts
|
|
2459
|
-
var GroupRefInvalidError = class extends Error {
|
|
2460
|
-
constructor(auditSlug, pluginSlug) {
|
|
2461
|
-
super(
|
|
2462
|
-
`Group has invalid ref - audit with slug ${auditSlug} from plugin ${pluginSlug} not found`
|
|
2463
|
-
);
|
|
2464
|
-
}
|
|
2465
|
-
};
|
|
2466
|
-
function scoreReport(report) {
|
|
2467
|
-
const allScoredAuditsAndGroups = /* @__PURE__ */ new Map();
|
|
2468
|
-
const scoredPlugins = report.plugins.map((plugin) => {
|
|
2469
|
-
const { groups: groups2, ...pluginProps } = plugin;
|
|
2470
|
-
plugin.audits.forEach((audit) => {
|
|
2471
|
-
allScoredAuditsAndGroups.set(`${plugin.slug}-${audit.slug}-audit`, audit);
|
|
2472
|
-
});
|
|
2473
|
-
function groupScoreFn(ref) {
|
|
2474
|
-
const score = allScoredAuditsAndGroups.get(
|
|
2475
|
-
`${plugin.slug}-${ref.slug}-audit`
|
|
2476
|
-
)?.score;
|
|
2477
|
-
if (score == null) {
|
|
2478
|
-
throw new GroupRefInvalidError(ref.slug, plugin.slug);
|
|
2479
|
-
}
|
|
2480
|
-
return score;
|
|
2481
|
-
}
|
|
2482
|
-
const scoredGroups = groups2?.map((group) => ({
|
|
2483
|
-
...group,
|
|
2484
|
-
score: calculateScore(group.refs, groupScoreFn)
|
|
2485
|
-
})) ?? [];
|
|
2486
|
-
scoredGroups.forEach((group) => {
|
|
2487
|
-
allScoredAuditsAndGroups.set(`${plugin.slug}-${group.slug}-group`, group);
|
|
2488
|
-
});
|
|
2489
|
-
return {
|
|
2490
|
-
...pluginProps,
|
|
2491
|
-
...scoredGroups.length > 0 && { groups: scoredGroups }
|
|
2492
|
-
};
|
|
2493
|
-
});
|
|
2494
|
-
function catScoreFn(ref) {
|
|
2495
|
-
const key = `${ref.plugin}-${ref.slug}-${ref.type}`;
|
|
2496
|
-
const item = allScoredAuditsAndGroups.get(key);
|
|
2497
|
-
if (!item) {
|
|
2498
|
-
throw new Error(
|
|
2499
|
-
`Category has invalid ref - ${ref.type} with slug ${key} not found in ${ref.plugin} plugin`
|
|
2500
|
-
);
|
|
2501
|
-
}
|
|
2502
|
-
return item.score;
|
|
2503
|
-
}
|
|
2504
|
-
const scoredCategories = report.categories?.map((category) => ({
|
|
2505
|
-
...category,
|
|
2506
|
-
score: calculateScore(category.refs, catScoreFn)
|
|
2507
|
-
}));
|
|
2508
|
-
return {
|
|
2509
|
-
...deepClone(report),
|
|
2510
|
-
plugins: scoredPlugins,
|
|
2511
|
-
categories: scoredCategories
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
function calculateScore(refs, scoreFn) {
|
|
2515
|
-
const validatedRefs = parseScoringParameters(refs, scoreFn);
|
|
2516
|
-
const { numerator, denominator } = validatedRefs.reduce(
|
|
2517
|
-
(acc, ref) => ({
|
|
2518
|
-
numerator: acc.numerator + ref.score * ref.weight,
|
|
2519
|
-
denominator: acc.denominator + ref.weight
|
|
2520
|
-
}),
|
|
2521
|
-
{ numerator: 0, denominator: 0 }
|
|
2522
|
-
);
|
|
2523
|
-
return numerator / denominator;
|
|
2524
|
-
}
|
|
2525
|
-
function parseScoringParameters(refs, scoreFn) {
|
|
2526
|
-
if (refs.length === 0) {
|
|
2527
|
-
throw new Error("Reference array cannot be empty.");
|
|
2528
|
-
}
|
|
2529
|
-
if (refs.some((ref) => ref.weight < 0)) {
|
|
2530
|
-
throw new Error("Weight cannot be negative.");
|
|
2531
|
-
}
|
|
2532
|
-
if (refs.every((ref) => ref.weight === 0)) {
|
|
2533
|
-
throw new Error("All references cannot have zero weight.");
|
|
2534
|
-
}
|
|
2535
|
-
const scoredRefs = refs.map((ref) => ({
|
|
2536
|
-
weight: ref.weight,
|
|
2537
|
-
score: scoreFn(ref)
|
|
2538
|
-
}));
|
|
2539
|
-
if (scoredRefs.some((ref) => ref.score < 0 || ref.score > 1)) {
|
|
2540
|
-
throw new Error("All scores must be in range 0-1.");
|
|
2541
|
-
}
|
|
2542
|
-
return scoredRefs;
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
// packages/utils/src/lib/verbose-utils.ts
|
|
2546
|
-
function getLogVerbose(verbose = false) {
|
|
2547
|
-
return (msg) => {
|
|
2548
|
-
if (verbose) {
|
|
2549
|
-
ui().logger.info(msg);
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
}
|
|
2553
|
-
function getExecVerbose(verbose = false) {
|
|
2554
|
-
return (fn) => {
|
|
2555
|
-
if (verbose) {
|
|
2556
|
-
fn();
|
|
2557
|
-
}
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
|
-
var verboseUtils = (verbose = false) => ({
|
|
2561
|
-
log: getLogVerbose(verbose),
|
|
2562
|
-
exec: getExecVerbose(verbose)
|
|
2563
|
-
});
|
|
2564
|
-
|
|
2565
|
-
// packages/core/package.json
|
|
2566
|
-
var name = "@code-pushup/core";
|
|
2567
|
-
var version = "0.55.0";
|
|
2568
|
-
|
|
2569
|
-
// packages/core/src/lib/implementation/execute-plugin.ts
|
|
2570
|
-
import { bold as bold5 } from "ansis";
|
|
2571
|
-
|
|
2572
|
-
// packages/core/src/lib/normalize.ts
|
|
2573
|
-
function normalizeIssue(issue, gitRoot) {
|
|
2574
|
-
const { source, ...issueWithoutSource } = issue;
|
|
2575
|
-
return source == null ? issue : {
|
|
2576
|
-
...issueWithoutSource,
|
|
2577
|
-
source: {
|
|
2578
|
-
...source,
|
|
2579
|
-
file: formatGitPath(source.file, gitRoot)
|
|
2580
|
-
}
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
async function normalizeAuditOutputs(audits) {
|
|
2584
|
-
const gitRoot = await getGitRoot();
|
|
2585
|
-
return audits.map((audit) => {
|
|
2586
|
-
if (audit.details?.issues == null || audit.details.issues.every((issue) => issue.source == null)) {
|
|
2587
|
-
return audit;
|
|
2588
|
-
}
|
|
2589
|
-
return {
|
|
2590
|
-
...audit,
|
|
2591
|
-
details: {
|
|
2592
|
-
...audit.details,
|
|
2593
|
-
issues: audit.details.issues.map(
|
|
2594
|
-
(issue) => normalizeIssue(issue, gitRoot)
|
|
2595
|
-
)
|
|
2596
|
-
}
|
|
2597
|
-
};
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
// packages/core/src/lib/implementation/runner.ts
|
|
2602
|
-
import { join as join3 } from "node:path";
|
|
2603
|
-
async function executeRunnerConfig(cfg, onProgress) {
|
|
2604
|
-
const { args, command: command2, outputFile, outputTransform } = cfg;
|
|
2605
|
-
const { duration, date } = await executeProcess({
|
|
2606
|
-
command: command2,
|
|
2607
|
-
args,
|
|
2608
|
-
observer: { onStdout: onProgress }
|
|
2609
|
-
});
|
|
2610
|
-
const outputs = await readJsonFile(join3(process.cwd(), outputFile));
|
|
2611
|
-
const audits = outputTransform ? await outputTransform(outputs) : outputs;
|
|
2612
|
-
return {
|
|
2613
|
-
duration,
|
|
2614
|
-
date,
|
|
2615
|
-
audits
|
|
2616
|
-
};
|
|
2617
|
-
}
|
|
2618
|
-
async function executeRunnerFunction(runner, onProgress) {
|
|
2619
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
2620
|
-
const start = performance.now();
|
|
2621
|
-
const audits = await runner(onProgress);
|
|
2622
|
-
return {
|
|
2623
|
-
date,
|
|
2624
|
-
duration: calcDuration(start),
|
|
2625
|
-
audits
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
// packages/core/src/lib/implementation/execute-plugin.ts
|
|
2630
|
-
var PluginOutputMissingAuditError = class extends Error {
|
|
2631
|
-
constructor(auditSlug) {
|
|
2632
|
-
super(
|
|
2633
|
-
`Audit metadata not present in plugin config. Missing slug: ${bold5(
|
|
2634
|
-
auditSlug
|
|
2635
|
-
)}`
|
|
2636
|
-
);
|
|
2637
|
-
}
|
|
2638
|
-
};
|
|
2639
|
-
async function executePlugin(pluginConfig, onProgress) {
|
|
2640
|
-
const {
|
|
2641
|
-
runner,
|
|
2642
|
-
audits: pluginConfigAudits,
|
|
2643
|
-
description,
|
|
2644
|
-
docsUrl,
|
|
2645
|
-
groups: groups2,
|
|
2646
|
-
...pluginMeta
|
|
2647
|
-
} = pluginConfig;
|
|
2648
|
-
const runnerResult = typeof runner === "object" ? await executeRunnerConfig(runner, onProgress) : await executeRunnerFunction(runner, onProgress);
|
|
2649
|
-
const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
|
|
2650
|
-
const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);
|
|
2651
|
-
if (!result.success) {
|
|
2652
|
-
throw new Error(`Audit output is invalid: ${result.error.message}`);
|
|
2653
|
-
}
|
|
2654
|
-
const auditOutputs = result.data;
|
|
2655
|
-
auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
|
|
2656
|
-
const normalizedAuditOutputs = await normalizeAuditOutputs(auditOutputs);
|
|
2657
|
-
const auditReports = normalizedAuditOutputs.map(
|
|
2658
|
-
(auditOutput) => ({
|
|
2659
|
-
...auditOutput,
|
|
2660
|
-
...pluginConfigAudits.find(
|
|
2661
|
-
(audit) => audit.slug === auditOutput.slug
|
|
2662
|
-
)
|
|
2663
|
-
})
|
|
2664
|
-
);
|
|
2665
|
-
return {
|
|
2666
|
-
...pluginMeta,
|
|
2667
|
-
...executionMeta,
|
|
2668
|
-
audits: auditReports,
|
|
2669
|
-
...description && { description },
|
|
2670
|
-
...docsUrl && { docsUrl },
|
|
2671
|
-
...groups2 && { groups: groups2 }
|
|
2672
|
-
};
|
|
2673
|
-
}
|
|
2674
|
-
var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
2675
|
-
progressBar?.updateTitle(`Executing ${bold5(pluginCfg.title)}`);
|
|
2676
|
-
try {
|
|
2677
|
-
const pluginReport = await executePlugin(pluginCfg);
|
|
2678
|
-
progressBar?.incrementInSteps(steps);
|
|
2679
|
-
return pluginReport;
|
|
2680
|
-
} catch (error) {
|
|
2681
|
-
progressBar?.incrementInSteps(steps);
|
|
2682
|
-
throw new Error(
|
|
2683
|
-
error instanceof Error ? `- Plugin ${bold5(pluginCfg.title)} (${bold5(
|
|
2684
|
-
pluginCfg.slug
|
|
2685
|
-
)}) produced the following error:
|
|
2686
|
-
- ${error.message}` : String(error)
|
|
2687
|
-
);
|
|
2688
|
-
}
|
|
2689
|
-
};
|
|
2690
|
-
async function executePlugins(plugins, options2) {
|
|
2691
|
-
const { progress = false } = options2 ?? {};
|
|
2692
|
-
const progressBar = progress ? getProgressBar("Run plugins") : null;
|
|
2693
|
-
const pluginsResult = await plugins.reduce(
|
|
2694
|
-
async (acc, pluginCfg) => [
|
|
2695
|
-
...await acc,
|
|
2696
|
-
wrapProgress(pluginCfg, plugins.length, progressBar)
|
|
2697
|
-
],
|
|
2698
|
-
Promise.resolve([])
|
|
2699
|
-
);
|
|
2700
|
-
progressBar?.endProgress("Done running plugins");
|
|
2701
|
-
const errorsTransform = ({ reason }) => String(reason);
|
|
2702
|
-
const results = await Promise.allSettled(pluginsResult);
|
|
2703
|
-
logMultipleResults(results, "Plugins", void 0, errorsTransform);
|
|
2704
|
-
const { fulfilled, rejected } = groupByStatus(results);
|
|
2705
|
-
if (rejected.length > 0) {
|
|
2706
|
-
const errorMessages = rejected.map(({ reason }) => String(reason)).join("\n");
|
|
2707
|
-
throw new Error(
|
|
2708
|
-
`Executing ${pluralizeToken(
|
|
2709
|
-
"plugin",
|
|
2710
|
-
rejected.length
|
|
2711
|
-
)} failed.
|
|
2712
|
-
|
|
2713
|
-
${errorMessages}
|
|
2714
|
-
|
|
2715
|
-
`
|
|
2716
|
-
);
|
|
2717
|
-
}
|
|
2718
|
-
return fulfilled.map((result) => result.value);
|
|
2719
|
-
}
|
|
2720
|
-
function auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits) {
|
|
2721
|
-
auditOutputs.forEach((auditOutput) => {
|
|
2722
|
-
const auditMetadata = pluginConfigAudits.find(
|
|
2723
|
-
(audit) => audit.slug === auditOutput.slug
|
|
2724
|
-
);
|
|
2725
|
-
if (!auditMetadata) {
|
|
2726
|
-
throw new PluginOutputMissingAuditError(auditOutput.slug);
|
|
2727
|
-
}
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
// packages/core/src/lib/implementation/collect.ts
|
|
2732
|
-
async function collect(options2) {
|
|
2733
|
-
const { plugins, categories } = options2;
|
|
2734
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
2735
|
-
const start = performance.now();
|
|
2736
|
-
const commit = await getLatestCommit();
|
|
2737
|
-
const pluginOutputs = await executePlugins(plugins, options2);
|
|
2738
|
-
return {
|
|
2739
|
-
commit,
|
|
2740
|
-
packageName: name,
|
|
2741
|
-
version,
|
|
2742
|
-
date,
|
|
2743
|
-
duration: calcDuration(start),
|
|
2744
|
-
categories,
|
|
2745
|
-
plugins: pluginOutputs
|
|
2746
|
-
};
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
// packages/core/src/lib/implementation/persist.ts
|
|
2750
|
-
import { mkdir as mkdir2, stat as stat2, writeFile } from "node:fs/promises";
|
|
2751
|
-
import { join as join4 } from "node:path";
|
|
2752
|
-
var PersistDirError = class extends Error {
|
|
2753
|
-
constructor(outputDir) {
|
|
2754
|
-
super(`outPath: ${outputDir} is no directory.`);
|
|
2755
|
-
}
|
|
2756
|
-
};
|
|
2757
|
-
var PersistError = class extends Error {
|
|
2758
|
-
constructor(reportPath) {
|
|
2759
|
-
super(`fileName: ${reportPath} could not be saved.`);
|
|
2760
|
-
}
|
|
2761
|
-
};
|
|
2762
|
-
async function persistReport(report, sortedScoredReport, options2) {
|
|
2763
|
-
const { outputDir, filename, format } = options2;
|
|
2764
|
-
const results = format.map((reportType) => {
|
|
2765
|
-
switch (reportType) {
|
|
2766
|
-
case "json":
|
|
2767
|
-
return {
|
|
2768
|
-
format: "json",
|
|
2769
|
-
content: JSON.stringify(report, null, 2)
|
|
2770
|
-
};
|
|
2771
|
-
case "md":
|
|
2772
|
-
return {
|
|
2773
|
-
format: "md",
|
|
2774
|
-
content: generateMdReport(sortedScoredReport, { outputDir })
|
|
2775
|
-
};
|
|
2776
|
-
}
|
|
2777
|
-
});
|
|
2778
|
-
if (!await directoryExists(outputDir)) {
|
|
2779
|
-
try {
|
|
2780
|
-
await mkdir2(outputDir, { recursive: true });
|
|
2781
|
-
} catch (error) {
|
|
2782
|
-
ui().logger.warning(error.toString());
|
|
2783
|
-
throw new PersistDirError(outputDir);
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
return Promise.allSettled(
|
|
2787
|
-
results.map(
|
|
2788
|
-
(result) => persistResult(
|
|
2789
|
-
join4(outputDir, `${filename}.${result.format}`),
|
|
2790
|
-
result.content
|
|
2791
|
-
)
|
|
2792
|
-
)
|
|
2793
|
-
);
|
|
2794
|
-
}
|
|
2795
|
-
async function persistResult(reportPath, content) {
|
|
2796
|
-
return writeFile(reportPath, content).then(() => stat2(reportPath)).then((stats) => [reportPath, stats.size]).catch((error) => {
|
|
2797
|
-
ui().logger.warning(error.toString());
|
|
2798
|
-
throw new PersistError(reportPath);
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
|
-
function logPersistedResults(persistResults) {
|
|
2802
|
-
logMultipleFileResults(persistResults, "Generated reports");
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
// packages/core/src/lib/collect-and-persist.ts
|
|
2806
|
-
async function collectAndPersistReports(options2) {
|
|
2807
|
-
const { exec } = verboseUtils(options2.verbose);
|
|
2808
|
-
const report = await collect(options2);
|
|
2809
|
-
const sortedScoredReport = sortReport(scoreReport(report));
|
|
2810
|
-
const persistResults = await persistReport(
|
|
2811
|
-
report,
|
|
2812
|
-
sortedScoredReport,
|
|
2813
|
-
options2.persist
|
|
2814
|
-
);
|
|
2815
|
-
logStdoutSummary(sortedScoredReport, options2.verbose);
|
|
2816
|
-
exec(() => {
|
|
2817
|
-
logPersistedResults(persistResults);
|
|
2818
|
-
});
|
|
2819
|
-
report.plugins.forEach((plugin) => {
|
|
2820
|
-
pluginReportSchema.parse(plugin);
|
|
2821
|
-
});
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
// packages/core/src/lib/compare.ts
|
|
2825
|
-
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
2826
|
-
import { join as join5 } from "node:path";
|
|
2827
|
-
|
|
2828
|
-
// packages/core/src/lib/implementation/compare-scorables.ts
|
|
2829
|
-
function compareCategories(reports) {
|
|
2830
|
-
const { pairs, added, removed } = matchArrayItemsByKey({
|
|
2831
|
-
before: reports.before.categories ?? [],
|
|
2832
|
-
after: reports.after.categories ?? [],
|
|
2833
|
-
key: "slug"
|
|
2834
|
-
});
|
|
2835
|
-
const { changed, unchanged } = comparePairs(
|
|
2836
|
-
pairs,
|
|
2837
|
-
({ before, after }) => before.score === after.score
|
|
2838
|
-
);
|
|
2839
|
-
return {
|
|
2840
|
-
changed: changed.map(categoryPairToDiff),
|
|
2841
|
-
unchanged: unchanged.map(categoryToResult),
|
|
2842
|
-
added: added.map(categoryToResult),
|
|
2843
|
-
removed: removed.map(categoryToResult)
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
function compareGroups(reports) {
|
|
2847
|
-
const { pairs, added, removed } = matchArrayItemsByKey({
|
|
2848
|
-
before: listGroupsFromAllPlugins(reports.before),
|
|
2849
|
-
after: listGroupsFromAllPlugins(reports.after),
|
|
2850
|
-
key: ({ plugin, group }) => `${plugin.slug}/${group.slug}`
|
|
2851
|
-
});
|
|
2852
|
-
const { changed, unchanged } = comparePairs(
|
|
2853
|
-
pairs,
|
|
2854
|
-
({ before, after }) => before.group.score === after.group.score
|
|
2855
|
-
);
|
|
2856
|
-
return {
|
|
2857
|
-
changed: changed.map(pluginGroupPairToDiff),
|
|
2858
|
-
unchanged: unchanged.map(pluginGroupToResult),
|
|
2859
|
-
added: added.map(pluginGroupToResult),
|
|
2860
|
-
removed: removed.map(pluginGroupToResult)
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
|
-
function compareAudits2(reports) {
|
|
2864
|
-
const { pairs, added, removed } = matchArrayItemsByKey({
|
|
2865
|
-
before: listAuditsFromAllPlugins(reports.before),
|
|
2866
|
-
after: listAuditsFromAllPlugins(reports.after),
|
|
2867
|
-
key: ({ plugin, audit }) => `${plugin.slug}/${audit.slug}`
|
|
2868
|
-
});
|
|
2869
|
-
const { changed, unchanged } = comparePairs(
|
|
2870
|
-
pairs,
|
|
2871
|
-
({ before, after }) => before.audit.value === after.audit.value && before.audit.score === after.audit.score
|
|
2872
|
-
);
|
|
2873
|
-
return {
|
|
2874
|
-
changed: changed.map(pluginAuditPairToDiff),
|
|
2875
|
-
unchanged: unchanged.map(pluginAuditToResult),
|
|
2876
|
-
added: added.map(pluginAuditToResult),
|
|
2877
|
-
removed: removed.map(pluginAuditToResult)
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
function categoryToResult(category) {
|
|
2881
|
-
return {
|
|
2882
|
-
...selectMeta(category),
|
|
2883
|
-
score: category.score
|
|
2884
|
-
};
|
|
2885
|
-
}
|
|
2886
|
-
function categoryPairToDiff({
|
|
2887
|
-
before,
|
|
2888
|
-
after
|
|
2889
|
-
}) {
|
|
2890
|
-
return {
|
|
2891
|
-
...selectMeta(after),
|
|
2892
|
-
scores: {
|
|
2893
|
-
before: before.score,
|
|
2894
|
-
after: after.score,
|
|
2895
|
-
diff: after.score - before.score
|
|
2896
|
-
}
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
|
-
function pluginGroupToResult({ group, plugin }) {
|
|
2900
|
-
return {
|
|
2901
|
-
...selectMeta(group),
|
|
2902
|
-
plugin: selectMeta(plugin),
|
|
2903
|
-
score: group.score
|
|
2904
|
-
};
|
|
2905
|
-
}
|
|
2906
|
-
function pluginGroupPairToDiff({
|
|
2907
|
-
before,
|
|
2908
|
-
after
|
|
2909
|
-
}) {
|
|
2910
|
-
return {
|
|
2911
|
-
...selectMeta(after.group),
|
|
2912
|
-
plugin: selectMeta(after.plugin),
|
|
2913
|
-
scores: {
|
|
2914
|
-
before: before.group.score,
|
|
2915
|
-
after: after.group.score,
|
|
2916
|
-
diff: after.group.score - before.group.score
|
|
2917
|
-
}
|
|
2918
|
-
};
|
|
2919
|
-
}
|
|
2920
|
-
function pluginAuditToResult({ audit, plugin }) {
|
|
2921
|
-
return {
|
|
2922
|
-
...selectMeta(audit),
|
|
2923
|
-
plugin: selectMeta(plugin),
|
|
2924
|
-
score: audit.score,
|
|
2925
|
-
value: audit.value,
|
|
2926
|
-
displayValue: audit.displayValue
|
|
2927
|
-
};
|
|
2928
|
-
}
|
|
2929
|
-
function pluginAuditPairToDiff({
|
|
2930
|
-
before,
|
|
2931
|
-
after
|
|
2932
|
-
}) {
|
|
2933
|
-
return {
|
|
2934
|
-
...selectMeta(after.audit),
|
|
2935
|
-
plugin: selectMeta(after.plugin),
|
|
2936
|
-
scores: {
|
|
2937
|
-
before: before.audit.score,
|
|
2938
|
-
after: after.audit.score,
|
|
2939
|
-
diff: after.audit.score - before.audit.score
|
|
2940
|
-
},
|
|
2941
|
-
values: {
|
|
2942
|
-
before: before.audit.value,
|
|
2943
|
-
after: after.audit.value,
|
|
2944
|
-
diff: after.audit.value - before.audit.value
|
|
2945
|
-
},
|
|
2946
|
-
displayValues: {
|
|
2947
|
-
before: before.audit.displayValue,
|
|
2948
|
-
after: after.audit.displayValue
|
|
2949
|
-
}
|
|
2950
|
-
};
|
|
2951
|
-
}
|
|
2952
|
-
function selectMeta(meta) {
|
|
2953
|
-
return {
|
|
2954
|
-
slug: meta.slug,
|
|
2955
|
-
title: meta.title,
|
|
2956
|
-
...meta.docsUrl && {
|
|
2957
|
-
docsUrl: meta.docsUrl
|
|
2958
|
-
}
|
|
2959
|
-
};
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
|
-
// packages/core/src/lib/load-portal-client.ts
|
|
2963
|
-
async function loadPortalClient() {
|
|
2964
|
-
try {
|
|
2965
|
-
return await import("@code-pushup/portal-client");
|
|
2966
|
-
} catch {
|
|
2967
|
-
ui().logger.error(
|
|
2968
|
-
"Optional peer dependency @code-pushup/portal-client is not available. Make sure it is installed to enable upload functionality."
|
|
2969
|
-
);
|
|
2970
|
-
return null;
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
|
|
2974
|
-
// packages/core/src/lib/compare.ts
|
|
2975
|
-
async function compareReportFiles(inputPaths, persistConfig, uploadConfig, label) {
|
|
2976
|
-
const { outputDir, filename, format } = persistConfig;
|
|
2977
|
-
const [reportBefore, reportAfter] = await Promise.all([
|
|
2978
|
-
readJsonFile(inputPaths.before),
|
|
2979
|
-
readJsonFile(inputPaths.after)
|
|
2980
|
-
]);
|
|
2981
|
-
const reports = {
|
|
2982
|
-
before: reportSchema.parse(reportBefore),
|
|
2983
|
-
after: reportSchema.parse(reportAfter)
|
|
2984
|
-
};
|
|
2985
|
-
const diff = compareReports(reports);
|
|
2986
|
-
if (label) {
|
|
2987
|
-
diff.label = label;
|
|
2988
|
-
}
|
|
2989
|
-
if (uploadConfig && diff.commits) {
|
|
2990
|
-
diff.portalUrl = await fetchPortalComparisonLink(
|
|
2991
|
-
uploadConfig,
|
|
2992
|
-
diff.commits
|
|
2993
|
-
);
|
|
2994
|
-
}
|
|
2995
|
-
return Promise.all(
|
|
2996
|
-
format.map(async (fmt) => {
|
|
2997
|
-
const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
|
|
2998
|
-
const content = reportsDiffToFileContent(diff, fmt);
|
|
2999
|
-
await ensureDirectoryExists(outputDir);
|
|
3000
|
-
await writeFile2(outputPath, content);
|
|
3001
|
-
return outputPath;
|
|
3002
|
-
})
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
function compareReports(reports) {
|
|
3006
|
-
const start = performance.now();
|
|
3007
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
3008
|
-
const commits = reports.before.commit != null && reports.after.commit != null ? { before: reports.before.commit, after: reports.after.commit } : null;
|
|
3009
|
-
const scoredReports = {
|
|
3010
|
-
before: scoreReport(reports.before),
|
|
3011
|
-
after: scoreReport(reports.after)
|
|
3012
|
-
};
|
|
3013
|
-
const categories = compareCategories(scoredReports);
|
|
3014
|
-
const groups2 = compareGroups(scoredReports);
|
|
3015
|
-
const audits = compareAudits2(scoredReports);
|
|
3016
|
-
const duration = calcDuration(start);
|
|
3017
|
-
return {
|
|
3018
|
-
commits,
|
|
3019
|
-
categories,
|
|
3020
|
-
groups: groups2,
|
|
3021
|
-
audits,
|
|
3022
|
-
packageName: name,
|
|
3023
|
-
version,
|
|
3024
|
-
date,
|
|
3025
|
-
duration
|
|
3026
|
-
};
|
|
3027
|
-
}
|
|
3028
|
-
function reportsDiffToFileContent(reportsDiff, format) {
|
|
3029
|
-
switch (format) {
|
|
3030
|
-
case "json":
|
|
3031
|
-
return JSON.stringify(reportsDiff, null, 2);
|
|
3032
|
-
case "md":
|
|
3033
|
-
return generateMdReportsDiff(reportsDiff);
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
async function fetchPortalComparisonLink(uploadConfig, commits) {
|
|
3037
|
-
const { server, apiKey, organization, project } = uploadConfig;
|
|
3038
|
-
const portalClient = await loadPortalClient();
|
|
3039
|
-
if (!portalClient) {
|
|
3040
|
-
return;
|
|
3041
|
-
}
|
|
3042
|
-
const { PortalOperationError, getPortalComparisonLink } = portalClient;
|
|
3043
|
-
try {
|
|
3044
|
-
return await getPortalComparisonLink({
|
|
3045
|
-
server,
|
|
3046
|
-
apiKey,
|
|
3047
|
-
parameters: {
|
|
3048
|
-
organization,
|
|
3049
|
-
project,
|
|
3050
|
-
before: commits.before.hash,
|
|
3051
|
-
after: commits.after.hash
|
|
3052
|
-
}
|
|
3053
|
-
});
|
|
3054
|
-
} catch (error) {
|
|
3055
|
-
if (error instanceof PortalOperationError) {
|
|
3056
|
-
ui().logger.warning(
|
|
3057
|
-
`Failed to fetch portal comparison link - ${error.message}`
|
|
3058
|
-
);
|
|
3059
|
-
return void 0;
|
|
3060
|
-
}
|
|
3061
|
-
throw error;
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
// packages/core/src/lib/implementation/report-to-gql.ts
|
|
3066
|
-
function reportToGQL(report) {
|
|
3067
|
-
return {
|
|
3068
|
-
packageName: report.packageName,
|
|
3069
|
-
packageVersion: report.version,
|
|
3070
|
-
commandStartDate: report.date,
|
|
3071
|
-
commandDuration: report.duration,
|
|
3072
|
-
plugins: report.plugins.map(pluginToGQL),
|
|
3073
|
-
categories: (report.categories ?? []).map(categoryToGQL)
|
|
3074
|
-
};
|
|
3075
|
-
}
|
|
3076
|
-
function pluginToGQL(plugin) {
|
|
3077
|
-
return {
|
|
3078
|
-
slug: plugin.slug,
|
|
3079
|
-
title: plugin.title,
|
|
3080
|
-
icon: plugin.icon,
|
|
3081
|
-
description: plugin.description,
|
|
3082
|
-
docsUrl: plugin.docsUrl,
|
|
3083
|
-
audits: plugin.audits.map(auditToGQL),
|
|
3084
|
-
groups: plugin.groups?.map(groupToGQL),
|
|
3085
|
-
packageName: plugin.packageName,
|
|
3086
|
-
packageVersion: plugin.version,
|
|
3087
|
-
runnerDuration: plugin.duration,
|
|
3088
|
-
runnerStartDate: plugin.date
|
|
3089
|
-
};
|
|
3090
|
-
}
|
|
3091
|
-
function groupToGQL(group) {
|
|
3092
|
-
return {
|
|
3093
|
-
slug: group.slug,
|
|
3094
|
-
title: group.title,
|
|
3095
|
-
description: group.description,
|
|
3096
|
-
refs: group.refs.map((ref) => ({ slug: ref.slug, weight: ref.weight }))
|
|
3097
|
-
};
|
|
3098
|
-
}
|
|
3099
|
-
function auditToGQL(audit) {
|
|
3100
|
-
const {
|
|
3101
|
-
slug,
|
|
3102
|
-
title,
|
|
3103
|
-
description,
|
|
3104
|
-
docsUrl,
|
|
3105
|
-
score,
|
|
3106
|
-
value,
|
|
3107
|
-
displayValue: formattedValue,
|
|
3108
|
-
details: details2
|
|
3109
|
-
} = audit;
|
|
3110
|
-
const { issues, table: table2 } = details2 ?? {};
|
|
3111
|
-
return {
|
|
3112
|
-
slug,
|
|
3113
|
-
title,
|
|
3114
|
-
description,
|
|
3115
|
-
docsUrl,
|
|
3116
|
-
score,
|
|
3117
|
-
value,
|
|
3118
|
-
formattedValue,
|
|
3119
|
-
...details2 && {
|
|
3120
|
-
details: {
|
|
3121
|
-
...issues && { issues: issues.map(issueToGQL) },
|
|
3122
|
-
...table2 && { tables: [tableToGQL(table2)] }
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3125
|
-
};
|
|
3126
|
-
}
|
|
3127
|
-
function issueToGQL(issue) {
|
|
3128
|
-
return {
|
|
3129
|
-
message: issue.message,
|
|
3130
|
-
severity: issueSeverityToGQL(issue.severity),
|
|
3131
|
-
...issue.source?.file && {
|
|
3132
|
-
sourceType: safeEnum("SourceCode"),
|
|
3133
|
-
sourceFilePath: issue.source.file,
|
|
3134
|
-
sourceStartLine: issue.source.position?.startLine,
|
|
3135
|
-
sourceStartColumn: issue.source.position?.startColumn,
|
|
3136
|
-
sourceEndLine: issue.source.position?.endLine,
|
|
3137
|
-
sourceEndColumn: issue.source.position?.endColumn
|
|
3138
|
-
}
|
|
3139
|
-
};
|
|
3140
|
-
}
|
|
3141
|
-
function tableToGQL(table2) {
|
|
3142
|
-
return {
|
|
3143
|
-
...table2.title && { title: table2.title },
|
|
3144
|
-
...table2.columns?.length && {
|
|
3145
|
-
columns: table2.columns.map(
|
|
3146
|
-
(column) => typeof column === "string" ? { alignment: tableAlignmentToGQL(column) } : {
|
|
3147
|
-
key: column.key,
|
|
3148
|
-
label: column.label,
|
|
3149
|
-
alignment: column.align && tableAlignmentToGQL(column.align)
|
|
3150
|
-
}
|
|
3151
|
-
)
|
|
3152
|
-
},
|
|
3153
|
-
rows: table2.rows.map(
|
|
3154
|
-
(row) => Array.isArray(row) ? row.map((content) => ({ content: content?.toString() ?? "" })) : Object.entries(row).map(([key, content]) => ({
|
|
3155
|
-
key,
|
|
3156
|
-
content: content?.toString() ?? ""
|
|
3157
|
-
}))
|
|
3158
|
-
)
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
function categoryToGQL(category) {
|
|
3162
|
-
return {
|
|
3163
|
-
slug: category.slug,
|
|
3164
|
-
title: category.title,
|
|
3165
|
-
description: category.description,
|
|
3166
|
-
isBinary: category.isBinary,
|
|
3167
|
-
refs: category.refs.map((ref) => ({
|
|
3168
|
-
plugin: ref.plugin,
|
|
3169
|
-
type: categoryRefTypeToGQL(ref.type),
|
|
3170
|
-
weight: ref.weight,
|
|
3171
|
-
slug: ref.slug
|
|
3172
|
-
}))
|
|
3173
|
-
};
|
|
3174
|
-
}
|
|
3175
|
-
function categoryRefTypeToGQL(type) {
|
|
3176
|
-
switch (type) {
|
|
3177
|
-
case "audit":
|
|
3178
|
-
return safeEnum("Audit");
|
|
3179
|
-
case "group":
|
|
3180
|
-
return safeEnum("Group");
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
function issueSeverityToGQL(severity) {
|
|
3184
|
-
switch (severity) {
|
|
3185
|
-
case "info":
|
|
3186
|
-
return safeEnum("Info");
|
|
3187
|
-
case "error":
|
|
3188
|
-
return safeEnum("Error");
|
|
3189
|
-
case "warning":
|
|
3190
|
-
return safeEnum("Warning");
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
function tableAlignmentToGQL(alignment) {
|
|
3194
|
-
switch (alignment) {
|
|
3195
|
-
case "left":
|
|
3196
|
-
return safeEnum("Left");
|
|
3197
|
-
case "center":
|
|
3198
|
-
return safeEnum("Center");
|
|
3199
|
-
case "right":
|
|
3200
|
-
return safeEnum("Right");
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
function safeEnum(value) {
|
|
3204
|
-
return value;
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
|
-
// packages/core/src/lib/upload.ts
|
|
3208
|
-
async function upload(options2) {
|
|
3209
|
-
if (options2.upload == null) {
|
|
3210
|
-
throw new Error("Upload configuration is not set.");
|
|
3211
|
-
}
|
|
3212
|
-
const portalClient = await loadPortalClient();
|
|
3213
|
-
if (!portalClient) {
|
|
3214
|
-
return;
|
|
3215
|
-
}
|
|
3216
|
-
const { uploadToPortal } = portalClient;
|
|
3217
|
-
const { apiKey, server, organization, project, timeout } = options2.upload;
|
|
3218
|
-
const report = await loadReport({
|
|
3219
|
-
...options2.persist,
|
|
3220
|
-
format: "json"
|
|
3221
|
-
});
|
|
3222
|
-
if (!report.commit) {
|
|
3223
|
-
throw new Error("Commit must be linked in order to upload report");
|
|
3224
|
-
}
|
|
3225
|
-
const data = {
|
|
3226
|
-
organization,
|
|
3227
|
-
project,
|
|
3228
|
-
commit: report.commit.hash,
|
|
3229
|
-
...reportToGQL(report)
|
|
3230
|
-
};
|
|
3231
|
-
return uploadToPortal({ apiKey, server, data, timeout });
|
|
3232
|
-
}
|
|
3233
|
-
|
|
3234
|
-
// packages/core/src/lib/history.ts
|
|
3235
|
-
async function history(config, commits) {
|
|
3236
|
-
const initialBranch = await getCurrentBranchOrTag();
|
|
3237
|
-
const { skipUploads = false, forceCleanStatus, persist } = config;
|
|
3238
|
-
const reports = [];
|
|
3239
|
-
for (const commit of commits) {
|
|
3240
|
-
ui().logger.info(`Collect ${commit}`);
|
|
3241
|
-
await safeCheckout(commit, forceCleanStatus);
|
|
3242
|
-
const currentConfig = {
|
|
3243
|
-
...config,
|
|
3244
|
-
persist: {
|
|
3245
|
-
...persist,
|
|
3246
|
-
format: ["json"],
|
|
3247
|
-
filename: `${commit}-report`
|
|
3248
|
-
}
|
|
3249
|
-
};
|
|
3250
|
-
await collectAndPersistReports(currentConfig);
|
|
3251
|
-
if (skipUploads) {
|
|
3252
|
-
ui().logger.info("Upload is skipped because skipUploads is set to true.");
|
|
3253
|
-
} else {
|
|
3254
|
-
if (currentConfig.upload) {
|
|
3255
|
-
await upload(currentConfig);
|
|
3256
|
-
} else {
|
|
3257
|
-
ui().logger.info(
|
|
3258
|
-
"Upload is skipped because upload config is undefined."
|
|
3259
|
-
);
|
|
3260
|
-
}
|
|
3261
|
-
}
|
|
3262
|
-
reports.push(currentConfig.persist.filename);
|
|
3263
|
-
}
|
|
3264
|
-
await safeCheckout(initialBranch, forceCleanStatus);
|
|
3265
|
-
return reports;
|
|
3266
|
-
}
|
|
3267
|
-
|
|
3268
|
-
// packages/core/src/lib/implementation/read-rc-file.ts
|
|
3269
|
-
import { join as join6 } from "node:path";
|
|
3270
|
-
var ConfigPathError = class extends Error {
|
|
3271
|
-
constructor(configPath) {
|
|
3272
|
-
super(`Provided path '${configPath}' is not valid.`);
|
|
3273
|
-
}
|
|
3274
|
-
};
|
|
3275
|
-
async function readRcByPath(filepath, tsconfig) {
|
|
3276
|
-
if (filepath.length === 0) {
|
|
3277
|
-
throw new Error("The path to the configuration file is empty.");
|
|
3278
|
-
}
|
|
3279
|
-
if (!await fileExists(filepath)) {
|
|
3280
|
-
throw new ConfigPathError(filepath);
|
|
3281
|
-
}
|
|
3282
|
-
const cfg = await importModule({ filepath, tsconfig, format: "esm" });
|
|
3283
|
-
return coreConfigSchema.parse(cfg);
|
|
3284
|
-
}
|
|
3285
|
-
async function autoloadRc(tsconfig) {
|
|
3286
|
-
let ext = "";
|
|
3287
|
-
for (const extension of SUPPORTED_CONFIG_FILE_FORMATS) {
|
|
3288
|
-
const path = `${CONFIG_FILE_NAME}.${extension}`;
|
|
3289
|
-
const exists2 = await fileExists(path);
|
|
3290
|
-
if (exists2) {
|
|
3291
|
-
ext = extension;
|
|
3292
|
-
break;
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
if (!ext) {
|
|
3296
|
-
throw new Error(
|
|
3297
|
-
`No file ${CONFIG_FILE_NAME}.(${SUPPORTED_CONFIG_FILE_FORMATS.join(
|
|
3298
|
-
"|"
|
|
3299
|
-
)}) present in ${process.cwd()}`
|
|
3300
|
-
);
|
|
3301
|
-
}
|
|
3302
|
-
return readRcByPath(
|
|
3303
|
-
join6(process.cwd(), `${CONFIG_FILE_NAME}.${ext}`),
|
|
3304
|
-
tsconfig
|
|
3305
|
-
);
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
// packages/core/src/lib/merge-diffs.ts
|
|
3309
|
-
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
3310
|
-
import { basename, dirname, join as join7 } from "node:path";
|
|
3311
|
-
async function mergeDiffs(files, persistConfig) {
|
|
3312
|
-
const results = await Promise.allSettled(
|
|
3313
|
-
files.map(async (file) => {
|
|
3314
|
-
const json = await readJsonFile(file).catch((error) => {
|
|
3315
|
-
throw new Error(
|
|
3316
|
-
`Failed to read JSON file ${file} - ${stringifyError(error)}`
|
|
3317
|
-
);
|
|
3318
|
-
});
|
|
3319
|
-
const result = await reportsDiffSchema.safeParseAsync(json);
|
|
3320
|
-
if (!result.success) {
|
|
3321
|
-
throw new Error(
|
|
3322
|
-
`Invalid reports diff in ${file} - ${result.error.message}`
|
|
3323
|
-
);
|
|
3324
|
-
}
|
|
3325
|
-
return { ...result.data, file };
|
|
3326
|
-
})
|
|
3327
|
-
);
|
|
3328
|
-
results.filter(isPromiseRejectedResult).forEach(({ reason }) => {
|
|
3329
|
-
ui().logger.warning(
|
|
3330
|
-
`Skipped invalid report diff - ${stringifyError(reason)}`
|
|
3331
|
-
);
|
|
3332
|
-
});
|
|
3333
|
-
const diffs = results.filter(isPromiseFulfilledResult).map(({ value }) => value);
|
|
3334
|
-
const labeledDiffs = diffs.map((diff) => ({
|
|
3335
|
-
...diff,
|
|
3336
|
-
label: diff.label || basename(dirname(diff.file))
|
|
3337
|
-
// fallback is parent folder name
|
|
3338
|
-
}));
|
|
3339
|
-
const markdown = generateMdReportsDiffForMonorepo(labeledDiffs);
|
|
3340
|
-
const { outputDir, filename } = persistConfig;
|
|
3341
|
-
const outputPath = join7(outputDir, `${filename}-diff.md`);
|
|
3342
|
-
await ensureDirectoryExists(outputDir);
|
|
3343
|
-
await writeFile3(outputPath, markdown);
|
|
3344
|
-
return outputPath;
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
// packages/cli/src/lib/constants.ts
|
|
3348
|
-
var CLI_NAME = "Code PushUp CLI";
|
|
3349
|
-
var CLI_SCRIPT_NAME = "code-pushup";
|
|
3350
|
-
|
|
3351
|
-
// packages/cli/src/lib/implementation/logging.ts
|
|
3352
|
-
import { bold as bold6, gray as gray3 } from "ansis";
|
|
3353
|
-
function renderConfigureCategoriesHint() {
|
|
3354
|
-
ui().logger.info(
|
|
3355
|
-
gray3(
|
|
3356
|
-
`\u{1F4A1} Configure categories to see the scores in an overview table. See: ${link(
|
|
3357
|
-
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md"
|
|
3358
|
-
)}`
|
|
3359
|
-
)
|
|
3360
|
-
);
|
|
3361
|
-
}
|
|
3362
|
-
function uploadSuccessfulLog(url) {
|
|
3363
|
-
ui().logger.success("Upload successful!");
|
|
3364
|
-
ui().logger.success(link(url));
|
|
3365
|
-
}
|
|
3366
|
-
function collectSuccessfulLog() {
|
|
3367
|
-
ui().logger.success("Collecting report successful!");
|
|
3368
|
-
}
|
|
3369
|
-
function renderIntegratePortalHint() {
|
|
3370
|
-
ui().sticker().add(bold6.gray("\u{1F4A1} Integrate the portal")).add("").add(
|
|
3371
|
-
`${gray3("\u276F")} Upload a report to the server - ${gray3(
|
|
3372
|
-
"npx code-pushup upload"
|
|
3373
|
-
)}`
|
|
3374
|
-
).add(
|
|
3375
|
-
` ${link(
|
|
3376
|
-
"https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
|
|
3377
|
-
)}`
|
|
3378
|
-
).add(
|
|
3379
|
-
`${gray3("\u276F")} ${gray3("Portal Integration")} - ${link(
|
|
3380
|
-
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
|
|
3381
|
-
)}`
|
|
3382
|
-
).add(
|
|
3383
|
-
`${gray3("\u276F")} ${gray3("Upload Command")} - ${link(
|
|
3384
|
-
"https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#portal-integration"
|
|
3385
|
-
)}`
|
|
3386
|
-
).render();
|
|
3387
|
-
}
|
|
3388
|
-
|
|
3389
|
-
// packages/cli/src/lib/autorun/autorun-command.ts
|
|
3390
|
-
function yargsAutorunCommandObject() {
|
|
3391
|
-
const command2 = "autorun";
|
|
3392
|
-
return {
|
|
3393
|
-
command: command2,
|
|
3394
|
-
describe: "Shortcut for running collect followed by upload",
|
|
3395
|
-
handler: async (args) => {
|
|
3396
|
-
ui().logger.log(bold7(CLI_NAME));
|
|
3397
|
-
ui().logger.info(gray4(`Run ${command2}...`));
|
|
3398
|
-
const options2 = args;
|
|
3399
|
-
const optionsWithFormat = {
|
|
3400
|
-
...options2,
|
|
3401
|
-
persist: {
|
|
3402
|
-
...options2.persist,
|
|
3403
|
-
format: [
|
|
3404
|
-
.../* @__PURE__ */ new Set([...options2.persist.format, "json"])
|
|
3405
|
-
]
|
|
3406
|
-
}
|
|
3407
|
-
};
|
|
3408
|
-
await collectAndPersistReports(optionsWithFormat);
|
|
3409
|
-
collectSuccessfulLog();
|
|
3410
|
-
if (!options2.categories || options2.categories.length === 0) {
|
|
3411
|
-
renderConfigureCategoriesHint();
|
|
3412
|
-
}
|
|
3413
|
-
if (options2.upload) {
|
|
3414
|
-
const report = await upload(options2);
|
|
3415
|
-
if (report?.url) {
|
|
3416
|
-
uploadSuccessfulLog(report.url);
|
|
3417
|
-
}
|
|
3418
|
-
} else {
|
|
3419
|
-
ui().logger.warning("Upload skipped because configuration is not set.");
|
|
3420
|
-
renderIntegratePortalHint();
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
};
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
// packages/cli/src/lib/collect/collect-command.ts
|
|
3427
|
-
import { bold as bold8, gray as gray5 } from "ansis";
|
|
3428
|
-
function yargsCollectCommandObject() {
|
|
3429
|
-
const command2 = "collect";
|
|
3430
|
-
return {
|
|
3431
|
-
command: command2,
|
|
3432
|
-
describe: "Run Plugins and collect results",
|
|
3433
|
-
handler: async (args) => {
|
|
3434
|
-
const options2 = args;
|
|
3435
|
-
ui().logger.log(bold8(CLI_NAME));
|
|
3436
|
-
ui().logger.info(gray5(`Run ${command2}...`));
|
|
3437
|
-
await collectAndPersistReports(options2);
|
|
3438
|
-
collectSuccessfulLog();
|
|
3439
|
-
if (!options2.categories || options2.categories.length === 0) {
|
|
3440
|
-
renderConfigureCategoriesHint();
|
|
3441
|
-
}
|
|
3442
|
-
const { upload: upload2 = {} } = args;
|
|
3443
|
-
if (Object.keys(upload2).length === 0) {
|
|
3444
|
-
renderUploadAutorunHint();
|
|
3445
|
-
}
|
|
3446
|
-
}
|
|
3447
|
-
};
|
|
3448
|
-
}
|
|
3449
|
-
function renderUploadAutorunHint() {
|
|
3450
|
-
ui().sticker().add(bold8.gray("\u{1F4A1} Visualize your reports")).add("").add(
|
|
3451
|
-
`${gray5("\u276F")} npx code-pushup upload - ${gray5(
|
|
3452
|
-
"Run upload to upload the created report to the server"
|
|
3453
|
-
)}`
|
|
3454
|
-
).add(
|
|
3455
|
-
` ${link(
|
|
3456
|
-
"https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command"
|
|
3457
|
-
)}`
|
|
3458
|
-
).add(
|
|
3459
|
-
`${gray5("\u276F")} npx code-pushup autorun - ${gray5("Run collect & upload")}`
|
|
3460
|
-
).add(
|
|
3461
|
-
` ${link(
|
|
3462
|
-
"https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command"
|
|
3463
|
-
)}`
|
|
3464
|
-
).render();
|
|
3465
|
-
}
|
|
3466
|
-
|
|
3467
|
-
// packages/cli/src/lib/compare/compare-command.ts
|
|
3468
|
-
import { bold as bold9, gray as gray6 } from "ansis";
|
|
3469
|
-
|
|
3470
|
-
// packages/cli/src/lib/implementation/compare.options.ts
|
|
3471
|
-
function yargsCompareOptionsDefinition() {
|
|
3472
|
-
return {
|
|
3473
|
-
before: {
|
|
3474
|
-
describe: "Path to source report.json",
|
|
3475
|
-
type: "string",
|
|
3476
|
-
demandOption: true
|
|
3477
|
-
},
|
|
3478
|
-
after: {
|
|
3479
|
-
describe: "Path to target report.json",
|
|
3480
|
-
type: "string",
|
|
3481
|
-
demandOption: true
|
|
3482
|
-
},
|
|
3483
|
-
label: {
|
|
3484
|
-
describe: "Label for diff (e.g. project name)",
|
|
3485
|
-
type: "string"
|
|
3486
|
-
}
|
|
3487
|
-
};
|
|
3488
|
-
}
|
|
3489
|
-
|
|
3490
|
-
// packages/cli/src/lib/compare/compare-command.ts
|
|
3491
|
-
function yargsCompareCommandObject() {
|
|
3492
|
-
const command2 = "compare";
|
|
3493
|
-
return {
|
|
3494
|
-
command: command2,
|
|
3495
|
-
describe: "Compare 2 report files and create a diff file",
|
|
3496
|
-
builder: yargsCompareOptionsDefinition(),
|
|
3497
|
-
handler: async (args) => {
|
|
3498
|
-
ui().logger.log(bold9(CLI_NAME));
|
|
3499
|
-
ui().logger.info(gray6(`Run ${command2}...`));
|
|
3500
|
-
const options2 = args;
|
|
3501
|
-
const { before, after, label, persist, upload: upload2 } = options2;
|
|
3502
|
-
const outputPaths = await compareReportFiles(
|
|
3503
|
-
{ before, after },
|
|
3504
|
-
persist,
|
|
3505
|
-
upload2,
|
|
3506
|
-
label
|
|
3507
|
-
);
|
|
3508
|
-
ui().logger.info(
|
|
3509
|
-
`Reports diff written to ${outputPaths.map((path) => bold9(path)).join(" and ")}`
|
|
3510
|
-
);
|
|
3511
|
-
}
|
|
3512
|
-
};
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
// packages/cli/src/lib/history/history-command.ts
|
|
3516
|
-
import { bold as bold10, gray as gray7 } from "ansis";
|
|
3517
|
-
|
|
3518
|
-
// packages/cli/src/lib/implementation/global.utils.ts
|
|
3519
|
-
import yargs from "yargs";
|
|
3520
|
-
|
|
3521
|
-
// packages/cli/src/lib/implementation/validate-filter-options.utils.ts
|
|
3522
|
-
var OptionValidationError = class extends Error {
|
|
3523
|
-
};
|
|
3524
|
-
function validateFilterOption(option, { plugins, categories = [] }, { itemsToFilter, verbose }) {
|
|
3525
|
-
const itemsToFilterSet = new Set(itemsToFilter);
|
|
3526
|
-
const validItems = isCategoryOption(option) ? categories : isPluginOption(option) ? plugins : [];
|
|
3527
|
-
const invalidItems = itemsToFilter.filter(
|
|
3528
|
-
(item) => !validItems.some(({ slug }) => slug === item)
|
|
3529
|
-
);
|
|
3530
|
-
const message = createValidationMessage(option, invalidItems, validItems);
|
|
3531
|
-
if (isOnlyOption(option) && itemsToFilterSet.size > 0 && itemsToFilterSet.size === invalidItems.length) {
|
|
3532
|
-
throw new OptionValidationError(message);
|
|
3533
|
-
}
|
|
3534
|
-
if (invalidItems.length > 0) {
|
|
3535
|
-
ui().logger.warning(message);
|
|
3536
|
-
}
|
|
3537
|
-
if (isPluginOption(option) && categories.length > 0 && verbose) {
|
|
3538
|
-
const removedCategorySlugs = filterItemRefsBy(
|
|
3539
|
-
categories,
|
|
3540
|
-
({ plugin }) => isOnlyOption(option) ? !itemsToFilterSet.has(plugin) : itemsToFilterSet.has(plugin)
|
|
3541
|
-
).map(({ slug }) => slug);
|
|
3542
|
-
if (removedCategorySlugs.length > 0) {
|
|
3543
|
-
ui().logger.info(
|
|
3544
|
-
`The --${option} argument removed the following categories: ${removedCategorySlugs.join(
|
|
3545
|
-
", "
|
|
3546
|
-
)}.`
|
|
3547
|
-
);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
}
|
|
3551
|
-
function validateFinalState(filteredItems, originalItems) {
|
|
3552
|
-
const { categories: filteredCategories = [], plugins: filteredPlugins } = filteredItems;
|
|
3553
|
-
const { categories: originalCategories = [], plugins: originalPlugins } = originalItems;
|
|
3554
|
-
if (filteredCategories.length === 0 && filteredPlugins.length === 0 && (originalPlugins.length > 0 || originalCategories.length > 0)) {
|
|
3555
|
-
const availablePlugins = originalPlugins.map((p) => p.slug).join(", ") || "none";
|
|
3556
|
-
const availableCategories = originalCategories.map((c) => c.slug).join(", ") || "none";
|
|
3557
|
-
throw new OptionValidationError(
|
|
3558
|
-
`Nothing to report. No plugins or categories are available after filtering. Available plugins: ${availablePlugins}. Available categories: ${availableCategories}.`
|
|
3559
|
-
);
|
|
3560
|
-
}
|
|
3561
|
-
}
|
|
3562
|
-
function isCategoryOption(option) {
|
|
3563
|
-
return option.endsWith("Categories");
|
|
3564
|
-
}
|
|
3565
|
-
function isPluginOption(option) {
|
|
3566
|
-
return option.endsWith("Plugins");
|
|
3567
|
-
}
|
|
3568
|
-
function isOnlyOption(option) {
|
|
3569
|
-
return option.startsWith("only");
|
|
3570
|
-
}
|
|
3571
|
-
function getItemType(option, count) {
|
|
3572
|
-
const itemType = isCategoryOption(option) ? "category" : isPluginOption(option) ? "plugin" : "item";
|
|
3573
|
-
return pluralize(itemType, count);
|
|
3574
|
-
}
|
|
3575
|
-
function createValidationMessage(option, invalidItems, validItems) {
|
|
3576
|
-
const invalidItem = getItemType(option, invalidItems.length);
|
|
3577
|
-
const invalidItemText = invalidItems.length === 1 ? `a ${invalidItem} that does not exist:` : `${invalidItem} that do not exist:`;
|
|
3578
|
-
const invalidSlugs = invalidItems.join(", ");
|
|
3579
|
-
const validItem = getItemType(option, validItems.length);
|
|
3580
|
-
const validItemText = validItems.length === 1 ? `The only valid ${validItem} is` : `Valid ${validItem} are`;
|
|
3581
|
-
const validSlugs = validItems.map(({ slug }) => slug).join(", ");
|
|
3582
|
-
return `The --${option} argument references ${invalidItemText} ${invalidSlugs}. ${validItemText} ${validSlugs}.`;
|
|
3583
|
-
}
|
|
3584
|
-
function handleConflictingOptions(type, onlyItems, skipItems) {
|
|
3585
|
-
const conflictingItems = onlyItems.filter((item) => skipItems.includes(item));
|
|
3586
|
-
if (conflictingItems.length > 0) {
|
|
3587
|
-
const conflictSubject = () => {
|
|
3588
|
-
switch (type) {
|
|
3589
|
-
case "categories":
|
|
3590
|
-
return conflictingItems.length > 1 ? "categories are" : "category is";
|
|
3591
|
-
case "plugins":
|
|
3592
|
-
return conflictingItems.length > 1 ? "plugins are" : "plugin is";
|
|
3593
|
-
}
|
|
3594
|
-
};
|
|
3595
|
-
const conflictingSlugs = conflictingItems.join(", ");
|
|
3596
|
-
throw new OptionValidationError(
|
|
3597
|
-
`The following ${conflictSubject()} specified in both --only${capitalize(
|
|
3598
|
-
type
|
|
3599
|
-
)} and --skip${capitalize(
|
|
3600
|
-
type
|
|
3601
|
-
)}: ${conflictingSlugs}. Please choose one option.`
|
|
3602
|
-
);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
|
-
// packages/cli/src/lib/implementation/global.utils.ts
|
|
3607
|
-
function filterKebabCaseKeys(obj) {
|
|
3608
|
-
return Object.entries(obj).filter(([key]) => !key.includes("-")).reduce(
|
|
3609
|
-
(acc, [key, value]) => typeof value === "string" || typeof value === "object" && Array.isArray(obj[key]) ? { ...acc, [key]: value } : typeof value === "object" && !Array.isArray(value) && value != null ? {
|
|
3610
|
-
...acc,
|
|
3611
|
-
[key]: filterKebabCaseKeys(value)
|
|
3612
|
-
} : { ...acc, [key]: value },
|
|
3613
|
-
{}
|
|
3614
|
-
);
|
|
3615
|
-
}
|
|
3616
|
-
function logErrorBeforeThrow(fn) {
|
|
3617
|
-
return async (...args) => {
|
|
3618
|
-
try {
|
|
3619
|
-
return await fn(...args);
|
|
3620
|
-
} catch (error) {
|
|
3621
|
-
if (error instanceof OptionValidationError) {
|
|
3622
|
-
ui().logger.error(error.message);
|
|
3623
|
-
await new Promise((resolve) => process.stdout.write("", resolve));
|
|
3624
|
-
yargs().exit(1, error);
|
|
3625
|
-
} else {
|
|
3626
|
-
console.error(error);
|
|
3627
|
-
await new Promise((resolve) => process.stdout.write("", resolve));
|
|
3628
|
-
throw error;
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
};
|
|
3632
|
-
}
|
|
3633
|
-
function coerceArray(param) {
|
|
3634
|
-
return [...new Set(toArray(param).flatMap((f) => f.split(",")))];
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
// packages/cli/src/lib/implementation/filter.options.ts
|
|
3638
|
-
var skipCategoriesOption = {
|
|
3639
|
-
describe: "List of categories to skip. If not set all categories are run.",
|
|
3640
|
-
type: "array",
|
|
3641
|
-
default: [],
|
|
3642
|
-
coerce: coerceArray
|
|
3643
|
-
};
|
|
3644
|
-
var onlyCategoriesOption = {
|
|
3645
|
-
describe: "List of categories to run. If not set all categories are run.",
|
|
3646
|
-
type: "array",
|
|
3647
|
-
default: [],
|
|
3648
|
-
coerce: coerceArray
|
|
3649
|
-
};
|
|
3650
|
-
var skipPluginsOption = {
|
|
3651
|
-
describe: "List of plugins to skip. If not set all plugins are run.",
|
|
3652
|
-
type: "array",
|
|
3653
|
-
default: [],
|
|
3654
|
-
coerce: coerceArray,
|
|
3655
|
-
alias: "P"
|
|
3656
|
-
};
|
|
3657
|
-
var onlyPluginsOption = {
|
|
3658
|
-
describe: "List of plugins to run. If not set all plugins are run.",
|
|
3659
|
-
type: "array",
|
|
3660
|
-
default: [],
|
|
3661
|
-
coerce: coerceArray,
|
|
3662
|
-
alias: "p"
|
|
3663
|
-
};
|
|
3664
|
-
function yargsFilterOptionsDefinition() {
|
|
3665
|
-
return {
|
|
3666
|
-
skipCategories: skipCategoriesOption,
|
|
3667
|
-
onlyCategories: onlyCategoriesOption,
|
|
3668
|
-
skipPlugins: skipPluginsOption,
|
|
3669
|
-
onlyPlugins: onlyPluginsOption
|
|
3670
|
-
};
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
|
-
// packages/cli/src/lib/history/history.options.ts
|
|
3674
|
-
function yargsHistoryOptionsDefinition() {
|
|
3675
|
-
return {
|
|
3676
|
-
targetBranch: {
|
|
3677
|
-
describe: "Branch to crawl history",
|
|
3678
|
-
type: "string"
|
|
3679
|
-
},
|
|
3680
|
-
onlySemverTags: {
|
|
3681
|
-
describe: "Skip commits not tagged with a semantic version",
|
|
3682
|
-
type: "boolean",
|
|
3683
|
-
default: false
|
|
3684
|
-
},
|
|
3685
|
-
forceCleanStatus: {
|
|
3686
|
-
describe: "If we reset the status to a clean git history forcefully or not.",
|
|
3687
|
-
type: "boolean",
|
|
3688
|
-
default: false
|
|
3689
|
-
},
|
|
3690
|
-
skipUploads: {
|
|
3691
|
-
describe: "Upload created reports",
|
|
3692
|
-
type: "boolean",
|
|
3693
|
-
default: false
|
|
3694
|
-
},
|
|
3695
|
-
maxCount: {
|
|
3696
|
-
// https://git-scm.com/docs/git-log#Documentation/git-log.txt---max-countltnumbergt
|
|
3697
|
-
describe: "Number of steps in history",
|
|
3698
|
-
type: "number",
|
|
3699
|
-
// eslint-disable-next-line no-magic-numbers
|
|
3700
|
-
default: 5
|
|
3701
|
-
},
|
|
3702
|
-
from: {
|
|
3703
|
-
// https://git-scm.com/docs/git-log#Documentation/git-log.txt-ltrevision-rangegt
|
|
3704
|
-
describe: "hash to first commit in history",
|
|
3705
|
-
type: "string"
|
|
3706
|
-
},
|
|
3707
|
-
to: {
|
|
3708
|
-
// https://git-scm.com/docs/git-log#Documentation/git-log.txt-ltrevision-rangegt
|
|
3709
|
-
describe: "hash to last commit in history",
|
|
3710
|
-
type: "string"
|
|
3711
|
-
}
|
|
3712
|
-
};
|
|
3713
|
-
}
|
|
3714
|
-
|
|
3715
|
-
// packages/cli/src/lib/history/utils.ts
|
|
3716
|
-
async function normalizeHashOptions(processArgs) {
|
|
3717
|
-
const {
|
|
3718
|
-
onlySemverTags,
|
|
3719
|
-
// overwritten
|
|
3720
|
-
maxCount,
|
|
3721
|
-
...opt
|
|
3722
|
-
} = processArgs;
|
|
3723
|
-
let { from, to, ...processOptions } = opt;
|
|
3724
|
-
if (!onlySemverTags) {
|
|
3725
|
-
if (from && isSemver(from)) {
|
|
3726
|
-
const { hash } = await getHashFromTag(from);
|
|
3727
|
-
from = hash;
|
|
3728
|
-
}
|
|
3729
|
-
if (to && isSemver(to)) {
|
|
3730
|
-
const { hash } = await getHashFromTag(to);
|
|
3731
|
-
to = hash;
|
|
3732
|
-
}
|
|
3733
|
-
}
|
|
3734
|
-
return {
|
|
3735
|
-
...processOptions,
|
|
3736
|
-
onlySemverTags,
|
|
3737
|
-
maxCount: maxCount && maxCount > 0 ? maxCount : void 0,
|
|
3738
|
-
from,
|
|
3739
|
-
to
|
|
3740
|
-
};
|
|
3741
|
-
}
|
|
3742
|
-
|
|
3743
|
-
// packages/cli/src/lib/history/history-command.ts
|
|
3744
|
-
var command = "history";
|
|
3745
|
-
async function handler(args) {
|
|
3746
|
-
ui().logger.info(bold10(CLI_NAME));
|
|
3747
|
-
ui().logger.info(gray7(`Run ${command}`));
|
|
3748
|
-
const currentBranch = await getCurrentBranchOrTag();
|
|
3749
|
-
const { targetBranch: rawTargetBranch, ...opt } = args;
|
|
3750
|
-
const {
|
|
3751
|
-
targetBranch,
|
|
3752
|
-
from,
|
|
3753
|
-
to,
|
|
3754
|
-
maxCount,
|
|
3755
|
-
onlySemverTags,
|
|
3756
|
-
...historyOptions
|
|
3757
|
-
} = await normalizeHashOptions({
|
|
3758
|
-
...opt,
|
|
3759
|
-
targetBranch: rawTargetBranch ?? currentBranch
|
|
3760
|
-
});
|
|
3761
|
-
const filterOptions = { targetBranch, from, to, maxCount };
|
|
3762
|
-
const results = onlySemverTags ? await getSemverTags(filterOptions) : await getHashes(filterOptions);
|
|
3763
|
-
try {
|
|
3764
|
-
const reports = await history(
|
|
3765
|
-
{
|
|
3766
|
-
targetBranch,
|
|
3767
|
-
...historyOptions
|
|
3768
|
-
},
|
|
3769
|
-
results.map(({ hash }) => hash)
|
|
3770
|
-
);
|
|
3771
|
-
ui().logger.log(`Reports: ${reports.length}`);
|
|
3772
|
-
} finally {
|
|
3773
|
-
await safeCheckout(currentBranch);
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
function yargsHistoryCommandObject() {
|
|
3777
|
-
return {
|
|
3778
|
-
command,
|
|
3779
|
-
describe: "Collect reports for commit history",
|
|
3780
|
-
builder: (yargs3) => {
|
|
3781
|
-
yargs3.options({
|
|
3782
|
-
...yargsHistoryOptionsDefinition(),
|
|
3783
|
-
...yargsFilterOptionsDefinition()
|
|
3784
|
-
});
|
|
3785
|
-
yargs3.group(
|
|
3786
|
-
Object.keys(yargsHistoryOptionsDefinition()),
|
|
3787
|
-
"History Options:"
|
|
3788
|
-
);
|
|
3789
|
-
return yargs3;
|
|
3790
|
-
},
|
|
3791
|
-
handler
|
|
3792
|
-
};
|
|
3793
|
-
}
|
|
3794
|
-
|
|
3795
|
-
// packages/cli/src/lib/merge-diffs/merge-diffs-command.ts
|
|
3796
|
-
import { bold as bold11, gray as gray8 } from "ansis";
|
|
3797
|
-
|
|
3798
|
-
// packages/cli/src/lib/implementation/merge-diffs.options.ts
|
|
3799
|
-
function yargsMergeDiffsOptionsDefinition() {
|
|
3800
|
-
return {
|
|
3801
|
-
files: {
|
|
3802
|
-
describe: "List of report-diff.json paths",
|
|
3803
|
-
type: "array",
|
|
3804
|
-
demandOption: true
|
|
3805
|
-
}
|
|
3806
|
-
};
|
|
3807
|
-
}
|
|
3808
|
-
|
|
3809
|
-
// packages/cli/src/lib/merge-diffs/merge-diffs-command.ts
|
|
3810
|
-
function yargsMergeDiffsCommandObject() {
|
|
3811
|
-
const command2 = "merge-diffs";
|
|
3812
|
-
return {
|
|
3813
|
-
command: command2,
|
|
3814
|
-
describe: "Combine many report diffs into a single diff file",
|
|
3815
|
-
builder: yargsMergeDiffsOptionsDefinition(),
|
|
3816
|
-
handler: async (args) => {
|
|
3817
|
-
ui().logger.log(bold11(CLI_NAME));
|
|
3818
|
-
ui().logger.info(gray8(`Run ${command2}...`));
|
|
3819
|
-
const options2 = args;
|
|
3820
|
-
const { files, persist } = options2;
|
|
3821
|
-
const outputPath = await mergeDiffs(files, persist);
|
|
3822
|
-
ui().logger.info(`Reports diff written to ${bold11(outputPath)}`);
|
|
3823
|
-
}
|
|
3824
|
-
};
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
// packages/cli/src/lib/print-config/print-config-command.ts
|
|
3828
|
-
function yargsConfigCommandObject() {
|
|
3829
|
-
const command2 = "print-config";
|
|
3830
|
-
return {
|
|
3831
|
-
command: command2,
|
|
3832
|
-
describe: "Print config",
|
|
3833
|
-
handler: (yargsArgs) => {
|
|
3834
|
-
const { _, $0, ...args } = yargsArgs;
|
|
3835
|
-
const cleanArgs = filterKebabCaseKeys(args);
|
|
3836
|
-
ui().logger.log(JSON.stringify(cleanArgs, null, 2));
|
|
3837
|
-
}
|
|
3838
|
-
};
|
|
3839
|
-
}
|
|
3840
|
-
|
|
3841
|
-
// packages/cli/src/lib/upload/upload-command.ts
|
|
3842
|
-
import { bold as bold12, gray as gray9 } from "ansis";
|
|
3843
|
-
function yargsUploadCommandObject() {
|
|
3844
|
-
const command2 = "upload";
|
|
3845
|
-
return {
|
|
3846
|
-
command: command2,
|
|
3847
|
-
describe: "Upload report results to the portal",
|
|
3848
|
-
handler: async (args) => {
|
|
3849
|
-
ui().logger.log(bold12(CLI_NAME));
|
|
3850
|
-
ui().logger.info(gray9(`Run ${command2}...`));
|
|
3851
|
-
const options2 = args;
|
|
3852
|
-
if (options2.upload == null) {
|
|
3853
|
-
renderIntegratePortalHint();
|
|
3854
|
-
throw new Error("Upload configuration not set");
|
|
3855
|
-
}
|
|
3856
|
-
const report = await upload(options2);
|
|
3857
|
-
if (report?.url) {
|
|
3858
|
-
uploadSuccessfulLog(report.url);
|
|
3859
|
-
}
|
|
3860
|
-
}
|
|
3861
|
-
};
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
// packages/cli/src/lib/commands.ts
|
|
3865
|
-
var commands = [
|
|
3866
|
-
{
|
|
3867
|
-
...yargsAutorunCommandObject(),
|
|
3868
|
-
command: "*"
|
|
3869
|
-
},
|
|
3870
|
-
yargsAutorunCommandObject(),
|
|
3871
|
-
yargsCollectCommandObject(),
|
|
3872
|
-
yargsUploadCommandObject(),
|
|
3873
|
-
yargsHistoryCommandObject(),
|
|
3874
|
-
yargsCompareCommandObject(),
|
|
3875
|
-
yargsConfigCommandObject(),
|
|
3876
|
-
yargsMergeDiffsCommandObject()
|
|
3877
|
-
];
|
|
3878
|
-
|
|
3879
|
-
// packages/cli/src/lib/implementation/core-config.middleware.ts
|
|
3880
|
-
async function coreConfigMiddleware(processArgs) {
|
|
3881
|
-
const {
|
|
3882
|
-
config,
|
|
3883
|
-
tsconfig,
|
|
3884
|
-
persist: cliPersist,
|
|
3885
|
-
upload: cliUpload,
|
|
3886
|
-
...remainingCliOptions
|
|
3887
|
-
} = processArgs;
|
|
3888
|
-
const importedRc = config ? await readRcByPath(config, tsconfig) : await autoloadRc(tsconfig);
|
|
3889
|
-
const {
|
|
3890
|
-
persist: rcPersist,
|
|
3891
|
-
upload: rcUpload,
|
|
3892
|
-
...remainingRcConfig
|
|
3893
|
-
} = importedRc;
|
|
3894
|
-
const upload2 = rcUpload == null && cliUpload == null ? void 0 : uploadConfigSchema.parse({
|
|
3895
|
-
...rcUpload,
|
|
3896
|
-
...cliUpload
|
|
3897
|
-
});
|
|
3898
|
-
return {
|
|
3899
|
-
...config != null && { config },
|
|
3900
|
-
persist: {
|
|
3901
|
-
outputDir: cliPersist?.outputDir ?? rcPersist?.outputDir ?? DEFAULT_PERSIST_OUTPUT_DIR,
|
|
3902
|
-
filename: cliPersist?.filename ?? rcPersist?.filename ?? DEFAULT_PERSIST_FILENAME,
|
|
3903
|
-
format: normalizeFormats(
|
|
3904
|
-
cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT
|
|
3905
|
-
)
|
|
3906
|
-
},
|
|
3907
|
-
...upload2 != null && { upload: upload2 },
|
|
3908
|
-
...remainingRcConfig,
|
|
3909
|
-
...remainingCliOptions
|
|
3910
|
-
};
|
|
3911
|
-
}
|
|
3912
|
-
var normalizeFormats = (formats) => (formats ?? []).flatMap((format) => format.split(","));
|
|
3913
|
-
|
|
3914
|
-
// packages/cli/src/lib/implementation/filter.middleware.ts
|
|
3915
|
-
function filterMiddleware(originalProcessArgs) {
|
|
3916
|
-
const {
|
|
3917
|
-
plugins,
|
|
3918
|
-
categories,
|
|
3919
|
-
skipCategories = [],
|
|
3920
|
-
onlyCategories = [],
|
|
3921
|
-
skipPlugins = [],
|
|
3922
|
-
onlyPlugins = [],
|
|
3923
|
-
verbose = false
|
|
3924
|
-
} = originalProcessArgs;
|
|
3925
|
-
if (skipCategories.length === 0 && onlyCategories.length === 0 && skipPlugins.length === 0 && onlyPlugins.length === 0) {
|
|
3926
|
-
return originalProcessArgs;
|
|
3927
|
-
}
|
|
3928
|
-
handleConflictingOptions("categories", onlyCategories, skipCategories);
|
|
3929
|
-
handleConflictingOptions("plugins", onlyPlugins, skipPlugins);
|
|
3930
|
-
const filteredCategories = applyCategoryFilters(
|
|
3931
|
-
{ categories, plugins },
|
|
3932
|
-
skipCategories,
|
|
3933
|
-
onlyCategories,
|
|
3934
|
-
verbose
|
|
3935
|
-
);
|
|
3936
|
-
const filteredPlugins = applyPluginFilters(
|
|
3937
|
-
{ categories: filteredCategories, plugins },
|
|
3938
|
-
skipPlugins,
|
|
3939
|
-
onlyPlugins,
|
|
3940
|
-
verbose
|
|
3941
|
-
);
|
|
3942
|
-
const finalCategories = filteredCategories ? filterItemRefsBy(
|
|
3943
|
-
filteredCategories,
|
|
3944
|
-
(ref) => filteredPlugins.some((plugin) => plugin.slug === ref.plugin)
|
|
3945
|
-
) : filteredCategories;
|
|
3946
|
-
validateFinalState(
|
|
3947
|
-
{ categories: finalCategories, plugins: filteredPlugins },
|
|
3948
|
-
{ categories, plugins }
|
|
3949
|
-
);
|
|
3950
|
-
return {
|
|
3951
|
-
...originalProcessArgs,
|
|
3952
|
-
plugins: filteredPlugins,
|
|
3953
|
-
categories: finalCategories
|
|
3954
|
-
};
|
|
3955
|
-
}
|
|
3956
|
-
function applyFilters(items, skipItems, onlyItems, key) {
|
|
3957
|
-
return items.filter((item) => {
|
|
3958
|
-
const itemKey = item[key];
|
|
3959
|
-
return !skipItems.includes(itemKey) && (onlyItems.length === 0 || onlyItems.includes(itemKey));
|
|
3960
|
-
});
|
|
3961
|
-
}
|
|
3962
|
-
function applyCategoryFilters({ categories, plugins }, skipCategories, onlyCategories, verbose) {
|
|
3963
|
-
if (skipCategories.length === 0 && onlyCategories.length === 0 || !categories || categories.length === 0) {
|
|
3964
|
-
return categories;
|
|
3965
|
-
}
|
|
3966
|
-
validateFilterOption(
|
|
3967
|
-
"skipCategories",
|
|
3968
|
-
{ plugins, categories },
|
|
3969
|
-
{ itemsToFilter: skipCategories, verbose }
|
|
3970
|
-
);
|
|
3971
|
-
validateFilterOption(
|
|
3972
|
-
"onlyCategories",
|
|
3973
|
-
{ plugins, categories },
|
|
3974
|
-
{ itemsToFilter: onlyCategories, verbose }
|
|
3975
|
-
);
|
|
3976
|
-
return applyFilters(categories, skipCategories, onlyCategories, "slug");
|
|
3977
|
-
}
|
|
3978
|
-
function applyPluginFilters({ categories, plugins }, skipPlugins, onlyPlugins, verbose) {
|
|
3979
|
-
const filteredPlugins = filterPluginsFromCategories({
|
|
3980
|
-
categories,
|
|
3981
|
-
plugins
|
|
3982
|
-
});
|
|
3983
|
-
if (skipPlugins.length === 0 && onlyPlugins.length === 0) {
|
|
3984
|
-
return filteredPlugins;
|
|
3985
|
-
}
|
|
3986
|
-
validateFilterOption(
|
|
3987
|
-
"skipPlugins",
|
|
3988
|
-
{ plugins: filteredPlugins, categories },
|
|
3989
|
-
{ itemsToFilter: skipPlugins, verbose }
|
|
3990
|
-
);
|
|
3991
|
-
validateFilterOption(
|
|
3992
|
-
"onlyPlugins",
|
|
3993
|
-
{ plugins: filteredPlugins, categories },
|
|
3994
|
-
{ itemsToFilter: onlyPlugins, verbose }
|
|
3995
|
-
);
|
|
3996
|
-
return applyFilters(filteredPlugins, skipPlugins, onlyPlugins, "slug");
|
|
3997
|
-
}
|
|
3998
|
-
function filterPluginsFromCategories({
|
|
3999
|
-
categories,
|
|
4000
|
-
plugins
|
|
4001
|
-
}) {
|
|
4002
|
-
if (!categories || categories.length === 0) {
|
|
4003
|
-
return plugins;
|
|
4004
|
-
}
|
|
4005
|
-
const validPluginSlugs = new Set(
|
|
4006
|
-
categories.flatMap((category) => category.refs.map((ref) => ref.plugin))
|
|
4007
|
-
);
|
|
4008
|
-
return plugins.filter((plugin) => validPluginSlugs.has(plugin.slug));
|
|
4009
|
-
}
|
|
4010
|
-
|
|
4011
|
-
// packages/cli/src/lib/middlewares.ts
|
|
4012
|
-
var middlewares = [
|
|
4013
|
-
{
|
|
4014
|
-
middlewareFunction: coreConfigMiddleware,
|
|
4015
|
-
applyBeforeValidation: false
|
|
4016
|
-
},
|
|
4017
|
-
{
|
|
4018
|
-
middlewareFunction: filterMiddleware,
|
|
4019
|
-
applyBeforeValidation: false
|
|
4020
|
-
}
|
|
4021
|
-
];
|
|
4022
|
-
|
|
4023
|
-
// packages/cli/src/lib/implementation/core-config.options.ts
|
|
4024
|
-
function yargsCoreConfigOptionsDefinition() {
|
|
4025
|
-
return {
|
|
4026
|
-
...yargsPersistConfigOptionsDefinition(),
|
|
4027
|
-
...yargsUploadConfigOptionsDefinition()
|
|
4028
|
-
};
|
|
4029
|
-
}
|
|
4030
|
-
function yargsPersistConfigOptionsDefinition() {
|
|
4031
|
-
return {
|
|
4032
|
-
"persist.outputDir": {
|
|
4033
|
-
describe: "Directory for the produced reports",
|
|
4034
|
-
type: "string"
|
|
4035
|
-
},
|
|
4036
|
-
"persist.filename": {
|
|
4037
|
-
describe: "Filename for the produced reports.",
|
|
4038
|
-
type: "string"
|
|
4039
|
-
},
|
|
4040
|
-
"persist.format": {
|
|
4041
|
-
describe: "Format of the report output. e.g. `md`, `json`",
|
|
4042
|
-
type: "array"
|
|
4043
|
-
}
|
|
4044
|
-
};
|
|
4045
|
-
}
|
|
4046
|
-
function yargsUploadConfigOptionsDefinition() {
|
|
4047
|
-
return {
|
|
4048
|
-
"upload.organization": {
|
|
4049
|
-
describe: "Organization slug from portal",
|
|
4050
|
-
type: "string"
|
|
4051
|
-
},
|
|
4052
|
-
"upload.project": {
|
|
4053
|
-
describe: "Project slug from portal",
|
|
4054
|
-
type: "string"
|
|
4055
|
-
},
|
|
4056
|
-
"upload.server": {
|
|
4057
|
-
describe: "URL to your portal server",
|
|
4058
|
-
type: "string"
|
|
4059
|
-
},
|
|
4060
|
-
"upload.apiKey": {
|
|
4061
|
-
describe: "API key for the portal server",
|
|
4062
|
-
type: "string"
|
|
4063
|
-
}
|
|
4064
|
-
};
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
// packages/cli/src/lib/implementation/global.options.ts
|
|
4068
|
-
function yargsGlobalOptionsDefinition() {
|
|
4069
|
-
return {
|
|
4070
|
-
progress: {
|
|
4071
|
-
describe: "Show progress bar in stdout.",
|
|
4072
|
-
type: "boolean",
|
|
4073
|
-
default: true
|
|
4074
|
-
},
|
|
4075
|
-
verbose: {
|
|
4076
|
-
describe: "When true creates more verbose output. This is helpful when debugging.",
|
|
4077
|
-
type: "boolean",
|
|
4078
|
-
default: false
|
|
4079
|
-
},
|
|
4080
|
-
config: {
|
|
4081
|
-
describe: "Path to config file. By default it loads code-pushup.config.(ts|mjs|js).",
|
|
4082
|
-
type: "string"
|
|
4083
|
-
},
|
|
4084
|
-
tsconfig: {
|
|
4085
|
-
describe: "Path to a TypeScript config, to be used when loading config file.",
|
|
4086
|
-
type: "string"
|
|
4087
|
-
}
|
|
4088
|
-
};
|
|
4089
|
-
}
|
|
4090
|
-
|
|
4091
|
-
// packages/cli/src/lib/options.ts
|
|
4092
|
-
var options = {
|
|
4093
|
-
...yargsGlobalOptionsDefinition(),
|
|
4094
|
-
...yargsCoreConfigOptionsDefinition(),
|
|
4095
|
-
...yargsFilterOptionsDefinition()
|
|
4096
|
-
};
|
|
4097
|
-
var groups = {
|
|
4098
|
-
"Global Options:": [
|
|
4099
|
-
...Object.keys(yargsGlobalOptionsDefinition()),
|
|
4100
|
-
...Object.keys(yargsFilterOptionsDefinition())
|
|
4101
|
-
],
|
|
4102
|
-
"Persist Options:": Object.keys(yargsPersistConfigOptionsDefinition()),
|
|
4103
|
-
"Upload Options:": Object.keys(yargsUploadConfigOptionsDefinition())
|
|
4104
|
-
};
|
|
4105
|
-
|
|
4106
|
-
// packages/cli/src/lib/yargs-cli.ts
|
|
4107
|
-
import { blue, dim as dim2, green as green4 } from "ansis";
|
|
4108
|
-
import yargs2 from "yargs";
|
|
4109
|
-
|
|
4110
|
-
// packages/cli/package.json
|
|
4111
|
-
var version2 = "0.55.0";
|
|
4112
|
-
|
|
4113
|
-
// packages/cli/src/lib/implementation/formatting.ts
|
|
4114
|
-
import { bold as bold13, dim, green as green3 } from "ansis";
|
|
4115
|
-
function titleStyle(title) {
|
|
4116
|
-
return `${bold13(title)}`;
|
|
4117
|
-
}
|
|
4118
|
-
function headerStyle(title) {
|
|
4119
|
-
return `${green3(title)}`;
|
|
4120
|
-
}
|
|
4121
|
-
function descriptionStyle(title) {
|
|
4122
|
-
return `${dim(title)}`;
|
|
4123
|
-
}
|
|
4124
|
-
function formatObjectValue(opts, propName) {
|
|
4125
|
-
const description = opts[propName];
|
|
4126
|
-
return {
|
|
4127
|
-
...opts,
|
|
4128
|
-
...typeof description === "string" && {
|
|
4129
|
-
[propName]: descriptionStyle(description)
|
|
4130
|
-
}
|
|
4131
|
-
};
|
|
4132
|
-
}
|
|
4133
|
-
function formatNestedValues(options2, propName) {
|
|
4134
|
-
return Object.fromEntries(
|
|
4135
|
-
Object.entries(options2).map(([key, opts]) => [
|
|
4136
|
-
key,
|
|
4137
|
-
formatObjectValue(opts, propName)
|
|
4138
|
-
])
|
|
4139
|
-
);
|
|
4140
|
-
}
|
|
4141
|
-
|
|
4142
|
-
// packages/cli/src/lib/yargs-cli.ts
|
|
4143
|
-
var yargsDecorator = {
|
|
4144
|
-
"Commands:": `${green4("Commands")}:`,
|
|
4145
|
-
"Options:": `${green4("Options")}:`,
|
|
4146
|
-
"Examples:": `${green4("Examples")}:`,
|
|
4147
|
-
boolean: blue("boolean"),
|
|
4148
|
-
count: blue("count"),
|
|
4149
|
-
string: blue("string"),
|
|
4150
|
-
array: blue("array"),
|
|
4151
|
-
required: blue("required"),
|
|
4152
|
-
"default:": `${blue("default")}:`,
|
|
4153
|
-
"choices:": `${blue("choices")}:`,
|
|
4154
|
-
"aliases:": `${blue("aliases")}:`
|
|
4155
|
-
};
|
|
4156
|
-
function yargsCli(argv, cfg) {
|
|
4157
|
-
const { usageMessage, scriptName, noExitProcess } = cfg;
|
|
4158
|
-
const commands2 = cfg.commands ?? [];
|
|
4159
|
-
const middlewares2 = cfg.middlewares ?? [];
|
|
4160
|
-
const options2 = cfg.options ?? {};
|
|
4161
|
-
const groups2 = cfg.groups ?? {};
|
|
4162
|
-
const examples = cfg.examples ?? [];
|
|
4163
|
-
const cli2 = yargs2(argv);
|
|
4164
|
-
cli2.updateLocale(yargsDecorator).wrap(Math.max(TERMINAL_WIDTH, cli2.terminalWidth())).help("help", descriptionStyle("Show help")).alias("h", "help").showHelpOnFail(false).version("version", dim2`Show version`, version2).check((args) => {
|
|
4165
|
-
const persist = args["persist"];
|
|
4166
|
-
return persist == null || validatePersistFormat(persist);
|
|
4167
|
-
}).parserConfiguration({
|
|
4168
|
-
"strip-dashed": true
|
|
4169
|
-
}).coerce(
|
|
4170
|
-
"config",
|
|
4171
|
-
(config) => Array.isArray(config) ? config.at(-1) : config
|
|
4172
|
-
).options(formatNestedValues(options2, "describe"));
|
|
4173
|
-
if (usageMessage) {
|
|
4174
|
-
cli2.usage(titleStyle(usageMessage));
|
|
4175
|
-
}
|
|
4176
|
-
if (scriptName) {
|
|
4177
|
-
cli2.scriptName(scriptName);
|
|
4178
|
-
}
|
|
4179
|
-
examples.forEach(
|
|
4180
|
-
([exampleName, description]) => cli2.example(exampleName, descriptionStyle(description))
|
|
4181
|
-
);
|
|
4182
|
-
Object.entries(groups2).forEach(
|
|
4183
|
-
([groupName, optionNames]) => cli2.group(optionNames, headerStyle(groupName))
|
|
4184
|
-
);
|
|
4185
|
-
middlewares2.forEach(({ middlewareFunction, applyBeforeValidation }) => {
|
|
4186
|
-
cli2.middleware(
|
|
4187
|
-
logErrorBeforeThrow(middlewareFunction),
|
|
4188
|
-
applyBeforeValidation
|
|
4189
|
-
);
|
|
4190
|
-
});
|
|
4191
|
-
commands2.forEach((commandObj) => {
|
|
4192
|
-
cli2.command(
|
|
4193
|
-
formatObjectValue(
|
|
4194
|
-
{
|
|
4195
|
-
...commandObj,
|
|
4196
|
-
handler: logErrorBeforeThrow(commandObj.handler),
|
|
4197
|
-
...typeof commandObj.builder === "function" && {
|
|
4198
|
-
builder: logErrorBeforeThrow(commandObj.builder)
|
|
4199
|
-
}
|
|
4200
|
-
},
|
|
4201
|
-
"describe"
|
|
4202
|
-
)
|
|
4203
|
-
);
|
|
4204
|
-
});
|
|
4205
|
-
if (noExitProcess) {
|
|
4206
|
-
cli2.exitProcess(false);
|
|
4207
|
-
}
|
|
4208
|
-
return cli2;
|
|
4209
|
-
}
|
|
4210
|
-
function validatePersistFormat(persist) {
|
|
4211
|
-
try {
|
|
4212
|
-
if (persist.format != null) {
|
|
4213
|
-
persist.format.flatMap((format) => format.split(",")).forEach((format) => formatSchema.parse(format));
|
|
4214
|
-
}
|
|
4215
|
-
return true;
|
|
4216
|
-
} catch {
|
|
4217
|
-
throw new Error(
|
|
4218
|
-
`Invalid persist.format option. Valid options are: ${Object.values(
|
|
4219
|
-
formatSchema.Values
|
|
4220
|
-
).join(", ")}`
|
|
4221
|
-
);
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
|
|
4225
|
-
// packages/cli/src/lib/cli.ts
|
|
4226
|
-
var cli = (args) => yargsCli(args, {
|
|
4227
|
-
usageMessage: CLI_NAME,
|
|
4228
|
-
scriptName: CLI_SCRIPT_NAME,
|
|
4229
|
-
options,
|
|
4230
|
-
groups,
|
|
4231
|
-
examples: [
|
|
4232
|
-
[
|
|
4233
|
-
"code-pushup",
|
|
4234
|
-
"Run collect followed by upload based on configuration from code-pushup.config.* file."
|
|
4235
|
-
],
|
|
4236
|
-
[
|
|
4237
|
-
"code-pushup collect --tsconfig=tsconfig.base.json",
|
|
4238
|
-
"Run collect using custom tsconfig to parse code-pushup.config.ts file."
|
|
4239
|
-
],
|
|
4240
|
-
[
|
|
4241
|
-
"code-pushup collect --onlyPlugins=coverage",
|
|
4242
|
-
"Run collect with only coverage plugin, other plugins from config file will be skipped."
|
|
4243
|
-
],
|
|
4244
|
-
[
|
|
4245
|
-
"code-pushup collect --skipPlugins=coverage",
|
|
4246
|
-
"Run collect skiping the coverage plugin, other plugins from config file will be included."
|
|
4247
|
-
],
|
|
4248
|
-
[
|
|
4249
|
-
"code-pushup upload --persist.outputDir=dist --upload.apiKey=$CP_API_KEY",
|
|
4250
|
-
"Upload dist/report.json to portal using API key from environment variable"
|
|
4251
|
-
],
|
|
4252
|
-
[
|
|
4253
|
-
"code-pushup print-config --config code-pushup.config.test.js",
|
|
4254
|
-
"Print resolved config object parsed from custom config location"
|
|
4255
|
-
]
|
|
4256
|
-
],
|
|
4257
|
-
middlewares,
|
|
4258
|
-
commands
|
|
4259
|
-
});
|
|
4260
|
-
|
|
4261
|
-
// packages/cli/src/index.ts
|
|
4262
|
-
await cli(hideBin(process.argv)).argv;
|