@code-pushup/ci 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.
Files changed (85) hide show
  1. package/README.md +38 -16
  2. package/package.json +10 -8
  3. package/src/index.d.ts +4 -4
  4. package/src/index.js +3 -0
  5. package/src/index.js.map +1 -0
  6. package/src/lib/cli/commands/collect.d.ts +2 -3
  7. package/src/lib/cli/commands/collect.js +16 -0
  8. package/src/lib/cli/commands/collect.js.map +1 -0
  9. package/src/lib/cli/commands/compare.d.ts +2 -3
  10. package/src/lib/cli/commands/compare.js +20 -0
  11. package/src/lib/cli/commands/compare.js.map +1 -0
  12. package/src/lib/cli/commands/merge-diffs.d.ts +2 -3
  13. package/src/lib/cli/commands/merge-diffs.js +23 -0
  14. package/src/lib/cli/commands/merge-diffs.js.map +1 -0
  15. package/src/lib/cli/commands/print-config.d.ts +2 -2
  16. package/src/lib/cli/commands/print-config.js +26 -0
  17. package/src/lib/cli/commands/print-config.js.map +1 -0
  18. package/src/lib/cli/context.d.ts +3 -5
  19. package/src/lib/cli/context.js +9 -0
  20. package/src/lib/cli/context.js.map +1 -0
  21. package/src/lib/cli/index.d.ts +6 -6
  22. package/src/lib/cli/index.js +7 -0
  23. package/src/lib/cli/index.js.map +1 -0
  24. package/src/lib/cli/persist.d.ts +5 -26
  25. package/src/lib/cli/persist.js +24 -0
  26. package/src/lib/cli/persist.js.map +1 -0
  27. package/src/lib/comment.d.ts +1 -1
  28. package/src/lib/comment.js +29 -0
  29. package/src/lib/comment.js.map +1 -0
  30. package/src/lib/constants.d.ts +1 -1
  31. package/src/lib/constants.js +15 -0
  32. package/src/lib/constants.js.map +1 -0
  33. package/src/lib/git.js +99 -0
  34. package/src/lib/git.js.map +1 -0
  35. package/src/lib/issues.d.ts +1 -1
  36. package/src/lib/issues.js +96 -0
  37. package/src/lib/issues.js.map +1 -0
  38. package/src/lib/models.d.ts +10 -11
  39. package/src/lib/models.js +2 -0
  40. package/src/lib/models.js.map +1 -0
  41. package/src/lib/monorepo/detect-tool.d.ts +1 -1
  42. package/src/lib/monorepo/detect-tool.js +11 -0
  43. package/src/lib/monorepo/detect-tool.js.map +1 -0
  44. package/src/lib/monorepo/handlers/index.d.ts +1 -1
  45. package/src/lib/monorepo/handlers/index.js +20 -0
  46. package/src/lib/monorepo/handlers/index.js.map +1 -0
  47. package/src/lib/monorepo/handlers/npm.d.ts +1 -1
  48. package/src/lib/monorepo/handlers/npm.js +29 -0
  49. package/src/lib/monorepo/handlers/npm.js.map +1 -0
  50. package/src/lib/monorepo/handlers/nx.d.ts +1 -1
  51. package/src/lib/monorepo/handlers/nx.js +62 -0
  52. package/src/lib/monorepo/handlers/nx.js.map +1 -0
  53. package/src/lib/monorepo/handlers/pnpm.d.ts +1 -1
  54. package/src/lib/monorepo/handlers/pnpm.js +47 -0
  55. package/src/lib/monorepo/handlers/pnpm.js.map +1 -0
  56. package/src/lib/monorepo/handlers/turbo.d.ts +1 -1
  57. package/src/lib/monorepo/handlers/turbo.js +48 -0
  58. package/src/lib/monorepo/handlers/turbo.js.map +1 -0
  59. package/src/lib/monorepo/handlers/yarn.d.ts +1 -1
  60. package/src/lib/monorepo/handlers/yarn.js +44 -0
  61. package/src/lib/monorepo/handlers/yarn.js.map +1 -0
  62. package/src/lib/monorepo/index.d.ts +2 -2
  63. package/src/lib/monorepo/index.js +3 -0
  64. package/src/lib/monorepo/index.js.map +1 -0
  65. package/src/lib/monorepo/list-projects.d.ts +9 -3
  66. package/src/lib/monorepo/list-projects.js +99 -0
  67. package/src/lib/monorepo/list-projects.js.map +1 -0
  68. package/src/lib/monorepo/packages.js +50 -0
  69. package/src/lib/monorepo/packages.js.map +1 -0
  70. package/src/lib/monorepo/tools.d.ts +7 -0
  71. package/src/lib/monorepo/tools.js +5 -0
  72. package/src/lib/monorepo/tools.js.map +1 -0
  73. package/src/lib/run-monorepo.d.ts +3 -0
  74. package/src/lib/run-monorepo.js +141 -0
  75. package/src/lib/run-monorepo.js.map +1 -0
  76. package/src/lib/run-standalone.d.ts +3 -0
  77. package/src/lib/run-standalone.js +19 -0
  78. package/src/lib/run-standalone.js.map +1 -0
  79. package/src/lib/run-utils.d.ts +45 -0
  80. package/src/lib/run-utils.js +158 -0
  81. package/src/lib/run-utils.js.map +1 -0
  82. package/src/lib/run.d.ts +1 -1
  83. package/src/lib/run.js +24 -0
  84. package/src/lib/run.js.map +1 -0
  85. package/index.js +0 -1731
