@code-pushup/ci 0.54.0 → 0.56.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/README.md CHANGED
@@ -94,20 +94,26 @@ A `Comment` object has the following required properties:
94
94
 
95
95
  Optionally, you can override default options for further customization:
96
96
 
97
- | Property | Type | Default | Description |
98
- | :---------------- | :------------------------ | :------------------------------- | :-------------------------------------------------------------------------------- |
99
- | `monorepo` | `boolean \| MonorepoTool` | `false` | Enables [monorepo mode](#monorepo-mode) |
100
- | `projects` | `string[] \| null` | `null` | Custom projects configuration for [monorepo mode](#monorepo-mode) |
101
- | `task` | `string` | `'code-pushup'` | Name of command to run Code PushUp per project in [monorepo mode](#monorepo-mode) |
102
- | `directory` | `string` | `process.cwd()` | Directory in which Code PushUp CLI should run |
103
- | `config` | `string \| null` | `null` [^1] | Path to config file (`--config` option) |
104
- | `silent` | `boolean` | `false` | Toggles if logs from CLI commands are printed |
105
- | `bin` | `string` | `'npx --no-install code-pushup'` | Command for executing Code PushUp CLI |
106
- | `detectNewIssues` | `boolean` | `true` | Toggles if new issues should be detected and returned in `newIssues` property |
107
- | `logger` | `Logger` | `console` | Logger for reporting progress and encountered problems |
97
+ | Property | Type | Default | Description |
98
+ | :----------------- | :------------------------ | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `monorepo` | `boolean \| MonorepoTool` | `false` | Enables [monorepo mode](#monorepo-mode) |
100
+ | `projects` | `string[] \| null` | `null` | Custom projects configuration for [monorepo mode](#monorepo-mode) |
101
+ | `task` | `string` | `'code-pushup'` | Name of command to run Code PushUp per project in [monorepo mode](#monorepo-mode) |
102
+ | `nxProjectsFilter` | `string \| string[]` | `'--with-target={task}'` | Arguments passed to [`nx show projects`](https://nx.dev/nx-api/nx/documents/show#projects), only relevant for Nx in [monorepo mode](#monorepo-mode) [^3] |
103
+ | `directory` | `string` | `process.cwd()` | Directory in which Code PushUp CLI should run |
104
+ | `config` | `string \| null` | `null` [^1] | Path to config file (`--config` option) |
105
+ | `silent` | `boolean` | `false` | Toggles if logs from CLI commands are printed |
106
+ | `bin` | `string` | `'npx --no-install code-pushup'` | Command for executing Code PushUp CLI |
107
+ | `detectNewIssues` | `boolean` | `true` | Toggles if new issues should be detected and returned in `newIssues` property |
108
+ | `logger` | `Logger` | `console` | Logger for reporting progress and encountered problems |
109
+ | `output` | `string` | `'.code-pushup'` | Directory where Code PushUp reports will be created (interpolates project name [^2]) |
108
110
 
109
111
  [^1]: By default, the `code-pushup.config` file is autodetected as described in [`@code-pushup/cli` docs](../cli/README.md#configuration).
110
112
 
113
+ [^2]: In monorepo mode, any occurrence of `{project}` in the `output` path will be replaced with a project name. This separation of folders per project (e.g. `output: '.code-pushup/{project}'`) may be useful for caching purposes.
114
+
115
+ [^3]: The `{task}` pattern is replaced with the `task` value, so the default behaviour is to list projects using `npx nx show projects --with-target=code-pushup --json`. The `nxProjectsFilter` options gives Nx users the flexibility to filter projects in alternative ways supported by the Nx CLI (e.g. `--affected`, `--projects`, `--exclude`, `--type`) - refer to [options in Nx docs](https://nx.dev/nx-api/nx/documents/show#options) for details.
116
+
111
117
  The `Logger` object has the following required properties:
112
118
 
113
119
  | Property | Type | Description |
package/index.js CHANGED
@@ -36,7 +36,7 @@ function exists(value) {
36
36
  return value != null;
37
37
  }
38
38
  function getMissingRefsForCategories(categories, plugins) {
39
- if (categories.length === 0) {
39
+ if (!categories || categories.length === 0) {
40
40
  return false;
41
41
  }
42
42
  const auditRefsFromCategory = categories.flatMap(
@@ -535,12 +535,9 @@ var unrefinedCoreConfigSchema = z14.object({
535
535
  var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
536
536
  function refineCoreConfig(schema) {
537
537
  return schema.refine(
538
- (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
539
- (coreCfg) => ({
540
- message: missingRefsForCategoriesErrorMsg(
541
- coreCfg.categories ?? [],
542
- coreCfg.plugins
543
- )
538
+ ({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
539
+ ({ categories, plugins }) => ({
540
+ message: missingRefsForCategoriesErrorMsg(categories, plugins)
544
541
  })
545
542
  );
546
543
  }
@@ -597,19 +594,16 @@ var reportSchema = packageVersionSchema({
597
594
  ).merge(
598
595
  z15.object(
599
596
  {
600
- categories: z15.array(categoryConfigSchema),
601
597
  plugins: z15.array(pluginReportSchema).min(1),
598
+ categories: z15.array(categoryConfigSchema).optional(),
602
599
  commit: commitSchema.describe("Git commit for which report was collected").nullable()
603
600
  },
604
601
  { description: "Collect output data" }
605
602
  )
606
603
  ).refine(
607
- (report) => !getMissingRefsForCategories(report.categories, report.plugins),
608
- (report) => ({
609
- message: missingRefsForCategoriesErrorMsg(
610
- report.categories,
611
- report.plugins
612
- )
604
+ ({ categories, plugins }) => !getMissingRefsForCategories(categories, plugins),
605
+ ({ categories, plugins }) => ({
606
+ message: missingRefsForCategoriesErrorMsg(categories, plugins)
613
607
  })
614
608
  );
615
609
 
@@ -819,6 +813,11 @@ function projectToFilename(project) {
819
813
  // packages/utils/src/lib/git/git.ts
820
814
  import { simpleGit } from "simple-git";
821
815
 
816
+ // packages/utils/src/lib/transform.ts
817
+ function toArray(val) {
818
+ return Array.isArray(val) ? val : [val];
819
+ }
820
+
822
821
  // packages/utils/src/lib/git/git.commits-and-tags.ts
823
822
  import { simpleGit as simpleGit2 } from "simple-git";
824
823
 
@@ -929,22 +928,26 @@ var nxHandler = {
929
928
  tool: "nx",
930
929
  async isConfigured(options) {
931
930
  return await fileExists(join3(options.cwd, "nx.json")) && (await executeProcess({
932
- ...options,
933
931
  command: "npx",
934
- args: ["nx", "report"]
932
+ args: ["nx", "report"],
933
+ cwd: options.cwd,
934
+ observer: options.observer
935
935
  })).code === 0;
936
936
  },
937
937
  async listProjects(options) {
938
938
  const { stdout } = await executeProcess({
939
- ...options,
940
939
  command: "npx",
941
940
  args: [
942
941
  "nx",
943
942
  "show",
944
943
  "projects",
945
- `--with-target=${options.task}`,
944
+ ...toArray(options.nxProjectsFilter).map(
945
+ (arg) => arg.replaceAll("{task}", options.task)
946
+ ),
946
947
  "--json"
947
- ]
948
+ ],
949
+ cwd: options.cwd,
950
+ observer: options.observer
948
951
  });
949
952
  const projects = parseProjects(stdout);
950
953
  return projects.map((project) => ({
@@ -1114,6 +1117,7 @@ function createMonorepoHandlerOptions(settings) {
1114
1117
  return {
1115
1118
  task: settings.task,
1116
1119
  cwd: settings.directory,
1120
+ nxProjectsFilter: settings.nxProjectsFilter,
1117
1121
  ...!settings.silent && {
1118
1122
  observer: {
1119
1123
  onStdout: (stdout) => {
@@ -1171,10 +1175,11 @@ import { simpleGit as simpleGit4 } from "simple-git";
1171
1175
  import path from "node:path";
1172
1176
  function persistCliOptions({
1173
1177
  directory,
1174
- project
1178
+ project,
1179
+ output
1175
1180
  }) {
1176
1181
  return [
1177
- `--persist.outputDir=${path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR)}`,
1182
+ `--persist.outputDir=${path.join(directory, output)}`,
1178
1183
  `--persist.filename=${createFilename(project)}`,
1179
1184
  ...DEFAULT_PERSIST_FORMAT.map((format) => `--persist.format=${format}`)
1180
1185
  ];
@@ -1183,9 +1188,10 @@ function persistedCliFiles({
1183
1188
  directory,
1184
1189
  isDiff,
1185
1190
  project,
1186
- formats
1191
+ formats,
1192
+ output
1187
1193
  }) {
1188
- const rootDir = path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR);
1194
+ const rootDir = path.join(directory, output);
1189
1195
  const filename = isDiff ? `${createFilename(project)}-diff` : createFilename(project);
1190
1196
  const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
1191
1197
  (acc, format) => ({
@@ -1218,24 +1224,25 @@ async function runCollect({
1218
1224
  config,
1219
1225
  directory,
1220
1226
  silent,
1221
- project
1227
+ project,
1228
+ output
1222
1229
  }) {
1223
1230
  const { stdout } = await executeProcess({
1224
1231
  command: bin,
1225
1232
  args: [
1226
1233
  ...config ? [`--config=${config}`] : [],
1227
- ...persistCliOptions({ directory, project })
1234
+ ...persistCliOptions({ directory, project, output })
1228
1235
  ],
1229
1236
  cwd: directory
1230
1237
  });
1231
1238
  if (!silent) {
1232
1239
  console.info(stdout);
1233
1240
  }
1234
- return persistedCliFiles({ directory, project });
1241
+ return persistedCliFiles({ directory, project, output });
1235
1242
  }
1236
1243
 
1237
1244
  // packages/ci/src/lib/cli/commands/compare.ts
1238
- async function runCompare({ before, after, label }, { bin, config, directory, silent, project }) {
1245
+ async function runCompare({ before, after, label }, { bin, config, directory, silent, project, output }) {
1239
1246
  const { stdout } = await executeProcess({
1240
1247
  command: bin,
1241
1248
  args: [
@@ -1244,32 +1251,37 @@ async function runCompare({ before, after, label }, { bin, config, directory, si
1244
1251
  `--after=${after}`,
1245
1252
  ...label ? [`--label=${label}`] : [],
1246
1253
  ...config ? [`--config=${config}`] : [],
1247
- ...persistCliOptions({ directory, project })
1254
+ ...persistCliOptions({ directory, project, output })
1248
1255
  ],
1249
1256
  cwd: directory
1250
1257
  });
1251
1258
  if (!silent) {
1252
1259
  console.info(stdout);
1253
1260
  }
1254
- return persistedCliFiles({ directory, isDiff: true, project });
1261
+ return persistedCliFiles({ directory, isDiff: true, project, output });
1255
1262
  }
1256
1263
 
1257
1264
  // packages/ci/src/lib/cli/commands/merge-diffs.ts
1258
- async function runMergeDiffs(files, { bin, config, directory, silent }) {
1265
+ async function runMergeDiffs(files, { bin, config, directory, silent, output }) {
1259
1266
  const { stdout } = await executeProcess({
1260
1267
  command: bin,
1261
1268
  args: [
1262
1269
  "merge-diffs",
1263
1270
  ...files.map((file) => `--files=${file}`),
1264
1271
  ...config ? [`--config=${config}`] : [],
1265
- ...persistCliOptions({ directory })
1272
+ ...persistCliOptions({ directory, output })
1266
1273
  ],
1267
1274
  cwd: directory
1268
1275
  });
1269
1276
  if (!silent) {
1270
1277
  console.info(stdout);
1271
1278
  }
1272
- return persistedCliFiles({ directory, isDiff: true, formats: ["md"] });
1279
+ return persistedCliFiles({
1280
+ directory,
1281
+ isDiff: true,
1282
+ formats: ["md"],
1283
+ output
1284
+ });
1273
1285
  }
1274
1286
 
1275
1287
  // packages/ci/src/lib/cli/commands/print-config.ts
@@ -1296,7 +1308,8 @@ function createCommandContext(settings, project) {
1296
1308
  bin: project?.bin ?? settings.bin,
1297
1309
  directory: project?.directory ?? settings.directory,
1298
1310
  config: settings.config,
1299
- silent: settings.silent
1311
+ silent: settings.silent,
1312
+ output: settings.output.replaceAll("{project}", project?.name ?? "")
1300
1313
  };
1301
1314
  }
1302
1315
 
@@ -1350,7 +1363,9 @@ var DEFAULT_SETTINGS = {
1350
1363
  silent: false,
1351
1364
  debug: false,
1352
1365
  detectNewIssues: true,
1353
- logger: console
1366
+ logger: console,
1367
+ output: DEFAULT_PERSIST_OUTPUT_DIR,
1368
+ nxProjectsFilter: "--with-target={task}"
1354
1369
  };
1355
1370
 
1356
1371
  // packages/ci/src/lib/git.ts
@@ -1510,6 +1525,9 @@ function createIssuesSortCompareFn(report) {
1510
1525
  return (a, b) => getAuditImpactValue(b, report) - getAuditImpactValue(a, report);
1511
1526
  }
1512
1527
  function getAuditImpactValue({ audit, plugin }, report) {
1528
+ if (!report.categories) {
1529
+ return 0;
1530
+ }
1513
1531
  return report.categories.map((category) => {
1514
1532
  const weights = category.refs.map((ref) => {
1515
1533
  if (ref.plugin !== plugin.slug) {
@@ -1519,17 +1537,20 @@ function getAuditImpactValue({ audit, plugin }, report) {
1519
1537
  case "audit":
1520
1538
  return ref.slug === audit.slug ? ref.weight : 0;
1521
1539
  case "group":
1522
- const group = report.plugins.find(({ slug }) => slug === ref.plugin)?.groups?.find(({ slug }) => slug === ref.slug);
1523
- if (!group?.refs.length) {
1524
- return 0;
1525
- }
1526
- const groupRatio = (group.refs.find(({ slug }) => slug === audit.slug)?.weight ?? 0) / group.refs.reduce((acc, { weight }) => acc + weight, 0);
1527
- return ref.weight * groupRatio;
1540
+ return calculateGroupImpact(ref, audit, report);
1528
1541
  }
1529
1542
  });
1530
1543
  return weights.reduce((acc, weight) => acc + weight, 0) / category.refs.reduce((acc, { weight }) => acc + weight, 0);
1531
1544
  }).reduce((acc, value) => acc + value, 0);
1532
1545
  }
1546
+ function calculateGroupImpact(ref, audit, report) {
1547
+ const group = report.plugins.find(({ slug }) => slug === ref.plugin)?.groups?.find(({ slug }) => slug === ref.slug);
1548
+ if (!group?.refs.length) {
1549
+ return 0;
1550
+ }
1551
+ const groupRatio = (group.refs.find(({ slug }) => slug === audit.slug)?.weight ?? 0) / group.refs.reduce((acc, { weight }) => acc + weight, 0);
1552
+ return ref.weight * groupRatio;
1553
+ }
1533
1554
 
1534
1555
  // packages/ci/src/lib/run.ts
1535
1556
  async function runInCI(refs, api, options, git = simpleGit4()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-pushup/ci",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "CI automation logic for Code PushUp (provider-agnostic)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/code-pushup/cli/tree/main/packages/ci#readme",
@@ -28,8 +28,8 @@
28
28
  "main": "./index.js",
29
29
  "types": "./src/index.d.ts",
30
30
  "dependencies": {
31
- "@code-pushup/models": "0.54.0",
32
- "@code-pushup/utils": "0.54.0",
31
+ "@code-pushup/models": "0.56.0",
32
+ "@code-pushup/utils": "0.56.0",
33
33
  "glob": "^10.4.5",
34
34
  "simple-git": "^3.20.0",
35
35
  "yaml": "^2.5.1"
@@ -1,3 +1,3 @@
1
1
  import type { CommandContext } from '../context';
2
2
  import { type PersistedCliFiles } from '../persist';
3
- export declare function runCollect({ bin, config, directory, silent, project, }: CommandContext): Promise<PersistedCliFiles>;
3
+ export declare function runCollect({ bin, config, directory, silent, project, output, }: CommandContext): Promise<PersistedCliFiles>;
@@ -5,5 +5,5 @@ type CompareOptions = {
5
5
  after: string;
6
6
  label?: string;
7
7
  };
8
- export declare function runCompare({ before, after, label }: CompareOptions, { bin, config, directory, silent, project }: CommandContext): Promise<PersistedCliFiles>;
8
+ export declare function runCompare({ before, after, label }: CompareOptions, { bin, config, directory, silent, project, output }: CommandContext): Promise<PersistedCliFiles>;
9
9
  export {};
@@ -1,3 +1,3 @@
1
1
  import type { CommandContext } from '../context';
2
2
  import { type PersistedCliFiles } from '../persist';
3
- export declare function runMergeDiffs(files: string[], { bin, config, directory, silent }: CommandContext): Promise<PersistedCliFiles<'md'>>;
3
+ export declare function runMergeDiffs(files: string[], { bin, config, directory, silent, output }: CommandContext): Promise<PersistedCliFiles<'md'>>;
@@ -1,6 +1,6 @@
1
1
  import type { Settings } from '../models';
2
2
  import type { ProjectConfig } from '../monorepo';
3
- export type CommandContext = Pick<Settings, 'bin' | 'config' | 'directory' | 'silent'> & {
3
+ export type CommandContext = Pick<Settings, 'bin' | 'config' | 'directory' | 'silent' | 'output'> & {
4
4
  project?: string;
5
5
  };
6
6
  export declare function createCommandContext(settings: Settings, project: ProjectConfig | null | undefined): CommandContext;
@@ -8,14 +8,20 @@ export type PersistedCliFiles<T extends Format = Format> = PersistedCliFilesForm
8
8
  export type PersistedCliFilesFormats<T extends Format = Format> = {
9
9
  [F in T as `${F}FilePath`]: string;
10
10
  };
11
- export declare function persistCliOptions({ directory, project, }: {
11
+ export declare function persistCliOptions({ directory, project, output, }: {
12
12
  directory: string;
13
13
  project?: string;
14
+ output: string;
14
15
  }): string[];
15
- export declare function persistedCliFiles<TFormat extends Format = Format>({ directory, isDiff, project, formats, }: {
16
+ export declare function persistedCliFiles<TFormat extends Format = Format>({ directory, isDiff, project, formats, output, }: {
16
17
  directory: string;
17
18
  isDiff?: boolean;
18
19
  project?: string;
19
20
  formats?: TFormat[];
21
+ output: string;
20
22
  }): PersistedCliFiles<TFormat>;
21
- export declare function findPersistedFiles(rootDir: string, files: string[], project?: string): PersistedCliFiles;
23
+ export declare function findPersistedFiles({ rootDir, files, project, }: {
24
+ rootDir: string;
25
+ files: string[];
26
+ project?: string;
27
+ }): PersistedCliFiles;
@@ -1,4 +1,4 @@
1
- import type { Audit, Issue, PluginMeta, Report, ReportsDiff } from '@code-pushup/models';
1
+ import type { Audit, CategoryRef, Issue, PluginMeta, Report, ReportsDiff } from '@code-pushup/models';
2
2
  import { type ChangedFiles } from './git';
3
3
  export type SourceFileIssue = Required<Issue> & IssueContext;
4
4
  type IssueContext = {
@@ -13,4 +13,5 @@ export declare function filterRelevantIssues({ currReport, prevReport, reportsDi
13
13
  }): SourceFileIssue[];
14
14
  export declare function issuesMatch(prev: SourceFileIssue, curr: SourceFileIssue, changedFiles: ChangedFiles): boolean;
15
15
  export declare function getAuditImpactValue({ audit, plugin }: IssueContext, report: Report): number;
16
+ export declare function calculateGroupImpact(ref: CategoryRef, audit: Audit, report: Report): number;
16
17
  export {};
@@ -8,6 +8,7 @@ export type Options = {
8
8
  monorepo?: boolean | MonorepoTool;
9
9
  projects?: string[] | null;
10
10
  task?: string;
11
+ nxProjectsFilter?: string | string[];
11
12
  bin?: string;
12
13
  config?: string | null;
13
14
  directory?: string;
@@ -15,6 +16,7 @@ export type Options = {
15
16
  debug?: boolean;
16
17
  detectNewIssues?: boolean;
17
18
  logger?: Logger;
19
+ output?: string;
18
20
  };
19
21
  /**
20
22
  * {@link Options} with filled-in defaults.
@@ -10,6 +10,7 @@ export type MonorepoHandlerOptions = {
10
10
  task: string;
11
11
  cwd: string;
12
12
  observer?: ProcessObserver;
13
+ nxProjectsFilter: string | string[];
13
14
  };
14
15
  export type ProjectConfig = {
15
16
  name: string;