package/index.js DELETED
@@ -1,1731 +0,0 @@
1
- // packages/ci/src/lib/monorepo/list-projects.ts
2
- import { glob as glob2 } from "glob";
3
- import { join as join7 } from "node:path";
4
-
5
- // packages/ci/src/lib/monorepo/handlers/npm.ts
6
- import { join as join2 } from "node:path";
7
-
8
- // packages/models/src/lib/implementation/schemas.ts
9
- import { MATERIAL_ICONS } from "vscode-material-icons";
10
- import { z } from "zod";
11
-
12
- // packages/models/src/lib/implementation/limits.ts
13
- var MAX_SLUG_LENGTH = 128;
14
- var MAX_TITLE_LENGTH = 256;
15
- var MAX_DESCRIPTION_LENGTH = 65536;
16
- var MAX_ISSUE_MESSAGE_LENGTH = 1024;
17
-
18
- // packages/models/src/lib/implementation/utils.ts
19
- var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
20
- var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
21
- function hasDuplicateStrings(strings) {
22
- const sortedStrings = strings.toSorted();
23
- const duplStrings = sortedStrings.filter(
24
- (item, index) => index !== 0 && item === sortedStrings[index - 1]
25
- );
26
- return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
27
- }
28
- function hasMissingStrings(toCheck, existing) {
29
- const nonExisting = toCheck.filter((s) => !existing.includes(s));
30
- return nonExisting.length === 0 ? false : nonExisting;
31
- }
32
- function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
33
- return transform(items || []);
34
- }
35
- function exists(value) {
36
- return value != null;
37
- }
38
- function getMissingRefsForCategories(categories, plugins) {
39
- if (!categories || categories.length === 0) {
40
- return false;
41
- }
42
- const auditRefsFromCategory = categories.flatMap(
43
- ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
44
- );
45
- const auditRefsFromPlugins = plugins.flatMap(
46
- ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
47
- );
48
- const missingAuditRefs = hasMissingStrings(
49
- auditRefsFromCategory,
50
- auditRefsFromPlugins
51
- );
52
- const groupRefsFromCategory = categories.flatMap(
53
- ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
54
- );
55
- const groupRefsFromPlugins = plugins.flatMap(
56
- ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
57
- );
58
- const missingGroupRefs = hasMissingStrings(
59
- groupRefsFromCategory,
60
- groupRefsFromPlugins
61
- );
62
- const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
63
- return missingRefs.length > 0 ? missingRefs : false;
64
- }
65
- function missingRefsForCategoriesErrorMsg(categories, plugins) {
66
- const missingRefs = getMissingRefsForCategories(categories, plugins);
67
- return `The following category references need to point to an audit or group: ${errorItems(
68
- missingRefs
69
- )}`;
70
- }
71
-
72
- // packages/models/src/lib/implementation/schemas.ts
73
- var tableCellValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]).default(null);
74
- function executionMetaSchema(options = {
75
- descriptionDate: "Execution start date and time",
76
- descriptionDuration: "Execution duration in ms"
77
- }) {
78
- return z.object({
79
- date: z.string({ description: options.descriptionDate }),
80
- duration: z.number({ description: options.descriptionDuration })
81
- });
82
- }
83
- var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
84
- message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
85
- }).max(MAX_SLUG_LENGTH, {
86
- message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
87
- });
88
- var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
89
- var urlSchema = z.string().url();
90
- var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
91
- var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
92
- var scoreSchema = z.number({
93
- description: "Value between 0 and 1"
94
- }).min(0).max(1);
95
- function metaSchema(options) {
96
- const {
97
- descriptionDescription,
98
- titleDescription,
99
- docsUrlDescription,
100
- description
101
- } = options ?? {};
102
- return z.object(
103
- {
104
- title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
105
- description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
106
- docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
107
- },
108
- { description }
109
- );
110
- }
111
- var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
112
- var fileNameSchema = z.string().trim().regex(filenameRegex, {
113
- message: `The filename has to be valid`
114
- }).min(1, { message: "file name is invalid" });
115
- var positiveIntSchema = z.number().int().positive();
116
- var nonnegativeNumberSchema = z.number().nonnegative();
117
- function packageVersionSchema(options) {
118
- const { versionDescription = "NPM version of the package", required } = options ?? {};
119
- const packageSchema = z.string({ description: "NPM package name" });
120
- const versionSchema = z.string({ description: versionDescription });
121
- return z.object(
122
- {
123
- packageName: required ? packageSchema : packageSchema.optional(),
124
- version: required ? versionSchema : versionSchema.optional()
125
- },
126
- { description: "NPM package name and version of a published package" }
127
- );
128
- }
129
- var weightSchema = nonnegativeNumberSchema.describe(
130
- "Coefficient for the given score (use weight 0 if only for display)"
131
- );
132
- function weightedRefSchema(description, slugDescription) {
133
- return z.object(
134
- {
135
- slug: slugSchema.describe(slugDescription),
136
- weight: weightSchema.describe("Weight used to calculate score")
137
- },
138
- { description }
139
- );
140
- }
141
- function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
142
- return z.object(
143
- {
144
- slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
145
- refs: z.array(refSchema).min(1).refine(
146
- (refs) => !duplicateCheckFn(refs),
147
- (refs) => ({
148
- message: duplicateMessageFn(refs)
149
- })
150
- ).refine(hasNonZeroWeightedRef, () => ({
151
- message: "In a category there has to be at least one ref with weight > 0"
152
- }))
153
- },
154
- { description }
155
- );
156
- }
157
- var materialIconSchema = z.enum(MATERIAL_ICONS, {
158
- description: "Icon from VSCode Material Icons extension"
159
- });
160
- function hasNonZeroWeightedRef(refs) {
161
- return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
162
- }
163
-
164
- // packages/models/src/lib/source.ts
165
- import { z as z2 } from "zod";
166
- var sourceFileLocationSchema = z2.object(
167
- {
168
- file: filePathSchema.describe("Relative path to source file in Git repo"),
169
- position: z2.object(
170
- {
171
- startLine: positiveIntSchema.describe("Start line"),
172
- startColumn: positiveIntSchema.describe("Start column").optional(),
173
- endLine: positiveIntSchema.describe("End line").optional(),
174
- endColumn: positiveIntSchema.describe("End column").optional()
175
- },
176
- { description: "Location in file" }
177
- ).optional()
178
- },
179
- { description: "Source file location" }
180
- );
181
-
182
- // packages/models/src/lib/audit.ts
183
- import { z as z3 } from "zod";
184
- var auditSchema = z3.object({
185
- slug: slugSchema.describe("ID (unique within plugin)")
186
- }).merge(
187
- metaSchema({
188
- titleDescription: "Descriptive name",
189
- descriptionDescription: "Description (markdown)",
190
- docsUrlDescription: "Link to documentation (rationale)",
191
- description: "List of scorable metrics for the given plugin"
192
- })
193
- );
194
- var pluginAuditsSchema = z3.array(auditSchema, {
195
- description: "List of audits maintained in a plugin"
196
- }).min(1).refine(
197
- (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
198
- (auditMetadata) => ({
199
- message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
200
- })
201
- );
202
- function duplicateSlugsInAuditsErrorMsg(audits) {
203
- const duplicateRefs = getDuplicateSlugsInAudits(audits);
204
- return `In plugin audits the following slugs are not unique: ${errorItems(
205
- duplicateRefs
206
- )}`;
207
- }
208
- function getDuplicateSlugsInAudits(audits) {
209
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
210
- }
211
-
212
- // packages/models/src/lib/audit-output.ts
213
- import { z as z6 } from "zod";
214
-
215
- // packages/models/src/lib/issue.ts
216
- import { z as z4 } from "zod";
217
- var issueSeveritySchema = z4.enum(["info", "warning", "error"], {
218
- description: "Severity level"
219
- });
220
- var issueSchema = z4.object(
221
- {
222
- message: z4.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
223
- severity: issueSeveritySchema,
224
- source: sourceFileLocationSchema.optional()
225
- },
226
- { description: "Issue information" }
227
- );
228
-
229
- // packages/models/src/lib/table.ts
230
- import { z as z5 } from "zod";
231
- var tableAlignmentSchema = z5.enum(["left", "center", "right"], {
232
- description: "Cell alignment"
233
- });
234
- var tableColumnObjectSchema = z5.object({
235
- key: z5.string(),
236
- label: z5.string().optional(),
237
- align: tableAlignmentSchema.optional()
238
- });
239
- var tableRowObjectSchema = z5.record(tableCellValueSchema, {
240
- description: "Object row"
241
- });
242
- var tableRowPrimitiveSchema = z5.array(tableCellValueSchema, {
243
- description: "Primitive row"
244
- });
245
- var tableSharedSchema = z5.object({
246
- title: z5.string().optional().describe("Display title for table")
247
- });
248
- var tablePrimitiveSchema = tableSharedSchema.merge(
249
- z5.object(
250
- {
251
- columns: z5.array(tableAlignmentSchema).optional(),
252
- rows: z5.array(tableRowPrimitiveSchema)
253
- },
254
- { description: "Table with primitive rows and optional alignment columns" }
255
- )
256
- );
257
- var tableObjectSchema = tableSharedSchema.merge(
258
- z5.object(
259
- {
260
- columns: z5.union([
261
- z5.array(tableAlignmentSchema),
262
- z5.array(tableColumnObjectSchema)
263
- ]).optional(),
264
- rows: z5.array(tableRowObjectSchema)
265
- },
266
- {
267
- description: "Table with object rows and optional alignment or object columns"
268
- }
269
- )
270
- );
271
- var tableSchema = (description = "Table information") => z5.union([tablePrimitiveSchema, tableObjectSchema], { description });
272
-
273
- // packages/models/src/lib/audit-output.ts
274
- var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
275
- var auditDisplayValueSchema = z6.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
276
- var auditDetailsSchema = z6.object(
277
- {
278
- issues: z6.array(issueSchema, { description: "List of findings" }).optional(),
279
- table: tableSchema("Table of related findings").optional()
280
- },
281
- { description: "Detailed information" }
282
- );
283
- var auditOutputSchema = z6.object(
284
- {
285
- slug: slugSchema.describe("Reference to audit"),
286
- displayValue: auditDisplayValueSchema,
287
- value: auditValueSchema,
288
- score: scoreSchema,
289
- details: auditDetailsSchema.optional()
290
- },
291
- { description: "Audit information" }
292
- );
293
- var auditOutputsSchema = z6.array(auditOutputSchema, {
294
- description: "List of JSON formatted audit output emitted by the runner process of a plugin"
295
- }).refine(
296
- (audits) => !getDuplicateSlugsInAudits2(audits),
297
- (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
298
- );
299
- function duplicateSlugsInAuditsErrorMsg2(audits) {
300
- const duplicateRefs = getDuplicateSlugsInAudits2(audits);
301
- return `In plugin audits the slugs are not unique: ${errorItems(
302
- duplicateRefs
303
- )}`;
304
- }
305
- function getDuplicateSlugsInAudits2(audits) {
306
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
307
- }
308
-
309
- // packages/models/src/lib/category-config.ts
310
- import { z as z7 } from "zod";
311
- var categoryRefSchema = weightedRefSchema(
312
- "Weighted references to audits and/or groups for the category",
313
- "Slug of an audit or group (depending on `type`)"
314
- ).merge(
315
- z7.object({
316
- type: z7.enum(["audit", "group"], {
317
- description: "Discriminant for reference kind, affects where `slug` is looked up"
318
- }),
319
- plugin: slugSchema.describe(
320
- "Plugin slug (plugin should contain referenced audit or group)"
321
- )
322
- })
323
- );
324
- var categoryConfigSchema = scorableSchema(
325
- "Category with a score calculated from audits and groups from various plugins",
326
- categoryRefSchema,
327
- getDuplicateRefsInCategoryMetrics,
328
- duplicateRefsInCategoryMetricsErrorMsg
329
- ).merge(
330
- metaSchema({
331
- titleDescription: "Category Title",
332
- docsUrlDescription: "Category docs URL",
333
- descriptionDescription: "Category description",
334
- description: "Meta info for category"
335
- })
336
- ).merge(
337
- z7.object({
338
- isBinary: z7.boolean({
339
- description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
340
- }).optional()
341
- })
342
- );
343
- function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
344
- const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
345
- return `In the categories, the following audit or group refs are duplicates: ${errorItems(
346
- duplicateRefs
347
- )}`;
348
- }
349
- function getDuplicateRefsInCategoryMetrics(metrics) {
350
- return hasDuplicateStrings(
351
- metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
352
- );
353
- }
354
- var categoriesSchema = z7.array(categoryConfigSchema, {
355
- description: "Categorization of individual audits"
356
- }).refine(
357
- (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
358
- (categoryCfg) => ({
359
- message: duplicateSlugCategoriesErrorMsg(categoryCfg)
360
- })
361
- );
362
- function duplicateSlugCategoriesErrorMsg(categories) {
363
- const duplicateStringSlugs = getDuplicateSlugCategories(categories);
364
- return `In the categories, the following slugs are duplicated: ${errorItems(
365
- duplicateStringSlugs
366
- )}`;
367
- }
368
- function getDuplicateSlugCategories(categories) {
369
- return hasDuplicateStrings(categories.map(({ slug }) => slug));
370
- }
371
-
372
- // packages/models/src/lib/commit.ts
373
- import { z as z8 } from "zod";
374
- var commitSchema = z8.object(
375
- {
376
- hash: z8.string({ description: "Commit SHA (full)" }).regex(
377
- /^[\da-f]{40}$/,
378
- "Commit SHA should be a 40-character hexadecimal string"
379
- ),
380
- message: z8.string({ description: "Commit message" }),
381
- date: z8.coerce.date({
382
- description: "Date and time when commit was authored"
383
- }),
384
- author: z8.string({
385
- description: "Commit author name"
386
- }).trim()
387
- },
388
- { description: "Git commit" }
389
- );
390
-
391
- // packages/models/src/lib/core-config.ts
392
- import { z as z14 } from "zod";
393
-
394
- // packages/models/src/lib/persist-config.ts
395
- import { z as z9 } from "zod";
396
- var formatSchema = z9.enum(["json", "md"]);
397
- var persistConfigSchema = z9.object({
398
- outputDir: filePathSchema.describe("Artifacts folder").optional(),
399
- filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
400
- format: z9.array(formatSchema).optional()
401
- });
402
-
403
- // packages/models/src/lib/plugin-config.ts
404
- import { z as z12 } from "zod";
405
-
406
- // packages/models/src/lib/group.ts
407
- import { z as z10 } from "zod";
408
- var groupRefSchema = weightedRefSchema(
409
- "Weighted reference to a group",
410
- "Reference slug to a group within this plugin (e.g. 'max-lines')"
411
- );
412
- var groupMetaSchema = metaSchema({
413
- titleDescription: "Descriptive name for the group",
414
- descriptionDescription: "Description of the group (markdown)",
415
- docsUrlDescription: "Group documentation site",
416
- description: "Group metadata"
417
- });
418
- var groupSchema = scorableSchema(
419
- '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',
420
- groupRefSchema,
421
- getDuplicateRefsInGroups,
422
- duplicateRefsInGroupsErrorMsg
423
- ).merge(groupMetaSchema);
424
- var groupsSchema = z10.array(groupSchema, {
425
- description: "List of groups"
426
- }).optional().refine(
427
- (groups) => !getDuplicateSlugsInGroups(groups),
428
- (groups) => ({
429
- message: duplicateSlugsInGroupsErrorMsg(groups)
430
- })
431
- );
432
- function duplicateRefsInGroupsErrorMsg(groups) {
433
- const duplicateRefs = getDuplicateRefsInGroups(groups);
434
- return `In plugin groups the following references are not unique: ${errorItems(
435
- duplicateRefs
436
- )}`;
437
- }
438
- function getDuplicateRefsInGroups(groups) {
439
- return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
440
- }
441
- function duplicateSlugsInGroupsErrorMsg(groups) {
442
- const duplicateRefs = getDuplicateSlugsInGroups(groups);
443
- return `In groups the following slugs are not unique: ${errorItems(
444
- duplicateRefs
445
- )}`;
446
- }
447
- function getDuplicateSlugsInGroups(groups) {
448
- return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
449
- }
450
-
451
- // packages/models/src/lib/runner-config.ts
452
- import { z as z11 } from "zod";
453
- var outputTransformSchema = z11.function().args(z11.unknown()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
454
- var runnerConfigSchema = z11.object(
455
- {
456
- command: z11.string({
457
- description: "Shell command to execute"
458
- }),
459
- args: z11.array(z11.string({ description: "Command arguments" })).optional(),
460
- outputFile: filePathSchema.describe("Output path"),
461
- outputTransform: outputTransformSchema.optional()
462
- },
463
- {
464
- description: "How to execute runner"
465
- }
466
- );
467
- var onProgressSchema = z11.function().args(z11.unknown()).returns(z11.void());
468
- var runnerFunctionSchema = z11.function().args(onProgressSchema.optional()).returns(z11.union([auditOutputsSchema, z11.promise(auditOutputsSchema)]));
469
-
470
- // packages/models/src/lib/plugin-config.ts
471
- var pluginMetaSchema = packageVersionSchema().merge(
472
- metaSchema({
473
- titleDescription: "Descriptive name",
474
- descriptionDescription: "Description (markdown)",
475
- docsUrlDescription: "Plugin documentation site",
476
- description: "Plugin metadata"
477
- })
478
- ).merge(
479
- z12.object({
480
- slug: slugSchema.describe("Unique plugin slug within core config"),
481
- icon: materialIconSchema
482
- })
483
- );
484
- var pluginDataSchema = z12.object({
485
- runner: z12.union([runnerConfigSchema, runnerFunctionSchema]),
486
- audits: pluginAuditsSchema,
487
- groups: groupsSchema
488
- });
489
- var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
490
- (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
491
- (pluginCfg) => ({
492
- message: missingRefsFromGroupsErrorMsg(pluginCfg)
493
- })
494
- );
495
- function missingRefsFromGroupsErrorMsg(pluginCfg) {
496
- const missingRefs = getMissingRefsFromGroups(pluginCfg);
497
- return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
498
- missingRefs
499
- )}`;
500
- }
501
- function getMissingRefsFromGroups(pluginCfg) {
502
- return hasMissingStrings(
503
- pluginCfg.groups?.flatMap(
504
- ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
505
- ) ?? [],
506
- pluginCfg.audits.map(({ slug }) => slug)
507
- );
508
- }
509
-
510
- // packages/models/src/lib/upload-config.ts
511
- import { z as z13 } from "zod";
512
- var uploadConfigSchema = z13.object({
513
- server: urlSchema.describe("URL of deployed portal API"),
514
- apiKey: z13.string({
515
- description: "API key with write access to portal (use `process.env` for security)"
516
- }),
517
- organization: slugSchema.describe(
518
- "Organization slug from Code PushUp portal"
519
- ),
520
- project: slugSchema.describe("Project slug from Code PushUp portal"),
521
- timeout: z13.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
522
- });
523
-
524
- // packages/models/src/lib/core-config.ts
525
- var unrefinedCoreConfigSchema = z14.object({
526
- plugins: z14.array(pluginConfigSchema, {
527
- description: "List of plugins to be used (official, community-provided, or custom)"
528
- }).min(1),
529
- /** portal configuration for persisting results */
530
- persist: persistConfigSchema.optional(),
531
- /** portal configuration for uploading results */
532
- upload: uploadConfigSchema.optional(),
533
- categories: categoriesSchema.optional()
534
- });
535
- var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
536
- function refineCoreConfig(schema) {
537
- return schema.refine(
538
- ({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
539
- ({ categories, plugins }) => ({
540
- message: missingRefsForCategoriesErrorMsg(categories, plugins)
541
- })
542
- );
543
- }
544
-
545
- // packages/models/src/lib/implementation/constants.ts
546
- var DEFAULT_PERSIST_OUTPUT_DIR = ".code-pushup";
547
- var DEFAULT_PERSIST_FILENAME = "report";
548
- var DEFAULT_PERSIST_FORMAT = ["json", "md"];
549
-
550
- // packages/models/src/lib/report.ts
551
- import { z as z15 } from "zod";
552
- var auditReportSchema = auditSchema.merge(auditOutputSchema);
553
- var pluginReportSchema = pluginMetaSchema.merge(
554
- executionMetaSchema({
555
- descriptionDate: "Start date and time of plugin run",
556
- descriptionDuration: "Duration of the plugin run in ms"
557
- })
558
- ).merge(
559
- z15.object({
560
- audits: z15.array(auditReportSchema).min(1),
561
- groups: z15.array(groupSchema).optional()
562
- })
563
- ).refine(
564
- (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
565
- (pluginReport) => ({
566
- message: missingRefsFromGroupsErrorMsg2(
567
- pluginReport.audits,
568
- pluginReport.groups ?? []
569
- )
570
- })
571
- );
572
- function missingRefsFromGroupsErrorMsg2(audits, groups) {
573
- const missingRefs = getMissingRefsFromGroups2(audits, groups);
574
- return `group references need to point to an existing audit in this plugin report: ${errorItems(
575
- missingRefs
576
- )}`;
577
- }
578
- function getMissingRefsFromGroups2(audits, groups) {
579
- return hasMissingStrings(
580
- groups.flatMap(
581
- ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
582
- ),
583
- audits.map(({ slug }) => slug)
584
- );
585
- }
586
- var reportSchema = packageVersionSchema({
587
- versionDescription: "NPM version of the CLI",
588
- required: true
589
- }).merge(
590
- executionMetaSchema({
591
- descriptionDate: "Start date and time of the collect run",
592
- descriptionDuration: "Duration of the collect run in ms"
593
- })
594
- ).merge(
595
- z15.object(
596
- {
597
- plugins: z15.array(pluginReportSchema).min(1),
598
- categories: z15.array(categoryConfigSchema).optional(),
599
- commit: commitSchema.describe("Git commit for which report was collected").nullable()
600
- },
601
- { description: "Collect output data" }
602
- )
603
- ).refine(
604
- ({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
605
- ({ categories, plugins }) => ({
606
- message: missingRefsForCategoriesErrorMsg(categories, plugins)
607
- })
608
- );
609
-
610
- // packages/models/src/lib/reports-diff.ts
611
- import { z as z16 } from "zod";
612
- function makeComparisonSchema(schema) {
613
- const sharedDescription = schema.description || "Result";
614
- return z16.object({
615
- before: schema.describe(`${sharedDescription} (source commit)`),
616
- after: schema.describe(`${sharedDescription} (target commit)`)
617
- });
618
- }
619
- function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
620
- return z16.object(
621
- {
622
- changed: z16.array(diffSchema),
623
- unchanged: z16.array(resultSchema),
624
- added: z16.array(resultSchema),
625
- removed: z16.array(resultSchema)
626
- },
627
- { description }
628
- );
629
- }
630
- var scorableMetaSchema = z16.object({
631
- slug: slugSchema,
632
- title: titleSchema,
633
- docsUrl: docsUrlSchema
634
- });
635
- var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
636
- z16.object({
637
- plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
638
- })
639
- );
640
- var scorableDiffSchema = scorableMetaSchema.merge(
641
- z16.object({
642
- scores: makeComparisonSchema(scoreSchema).merge(
643
- z16.object({
644
- diff: z16.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
645
- })
646
- ).describe("Score comparison")
647
- })
648
- );
649
- var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
650
- scorableWithPluginMetaSchema
651
- );
652
- var categoryDiffSchema = scorableDiffSchema;
653
- var groupDiffSchema = scorableWithPluginDiffSchema;
654
- var auditDiffSchema = scorableWithPluginDiffSchema.merge(
655
- z16.object({
656
- values: makeComparisonSchema(auditValueSchema).merge(
657
- z16.object({
658
- diff: z16.number().describe("Value change (`values.after - values.before`)")
659
- })
660
- ).describe("Audit `value` comparison"),
661
- displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
662
- "Audit `displayValue` comparison"
663
- )
664
- })
665
- );
666
- var categoryResultSchema = scorableMetaSchema.merge(
667
- z16.object({ score: scoreSchema })
668
- );
669
- var groupResultSchema = scorableWithPluginMetaSchema.merge(
670
- z16.object({ score: scoreSchema })
671
- );
672
- var auditResultSchema = scorableWithPluginMetaSchema.merge(
673
- auditOutputSchema.pick({ score: true, value: true, displayValue: true })
674
- );
675
- var reportsDiffSchema = z16.object({
676
- commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
677
- portalUrl: urlSchema.optional().describe("Link to comparison page in Code PushUp portal"),
678
- label: z16.string().optional().describe("Label (e.g. project name)"),
679
- categories: makeArraysComparisonSchema(
680
- categoryDiffSchema,
681
- categoryResultSchema,
682
- "Changes affecting categories"
683
- ),
684
- groups: makeArraysComparisonSchema(
685
- groupDiffSchema,
686
- groupResultSchema,
687
- "Changes affecting groups"
688
- ),
689
- audits: makeArraysComparisonSchema(
690
- auditDiffSchema,
691
- auditResultSchema,
692
- "Changes affecting audits"
693
- )
694
- }).merge(
695
- packageVersionSchema({
696
- versionDescription: "NPM version of the CLI (when `compare` was run)",
697
- required: true
698
- })
699
- ).merge(
700
- executionMetaSchema({
701
- descriptionDate: "Start date and time of the compare run",
702
- descriptionDuration: "Duration of the compare run in ms"
703
- })
704
- );
705
-
706
- // packages/utils/src/lib/errors.ts
707
- function stringifyError(error) {
708
- if (error instanceof Error) {
709
- if (error.name === "Error" || error.message.startsWith(error.name)) {
710
- return error.message;
711
- }
712
- return `${error.name}: ${error.message}`;
713
- }
714
- if (typeof error === "string") {
715
- return error;
716
- }
717
- return JSON.stringify(error);
718
- }
719
-
720
- // packages/utils/src/lib/execute-process.ts
721
- import {
722
- spawn
723
- } from "node:child_process";
724
-
725
- // packages/utils/src/lib/reports/utils.ts
726
- import ansis from "ansis";
727
- import { md } from "build-md";
728
- function calcDuration(start, stop) {
729
- return Math.round((stop ?? performance.now()) - start);
730
- }
731
-
732
- // packages/utils/src/lib/execute-process.ts
733
- var ProcessError = class extends Error {
734
- code;
735
- stderr;
736
- stdout;
737
- constructor(result) {
738
- super(result.stderr);
739
- this.code = result.code;
740
- this.stderr = result.stderr;
741
- this.stdout = result.stdout;
742
- }
743
- };
744
- function executeProcess(cfg) {
745
- const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
746
- const { onStdout, onStderr, onError, onComplete } = observer ?? {};
747
- const date = (/* @__PURE__ */ new Date()).toISOString();
748
- const start = performance.now();
749
- return new Promise((resolve, reject) => {
750
- const spawnedProcess = spawn(command, args ?? [], {
751
- shell: true,
752
- windowsHide: true,
753
- ...options
754
- });
755
- let stdout = "";
756
- let stderr = "";
757
- spawnedProcess.stdout.on("data", (data) => {
758
- stdout += String(data);
759
- onStdout?.(String(data), spawnedProcess);
760
- });
761
- spawnedProcess.stderr.on("data", (data) => {
762
- stderr += String(data);
763
- onStderr?.(String(data), spawnedProcess);
764
- });
765
- spawnedProcess.on("error", (err) => {
766
- stderr += err.toString();
767
- });
768
- spawnedProcess.on("close", (code2) => {
769
- const timings = { date, duration: calcDuration(start) };
770
- if (code2 === 0 || ignoreExitCode) {
771
- onComplete?.();
772
- resolve({ code: code2, stdout, stderr, ...timings });
773
- } else {
774
- const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
775
- onError?.(errorMsg);
776
- reject(errorMsg);
777
- }
778
- });
779
- });
780
- }
781
-
782
- // packages/utils/src/lib/file-system.ts
783
- import { bold, gray } from "ansis";
784
- import { bundleRequire } from "bundle-require";
785
- import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
786
-
787
- // packages/utils/src/lib/logging.ts
788
- import isaacs_cliui from "@isaacs/cliui";
789
- import { cliui } from "@poppinss/cliui";
790
- import { underline } from "ansis";
791
-
792
- // packages/utils/src/lib/file-system.ts
793
- async function readTextFile(path3) {
794
- const buffer = await readFile(path3);
795
- return buffer.toString();
796
- }
797
- async function readJsonFile(path3) {
798
- const text = await readTextFile(path3);
799
- return JSON.parse(text);
800
- }
801
- async function fileExists(path3) {
802
- try {
803
- const stats = await stat(path3);
804
- return stats.isFile();
805
- } catch {
806
- return false;
807
- }
808
- }
809
- function projectToFilename(project) {
810
- return project.replace(/[/\\\s]+/g, "-").replace(/@/g, "");
811
- }
812
-
813
- // packages/utils/src/lib/git/git.ts
814
- import { simpleGit } from "simple-git";
815
-
816
- // packages/utils/src/lib/git/git.commits-and-tags.ts
817
- import { simpleGit as simpleGit2 } from "simple-git";
818
-
819
- // packages/utils/src/lib/semver.ts
820
- import { rcompare, valid } from "semver";
821
-
822
- // packages/utils/src/lib/progress.ts
823
- import { black, bold as bold2, gray as gray2, green } from "ansis";
824
- import { MultiProgressBars } from "multi-progress-bars";
825
-
826
- // packages/utils/src/lib/reports/generate-md-report.ts
827
- import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
828
-
829
- // packages/utils/src/lib/reports/formatting.ts
830
- import {
831
- MarkdownDocument,
832
- md as md2
833
- } from "build-md";
834
-
835
- // packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
836
- import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
837
-
838
- // packages/utils/src/lib/reports/generate-md-reports-diff.ts
839
- import {
840
- MarkdownDocument as MarkdownDocument5,
841
- md as md6
842
- } from "build-md";
843
-
844
- // packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts
845
- import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
846
-
847
- // packages/utils/src/lib/reports/log-stdout-summary.ts
848
- import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
849
-
850
- // packages/ci/src/lib/monorepo/packages.ts
851
- import { glob } from "glob";
852
- import { basename, dirname, join } from "node:path";
853
- async function listPackages(cwd, patterns = ["**"]) {
854
- const files = await glob(
855
- patterns.map((pattern) => pattern.replace(/\/?$/, "/package.json")),
856
- { cwd }
857
- );
858
- return Promise.all(
859
- files.toSorted().map(async (file) => {
860
- const packageJson = await readJsonFile(join(cwd, file));
861
- const directory = join(cwd, dirname(file));
862
- const name = packageJson.name || basename(directory);
863
- return { name, directory, packageJson };
864
- })
865
- );
866
- }
867
- async function listWorkspaces(cwd) {
868
- const rootPackageJson = await readRootPackageJson(cwd);
869
- const patterns = Array.isArray(rootPackageJson.workspaces) ? rootPackageJson.workspaces : rootPackageJson.workspaces?.packages;
870
- return {
871
- workspaces: await listPackages(cwd, patterns),
872
- rootPackageJson
873
- };
874
- }
875
- async function hasWorkspacesEnabled(cwd) {
876
- const packageJson = await readRootPackageJson(cwd);
877
- if (!packageJson.private) {
878
- return false;
879
- }
880
- if (Array.isArray(packageJson.workspaces)) {
881
- return packageJson.workspaces.length > 0;
882
- }
883
- if (typeof packageJson.workspaces === "object") {
884
- return Boolean(packageJson.workspaces.packages?.length);
885
- }
886
- return false;
887
- }
888
- async function readRootPackageJson(cwd) {
889
- return await readJsonFile(join(cwd, "package.json"));
890
- }
891
- function hasDependency(packageJson, name) {
892
- const { dependencies = {}, devDependencies = {} } = packageJson;
893
- return name in devDependencies || name in dependencies;
894
- }
895
- function hasScript(packageJson, script) {
896
- const { scripts = {} } = packageJson;
897
- return script in scripts;
898
- }
899
- function hasCodePushUpDependency(packageJson) {
900
- return hasDependency(packageJson, "@code-pushup/cli");
901
- }
902
-
903
- // packages/ci/src/lib/monorepo/handlers/npm.ts
904
- var npmHandler = {
905
- tool: "npm",
906
- async isConfigured(options) {
907
- return await fileExists(join2(options.cwd, "package-lock.json")) && await hasWorkspacesEnabled(options.cwd);
908
- },
909
- async listProjects(options) {
910
- const { workspaces, rootPackageJson } = await listWorkspaces(options.cwd);
911
- return workspaces.filter(
912
- ({ packageJson }) => hasScript(packageJson, options.task) || hasCodePushUpDependency(packageJson) || hasCodePushUpDependency(rootPackageJson)
913
- ).map(({ name, packageJson }) => ({
914
- name,
915
- bin: hasScript(packageJson, options.task) ? `npm -w ${name} run ${options.task} --` : `npm -w ${name} exec ${options.task} --`
916
- }));
917
- }
918
- };
919
-
920
- // packages/ci/src/lib/monorepo/handlers/nx.ts
921
- import { join as join3 } from "node:path";
922
- var nxHandler = {
923
- tool: "nx",
924
- async isConfigured(options) {
925
- return await fileExists(join3(options.cwd, "nx.json")) && (await executeProcess({
926
- ...options,
927
- command: "npx",
928
- args: ["nx", "report"]
929
- })).code === 0;
930
- },
931
- async listProjects(options) {
932
- const { stdout } = await executeProcess({
933
- ...options,
934
- command: "npx",
935
- args: [
936
- "nx",
937
- "show",
938
- "projects",
939
- `--with-target=${options.task}`,
940
- "--json"
941
- ]
942
- });
943
- const projects = parseProjects(stdout);
944
- return projects.map((project) => ({
945
- name: project,
946
- bin: `npx nx run ${project}:${options.task} --`
947
- }));
948
- }
949
- };
950
- function parseProjects(stdout) {
951
- let json;
952
- try {
953
- json = JSON.parse(stdout);
954
- } catch (error) {
955
- throw new Error(
956
- `Invalid non-JSON output from 'nx show projects' - ${stringifyError(
957
- error
958
- )}`
959
- );
960
- }
961
- if (Array.isArray(json) && json.every((item) => typeof item === "string")) {
962
- return json;
963
- }
964
- throw new Error(
965
- `Invalid JSON output from 'nx show projects', expected array of strings, received ${JSON.stringify(
966
- json
967
- )}`
968
- );
969
- }
970
-
971
- // packages/ci/src/lib/monorepo/handlers/pnpm.ts
972
- import { join as join4 } from "node:path";
973
- import * as YAML from "yaml";
974
- var WORKSPACE_FILE = "pnpm-workspace.yaml";
975
- var pnpmHandler = {
976
- tool: "pnpm",
977
- async isConfigured(options) {
978
- return await fileExists(join4(options.cwd, WORKSPACE_FILE)) && await fileExists(join4(options.cwd, "package.json"));
979
- },
980
- async listProjects(options) {
981
- const yaml = await readTextFile(join4(options.cwd, WORKSPACE_FILE));
982
- const workspace = YAML.parse(yaml);
983
- const packages = await listPackages(options.cwd, workspace.packages);
984
- const rootPackageJson = await readRootPackageJson(options.cwd);
985
- return packages.filter(
986
- ({ packageJson }) => hasScript(packageJson, options.task) || hasCodePushUpDependency(packageJson) || hasCodePushUpDependency(rootPackageJson)
987
- ).map(({ name, packageJson }) => ({
988
- name,
989
- bin: hasScript(packageJson, options.task) ? `pnpm -F ${name} run ${options.task}` : `pnpm -F ${name} exec ${options.task}`
990
- }));
991
- }
992
- };
993
-
994
- // packages/ci/src/lib/monorepo/handlers/turbo.ts
995
- import { join as join6 } from "node:path";
996
-
997
- // packages/ci/src/lib/monorepo/handlers/yarn.ts
998
- import { join as join5 } from "node:path";
999
- var yarnHandler = {
1000
- tool: "yarn",
1001
- async isConfigured(options) {
1002
- return await fileExists(join5(options.cwd, "yarn.lock")) && await hasWorkspacesEnabled(options.cwd);
1003
- },
1004
- async listProjects(options) {
1005
- const { workspaces, rootPackageJson } = await listWorkspaces(options.cwd);
1006
- return workspaces.filter(
1007
- ({ packageJson }) => hasScript(packageJson, options.task) || hasCodePushUpDependency(packageJson) || hasCodePushUpDependency(rootPackageJson)
1008
- ).map(({ name, packageJson }) => ({
1009
- name,
1010
- bin: hasScript(packageJson, options.task) ? `yarn workspace ${name} run ${options.task}` : `yarn workspace ${name} exec ${options.task}`
1011
- }));
1012
- }
1013
- };
1014
-
1015
- // packages/ci/src/lib/monorepo/handlers/turbo.ts
1016
- var WORKSPACE_HANDLERS = [pnpmHandler, yarnHandler, npmHandler];
1017
- var turboHandler = {
1018
- tool: "turbo",
1019
- async isConfigured(options) {
1020
- const configPath = join6(options.cwd, "turbo.json");
1021
- return await fileExists(configPath) && options.task in (await readJsonFile(configPath)).tasks;
1022
- },
1023
- async listProjects(options) {
1024
- for (const handler of WORKSPACE_HANDLERS) {
1025
- if (await handler.isConfigured(options)) {
1026
- const projects = await handler.listProjects(options);
1027
- return projects.filter(({ bin }) => bin.includes(`run ${options.task}`)).map(({ name }) => ({
1028
- name,
1029
- bin: `npx turbo run ${options.task} -F ${name} --`
1030
- }));
1031
- }
1032
- }
1033
- throw new Error(
1034
- `Package manager for Turborepo not found, expected one of ${WORKSPACE_HANDLERS.map(
1035
- ({ tool }) => tool
1036
- ).join("/")}`
1037
- );
1038
- }
1039
- };
1040
-
1041
- // packages/ci/src/lib/monorepo/handlers/index.ts
1042
- var MONOREPO_TOOL_HANDLERS = [
1043
- nxHandler,
1044
- turboHandler,
1045
- yarnHandler,
1046
- pnpmHandler,
1047
- npmHandler
1048
- ];
1049
- function getToolHandler(tool) {
1050
- const matchedHandler = MONOREPO_TOOL_HANDLERS.find(
1051
- (handler) => handler.tool === tool
1052
- );
1053
- if (!matchedHandler) {
1054
- throw new Error(`No handler available for monorepo tool "${tool}"`);
1055
- }
1056
- return matchedHandler;
1057
- }
1058
-
1059
- // packages/ci/src/lib/monorepo/detect-tool.ts
1060
- async function detectMonorepoTool(options) {
1061
- for (const handler of MONOREPO_TOOL_HANDLERS) {
1062
- if (await handler.isConfigured(options)) {
1063
- return handler.tool;
1064
- }
1065
- }
1066
- return null;
1067
- }
1068
-
1069
- // packages/ci/src/lib/monorepo/list-projects.ts
1070
- async function listMonorepoProjects(settings) {
1071
- if (!settings.monorepo) {
1072
- throw new Error("Monorepo mode not enabled");
1073
- }
1074
- const logger = settings.logger;
1075
- const options = createMonorepoHandlerOptions(settings);
1076
- const tool = settings.monorepo === true ? await detectMonorepoTool(options) : settings.monorepo;
1077
- if (settings.monorepo === true) {
1078
- if (tool) {
1079
- logger.info(`Auto-detected monorepo tool ${tool}`);
1080
- } else {
1081
- logger.info("Couldn't auto-detect any supported monorepo tool");
1082
- }
1083
- } else {
1084
- logger.info(`Using monorepo tool "${tool}" from inputs`);
1085
- }
1086
- if (tool) {
1087
- const handler = getToolHandler(tool);
1088
- const projects = await handler.listProjects(options);
1089
- logger.info(`Found ${projects.length} projects in ${tool} monorepo`);
1090
- logger.debug(`Projects: ${projects.map(({ name }) => name).join(", ")}`);
1091
- return projects;
1092
- }
1093
- if (settings.projects) {
1094
- return listProjectsByGlobs({
1095
- patterns: settings.projects,
1096
- cwd: options.cwd,
1097
- bin: settings.bin,
1098
- logger
1099
- });
1100
- }
1101
- return listProjectsByNpmPackages({
1102
- cwd: options.cwd,
1103
- bin: settings.bin,
1104
- logger
1105
- });
1106
- }
1107
- function createMonorepoHandlerOptions(settings) {
1108
- return {
1109
- task: settings.task,
1110
- cwd: settings.directory,
1111
- ...!settings.silent && {
1112
- observer: {
1113
- onStdout: (stdout) => {
1114
- console.info(stdout);
1115
- },
1116
- onStderr: (stderr) => {
1117
- console.warn(stderr);
1118
- }
1119
- }
1120
- }
1121
- };
1122
- }
1123
- async function listProjectsByGlobs(args) {
1124
- const { patterns, cwd, bin, logger } = args;
1125
- const directories = await glob2(
1126
- patterns.map((path3) => path3.replace(/\/$/, "/")),
1127
- { cwd }
1128
- );
1129
- logger.info(
1130
- `Found ${directories.length} project folders matching "${patterns.join(
1131
- ", "
1132
- )}" from configuration`
1133
- );
1134
- logger.debug(`Projects: ${directories.join(", ")}`);
1135
- return directories.toSorted().map((directory) => ({
1136
- name: directory,
1137
- bin,
1138
- directory: join7(cwd, directory)
1139
- }));
1140
- }
1141
- async function listProjectsByNpmPackages(args) {
1142
- const { cwd, bin, logger } = args;
1143
- const packages = await listPackages(cwd);
1144
- logger.info(`Found ${packages.length} NPM packages in repository`);
1145
- logger.debug(`Projects: ${packages.map(({ name }) => name).join(", ")}`);
1146
- return packages.map(({ name, directory }) => ({
1147
- name,
1148
- bin,
1149
- directory
1150
- }));
1151
- }
1152
-
1153
- // packages/ci/src/lib/monorepo/tools.ts
1154
- var MONOREPO_TOOLS = ["nx", "turbo", "yarn", "pnpm", "npm"];
1155
- function isMonorepoTool(value) {
1156
- return MONOREPO_TOOLS.includes(value);
1157
- }
1158
-
1159
- // packages/ci/src/lib/run.ts
1160
- import fs from "node:fs/promises";
1161
- import path2 from "node:path";
1162
- import { simpleGit as simpleGit4 } from "simple-git";
1163
-
1164
- // packages/ci/src/lib/cli/persist.ts
1165
- import path from "node:path";
1166
- function persistCliOptions({
1167
- directory,
1168
- project,
1169
- output
1170
- }) {
1171
- return [
1172
- `--persist.outputDir=${path.join(directory, output)}`,
1173
- `--persist.filename=${createFilename(project)}`,
1174
- ...DEFAULT_PERSIST_FORMAT.map((format) => `--persist.format=${format}`)
1175
- ];
1176
- }
1177
- function persistedCliFiles({
1178
- directory,
1179
- isDiff,
1180
- project,
1181
- formats,
1182
- output
1183
- }) {
1184
- const rootDir = path.join(directory, output);
1185
- const filename = isDiff ? `${createFilename(project)}-diff` : createFilename(project);
1186
- const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
1187
- (acc, format) => ({
1188
- ...acc,
1189
- [`${format}FilePath`]: path.join(rootDir, `${filename}.${format}`)
1190
- }),
1191
- // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
1192
- {}
1193
- );
1194
- const files = Object.values(filePaths);
1195
- return {
1196
- ...filePaths,
1197
- artifactData: {
1198
- rootDir,
1199
- files
1200
- }
1201
- };
1202
- }
1203
- function createFilename(project) {
1204
- if (!project) {
1205
- return DEFAULT_PERSIST_FILENAME;
1206
- }
1207
- const prefix = projectToFilename(project);
1208
- return `${prefix}-${DEFAULT_PERSIST_FILENAME}`;
1209
- }
1210
-
1211
- // packages/ci/src/lib/cli/commands/collect.ts
1212
- async function runCollect({
1213
- bin,
1214
- config,
1215
- directory,
1216
- silent,
1217
- project,
1218
- output
1219
- }) {
1220
- const { stdout } = await executeProcess({
1221
- command: bin,
1222
- args: [
1223
- ...config ? [`--config=${config}`] : [],
1224
- ...persistCliOptions({ directory, project, output })
1225
- ],
1226
- cwd: directory
1227
- });
1228
- if (!silent) {
1229
- console.info(stdout);
1230
- }
1231
- return persistedCliFiles({ directory, project, output });
1232
- }
1233
-
1234
- // packages/ci/src/lib/cli/commands/compare.ts
1235
- async function runCompare({ before, after, label }, { bin, config, directory, silent, project, output }) {
1236
- const { stdout } = await executeProcess({
1237
- command: bin,
1238
- args: [
1239
- "compare",
1240
- `--before=${before}`,
1241
- `--after=${after}`,
1242
- ...label ? [`--label=${label}`] : [],
1243
- ...config ? [`--config=${config}`] : [],
1244
- ...persistCliOptions({ directory, project, output })
1245
- ],
1246
- cwd: directory
1247
- });
1248
- if (!silent) {
1249
- console.info(stdout);
1250
- }
1251
- return persistedCliFiles({ directory, isDiff: true, project, output });
1252
- }
1253
-
1254
- // packages/ci/src/lib/cli/commands/merge-diffs.ts
1255
- async function runMergeDiffs(files, { bin, config, directory, silent, output }) {
1256
- const { stdout } = await executeProcess({
1257
- command: bin,
1258
- args: [
1259
- "merge-diffs",
1260
- ...files.map((file) => `--files=${file}`),
1261
- ...config ? [`--config=${config}`] : [],
1262
- ...persistCliOptions({ directory, output })
1263
- ],
1264
- cwd: directory
1265
- });
1266
- if (!silent) {
1267
- console.info(stdout);
1268
- }
1269
- return persistedCliFiles({
1270
- directory,
1271
- isDiff: true,
1272
- formats: ["md"],
1273
- output
1274
- });
1275
- }
1276
-
1277
- // packages/ci/src/lib/cli/commands/print-config.ts
1278
- async function runPrintConfig({
1279
- bin,
1280
- config,
1281
- directory,
1282
- silent
1283
- }) {
1284
- const { stdout } = await executeProcess({
1285
- command: bin,
1286
- args: [...config ? [`--config=${config}`] : [], "print-config"],
1287
- cwd: directory
1288
- });
1289
- if (!silent) {
1290
- console.info(stdout);
1291
- }
1292
- }
1293
-
1294
- // packages/ci/src/lib/cli/context.ts
1295
- function createCommandContext(settings, project) {
1296
- return {
1297
- project: project?.name,
1298
- bin: project?.bin ?? settings.bin,
1299
- directory: project?.directory ?? settings.directory,
1300
- config: settings.config,
1301
- silent: settings.silent,
1302
- output: settings.output.replaceAll("{project}", project?.name ?? "")
1303
- };
1304
- }
1305
-
1306
- // packages/ci/src/lib/comment.ts
1307
- import { readFile as readFile2 } from "node:fs/promises";
1308
- async function commentOnPR(mdPath, api, logger) {
1309
- const markdown = await readFile2(mdPath, "utf8");
1310
- const identifier = `<!-- generated by @code-pushup/ci -->`;
1311
- const body = truncateBody(
1312
- `${markdown}
1313
-
1314
- ${identifier}
1315
- `,
1316
- api.maxCommentChars,
1317
- logger
1318
- );
1319
- const comments = await api.listComments();
1320
- logger.debug(`Fetched ${comments.length} comments for pull request`);
1321
- const prevComment = comments.find(
1322
- (comment) => comment.body.includes(identifier)
1323
- );
1324
- logger.debug(
1325
- prevComment ? `Found previous comment ${prevComment.id} from Code PushUp` : "Previous Code PushUp comment not found"
1326
- );
1327
- if (prevComment) {
1328
- const updatedComment = await api.updateComment(prevComment.id, body);
1329
- logger.info(`Updated body of comment ${updatedComment.url}`);
1330
- return updatedComment.id;
1331
- }
1332
- const createdComment = await api.createComment(body);
1333
- logger.info(`Created new comment ${createdComment.url}`);
1334
- return createdComment.id;
1335
- }
1336
- function truncateBody(body, max, logger) {
1337
- const truncateWarning = "...*[Comment body truncated]*";
1338
- if (body.length > max) {
1339
- logger.warn(`Comment body is too long. Truncating to ${max} characters.`);
1340
- return body.slice(0, max - truncateWarning.length) + truncateWarning;
1341
- }
1342
- return body;
1343
- }
1344
-
1345
- // packages/ci/src/lib/constants.ts
1346
- var DEFAULT_SETTINGS = {
1347
- monorepo: false,
1348
- projects: null,
1349
- task: "code-pushup",
1350
- bin: "npx --no-install code-pushup",
1351
- config: null,
1352
- directory: process.cwd(),
1353
- silent: false,
1354
- debug: false,
1355
- detectNewIssues: true,
1356
- logger: console,
1357
- output: DEFAULT_PERSIST_OUTPUT_DIR
1358
- };
1359
-
1360
- // packages/ci/src/lib/git.ts
1361
- import { DiffNameStatus, simpleGit as simpleGit3 } from "simple-git";
1362
- async function listChangedFiles(refs, git = simpleGit3()) {
1363
- const statuses = [
1364
- DiffNameStatus.ADDED,
1365
- DiffNameStatus.COPIED,
1366
- DiffNameStatus.MODIFIED,
1367
- DiffNameStatus.RENAMED
1368
- ];
1369
- const { files } = await git.diffSummary([
1370
- refs.base,
1371
- refs.head,
1372
- `--diff-filter=${statuses.join("")}`,
1373
- "--find-renames",
1374
- "--find-copies"
1375
- ]);
1376
- const entries = await Promise.all(
1377
- files.filter(({ binary }) => !binary).map(({ file }) => {
1378
- const rename = parseFileRename(file);
1379
- if (rename) {
1380
- return { file: rename.curr, originalFile: rename.prev };
1381
- }
1382
- return { file };
1383
- }).map(async ({ file, originalFile }) => {
1384
- const diff = await git.diff([
1385
- "--unified=0",
1386
- refs.base,
1387
- refs.head,
1388
- "--",
1389
- file,
1390
- ...originalFile ? [originalFile] : []
1391
- ]);
1392
- const lineChanges = parseDiff(diff);
1393
- return [
1394
- file,
1395
- { ...originalFile && { originalFile }, lineChanges }
1396
- ];
1397
- })
1398
- );
1399
- return Object.fromEntries(entries);
1400
- }
1401
- function parseFileRename(file) {
1402
- const partialRenameMatch = file.match(/^(.*){(.*) => (.*)}(.*)$/);
1403
- if (partialRenameMatch) {
1404
- const [, prefix = "", prev, curr, suffix] = partialRenameMatch;
1405
- return {
1406
- prev: prefix + prev + suffix,
1407
- curr: prefix + curr + suffix
1408
- };
1409
- }
1410
- const fullRenameMatch = file.match(/^(.*) => (.*)$/);
1411
- if (fullRenameMatch) {
1412
- const [, prev = "", curr = ""] = fullRenameMatch;
1413
- return { prev, curr };
1414
- }
1415
- return null;
1416
- }
1417
- function parseDiff(diff) {
1418
- const changeSummaries = diff.match(/@@ [ \d,+-]+ @@/g);
1419
- if (changeSummaries == null) {
1420
- return [];
1421
- }
1422
- return changeSummaries.map((summary) => summary.match(/^@@ -(\d+|\d+,\d+) \+(\d+|\d+,\d+) @@$/)).filter((matches) => matches != null).map((matches) => {
1423
- const [prevLine = "", prevAdded = "1"] = matches[1].split(",");
1424
- const [currLine = "", currAdded = "1"] = matches[2].split(",");
1425
- return {
1426
- prev: {
1427
- line: Number.parseInt(prevLine, 10),
1428
- count: Number.parseInt(prevAdded, 10)
1429
- },
1430
- curr: {
1431
- line: Number.parseInt(currLine, 10),
1432
- count: Number.parseInt(currAdded, 10)
1433
- }
1434
- };
1435
- });
1436
- }
1437
- function isFileChanged(changedFiles, file) {
1438
- return file in changedFiles;
1439
- }
1440
- function adjustFileName(changedFiles, file) {
1441
- return Object.entries(changedFiles).find(
1442
- ([, { originalFile }]) => originalFile === file
1443
- )?.[0] ?? file;
1444
- }
1445
- function adjustLine(changedFiles, file, line) {
1446
- const changedFile = changedFiles[adjustFileName(changedFiles, file)];
1447
- if (!changedFile) {
1448
- return line;
1449
- }
1450
- const offset = changedFile.lineChanges.filter(({ prev }) => prev.line < line).reduce((acc, { prev, curr }) => acc + (curr.count - prev.count), 0);
1451
- return line + offset;
1452
- }
1453
-
1454
- // packages/ci/src/lib/issues.ts
1455
- function filterRelevantIssues({
1456
- currReport,
1457
- prevReport,
1458
- reportsDiff,
1459
- changedFiles
1460
- }) {
1461
- const auditsWithPlugin = [
1462
- ...reportsDiff.audits.changed,
1463
- ...reportsDiff.audits.added
1464
- ].map((auditLink) => {
1465
- const plugin = currReport.plugins.find(
1466
- ({ slug }) => slug === auditLink.plugin.slug
1467
- );
1468
- const audit = plugin?.audits.find(({ slug }) => slug === auditLink.slug);
1469
- return plugin && audit && [plugin, audit];
1470
- }).filter((ctx) => ctx != null);
1471
- const issues = auditsWithPlugin.flatMap(
1472
- ([plugin, audit]) => getAuditIssues(audit, plugin)
1473
- );
1474
- const prevIssues = prevReport.plugins.flatMap(
1475
- (plugin) => plugin.audits.flatMap((audit) => getAuditIssues(audit, plugin))
1476
- );
1477
- return issues.filter(
1478
- (issue) => isFileChanged(changedFiles, issue.source.file) && !prevIssues.some(
1479
- (prevIssue) => issuesMatch(prevIssue, issue, changedFiles)
1480
- )
1481
- ).sort(createIssuesSortCompareFn(currReport));
1482
- }
1483
- function getAuditIssues(audit, plugin) {
1484
- return audit.details?.issues?.filter((issue) => issue.source?.file != null).map((issue) => ({ ...issue, audit, plugin })) ?? [];
1485
- }
1486
- function issuesMatch(prev, curr, changedFiles) {
1487
- return prev.plugin.slug === curr.plugin.slug && prev.audit.slug === curr.audit.slug && prev.severity === curr.severity && removeDigits(prev.message) === removeDigits(curr.message) && adjustFileName(changedFiles, prev.source.file) === curr.source.file && positionsMatch(prev.source, curr.source, changedFiles);
1488
- }
1489
- function removeDigits(message) {
1490
- return message.replace(/\d+/g, "");
1491
- }
1492
- function positionsMatch(prev, curr, changedFiles) {
1493
- if (!hasPosition(prev) || !hasPosition(curr)) {
1494
- return hasPosition(prev) === hasPosition(curr);
1495
- }
1496
- return adjustedStartLinesMatch(prev, curr, changedFiles) || adjustedLineSpansMatch(prev, curr, changedFiles);
1497
- }
1498
- function hasPosition(source) {
1499
- return source.position != null;
1500
- }
1501
- function adjustedStartLinesMatch(prev, curr, changedFiles) {
1502
- return adjustLine(changedFiles, prev.file, prev.position.startLine) === curr.position.startLine;
1503
- }
1504
- function adjustedLineSpansMatch(prev, curr, changedFiles) {
1505
- if (prev.position?.endLine == null || curr.position?.endLine == null) {
1506
- return false;
1507
- }
1508
- const prevLineCount = prev.position.endLine - prev.position.startLine;
1509
- const currLineCount = curr.position.endLine - curr.position.startLine;
1510
- const currStartLineOffset = adjustLine(changedFiles, curr.file, curr.position.startLine) - curr.position.startLine;
1511
- return prevLineCount === currLineCount - currStartLineOffset;
1512
- }
1513
- function createIssuesSortCompareFn(report) {
1514
- return (a, b) => getAuditImpactValue(b, report) - getAuditImpactValue(a, report);
1515
- }
1516
- function getAuditImpactValue({ audit, plugin }, report) {
1517
- if (!report.categories) {
1518
- return 0;
1519
- }
1520
- return report.categories.map((category) => {
1521
- const weights = category.refs.map((ref) => {
1522
- if (ref.plugin !== plugin.slug) {
1523
- return 0;
1524
- }
1525
- switch (ref.type) {
1526
- case "audit":
1527
- return ref.slug === audit.slug ? ref.weight : 0;
1528
- case "group":
1529
- return calculateGroupImpact(ref, audit, report);
1530
- }
1531
- });
1532
- return weights.reduce((acc, weight) => acc + weight, 0) / category.refs.reduce((acc, { weight }) => acc + weight, 0);
1533
- }).reduce((acc, value) => acc + value, 0);
1534
- }
1535
- function calculateGroupImpact(ref, audit, report) {
1536
- const group = report.plugins.find(({ slug }) => slug === ref.plugin)?.groups?.find(({ slug }) => slug === ref.slug);
1537
- if (!group?.refs.length) {
1538
- return 0;
1539
- }
1540
- const groupRatio = (group.refs.find(({ slug }) => slug === audit.slug)?.weight ?? 0) / group.refs.reduce((acc, { weight }) => acc + weight, 0);
1541
- return ref.weight * groupRatio;
1542
- }
1543
-
1544
- // packages/ci/src/lib/run.ts
1545
- async function runInCI(refs, api, options, git = simpleGit4()) {
1546
- const settings = { ...DEFAULT_SETTINGS, ...options };
1547
- const logger = settings.logger;
1548
- if (settings.monorepo) {
1549
- logger.info("Running Code PushUp in monorepo mode");
1550
- const projects = await listMonorepoProjects(settings);
1551
- const projectResults = await projects.reduce(
1552
- async (acc, project) => [
1553
- ...await acc,
1554
- await runOnProject({ project, settings, refs, api, git })
1555
- ],
1556
- Promise.resolve([])
1557
- );
1558
- const diffJsonPaths = projectResults.map(
1559
- ({ artifacts: { diff } }) => diff?.files.find((file) => file.endsWith(".json"))
1560
- ).filter((file) => file != null);
1561
- if (diffJsonPaths.length > 0) {
1562
- const { mdFilePath, artifactData: diffArtifact } = await runMergeDiffs(
1563
- diffJsonPaths,
1564
- createCommandContext(settings, projects[0])
1565
- );
1566
- logger.debug(`Merged ${diffJsonPaths.length} diffs into ${mdFilePath}`);
1567
- const commentId = await commentOnPR(mdFilePath, api, logger);
1568
- return {
1569
- mode: "monorepo",
1570
- projects: projectResults,
1571
- commentId,
1572
- diffArtifact
1573
- };
1574
- }
1575
- return { mode: "monorepo", projects: projectResults };
1576
- }
1577
- logger.info("Running Code PushUp in standalone project mode");
1578
- const { artifacts, newIssues } = await runOnProject({
1579
- project: null,
1580
- settings,
1581
- api,
1582
- refs,
1583
- git
1584
- });
1585
- const commentMdPath = artifacts.diff?.files.find(
1586
- (file) => file.endsWith(".md")
1587
- );
1588
- if (commentMdPath) {
1589
- const commentId = await commentOnPR(commentMdPath, api, logger);
1590
- return {
1591
- mode: "standalone",
1592
- artifacts,
1593
- commentId,
1594
- newIssues
1595
- };
1596
- }
1597
- return { mode: "standalone", artifacts, newIssues };
1598
- }
1599
- async function runOnProject(args) {
1600
- const {
1601
- project,
1602
- refs: { head, base },
1603
- settings,
1604
- git
1605
- } = args;
1606
- const logger = settings.logger;
1607
- const ctx = createCommandContext(settings, project);
1608
- if (project) {
1609
- logger.info(`Running Code PushUp on monorepo project ${project.name}`);
1610
- }
1611
- const { jsonFilePath: currReportPath, artifactData: reportArtifact } = await runCollect(ctx);
1612
- const currReport = await fs.readFile(currReportPath, "utf8");
1613
- logger.debug(`Collected current report at ${currReportPath}`);
1614
- const noDiffOutput = {
1615
- name: project?.name ?? "-",
1616
- artifacts: {
1617
- report: reportArtifact
1618
- }
1619
- };
1620
- if (base == null) {
1621
- return noDiffOutput;
1622
- }
1623
- logger.info(
1624
- `PR/MR detected, preparing to compare base branch ${base.ref} to head ${head.ref}`
1625
- );
1626
- const prevReport = await collectPreviousReport({ ...args, base, ctx });
1627
- if (!prevReport) {
1628
- return noDiffOutput;
1629
- }
1630
- const reportsDir = path2.join(settings.directory, ".code-pushup");
1631
- const currPath = path2.join(reportsDir, "curr-report.json");
1632
- const prevPath = path2.join(reportsDir, "prev-report.json");
1633
- await fs.writeFile(currPath, currReport);
1634
- await fs.writeFile(prevPath, prevReport);
1635
- logger.debug(`Saved reports to ${currPath} and ${prevPath}`);
1636
- const comparisonFiles = await runCompare(
1637
- { before: prevPath, after: currPath, label: project?.name },
1638
- ctx
1639
- );
1640
- logger.info("Compared reports and generated diff files");
1641
- logger.debug(
1642
- `Generated diff files at ${comparisonFiles.jsonFilePath} and ${comparisonFiles.mdFilePath}`
1643
- );
1644
- const diffOutput = {
1645
- ...noDiffOutput,
1646
- artifacts: {
1647
- ...noDiffOutput.artifacts,
1648
- diff: comparisonFiles.artifactData
1649
- }
1650
- };
1651
- if (!settings.detectNewIssues) {
1652
- return diffOutput;
1653
- }
1654
- const newIssues = await findNewIssues({
1655
- base,
1656
- currReport,
1657
- prevReport,
1658
- comparisonFiles,
1659
- logger,
1660
- git
1661
- });
1662
- return { ...diffOutput, newIssues };
1663
- }
1664
- async function collectPreviousReport(args) {
1665
- const { project, base, api, settings, ctx, git } = args;
1666
- const logger = settings.logger;
1667
- const cachedBaseReport = await api.downloadReportArtifact?.(project?.name).catch((error) => {
1668
- logger.warn(
1669
- `Error when downloading previous report artifact, skipping - ${stringifyError(error)}`
1670
- );
1671
- });
1672
- if (api.downloadReportArtifact != null) {
1673
- logger.info(
1674
- `Previous report artifact ${cachedBaseReport ? "found" : "not found"}`
1675
- );
1676
- if (cachedBaseReport) {
1677
- logger.debug(
1678
- `Previous report artifact downloaded to ${cachedBaseReport}`
1679
- );
1680
- }
1681
- }
1682
- if (cachedBaseReport) {
1683
- return fs.readFile(cachedBaseReport, "utf8");
1684
- } else {
1685
- await git.fetch("origin", base.ref, ["--depth=1"]);
1686
- await git.checkout(["-f", base.ref]);
1687
- logger.info(`Switched to base branch ${base.ref}`);
1688
- try {
1689
- await runPrintConfig({ ...ctx, silent: !settings.debug });
1690
- logger.debug(
1691
- `Executing print-config verified code-pushup installed in base branch ${base.ref}`
1692
- );
1693
- } catch (error) {
1694
- logger.debug(`Error from print-config - ${stringifyError(error)}`);
1695
- logger.info(
1696
- `Executing print-config failed, assuming code-pushup not installed in base branch ${base.ref} and skipping comparison`
1697
- );
1698
- return null;
1699
- }
1700
- const { jsonFilePath: prevReportPath } = await runCollect(ctx);
1701
- const prevReport = await fs.readFile(prevReportPath, "utf8");
1702
- logger.debug(`Collected previous report at ${prevReportPath}`);
1703
- await git.checkout(["-f", "-"]);
1704
- logger.info("Switched back to PR/MR branch");
1705
- return prevReport;
1706
- }
1707
- }
1708
- async function findNewIssues(args) {
1709
- const { base, currReport, prevReport, comparisonFiles, logger, git } = args;
1710
- await git.fetch("origin", base.ref, ["--depth=1"]);
1711
- const reportsDiff = await fs.readFile(comparisonFiles.jsonFilePath, "utf8");
1712
- const changedFiles = await listChangedFiles(
1713
- { base: "FETCH_HEAD", head: "HEAD" },
1714
- git
1715
- );
1716
- const issues = filterRelevantIssues({
1717
- currReport: JSON.parse(currReport),
1718
- prevReport: JSON.parse(prevReport),
1719
- reportsDiff: JSON.parse(reportsDiff),
1720
- changedFiles
1721
- });
1722
- logger.debug(
1723
- `Found ${issues.length} relevant issues for ${Object.keys(changedFiles).length} changed files`
1724
- );
1725
- return issues;
1726
- }
1727
- export {
1728
- MONOREPO_TOOLS,
1729
- isMonorepoTool,
1730
- runInCI
1731
- };