From bb5afa3b24e1884c7fad8fda6efa9fd5742036c8 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 8 Aug 2025 23:42:14 +0200 Subject: [PATCH 01/33] feat: add persist.report output to CLI --- e2e/cli-e2e/tests/collect.e2e.test.ts | 24 ++++++++++- packages/ci/src/lib/run-utils.ts | 1 + packages/cli/README.md | 25 ++++++----- .../lib/collect/collect-command.unit.test.ts | 2 +- .../implementation/core-config.middleware.ts | 7 ++++ .../core-config.middleware.unit.test.ts | 10 +++++ .../lib/implementation/core-config.model.ts | 2 +- packages/core/src/lib/collect-and-persist.ts | 41 ++++++++++++------- .../src/lib/collect-and-persist.unit.test.ts | 25 +++++++++++ .../core/src/lib/implementation/persist.ts | 2 +- packages/models/src/index.ts | 1 + packages/models/src/lib/cache-config.ts | 13 ++++++ packages/models/src/lib/core-config.ts | 2 + packages/models/src/lib/persist-config.ts | 1 + packages/utils/src/lib/file-system.ts | 2 +- 15 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 packages/models/src/lib/cache-config.ts diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index b29f26471..52ee4087f 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -7,7 +7,7 @@ import { TEST_OUTPUT_DIR, teardownTestFolder, } from '@code-pushup/test-utils'; -import { executeProcess, readTextFile } from '@code-pushup/utils'; +import { executeProcess, fileExists, readTextFile } from '@code-pushup/utils'; describe('CLI collect', () => { const dummyPluginTitle = 'Dummy Plugin'; @@ -61,6 +61,28 @@ describe('CLI collect', () => { expect(md).toContain(dummyAuditTitle); }); + it('should not create reports if --persist.no-report is given', async () => { + const { code } = await executeProcess({ + command: 'npx', + args: [ + '@code-pushup/cli', + '--no-progress', + 'collect', + '--persist.no-report', + ], + cwd: dummyDir, + }); + + expect(code).toBe(0); + + await expect( + fileExists(path.join(dummyOutputDir, 'report.md')), + ).resolves.toBeFalsy(); + await expect( + fileExists(path.join(dummyOutputDir, 'report.json')), + ).resolves.toBeFalsy(); + }); + it('should print report summary to stdout', async () => { const { code, stdout } = await executeProcess({ command: 'npx', diff --git a/packages/ci/src/lib/run-utils.ts b/packages/ci/src/lib/run-utils.ts index ae41c2532..abbb1dbce 100644 --- a/packages/ci/src/lib/run-utils.ts +++ b/packages/ci/src/lib/run-utils.ts @@ -483,6 +483,7 @@ export function configFromPatterns( outputDir: interpolate(persist.outputDir, variables), filename: interpolate(persist.filename, variables), format: persist.format, + report: persist.report, }, ...(upload && { upload: { diff --git a/packages/cli/README.md b/packages/cli/README.md index 0d09ad1ad..c607e28a8 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -207,17 +207,20 @@ Each example is fully tested to demonstrate best practices for plugin testing as ### Common Command Options -| Option | Type | Default | Description | -| --------------------------- | -------------------- | -------- | --------------------------------------------------------------------------- | -| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | -| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | -| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | -| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | -| **`--upload.project`** | `string` | n/a | Project slug from portal. | -| **`--upload.server`** | `string` | n/a | URL to your portal server. | -| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | -| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | -| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | +| Option | Type | Default | Description | +| --------------------------- | -------------------- | -------- | -------------------------------------------------------------------------------- | +| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | +| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | +| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | +| **`--persist.report`** | `boolean` | `true` | Generate the report files for given formats. (useful in combination wit caching) | +| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | +| **`--upload.project`** | `string` | n/a | Project slug from portal. | +| **`--upload.server`** | `string` | n/a | URL to your portal server. | +| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | +| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. | +| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. | +| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | +| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | > [!NOTE] > All common options, except `--onlyPlugins` and `--skipPlugins`, can be specified in the configuration file as well. diff --git a/packages/cli/src/lib/collect/collect-command.unit.test.ts b/packages/cli/src/lib/collect/collect-command.unit.test.ts index 796fee62b..6765add61 100644 --- a/packages/cli/src/lib/collect/collect-command.unit.test.ts +++ b/packages/cli/src/lib/collect/collect-command.unit.test.ts @@ -37,7 +37,7 @@ describe('collect-command', () => { expect(collectAndPersistReports).toHaveBeenCalledWith( expect.objectContaining({ config: '/test/code-pushup.config.ts', - persist: expect.objectContaining>({ + persist: expect.objectContaining({ filename: DEFAULT_PERSIST_FILENAME, outputDir: DEFAULT_PERSIST_OUTPUT_DIR, format: DEFAULT_PERSIST_FORMAT, diff --git a/packages/cli/src/lib/implementation/core-config.middleware.ts b/packages/cli/src/lib/implementation/core-config.middleware.ts index 0f2891178..735c2fb89 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.ts @@ -23,6 +23,7 @@ export async function coreConfigMiddleware< tsconfig, persist: cliPersist, upload: cliUpload, + cache: cliCache, ...remainingCliOptions } = processArgs; // Search for possible configuration file extensions if path is not given @@ -43,6 +44,11 @@ export async function coreConfigMiddleware< }); return { ...(config != null && { config }), + cache: { + write: false, + read: false, + ...cliCache, + }, persist: { outputDir: cliPersist?.outputDir ?? @@ -53,6 +59,7 @@ export async function coreConfigMiddleware< format: normalizeFormats( cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT, ), + report: !('no-report' in (cliPersist ?? {})), }, ...(upload != null && { upload }), ...remainingRcConfig, diff --git a/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts b/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts index 134b8f183..9088bdd37 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts @@ -73,4 +73,14 @@ describe('coreConfigMiddleware', () => { 'apps/website/tsconfig.json', ); }); + + it('should forward normalize --persist.report option', async () => { + await coreConfigMiddleware({ + config: 'apps/website/code-pushup.config.ts', + } as GeneralCliOptions & CoreConfigCliOptions & FilterOptions); + expect(readRcByPath).toHaveBeenCalledWith( + 'apps/website/code-pushup.config.ts', + 'apps/website/tsconfig.json', + ); + }); }); diff --git a/packages/cli/src/lib/implementation/core-config.model.ts b/packages/cli/src/lib/implementation/core-config.model.ts index ed011da56..f6675befc 100644 --- a/packages/cli/src/lib/implementation/core-config.model.ts +++ b/packages/cli/src/lib/implementation/core-config.model.ts @@ -19,6 +19,6 @@ export type ConfigCliOptions = { verbose?: string; }; -export type CoreConfigCliOptions = Pick & { +export type CoreConfigCliOptions = Pick & { upload?: Partial>; }; diff --git a/packages/core/src/lib/collect-and-persist.ts b/packages/core/src/lib/collect-and-persist.ts index dfa48ba76..12ddb8ef8 100644 --- a/packages/core/src/lib/collect-and-persist.ts +++ b/packages/core/src/lib/collect-and-persist.ts @@ -8,6 +8,7 @@ import { logStdoutSummary, scoreReport, sortReport, + ui, } from '@code-pushup/utils'; import { collect } from './implementation/collect.js'; import { @@ -18,30 +19,42 @@ import type { GlobalOptions } from './types.js'; export type CollectAndPersistReportsOptions = Pick< CoreConfig, - 'plugins' | 'categories' -> & { persist: Required } & Partial; + 'plugins' | 'categories' | 'cache' +> & { + persist: Required> & + Pick; +} & Partial; export async function collectAndPersistReports( options: CollectAndPersistReportsOptions, ): Promise { - const report = await collect(options); - const sortedScoredReport = sortReport(scoreReport(report)); + const logger = ui().logger; + const reportResult = await collect(options); + const sortedScoredReport = sortReport(scoreReport(reportResult)); - const persistResults = await persistReport( - report, - sortedScoredReport, - options.persist, - ); + const { persist } = options; + const { report: shouldGenerateReport = true, ...persistOptions } = + persist ?? {}; - // terminal output - logStdoutSummary(sortedScoredReport); + if (shouldGenerateReport === true) { + const persistResults = await persistReport( + reportResult, + sortedScoredReport, + persistOptions, + ); - if (isVerbose()) { - logPersistedResults(persistResults); + if (isVerbose()) { + logPersistedResults(persistResults); + } + } else { + logger.info('Skipping saving reports as `persist.report` is false'); } + // terminal output + logStdoutSummary(sortedScoredReport); + // validate report and throw if invalid - report.plugins.forEach(plugin => { + reportResult.plugins.forEach(plugin => { // Running checks after persisting helps while debugging as you can check the invalid output after the error is thrown pluginReportSchema.parse(plugin); }); diff --git a/packages/core/src/lib/collect-and-persist.unit.test.ts b/packages/core/src/lib/collect-and-persist.unit.test.ts index 1ecb5c5fe..00d38c76c 100644 --- a/packages/core/src/lib/collect-and-persist.unit.test.ts +++ b/packages/core/src/lib/collect-and-persist.unit.test.ts @@ -115,6 +115,31 @@ describe('collectAndPersistReports', () => { expect(logPersistedResults).toHaveBeenCalled(); }); + it('should call collect and not persistReport if report options is false in verbose mode', async () => { + const sortedScoredReport = sortReport(scoreReport(MINIMAL_REPORT_MOCK)); + + vi.stubEnv('CP_VERBOSE', 'true'); + + const verboseConfig: CollectAndPersistReportsOptions = { + ...MINIMAL_CONFIG_MOCK, + persist: { + outputDir: 'output', + filename: 'report', + format: ['md'], + report: false, + }, + progress: false, + }; + await collectAndPersistReports(verboseConfig); + + expect(collect).toHaveBeenCalledWith(verboseConfig); + + expect(persistReport).not.toHaveBeenCalled(); + expect(logPersistedResults).not.toHaveBeenCalled(); + + expect(logStdoutSummary).toHaveBeenCalledWith(sortedScoredReport); + }); + it('should print a summary to stdout', async () => { await collectAndPersistReports( MINIMAL_CONFIG_MOCK as CollectAndPersistReportsOptions, diff --git a/packages/core/src/lib/implementation/persist.ts b/packages/core/src/lib/implementation/persist.ts index 915af9459..c55a0103e 100644 --- a/packages/core/src/lib/implementation/persist.ts +++ b/packages/core/src/lib/implementation/persist.ts @@ -25,7 +25,7 @@ export class PersistError extends Error { export async function persistReport( report: Report, sortedScoredReport: ScoredReport, - options: Required, + options: Required>, ): Promise { const { outputDir, filename, format } = options; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 91c22fb10..5050bad26 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -6,6 +6,7 @@ export { sourceFileLocationSchema, type SourceFileLocation, } from './lib/source.js'; +export { cacheConfigSchema, type CacheConfig } from './lib/cache-config.js'; export { auditDetailsSchema, diff --git a/packages/models/src/lib/cache-config.ts b/packages/models/src/lib/cache-config.ts new file mode 100644 index 000000000..96b5cff35 --- /dev/null +++ b/packages/models/src/lib/cache-config.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +export const cacheConfigSchema = z + .object({ + read: z + .boolean() + .describe('Whether to read from cache if available') + .optional(), + write: z.boolean().describe('Whether to write results to cache').optional(), + }) + .describe('Cache configuration for read and write operations'); + +export type CacheConfig = z.infer; diff --git a/packages/models/src/lib/core-config.ts b/packages/models/src/lib/core-config.ts index a4e6469f7..ea54f5745 100644 --- a/packages/models/src/lib/core-config.ts +++ b/packages/models/src/lib/core-config.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { cacheConfigSchema } from './cache-config.js'; import { categoriesSchema } from './category-config.js'; import { createCheck } from './implementation/checks.js'; import { findMissingSlugsInCategoryRefs } from './implementation/utils.js'; @@ -13,6 +14,7 @@ export const unrefinedCoreConfigSchema = z.object({ .describe( 'List of plugins to be used (official, community-provided, or custom)', ), + cache: cacheConfigSchema.optional(), /** portal configuration for persisting results */ persist: persistConfigSchema.optional(), /** portal configuration for uploading results */ diff --git a/packages/models/src/lib/persist-config.ts b/packages/models/src/lib/persist-config.ts index b349343ff..c12e85ed8 100644 --- a/packages/models/src/lib/persist-config.ts +++ b/packages/models/src/lib/persist-config.ts @@ -10,6 +10,7 @@ export const persistConfigSchema = z.object({ .describe('Artifacts file name (without extension)') .optional(), format: z.array(formatSchema).optional(), + report: z.boolean().optional(), }); export type PersistConfig = z.infer; diff --git a/packages/utils/src/lib/file-system.ts b/packages/utils/src/lib/file-system.ts index d97982bfe..51d54d671 100644 --- a/packages/utils/src/lib/file-system.ts +++ b/packages/utils/src/lib/file-system.ts @@ -90,7 +90,7 @@ export function createReportPath({ filename, format, suffix, -}: Omit, 'format'> & { +}: Omit, 'format' | 'report'> & { format: Format; suffix?: string; }): string { From b992caf297e514ab84e564fa38c4e1b41bd94087 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Aug 2025 00:32:24 +0200 Subject: [PATCH 02/33] fix: revert changes --- packages/ci/src/lib/run-utils.ts | 1 - packages/cli/README.md | 27 ++++++------ .../lib/collect/collect-command.unit.test.ts | 2 +- .../implementation/core-config.middleware.ts | 1 - .../core-config.middleware.unit.test.ts | 10 ----- .../lib/implementation/core-config.model.ts | 2 +- packages/core/src/lib/collect-and-persist.ts | 41 +++++++------------ .../src/lib/collect-and-persist.unit.test.ts | 25 ----------- .../core/src/lib/implementation/persist.ts | 2 +- packages/models/src/index.ts | 1 - packages/models/src/lib/cache-config.ts | 13 ------ packages/models/src/lib/core-config.ts | 2 - packages/models/src/lib/persist-config.ts | 1 - packages/utils/src/lib/file-system.ts | 2 +- 14 files changed, 31 insertions(+), 99 deletions(-) delete mode 100644 packages/models/src/lib/cache-config.ts diff --git a/packages/ci/src/lib/run-utils.ts b/packages/ci/src/lib/run-utils.ts index abbb1dbce..ae41c2532 100644 --- a/packages/ci/src/lib/run-utils.ts +++ b/packages/ci/src/lib/run-utils.ts @@ -483,7 +483,6 @@ export function configFromPatterns( outputDir: interpolate(persist.outputDir, variables), filename: interpolate(persist.filename, variables), format: persist.format, - report: persist.report, }, ...(upload && { upload: { diff --git a/packages/cli/README.md b/packages/cli/README.md index c607e28a8..57d39bf37 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -207,20 +207,19 @@ Each example is fully tested to demonstrate best practices for plugin testing as ### Common Command Options -| Option | Type | Default | Description | -| --------------------------- | -------------------- | -------- | -------------------------------------------------------------------------------- | -| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | -| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | -| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | -| **`--persist.report`** | `boolean` | `true` | Generate the report files for given formats. (useful in combination wit caching) | -| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | -| **`--upload.project`** | `string` | n/a | Project slug from portal. | -| **`--upload.server`** | `string` | n/a | URL to your portal server. | -| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | -| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. | -| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. | -| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | -| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | +| Option | Type | Default | Description | +| --------------------------- | -------------------- | -------- | --------------------------------------------------------------------------- | +| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | +| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | +| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | +| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | +| **`--upload.project`** | `string` | n/a | Project slug from portal. | +| **`--upload.server`** | `string` | n/a | URL to your portal server. | +| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | +| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. | +| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. | +| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | +| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | > [!NOTE] > All common options, except `--onlyPlugins` and `--skipPlugins`, can be specified in the configuration file as well. diff --git a/packages/cli/src/lib/collect/collect-command.unit.test.ts b/packages/cli/src/lib/collect/collect-command.unit.test.ts index 6765add61..796fee62b 100644 --- a/packages/cli/src/lib/collect/collect-command.unit.test.ts +++ b/packages/cli/src/lib/collect/collect-command.unit.test.ts @@ -37,7 +37,7 @@ describe('collect-command', () => { expect(collectAndPersistReports).toHaveBeenCalledWith( expect.objectContaining({ config: '/test/code-pushup.config.ts', - persist: expect.objectContaining({ + persist: expect.objectContaining>({ filename: DEFAULT_PERSIST_FILENAME, outputDir: DEFAULT_PERSIST_OUTPUT_DIR, format: DEFAULT_PERSIST_FORMAT, diff --git a/packages/cli/src/lib/implementation/core-config.middleware.ts b/packages/cli/src/lib/implementation/core-config.middleware.ts index 735c2fb89..8363fdd75 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.ts @@ -59,7 +59,6 @@ export async function coreConfigMiddleware< format: normalizeFormats( cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT, ), - report: !('no-report' in (cliPersist ?? {})), }, ...(upload != null && { upload }), ...remainingRcConfig, diff --git a/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts b/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts index 9088bdd37..134b8f183 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts @@ -73,14 +73,4 @@ describe('coreConfigMiddleware', () => { 'apps/website/tsconfig.json', ); }); - - it('should forward normalize --persist.report option', async () => { - await coreConfigMiddleware({ - config: 'apps/website/code-pushup.config.ts', - } as GeneralCliOptions & CoreConfigCliOptions & FilterOptions); - expect(readRcByPath).toHaveBeenCalledWith( - 'apps/website/code-pushup.config.ts', - 'apps/website/tsconfig.json', - ); - }); }); diff --git a/packages/cli/src/lib/implementation/core-config.model.ts b/packages/cli/src/lib/implementation/core-config.model.ts index f6675befc..ed011da56 100644 --- a/packages/cli/src/lib/implementation/core-config.model.ts +++ b/packages/cli/src/lib/implementation/core-config.model.ts @@ -19,6 +19,6 @@ export type ConfigCliOptions = { verbose?: string; }; -export type CoreConfigCliOptions = Pick & { +export type CoreConfigCliOptions = Pick & { upload?: Partial>; }; diff --git a/packages/core/src/lib/collect-and-persist.ts b/packages/core/src/lib/collect-and-persist.ts index 12ddb8ef8..dfa48ba76 100644 --- a/packages/core/src/lib/collect-and-persist.ts +++ b/packages/core/src/lib/collect-and-persist.ts @@ -8,7 +8,6 @@ import { logStdoutSummary, scoreReport, sortReport, - ui, } from '@code-pushup/utils'; import { collect } from './implementation/collect.js'; import { @@ -19,42 +18,30 @@ import type { GlobalOptions } from './types.js'; export type CollectAndPersistReportsOptions = Pick< CoreConfig, - 'plugins' | 'categories' | 'cache' -> & { - persist: Required> & - Pick; -} & Partial; + 'plugins' | 'categories' +> & { persist: Required } & Partial; export async function collectAndPersistReports( options: CollectAndPersistReportsOptions, ): Promise { - const logger = ui().logger; - const reportResult = await collect(options); - const sortedScoredReport = sortReport(scoreReport(reportResult)); + const report = await collect(options); + const sortedScoredReport = sortReport(scoreReport(report)); - const { persist } = options; - const { report: shouldGenerateReport = true, ...persistOptions } = - persist ?? {}; - - if (shouldGenerateReport === true) { - const persistResults = await persistReport( - reportResult, - sortedScoredReport, - persistOptions, - ); - - if (isVerbose()) { - logPersistedResults(persistResults); - } - } else { - logger.info('Skipping saving reports as `persist.report` is false'); - } + const persistResults = await persistReport( + report, + sortedScoredReport, + options.persist, + ); // terminal output logStdoutSummary(sortedScoredReport); + if (isVerbose()) { + logPersistedResults(persistResults); + } + // validate report and throw if invalid - reportResult.plugins.forEach(plugin => { + report.plugins.forEach(plugin => { // Running checks after persisting helps while debugging as you can check the invalid output after the error is thrown pluginReportSchema.parse(plugin); }); diff --git a/packages/core/src/lib/collect-and-persist.unit.test.ts b/packages/core/src/lib/collect-and-persist.unit.test.ts index 00d38c76c..1ecb5c5fe 100644 --- a/packages/core/src/lib/collect-and-persist.unit.test.ts +++ b/packages/core/src/lib/collect-and-persist.unit.test.ts @@ -115,31 +115,6 @@ describe('collectAndPersistReports', () => { expect(logPersistedResults).toHaveBeenCalled(); }); - it('should call collect and not persistReport if report options is false in verbose mode', async () => { - const sortedScoredReport = sortReport(scoreReport(MINIMAL_REPORT_MOCK)); - - vi.stubEnv('CP_VERBOSE', 'true'); - - const verboseConfig: CollectAndPersistReportsOptions = { - ...MINIMAL_CONFIG_MOCK, - persist: { - outputDir: 'output', - filename: 'report', - format: ['md'], - report: false, - }, - progress: false, - }; - await collectAndPersistReports(verboseConfig); - - expect(collect).toHaveBeenCalledWith(verboseConfig); - - expect(persistReport).not.toHaveBeenCalled(); - expect(logPersistedResults).not.toHaveBeenCalled(); - - expect(logStdoutSummary).toHaveBeenCalledWith(sortedScoredReport); - }); - it('should print a summary to stdout', async () => { await collectAndPersistReports( MINIMAL_CONFIG_MOCK as CollectAndPersistReportsOptions, diff --git a/packages/core/src/lib/implementation/persist.ts b/packages/core/src/lib/implementation/persist.ts index c55a0103e..915af9459 100644 --- a/packages/core/src/lib/implementation/persist.ts +++ b/packages/core/src/lib/implementation/persist.ts @@ -25,7 +25,7 @@ export class PersistError extends Error { export async function persistReport( report: Report, sortedScoredReport: ScoredReport, - options: Required>, + options: Required, ): Promise { const { outputDir, filename, format } = options; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 5050bad26..91c22fb10 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -6,7 +6,6 @@ export { sourceFileLocationSchema, type SourceFileLocation, } from './lib/source.js'; -export { cacheConfigSchema, type CacheConfig } from './lib/cache-config.js'; export { auditDetailsSchema, diff --git a/packages/models/src/lib/cache-config.ts b/packages/models/src/lib/cache-config.ts deleted file mode 100644 index 96b5cff35..000000000 --- a/packages/models/src/lib/cache-config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { z } from 'zod'; - -export const cacheConfigSchema = z - .object({ - read: z - .boolean() - .describe('Whether to read from cache if available') - .optional(), - write: z.boolean().describe('Whether to write results to cache').optional(), - }) - .describe('Cache configuration for read and write operations'); - -export type CacheConfig = z.infer; diff --git a/packages/models/src/lib/core-config.ts b/packages/models/src/lib/core-config.ts index ea54f5745..a4e6469f7 100644 --- a/packages/models/src/lib/core-config.ts +++ b/packages/models/src/lib/core-config.ts @@ -1,5 +1,4 @@ import { z } from 'zod'; -import { cacheConfigSchema } from './cache-config.js'; import { categoriesSchema } from './category-config.js'; import { createCheck } from './implementation/checks.js'; import { findMissingSlugsInCategoryRefs } from './implementation/utils.js'; @@ -14,7 +13,6 @@ export const unrefinedCoreConfigSchema = z.object({ .describe( 'List of plugins to be used (official, community-provided, or custom)', ), - cache: cacheConfigSchema.optional(), /** portal configuration for persisting results */ persist: persistConfigSchema.optional(), /** portal configuration for uploading results */ diff --git a/packages/models/src/lib/persist-config.ts b/packages/models/src/lib/persist-config.ts index c12e85ed8..b349343ff 100644 --- a/packages/models/src/lib/persist-config.ts +++ b/packages/models/src/lib/persist-config.ts @@ -10,7 +10,6 @@ export const persistConfigSchema = z.object({ .describe('Artifacts file name (without extension)') .optional(), format: z.array(formatSchema).optional(), - report: z.boolean().optional(), }); export type PersistConfig = z.infer; diff --git a/packages/utils/src/lib/file-system.ts b/packages/utils/src/lib/file-system.ts index 51d54d671..d97982bfe 100644 --- a/packages/utils/src/lib/file-system.ts +++ b/packages/utils/src/lib/file-system.ts @@ -90,7 +90,7 @@ export function createReportPath({ filename, format, suffix, -}: Omit, 'format' | 'report'> & { +}: Omit, 'format'> & { format: Format; suffix?: string; }): string { From ccfc1dffeebaa084491c7e2f747de884a1f94607 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 11 Aug 2025 22:02:08 +0200 Subject: [PATCH 03/33] fix: adjust models --- .../lib/implementation/core-config.model.ts | 14 ++++++++++++-- .../lib/implementation/core-config.options.ts | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/implementation/core-config.model.ts b/packages/cli/src/lib/implementation/core-config.model.ts index ed011da56..4f082c6a0 100644 --- a/packages/cli/src/lib/implementation/core-config.model.ts +++ b/packages/cli/src/lib/implementation/core-config.model.ts @@ -1,4 +1,9 @@ -import type { CoreConfig, Format, UploadConfig } from '@code-pushup/models'; +import type { + CacheConfig, + CoreConfig, + Format, + UploadConfig, +} from '@code-pushup/models'; export type PersistConfigCliOptions = { 'persist.outputDir'?: string; @@ -13,6 +18,11 @@ export type UploadConfigCliOptions = { 'upload.server'?: string; }; +export type CacheConfigCliOptions = { + 'cache.read'?: boolean; + 'cache.write'?: boolean; +}; + export type ConfigCliOptions = { config?: string; tsconfig?: string; @@ -21,4 +31,4 @@ export type ConfigCliOptions = { export type CoreConfigCliOptions = Pick & { upload?: Partial>; -}; +} & CacheConfig; diff --git a/packages/cli/src/lib/implementation/core-config.options.ts b/packages/cli/src/lib/implementation/core-config.options.ts index 79d62346a..0a20f827b 100644 --- a/packages/cli/src/lib/implementation/core-config.options.ts +++ b/packages/cli/src/lib/implementation/core-config.options.ts @@ -1,5 +1,6 @@ import type { Options } from 'yargs'; import type { + CacheConfigCliOptions, PersistConfigCliOptions, UploadConfigCliOptions, } from './core-config.model.js'; @@ -11,6 +12,7 @@ export function yargsCoreConfigOptionsDefinition(): Record< return { ...yargsPersistConfigOptionsDefinition(), ...yargsUploadConfigOptionsDefinition(), + ...yargsCacheConfigOptionsDefinition(), }; } @@ -57,3 +59,19 @@ export function yargsUploadConfigOptionsDefinition(): Record< }, }; } + +export function yargsCacheConfigOptionsDefinition(): Record< + keyof CacheConfigCliOptions, + Options +> { + return { + 'cache.read': { + describe: 'Read runner-output.json to file system', + type: 'boolean', + }, + 'cache.write': { + describe: 'Write runner-output.json to file system', + type: 'boolean', + }, + }; +} From 894cf68e007fd2cc9b1ea41cad3d6bfe5e178989 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 11 Aug 2025 22:18:58 +0200 Subject: [PATCH 04/33] test: adjust e2e tests for caching --- e2e/cli-e2e/tests/collect.e2e.test.ts | 38 +++++++++++++++++-- packages/core/src/index.ts | 7 ++++ .../src/lib/implementation/runner.int.test.ts | 6 +-- .../core/src/lib/implementation/runner.ts | 6 +-- .../lib/implementation/runner.unit.test.ts | 6 +-- packages/models/docs/models-reference.md | 30 +++++++++++++++ 6 files changed, 81 insertions(+), 12 deletions(-) diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index 52ee4087f..e23a3aba8 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -1,13 +1,23 @@ import { cp } from 'node:fs/promises'; import path from 'node:path'; import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { + type ValidatedRunnerResult, + getRunnerOutputsPath, +} from '@code-pushup/core'; import { nxTargetProject } from '@code-pushup/test-nx-utils'; import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR, teardownTestFolder, } from '@code-pushup/test-utils'; -import { executeProcess, fileExists, readTextFile } from '@code-pushup/utils'; +import { + executeProcess, + fileExists, + readJsonFile, + readTextFile, +} from '@code-pushup/utils'; +import { dummyPluginSlug } from '../mocks/fixtures/dummy-setup/dummy.plugin'; describe('CLI collect', () => { const dummyPluginTitle = 'Dummy Plugin'; @@ -61,14 +71,14 @@ describe('CLI collect', () => { expect(md).toContain(dummyAuditTitle); }); - it('should not create reports if --persist.no-report is given', async () => { + it('should not create reports if --persist.skipReports is given', async () => { const { code } = await executeProcess({ command: 'npx', args: [ '@code-pushup/cli', '--no-progress', 'collect', - '--persist.no-report', + '--persist.skipReports', ], cwd: dummyDir, }); @@ -83,6 +93,28 @@ describe('CLI collect', () => { ).resolves.toBeFalsy(); }); + it('should write runner outputs if --cache is given', async () => { + const { code } = await executeProcess({ + command: 'npx', + args: ['@code-pushup/cli', '--no-progress', 'collect', '--cache'], + cwd: dummyDir, + }); + + expect(code).toBe(0); + + await expect( + readJsonFile( + getRunnerOutputsPath(dummyPluginSlug, dummyOutputDir), + ), + ).resolves.toStrictEqual([ + { + slug: 'dummy-audit', + score: 1, + value: 10, + }, + ]); + }); + it('should print report summary to stdout', async () => { const { code, stdout } = await executeProcess({ command: 'npx', diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0c3e18080..77244f546 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,5 @@ +import type { ValidatedRunnerResult } from './lib/implementation/runner'; + export { collectAndPersistReports, type CollectAndPersistReportsOptions, @@ -7,6 +9,11 @@ export { compareReports, type CompareOptions, } from './lib/compare.js'; +export { + getRunnerOutputsPath, + type ValidatedRunnerResult, +} from './lib/implementation/runner.js'; + export { history, type HistoryOnlyOptions, diff --git a/packages/core/src/lib/implementation/runner.int.test.ts b/packages/core/src/lib/implementation/runner.int.test.ts index b26ee6ebe..53acb0036 100644 --- a/packages/core/src/lib/implementation/runner.int.test.ts +++ b/packages/core/src/lib/implementation/runner.int.test.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import { cleanTestFolder } from '@code-pushup/test-utils'; import { ensureDirectoryExists } from '@code-pushup/utils'; import { - getAuditOutputsPath, + getRunnerOutputsPath, readRunnerResults, writeRunnerResults, } from './runner.js'; @@ -23,7 +23,7 @@ describe('readRunnerResults', () => { beforeEach(async () => { await ensureDirectoryExists(cacheDir); await writeFile( - getAuditOutputsPath(pluginSlug, outputDir), + getRunnerOutputsPath(pluginSlug, outputDir), JSON.stringify([ { slug: 'node-version', @@ -77,7 +77,7 @@ describe('writeRunnerResults', () => { beforeEach(async () => { await ensureDirectoryExists(cacheDir); await writeFile( - getAuditOutputsPath(pluginSlug, outputDir), + getRunnerOutputsPath(pluginSlug, outputDir), JSON.stringify([ { slug: 'node-version', diff --git a/packages/core/src/lib/implementation/runner.ts b/packages/core/src/lib/implementation/runner.ts index 517d1e971..743f3acad 100644 --- a/packages/core/src/lib/implementation/runner.ts +++ b/packages/core/src/lib/implementation/runner.ts @@ -131,7 +131,7 @@ function auditOutputsCorrelateWithPluginOutput( }); } -export function getAuditOutputsPath(pluginSlug: string, outputDir: string) { +export function getRunnerOutputsPath(pluginSlug: string, outputDir: string) { return path.join(outputDir, pluginSlug, 'runner-output.json'); } @@ -148,7 +148,7 @@ export async function writeRunnerResults( ): Promise { await ensureDirectoryExists(outputDir); await writeFile( - getAuditOutputsPath(pluginSlug, outputDir), + getRunnerOutputsPath(pluginSlug, outputDir), JSON.stringify(runnerResult.audits, null, 2), ); } @@ -157,7 +157,7 @@ export async function readRunnerResults( pluginSlug: string, outputDir: string, ): Promise { - const auditOutputsPath = getAuditOutputsPath(pluginSlug, outputDir); + const auditOutputsPath = getRunnerOutputsPath(pluginSlug, outputDir); if (await fileExists(auditOutputsPath)) { const cachedResult = await readJsonFile(auditOutputsPath); diff --git a/packages/core/src/lib/implementation/runner.unit.test.ts b/packages/core/src/lib/implementation/runner.unit.test.ts index a9aea1270..06e962e3a 100644 --- a/packages/core/src/lib/implementation/runner.unit.test.ts +++ b/packages/core/src/lib/implementation/runner.unit.test.ts @@ -13,13 +13,13 @@ import { executePluginRunner, executeRunnerConfig, executeRunnerFunction, - getAuditOutputsPath, + getRunnerOutputsPath, } from './runner.js'; -describe('getAuditOutputsPath', () => { +describe('getRunnerOutputsPath', () => { it('should read runner results from a file', async () => { expect( - osAgnosticPath(getAuditOutputsPath('plugin-with-cache', 'output')), + osAgnosticPath(getRunnerOutputsPath('plugin-with-cache', 'output')), ).toBe(osAgnosticPath('output/plugin-with-cache/runner-output.json')); }); }); diff --git a/packages/models/docs/models-reference.md b/packages/models/docs/models-reference.md index bffd0633f..a53b5d5ec 100644 --- a/packages/models/docs/models-reference.md +++ b/packages/models/docs/models-reference.md @@ -133,6 +133,36 @@ _Object containing the following properties:_ _(\*) Required._ +## CacheConfigObject + +Cache configuration object for read and/or write operations + +_Object containing the following properties:_ + +| Property | Description | Type | Default | +| :------- | :-------------------------------------- | :-------- | :------ | +| `read` | Whether to read from cache if available | `boolean` | `false` | +| `write` | Whether to write results to cache | `boolean` | `false` | + +_All properties are optional._ + +## CacheConfig + +Cache configuration for read and write operations + +_Union of the following possible types:_ + +- [CacheConfigShorthand](#cacheconfigshorthand) +- [CacheConfigObject](#cacheconfigobject) + +_Default value:_ `false` + +## CacheConfigShorthand + +Cache configuration shorthand for both, read and write operations + +_Boolean._ + ## CategoryConfig _Object containing the following properties:_ From 26d5f7b1af84c705b84c315a9112d398b1c37b50 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 00:21:53 +0200 Subject: [PATCH 05/33] test: add int tests for caching --- .../tests/__snapshots__/help.e2e.test.ts.snap | 10 +- packages/cli/README.md | 25 +++ .../implementation/core-config.middleware.ts | 19 ++- .../lib/implementation/core-config.model.ts | 4 +- .../lib/implementation/core-config.options.ts | 8 +- packages/core/src/index.ts | 2 - packages/core/src/lib/collect-and-persist.ts | 6 +- .../src/lib/collect-and-persist.unit.test.ts | 8 + .../lib/implementation/collect.int.test.ts | 151 +++++++++++++++--- .../core/src/lib/implementation/collect.ts | 7 +- .../core/src/lib/implementation/runner.ts | 8 +- 11 files changed, 209 insertions(+), 39 deletions(-) diff --git a/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap b/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap index beb76af1f..f2de66cb0 100644 --- a/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap +++ b/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap @@ -54,8 +54,14 @@ Upload Options: [string] Options: - --version Show version [boolean] - -h, --help Show help [boolean] + --version Show version [boolean] + --cache Cache runner outputs (both read and write) + [boolean] + --cache.read Read runner-output.json to file system + [boolean] + --cache.write Write runner-output.json to file system + [boolean] + -h, --help Show help [boolean] Examples: code-pushup Run collect followed by upload based diff --git a/packages/cli/README.md b/packages/cli/README.md index 57d39bf37..334d089d5 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -328,3 +328,28 @@ In addition to the [Common Command Options](#common-command-options), the follow | Option | Required | Type | Description | | ------------- | :------: | ---------- | --------------------------------- | | **`--files`** | yes | `string[]` | List of `report-diff.json` paths. | + +## Caching + +The CLI supports caching to speed up subsequent runs and is compatible with Nx and turborepo. + +Depending on your strategy, you can cache the generated reports files or plugin runner output. +For fine-grained caching, we suggest caching plugin runner output. + +### Caching Example Nx + +To cache plugin runner output, you can use the `--cache.write` and `--cache.read` options. + +Cache plugin level output files: + +```bash +# CACHE OUTPUTS +# write cache to persist.outputDir//runner-output.json +# npx @code-pushup/cli collect --persist.outputDir {projectRoot}/.code-pushup --persist.skipReports --onlyPlugins coverage --cache +nx run-many -t code-pushup:collect:coverage +# npx @code-pushup/cli collect --persist.outputDir {projectRoot}/.code-pushup --persist.skipReports --onlyPlugins eslint --cache +nx run-many -t code-pushup:collect:eslint +# READ CACHED OUTPUTS +# npx @code-pushup/cli collect --cache.read +nx run-many -t code-pushup:collect +``` diff --git a/packages/cli/src/lib/implementation/core-config.middleware.ts b/packages/cli/src/lib/implementation/core-config.middleware.ts index 8363fdd75..424c8c303 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.ts @@ -1,5 +1,7 @@ import { autoloadRc, readRcByPath } from '@code-pushup/core'; import { + type CacheConfig, + type CacheConfigObject, type CoreConfig, DEFAULT_PERSIST_FILENAME, DEFAULT_PERSIST_FORMAT, @@ -42,13 +44,10 @@ export async function coreConfigMiddleware< ...rcUpload, ...cliUpload, }); + return { ...(config != null && { config }), - cache: { - write: false, - read: false, - ...cliCache, - }, + cache: normalizeCache(cliCache), persist: { outputDir: cliPersist?.outputDir ?? @@ -66,5 +65,15 @@ export async function coreConfigMiddleware< }; } +export const normalizeCache = (cache?: CacheConfig): CacheConfigObject => { + if (cache == null) { + return { write: false, read: false }; + } + if (typeof cache === 'boolean') { + return { write: cache, read: cache }; + } + return { write: cache.write ?? false, read: cache.read ?? false }; +}; + export const normalizeFormats = (formats?: string[]): Format[] => (formats ?? []).flatMap(format => format.split(',') as Format[]); diff --git a/packages/cli/src/lib/implementation/core-config.model.ts b/packages/cli/src/lib/implementation/core-config.model.ts index 4f082c6a0..eeb296ca3 100644 --- a/packages/cli/src/lib/implementation/core-config.model.ts +++ b/packages/cli/src/lib/implementation/core-config.model.ts @@ -21,6 +21,7 @@ export type UploadConfigCliOptions = { export type CacheConfigCliOptions = { 'cache.read'?: boolean; 'cache.write'?: boolean; + cache?: boolean; }; export type ConfigCliOptions = { @@ -31,4 +32,5 @@ export type ConfigCliOptions = { export type CoreConfigCliOptions = Pick & { upload?: Partial>; -} & CacheConfig; + cache?: CacheConfig; +}; diff --git a/packages/cli/src/lib/implementation/core-config.options.ts b/packages/cli/src/lib/implementation/core-config.options.ts index 0a20f827b..f0f4423d8 100644 --- a/packages/cli/src/lib/implementation/core-config.options.ts +++ b/packages/cli/src/lib/implementation/core-config.options.ts @@ -6,7 +6,9 @@ import type { } from './core-config.model.js'; export function yargsCoreConfigOptionsDefinition(): Record< - keyof (PersistConfigCliOptions & UploadConfigCliOptions), + keyof (PersistConfigCliOptions & + UploadConfigCliOptions & + CacheConfigCliOptions), Options > { return { @@ -65,6 +67,10 @@ export function yargsCacheConfigOptionsDefinition(): Record< Options > { return { + cache: { + describe: 'Cache runner outputs (both read and write)', + type: 'boolean', + }, 'cache.read': { describe: 'Read runner-output.json to file system', type: 'boolean', diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 77244f546..f223a1f06 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,3 @@ -import type { ValidatedRunnerResult } from './lib/implementation/runner'; - export { collectAndPersistReports, type CollectAndPersistReportsOptions, diff --git a/packages/core/src/lib/collect-and-persist.ts b/packages/core/src/lib/collect-and-persist.ts index dfa48ba76..5bab68b12 100644 --- a/packages/core/src/lib/collect-and-persist.ts +++ b/packages/core/src/lib/collect-and-persist.ts @@ -1,4 +1,5 @@ import { + type CacheConfigObject, type CoreConfig, type PersistConfig, pluginReportSchema, @@ -19,7 +20,10 @@ import type { GlobalOptions } from './types.js'; export type CollectAndPersistReportsOptions = Pick< CoreConfig, 'plugins' | 'categories' -> & { persist: Required } & Partial; +> & { + persist: Required; + cache: CacheConfigObject; +} & Partial; export async function collectAndPersistReports( options: CollectAndPersistReportsOptions, diff --git a/packages/core/src/lib/collect-and-persist.unit.test.ts b/packages/core/src/lib/collect-and-persist.unit.test.ts index 3a7ee4d43..0e424be50 100644 --- a/packages/core/src/lib/collect-and-persist.unit.test.ts +++ b/packages/core/src/lib/collect-and-persist.unit.test.ts @@ -58,6 +58,10 @@ describe('collectAndPersistReports', () => { filename: 'report', format: ['md'], }, + cache: { + read: false, + write: false, + }, progress: false, }; await collectAndPersistReports(nonVerboseConfig); @@ -99,6 +103,10 @@ describe('collectAndPersistReports', () => { filename: 'report', format: ['md'], }, + cache: { + read: false, + write: false, + }, progress: false, }; await collectAndPersistReports(verboseConfig); diff --git a/packages/core/src/lib/implementation/collect.int.test.ts b/packages/core/src/lib/implementation/collect.int.test.ts index ec2365aad..2d1e5dc3a 100644 --- a/packages/core/src/lib/implementation/collect.int.test.ts +++ b/packages/core/src/lib/implementation/collect.int.test.ts @@ -1,34 +1,147 @@ -import { vol } from 'memfs'; +import { writeFile } from 'node:fs/promises'; +import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { commitSchema } from '@code-pushup/models'; -import { MEMFS_VOLUME, MINIMAL_CONFIG_MOCK } from '@code-pushup/test-utils'; +import { MINIMAL_CONFIG_MOCK, cleanTestFolder } from '@code-pushup/test-utils'; +import { + ensureDirectoryExists, + fileExists, + readJsonFile, +} from '@code-pushup/utils'; import { collect } from './collect.js'; +import { getRunnerOutputsPath } from './runner.js'; describe('collect', () => { + const outputDir = path.join('tmp', 'int', 'core', 'collect', '.code-pushup'); + + const expectedCachedOutput = [ + { + slug: 'node-version', + score: 0.3, + value: 16, + displayValue: '16.0.0', + details: { + issues: [ + { + severity: 'error', + message: 'The required Node version to run Code PushUp CLI is 18.', + }, + ], + }, + }, + ]; + + const expectedCachedTestData = [ + { + slug: 'node-version', + score: 0.8, + value: 18, + displayValue: '18.0.0', + details: { issues: [] }, + }, + ]; + + const expectedPluginOutput = { + slug: 'node', + title: 'Node', + icon: 'javascript', + date: expect.any(String), + audits: expectedCachedOutput.map(audit => ({ + ...audit, + title: 'Node version', + description: 'Returns node version', + docsUrl: 'https://nodejs.org/', + })), + }; + + beforeEach(async () => { + await cleanTestFolder(outputDir); + await ensureDirectoryExists(outputDir); + }); + it('should execute with valid options', async () => { - vol.fromJSON({}, MEMFS_VOLUME); const report = await collect({ ...MINIMAL_CONFIG_MOCK, - verbose: true, + persist: { outputDir }, + cache: { read: false, write: false }, progress: false, }); - expect(report.plugins[0]?.audits[0]).toEqual( - expect.objectContaining({ - slug: 'node-version', - displayValue: '16.0.0', - details: { - issues: [ - { - severity: 'error', - message: - 'The required Node version to run Code PushUp CLI is 18.', - }, - ], - }, - }), - ); + expect(report.plugins[0]).toStrictEqual({ + ...expectedPluginOutput, + duration: expect.any(Number), + }); + expect(report.plugins[0]?.duration).toBeGreaterThanOrEqual(0); expect(() => commitSchema.parse(report.commit)).not.toThrow(); + + await expect( + fileExists(getRunnerOutputsPath('node', outputDir)), + ).resolves.toBeFalsy(); + }); + + it('should write runner outputs with --cache.write option', async () => { + const report = await collect({ + ...MINIMAL_CONFIG_MOCK, + persist: { outputDir }, + cache: { read: false, write: true }, + progress: false, + }); + + expect(report.plugins[0]).toStrictEqual({ + ...expectedPluginOutput, + duration: expect.any(Number), + }); + expect(report.plugins[0]?.duration).toBeGreaterThanOrEqual(0); + + await expect( + readJsonFile(getRunnerOutputsPath('node', outputDir)), + ).resolves.toStrictEqual(expectedCachedOutput); + }); + + it('should read runner outputs with --cache.read option and have plugin duraton 0', async () => { + const cacheFilePath = getRunnerOutputsPath('node', outputDir); + await ensureDirectoryExists(path.dirname(cacheFilePath)); + await writeFile(cacheFilePath, JSON.stringify(expectedCachedTestData)); + + const report = await collect({ + ...MINIMAL_CONFIG_MOCK, + persist: { outputDir }, + cache: { read: true, write: false }, + progress: false, + }); + + expect(report.plugins[0]?.audits[0]).toStrictEqual( + expect.objectContaining(expectedCachedTestData[0]), + ); + + expect(report.plugins[0]).toStrictEqual({ + ...expectedPluginOutput, + duration: 0, + audits: expect.any(Array), + }); + + await expect(readJsonFile(cacheFilePath)).resolves.toStrictEqual( + expectedCachedTestData, + ); + }); + + it('should execute runner and write cache with --cache option', async () => { + const report = await collect({ + ...MINIMAL_CONFIG_MOCK, + persist: { outputDir }, + cache: { read: true, write: true }, + progress: false, + }); + + expect(report.plugins[0]).toStrictEqual({ + ...expectedPluginOutput, + duration: expect.any(Number), + }); + expect(report.plugins[0]?.duration).toBeGreaterThanOrEqual(0); + + await expect( + readJsonFile(getRunnerOutputsPath('node', outputDir)), + ).resolves.toStrictEqual(expectedCachedOutput); }); }); diff --git a/packages/core/src/lib/implementation/collect.ts b/packages/core/src/lib/implementation/collect.ts index 59803c406..9a07800ea 100644 --- a/packages/core/src/lib/implementation/collect.ts +++ b/packages/core/src/lib/implementation/collect.ts @@ -1,5 +1,6 @@ import { createRequire } from 'node:module'; import { + type CacheConfigObject, type CoreConfig, DEFAULT_PERSIST_OUTPUT_DIR, type PersistConfig, @@ -11,6 +12,7 @@ import { executePlugins } from './execute-plugin.js'; export type CollectOptions = Pick & { persist?: Required>; + cache: CacheConfigObject; } & Partial; /** @@ -18,7 +20,7 @@ export type CollectOptions = Pick & { * @param options */ export async function collect(options: CollectOptions): Promise { - const { plugins, categories, persist, ...otherOptions } = options; + const { plugins, categories, persist, cache, ...otherOptions } = options; const date = new Date().toISOString(); const start = performance.now(); const commit = await getLatestCommit(); @@ -26,8 +28,7 @@ export async function collect(options: CollectOptions): Promise { { plugins, persist: { outputDir: DEFAULT_PERSIST_OUTPUT_DIR, ...persist }, - // implement together with CLI option - cache: { read: false, write: false }, + cache, }, otherOptions, ); diff --git a/packages/core/src/lib/implementation/runner.ts b/packages/core/src/lib/implementation/runner.ts index 743f3acad..afdf17546 100644 --- a/packages/core/src/lib/implementation/runner.ts +++ b/packages/core/src/lib/implementation/runner.ts @@ -146,11 +146,9 @@ export async function writeRunnerResults( outputDir: string, runnerResult: ValidatedRunnerResult, ): Promise { - await ensureDirectoryExists(outputDir); - await writeFile( - getRunnerOutputsPath(pluginSlug, outputDir), - JSON.stringify(runnerResult.audits, null, 2), - ); + const cacheFilePath = getRunnerOutputsPath(pluginSlug, outputDir); + await ensureDirectoryExists(path.dirname(cacheFilePath)); + await writeFile(cacheFilePath, JSON.stringify(runnerResult.audits, null, 2)); } export async function readRunnerResults( From 2688343adeac2b2a6fa4d7bb2843d769a7746049 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 00:40:03 +0200 Subject: [PATCH 06/33] test: fix int tests --- e2e/cli-e2e/tests/collect.e2e.test.ts | 8 ++------ packages/core/src/lib/history.ts | 6 ++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index e23a3aba8..041806170 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -1,10 +1,6 @@ import { cp } from 'node:fs/promises'; import path from 'node:path'; import { afterEach, beforeAll, describe, expect, it } from 'vitest'; -import { - type ValidatedRunnerResult, - getRunnerOutputsPath, -} from '@code-pushup/core'; import { nxTargetProject } from '@code-pushup/test-nx-utils'; import { E2E_ENVIRONMENTS_DIR, @@ -103,8 +99,8 @@ describe('CLI collect', () => { expect(code).toBe(0); await expect( - readJsonFile( - getRunnerOutputsPath(dummyPluginSlug, dummyOutputDir), + readJsonFile( + path.join(dummyOutputDir, dummyPluginSlug, 'runner-output.json'), ), ).resolves.toStrictEqual([ { diff --git a/packages/core/src/lib/history.ts b/packages/core/src/lib/history.ts index 9f094bb71..58bdf6ba9 100644 --- a/packages/core/src/lib/history.ts +++ b/packages/core/src/lib/history.ts @@ -1,4 +1,5 @@ import type { + CacheConfigObject, CoreConfig, PersistConfig, UploadConfig, @@ -15,6 +16,7 @@ export type HistoryOnlyOptions = { }; export type HistoryOptions = Pick & { persist: Required; + cache: CacheConfigObject; upload?: Required; } & HistoryOnlyOptions & Partial; @@ -40,6 +42,10 @@ export async function history( format: ['json'], filename: `${commit}-report`, }, + cache: { + read: false, + write: false, + }, }; await collectAndPersistReports(currentConfig); From 8f421a66020cbb6ee5f893d17420afd819671f06 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 01:39:25 +0200 Subject: [PATCH 07/33] test: add e2e tests --- e2e/cli-e2e/tests/collect.e2e.test.ts | 26 +---- packages/cli/docs/nx-caching.md | 131 ++++++++++++++++++++++++++ packages/cli/docs/turbo-caching.md | 106 +++++++++++++++++++++ 3 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 packages/cli/docs/nx-caching.md create mode 100644 packages/cli/docs/turbo-caching.md diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index 041806170..0a407335a 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -67,28 +67,6 @@ describe('CLI collect', () => { expect(md).toContain(dummyAuditTitle); }); - it('should not create reports if --persist.skipReports is given', async () => { - const { code } = await executeProcess({ - command: 'npx', - args: [ - '@code-pushup/cli', - '--no-progress', - 'collect', - '--persist.skipReports', - ], - cwd: dummyDir, - }); - - expect(code).toBe(0); - - await expect( - fileExists(path.join(dummyOutputDir, 'report.md')), - ).resolves.toBeFalsy(); - await expect( - fileExists(path.join(dummyOutputDir, 'report.json')), - ).resolves.toBeFalsy(); - }); - it('should write runner outputs if --cache is given', async () => { const { code } = await executeProcess({ command: 'npx', @@ -105,8 +83,8 @@ describe('CLI collect', () => { ).resolves.toStrictEqual([ { slug: 'dummy-audit', - score: 1, - value: 10, + score: 0.3, + value: 3, }, ]); }); diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md new file mode 100644 index 000000000..430aa21ff --- /dev/null +++ b/packages/cli/docs/nx-caching.md @@ -0,0 +1,131 @@ +# Caching Example Nx + +To cache plugin runner output, you can use the `--cache.write` and `--cache.read` options in combination with `--onlyPlugins` and `--persist.skipReports` command options. + +## `{projectRoot}/code-pushup.config.ts` + +```ts +import coveragePlugin from '@code-pushup/coverage-plugin'; +import jsPackagesPlugin from '@code-pushup/js-packages-plugin'; +import type { CoreConfig } from '@code-pushup/models'; + +export default { + plugins: [ + await coveragePlugin({ + reports: ['coverage/lcov.info'], + coverageTypes: ['function', 'branch', 'line'], + }), + await jsPackagesPlugin({ + packageManager: 'npm', + }), + ], + upload: { + server: 'https://portal.code-pushup.dev/api', + organization: 'my-org', + project: 'lib-a', + apiKey: process.env.CP_API_KEY, + }, +} satisfies CoreConfig; +``` + +## `nx.json` + +```json +{ + "defaultTarget": { + "code-pushup": { + "cache": true, + "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "options": { + "command": "node packages/cli/src/index.ts", + "config": "{projectRoot}/code-pushup.config.ts", + "cache.read": true, + "upload.project": "{projectName}", + "outputPath": "{projectRoot}/.code-pushup" + } + } + }, + "code-pushup:coverage": { + "cache": true, + "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "options": { + "command": "npx @code-pushup/cli collect", + "config": "{projectRoot}/code-pushup.config.ts", + "cache.write": true, + "persist.skipReports": true, + "persist.outputDir": "{projectRoot}/.code-pushup", + "upload.project": "{projectName}", + "outputPath": "{projectRoot}/.code-pushup/coverage" + } + } +} +``` + +## `{projectRoot}/package.json` + +```json +{ + "name": "lib-a", + "targets": { + "int-test": { + "cache": true, + "outputs": ["{options.coverage.reportsDirectory}"], + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/lib-a/vitest.int.config.ts", + "coverage.reportsDirectory": "{projectRoot}/coverage/int-test" + } + }, + "unit-test": { + "cache": true, + "outputs": ["{options.coverage.reportsDirectory}"], + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/lib-a/vitest.unit.config.ts", + "coverage.reportsDirectory": "{projectRoot}/coverage/unit-test" + } + }, + "code-pushup:coverage": { + "dependsOn": ["unit-test", "int-test"] + }, + "code-pushup": { + "dependsOn": ["code-pushup:coverage"] + } + } +} +``` + +## Nx Task Graph + +This configuration creates the following task dependency graph: + +**Legend:** + +- 🐳 = Cached target + +```mermaid +graph TD + A[lib-a:code-pushup 🐳] --> B[lib-a:code-pushup:coverage] + A --> E[lib-a:code-pushup:js-packages] + B --> C[lib-a:unit-test 🐳] + B --> D[lib-a:int-test 🐳] +``` + +## Command Line Example + +```bash +# Run all affected projects e.g. nx run lib-a:code-pushup:coverage +nx affected --target=code-pushup:* + +# Run all affected projects and upload the report to the portal +nx run reop-source:code-pushup autorun +``` + +This approach has the following benefits: + +1. **Parallel Execution**: Plugins can run in parallel +2. **Finegrained Caching**: Code level cache invalidation enables usage of [affected](https://nx.dev/recipes/affected-tasks) command +3. **Dependency Management**: Leverage Nx task dependencies and its caching strategy +4. **Clear Separation**: Each plugin has its own target for better debugging and maintainability diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md new file mode 100644 index 000000000..ab3d4c119 --- /dev/null +++ b/packages/cli/docs/turbo-caching.md @@ -0,0 +1,106 @@ +# Caching Example Turborepo + +To cache plugin runner output with Turborepo, wire Code Pushup into your turbo.json pipeline and pass Code Pushup flags (`--cache.write`, `--cache.read`, `--onlyPlugins`, `--persist.skipReports`) through task scripts. Turborepo will cache task outputs declared in outputs, and you can target affected packages with `--filter=[origin/main]`. + +## `{projectRoot}/code-pushup.config.ts` + +```ts +import coveragePlugin from '@code-pushup/coverage-plugin'; +import jsPackagesPlugin from '@code-pushup/js-packages-plugin'; +import type { CoreConfig } from '@code-pushup/models'; + +export default { + plugins: [ + await coveragePlugin({ + reports: ['coverage/lcov.info'], + coverageTypes: ['function', 'branch', 'line'], + }), + await jsPackagesPlugin({ + packageManager: 'npm', + }), + ], + upload: { + server: 'https://portal.code-pushup.dev/api', + organization: 'my-org', + project: 'lib-a', + apiKey: process.env.CP_API_KEY, + }, +} satisfies CoreConfig; +``` + +## Root `turbo.json` + +```json +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "unit-test": { + "outputs": ["coverage/unit-test/**"] + }, + "int-test": { + "outputs": ["coverage/int-test/**"] + }, + "code-pushup:coverage": { + "dependsOn": ["unit-test", "int-test"], + "outputs": [".code-pushup/coverage/**"] + }, + "code-pushup:js-packages": { + "outputs": [".code-pushup/js-packages/**"] + }, + "code-pushup": { + "dependsOn": ["code-pushup:coverage", "code-pushup:js-packages"], + "outputs": [".code-pushup/**"] + } + } +} +``` + +## `packages/lib-a/package.json` + +```json +{ + "name": "lib-a", + "scripts": { + "unit-test": "vitest --config packages/lib-a/vitest.unit.config.ts --coverage", + "int-test": "vitest --config packages/lib-a/vitest.int.config.ts --coverage", + "code-pushup:coverage": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=coverage", + "code-pushup:js-packages": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=js-packages", + "code-pushup": "code-pushup autorun --config packages/lib-a/code-pushup.config.ts --cache.read --persist.outputDir packages/lib-a/.code-pushup" + } +} +``` + +> **Note:** `--cache.write` is used on the collect step to persist each plugin's audit-outputs.json; `--cache.read` is used on the autorun step to reuse those outputs. + +## Turborepo Task Graph + +This configuration creates the following task dependency graph: + +**Legend:** + +- ⚡ = Cached target (via outputs) + +```mermaid +graph TD + A[lib-a:code-pushup ⚡] --> B[lib-a:code-pushup:coverage] + A --> E[lib-a:code-pushup:js-packages] + B --> C[lib-a:unit-test ⚡] + B --> D[lib-a:int-test ⚡] +``` + +## Command Line Examples + +```bash +# Run all affected packages e.g. turbo run code-pushup:coverage --filter=lib-a +turbo run code-pushup:* --filter=[origin/main] + +# Run all affected packages and upload the report to the portal +turbo run code-pushup --filter=[origin/main] +``` + +This approach has the following benefits: + +1. **Parallel Execution**: Plugins can run in parallel +2. **Finegrained Caching**: Code level cache invalidation enables usage of affected packages filtering +3. **Dependency Management**: Leverage Turborepo task dependencies and its caching strategy +4. **Clear Separation**: Each plugin has its own target for better debugging and maintainability From 30e904c0846090cde1d6088f806ac0d5ff3bcad4 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 01:39:35 +0200 Subject: [PATCH 08/33] docs: add docs --- packages/cli/README.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 334d089d5..724207d8b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -336,20 +336,4 @@ The CLI supports caching to speed up subsequent runs and is compatible with Nx a Depending on your strategy, you can cache the generated reports files or plugin runner output. For fine-grained caching, we suggest caching plugin runner output. -### Caching Example Nx - -To cache plugin runner output, you can use the `--cache.write` and `--cache.read` options. - -Cache plugin level output files: - -```bash -# CACHE OUTPUTS -# write cache to persist.outputDir//runner-output.json -# npx @code-pushup/cli collect --persist.outputDir {projectRoot}/.code-pushup --persist.skipReports --onlyPlugins coverage --cache -nx run-many -t code-pushup:collect:coverage -# npx @code-pushup/cli collect --persist.outputDir {projectRoot}/.code-pushup --persist.skipReports --onlyPlugins eslint --cache -nx run-many -t code-pushup:collect:eslint -# READ CACHED OUTPUTS -# npx @code-pushup/cli collect --cache.read -nx run-many -t code-pushup:collect -``` +The detailed example for [Nx caching](./docs/nx-caching.md) and [Turborepo caching](./docs/turbo-caching.md) is available in the docs. From e8ac86f05885077d9d49190325dc396530f6b3ae Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 01:47:35 +0200 Subject: [PATCH 09/33] fix: fix lint violation --- e2e/cli-e2e/tests/collect.e2e.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index 0a407335a..1f06f87f3 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -7,12 +7,7 @@ import { TEST_OUTPUT_DIR, teardownTestFolder, } from '@code-pushup/test-utils'; -import { - executeProcess, - fileExists, - readJsonFile, - readTextFile, -} from '@code-pushup/utils'; +import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; import { dummyPluginSlug } from '../mocks/fixtures/dummy-setup/dummy.plugin'; describe('CLI collect', () => { From f510678464ec44e30769fd9727380d867b1b39c7 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 02:20:04 +0200 Subject: [PATCH 10/33] docs: update Nx example --- packages/cli/docs/nx-caching.md | 76 +++++++++++++++++---------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 430aa21ff..52fef0f9c 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -28,41 +28,6 @@ export default { } satisfies CoreConfig; ``` -## `nx.json` - -```json -{ - "defaultTarget": { - "code-pushup": { - "cache": true, - "outputs": ["{options.outputPath}"], - "executor": "nx:run-commands", - "options": { - "command": "node packages/cli/src/index.ts", - "config": "{projectRoot}/code-pushup.config.ts", - "cache.read": true, - "upload.project": "{projectName}", - "outputPath": "{projectRoot}/.code-pushup" - } - } - }, - "code-pushup:coverage": { - "cache": true, - "outputs": ["{options.outputPath}"], - "executor": "nx:run-commands", - "options": { - "command": "npx @code-pushup/cli collect", - "config": "{projectRoot}/code-pushup.config.ts", - "cache.write": true, - "persist.skipReports": true, - "persist.outputDir": "{projectRoot}/.code-pushup", - "upload.project": "{projectName}", - "outputPath": "{projectRoot}/.code-pushup/coverage" - } - } -} -``` - ## `{projectRoot}/package.json` ```json @@ -88,10 +53,47 @@ export default { } }, "code-pushup:coverage": { + "cache": true, + "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "options": { + "command": "npx @code-pushup/cli collect", + "config": "{projectRoot}/code-pushup.config.ts", + "cache.write": true, + "persist.skipReports": true, + "persist.outputDir": "{projectRoot}/.code-pushup", + "upload.project": "{projectName}", + "outputPath": "{projectRoot}/.code-pushup/coverage" + }, "dependsOn": ["unit-test", "int-test"] }, + "code-pushup:js-packages": { + "cache": true, + "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "options": { + "command": "npx @code-pushup/cli collect", + "config": "{projectRoot}/code-pushup.config.ts", + "cache.write": true, + "persist.skipReports": true, + "persist.outputDir": "{projectRoot}/.code-pushup", + "upload.project": "{projectName}", + "onlyPlugins": "js-packages", + "outputPath": "{projectRoot}/.code-pushup/js-packages" + } + }, "code-pushup": { - "dependsOn": ["code-pushup:coverage"] + "cache": true, + "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "options": { + "command": "node packages/cli/src/index.ts", + "config": "{projectRoot}/code-pushup.config.ts", + "cache.read": true, + "upload.project": "{projectName}", + "outputPath": "{projectRoot}/.code-pushup" + }, + "dependsOn": ["code-pushup:coverage", "code-pushup:js-packages"] } } } @@ -107,7 +109,7 @@ This configuration creates the following task dependency graph: ```mermaid graph TD - A[lib-a:code-pushup 🐳] --> B[lib-a:code-pushup:coverage] + A[lib-a:code-pushup 🐳] --> B[lib-a:code-pushup:coverage 🐳] A --> E[lib-a:code-pushup:js-packages] B --> C[lib-a:unit-test 🐳] B --> D[lib-a:int-test 🐳] From d5e9fab0d14a8f6d975593e5c61b794bd8d64f72 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 02:40:32 +0200 Subject: [PATCH 11/33] docs: update caching examples --- packages/cli/docs/nx-caching.md | 4 +--- packages/cli/docs/turbo-caching.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 52fef0f9c..df338b3c6 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -15,9 +15,7 @@ export default { reports: ['coverage/lcov.info'], coverageTypes: ['function', 'branch', 'line'], }), - await jsPackagesPlugin({ - packageManager: 'npm', - }), + await jsPackagesPlugin(), ], upload: { server: 'https://portal.code-pushup.dev/api', diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index ab3d4c119..6bec1e604 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -15,9 +15,7 @@ export default { reports: ['coverage/lcov.info'], coverageTypes: ['function', 'branch', 'line'], }), - await jsPackagesPlugin({ - packageManager: 'npm', - }), + await jsPackagesPlugin(), ], upload: { server: 'https://portal.code-pushup.dev/api', From f9d63b8aad788ab0c6c920971c20bf5b352d2892 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:08:04 +0200 Subject: [PATCH 12/33] Update packages/cli/src/lib/implementation/core-config.options.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/src/lib/implementation/core-config.options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/implementation/core-config.options.ts b/packages/cli/src/lib/implementation/core-config.options.ts index 0a9de047c..86151122f 100644 --- a/packages/cli/src/lib/implementation/core-config.options.ts +++ b/packages/cli/src/lib/implementation/core-config.options.ts @@ -77,7 +77,7 @@ export function yargsCacheConfigOptionsDefinition(): Record< type: 'boolean', }, 'cache.read': { - describe: 'Read runner-output.json to file system', + describe: 'Read runner-output.json from file system', type: 'boolean', }, 'cache.write': { From b2e4fac6abb739bd92ce72542c7f6e0e014185fd Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:08:42 +0200 Subject: [PATCH 13/33] Update packages/cli/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index f456ab907..448c1979e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -332,7 +332,7 @@ In addition to the [Common Command Options](#common-command-options), the follow ## Caching -The CLI supports caching to speed up subsequent runs and is compatible with Nx and turborepo. +The CLI supports caching to speed up subsequent runs and is compatible with Nx and Turborepo. Depending on your strategy, you can cache the generated reports files or plugin runner output. For fine-grained caching, we suggest caching plugin runner output. From fd8c37ee89f80e3a2570e1a7d6a42ad360769dc4 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:09:21 +0200 Subject: [PATCH 14/33] Update packages/cli/docs/nx-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/nx-caching.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index df338b3c6..35aec9d65 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -13,7 +13,6 @@ export default { plugins: [ await coveragePlugin({ reports: ['coverage/lcov.info'], - coverageTypes: ['function', 'branch', 'line'], }), await jsPackagesPlugin(), ], From bf1718c23d5f93eecc14fc21a50c4f754da8a5f8 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:54:52 +0200 Subject: [PATCH 15/33] Update packages/cli/docs/nx-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/nx-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 35aec9d65..a8a473020 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -84,7 +84,7 @@ export default { "outputs": ["{options.outputPath}"], "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts", + "command": "npx @code-pushup/cli", "config": "{projectRoot}/code-pushup.config.ts", "cache.read": true, "upload.project": "{projectName}", From caf8053849ba8622519bf1ff3268e0b8ea6aa1ab Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:55:27 +0200 Subject: [PATCH 16/33] Update packages/cli/docs/turbo-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/turbo-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index 6bec1e604..ced56c841 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -31,7 +31,7 @@ export default { ```json { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "unit-test": { "outputs": ["coverage/unit-test/**"] }, From f3b92238a7810257be8f968d915bdf7d4e780d03 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:56:22 +0200 Subject: [PATCH 17/33] Update packages/cli/docs/nx-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/nx-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index a8a473020..94567afbc 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -25,7 +25,7 @@ export default { } satisfies CoreConfig; ``` -## `{projectRoot}/package.json` +## `{projectRoot}/project.json` ```json { From 531bedbcdd8bfdd4f09c8e6decf2c3a3654314ff Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 13:38:12 +0200 Subject: [PATCH 18/33] fix: update help output with caching options --- .../tests/__snapshots__/help.e2e.test.ts.snap | 18 ++++++++++-------- packages/cli/src/lib/options.ts | 2 ++ packages/models/docs/models-reference.md | 11 ++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap b/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap index acd37bfee..e9f9d47fd 100644 --- a/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap +++ b/e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap @@ -36,6 +36,14 @@ Global Options: -p, --onlyPlugins List of plugins to run. If not set all plugins are run. [array] [default: []] +Cache Options: + --cache Cache runner outputs (both read and write) + [boolean] + --cache.read Read runner-output.json from file system + [boolean] + --cache.write Write runner-output.json to file system + [boolean] + Persist Options: --persist.outputDir Directory for the produced reports [string] @@ -56,14 +64,8 @@ Upload Options: [string] Options: - --version Show version [boolean] - --cache Cache runner outputs (both read and write) - [boolean] - --cache.read Read runner-output.json to file system - [boolean] - --cache.write Write runner-output.json to file system - [boolean] - -h, --help Show help [boolean] + --version Show version [boolean] + -h, --help Show help [boolean] Examples: code-pushup Run collect followed by upload based diff --git a/packages/cli/src/lib/options.ts b/packages/cli/src/lib/options.ts index e1835ed5c..3085affd0 100644 --- a/packages/cli/src/lib/options.ts +++ b/packages/cli/src/lib/options.ts @@ -1,4 +1,5 @@ import { + yargsCacheConfigOptionsDefinition, yargsCoreConfigOptionsDefinition, yargsPersistConfigOptionsDefinition, yargsUploadConfigOptionsDefinition, @@ -17,6 +18,7 @@ export const groups = { ...Object.keys(yargsGlobalOptionsDefinition()), ...Object.keys(yargsFilterOptionsDefinition()), ], + 'Cache Options:': Object.keys(yargsCacheConfigOptionsDefinition()), 'Persist Options:': Object.keys(yargsPersistConfigOptionsDefinition()), 'Upload Options:': Object.keys(yargsUploadConfigOptionsDefinition()), }; diff --git a/packages/models/docs/models-reference.md b/packages/models/docs/models-reference.md index a53b5d5ec..be7d688c1 100644 --- a/packages/models/docs/models-reference.md +++ b/packages/models/docs/models-reference.md @@ -1258,11 +1258,12 @@ _Enum, one of the following possible values:_ _Object containing the following properties:_ -| Property | Description | Type | -| :---------- | :-------------------------------------- | :--------------------------------- | -| `outputDir` | Artifacts folder | [FilePath](#filepath) | -| `filename` | Artifacts file name (without extension) | [FileName](#filename) | -| `format` | | _Array of [Format](#format) items_ | +| Property | Description | Type | +| :------------ | :-------------------------------------- | :--------------------------------- | +| `outputDir` | Artifacts folder | [FilePath](#filepath) | +| `filename` | Artifacts file name (without extension) | [FileName](#filename) | +| `format` | | _Array of [Format](#format) items_ | +| `skipReports` | | `boolean` | _All properties are optional._ From 520faa5177ee89cbe441db7309ab45a6efb1765d Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 13:42:12 +0200 Subject: [PATCH 19/33] docs: fix typos --- packages/cli/docs/nx-caching.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 94567afbc..e9747f815 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -49,7 +49,7 @@ export default { "coverage.reportsDirectory": "{projectRoot}/coverage/unit-test" } }, - "code-pushup:coverage": { + "code-pushup-coverage": { "cache": true, "outputs": ["{options.outputPath}"], "executor": "nx:run-commands", @@ -64,7 +64,7 @@ export default { }, "dependsOn": ["unit-test", "int-test"] }, - "code-pushup:js-packages": { + "code-pushup-js-packages": { "cache": true, "outputs": ["{options.outputPath}"], "executor": "nx:run-commands", @@ -115,11 +115,11 @@ graph TD ## Command Line Example ```bash -# Run all affected projects e.g. nx run lib-a:code-pushup:coverage +# Run all affected projects e.g. nx run lib-a:code-pushup-coverage nx affected --target=code-pushup:* # Run all affected projects and upload the report to the portal -nx run reop-source:code-pushup autorun +nx affected --target=code-pushup ``` This approach has the following benefits: From 3d1c1cdc992243017bcc5429d9acefc65ce090d3 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 13:54:25 +0200 Subject: [PATCH 20/33] docs: adjust command example --- packages/cli/docs/nx-caching.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index e9747f815..59b6b38a7 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -115,8 +115,8 @@ graph TD ## Command Line Example ```bash -# Run all affected projects e.g. nx run lib-a:code-pushup-coverage -nx affected --target=code-pushup:* +# Run all affected project plugins and cache the output if configured +nx affected --target=code-pushup-coverage,code-pushup-jspackages # Run all affected projects and upload the report to the portal nx affected --target=code-pushup From 4373c4c8949188593583c2a4be4fb5f415f7084d Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 13:55:42 +0200 Subject: [PATCH 21/33] docs: adjust command example --- packages/cli/docs/turbo-caching.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index ced56c841..65df35a82 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -89,10 +89,10 @@ graph TD ## Command Line Examples ```bash -# Run all affected packages e.g. turbo run code-pushup:coverage --filter=lib-a -turbo run code-pushup:* --filter=[origin/main] +# Run all affected project plugins and cache the output if configured +turbo run code-pushup-coverage code-pushup-jspackages --filter=[origin/main] -# Run all affected packages and upload the report to the portal +# Run all affected projects and upload the report to the portal turbo run code-pushup --filter=[origin/main] ``` From 121ba4e046c6d143b978eab4d52f18674419af45 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:02:31 +0200 Subject: [PATCH 22/33] docs: adjust config example --- packages/cli/docs/nx-caching.md | 40 +++++++++++------------------- packages/cli/docs/turbo-caching.md | 6 ++--- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 59b6b38a7..8a67602bd 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -56,29 +56,20 @@ export default { "options": { "command": "npx @code-pushup/cli collect", "config": "{projectRoot}/code-pushup.config.ts", - "cache.write": true, - "persist.skipReports": true, - "persist.outputDir": "{projectRoot}/.code-pushup", - "upload.project": "{projectName}", + "cache": { + "write": true + }, + "persist": { + "skipReports": true, + "outputDir": "{projectRoot}/.code-pushup" + }, + "upload": { + "project": "{projectName}" + }, "outputPath": "{projectRoot}/.code-pushup/coverage" }, "dependsOn": ["unit-test", "int-test"] }, - "code-pushup-js-packages": { - "cache": true, - "outputs": ["{options.outputPath}"], - "executor": "nx:run-commands", - "options": { - "command": "npx @code-pushup/cli collect", - "config": "{projectRoot}/code-pushup.config.ts", - "cache.write": true, - "persist.skipReports": true, - "persist.outputDir": "{projectRoot}/.code-pushup", - "upload.project": "{projectName}", - "onlyPlugins": "js-packages", - "outputPath": "{projectRoot}/.code-pushup/js-packages" - } - }, "code-pushup": { "cache": true, "outputs": ["{options.outputPath}"], @@ -90,7 +81,7 @@ export default { "upload.project": "{projectName}", "outputPath": "{projectRoot}/.code-pushup" }, - "dependsOn": ["code-pushup:coverage", "code-pushup:js-packages"] + "dependsOn": ["code-pushup:coverage"] } } } @@ -106,8 +97,7 @@ This configuration creates the following task dependency graph: ```mermaid graph TD - A[lib-a:code-pushup 🐳] --> B[lib-a:code-pushup:coverage 🐳] - A --> E[lib-a:code-pushup:js-packages] + A[lib-a:code-pushup 🐳] --> B[lib-a:code-pushup-coverage 🐳] B --> C[lib-a:unit-test 🐳] B --> D[lib-a:int-test 🐳] ``` @@ -115,10 +105,10 @@ graph TD ## Command Line Example ```bash -# Run all affected project plugins and cache the output if configured -nx affected --target=code-pushup-coverage,code-pushup-jspackages +# Run all affected project plugins `coverage` and cache the output if configured +nx affected --target=code-pushup-coverage -# Run all affected projects and upload the report to the portal +# Run all affected projects with plugins `coverage` and `js-packages` and upload the report to the portal nx affected --target=code-pushup ``` diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index 65df35a82..8b9293b4e 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -89,10 +89,10 @@ graph TD ## Command Line Examples ```bash -# Run all affected project plugins and cache the output if configured -turbo run code-pushup-coverage code-pushup-jspackages --filter=[origin/main] +# Run all affected project plugins `coverage` and cache the output if configured +turbo run code-pushup-coverage --filter=[origin/main] -# Run all affected projects and upload the report to the portal +# Run all affected projects with plugins `coverage` and `js-packages` and upload the report to the portal turbo run code-pushup --filter=[origin/main] ``` From 0b52c073e886ac8184f2eea8ae00b995e44e0dba Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:04:46 +0200 Subject: [PATCH 23/33] Update packages/cli/docs/nx-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/nx-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 8a67602bd..004c71cd2 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -17,7 +17,7 @@ export default { await jsPackagesPlugin(), ], upload: { - server: 'https://portal.code-pushup.dev/api', + server: 'https://api.code-pushup.example.com/graphql', organization: 'my-org', project: 'lib-a', apiKey: process.env.CP_API_KEY, From 5fb3d87528c71d5976e3f2ade94bd2876a9f3130 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:08:32 +0200 Subject: [PATCH 24/33] docs: options docs --- packages/cli/README.md | 46 +++++++++++++++++++++++---------- packages/cli/docs/nx-caching.md | 8 ++++-- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 448c1979e..02f0cc77e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -207,20 +207,38 @@ Each example is fully tested to demonstrate best practices for plugin testing as ### Common Command Options -| Option | Type | Default | Description | -| --------------------------- | -------------------- | -------- | --------------------------------------------------------------------------- | -| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | -| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | -| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | -| **`--persist.skipReports`** | `boolean` | `false` | Skip generating report files. (useful in combination with caching) | -| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | -| **`--upload.project`** | `string` | n/a | Project slug from portal. | -| **`--upload.server`** | `string` | n/a | URL to your portal server. | -| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | -| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. | -| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. | -| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | -| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | +#### Global Options + +| Option | Type | Default | Description | +| ------------------- | ---------- | ------- | --------------------------------------------------------------------------- | +| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | +| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | + +#### Cache Options + +| Option | Type | Default | Description | +| ------------------- | --------- | ------- | --------------------------------------------------------------- | +| **`--cache`** | `boolean` | `false` | Cache runner outputs (both read and write). | +| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. | +| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. | + +#### Persist Options + +| Option | Type | Default | Description | +| --------------------------- | -------------------- | -------- | ------------------------------------------------------------------ | +| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. | +| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. | +| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. | +| **`--persist.skipReports`** | `boolean` | `false` | Skip generating report files. (useful in combination with caching) | + +#### Upload Options + +| Option | Type | Default | Description | +| --------------------------- | -------- | ------- | ------------------------------ | +| **`--upload.organization`** | `string` | n/a | Organization slug from portal. | +| **`--upload.project`** | `string` | n/a | Project slug from portal. | +| **`--upload.server`** | `string` | n/a | URL to your portal server. | +| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. | > [!NOTE] > All common options, except `--onlyPlugins` and `--skipPlugins`, can be specified in the configuration file as well. diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 004c71cd2..17bef2a9d 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -77,8 +77,12 @@ export default { "options": { "command": "npx @code-pushup/cli", "config": "{projectRoot}/code-pushup.config.ts", - "cache.read": true, - "upload.project": "{projectName}", + "cache": { + "read": true + }, + "upload": { + "project": "{projectName}" + }, "outputPath": "{projectRoot}/.code-pushup" }, "dependsOn": ["code-pushup:coverage"] From fdcd289ee763685a88971cf6a4214065206799d1 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:28:09 +0200 Subject: [PATCH 25/33] docs: adjust nx targets --- packages/cli/docs/nx-caching.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 17bef2a9d..48adfa9b8 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -51,7 +51,7 @@ export default { }, "code-pushup-coverage": { "cache": true, - "outputs": ["{options.outputPath}"], + "outputs": ["{options.persist.outputDir}/coverage"], "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli collect", @@ -65,14 +65,13 @@ export default { }, "upload": { "project": "{projectName}" - }, - "outputPath": "{projectRoot}/.code-pushup/coverage" + } }, "dependsOn": ["unit-test", "int-test"] }, "code-pushup": { "cache": true, - "outputs": ["{options.outputPath}"], + "outputs": ["{options.persist.outputDir}"], "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli", @@ -80,10 +79,12 @@ export default { "cache": { "read": true }, + "persist": { + "outputDir": ".code-pushup" + }, "upload": { "project": "{projectName}" - }, - "outputPath": "{projectRoot}/.code-pushup" + } }, "dependsOn": ["code-pushup:coverage"] } From 9e6702ad78605d770d9f18018bdb7509cdf48b06 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:38:52 +0200 Subject: [PATCH 26/33] docs: adjust nx targets 2 --- packages/cli/docs/nx-caching.md | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 48adfa9b8..fc4fe8962 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -55,17 +55,7 @@ export default { "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli collect", - "config": "{projectRoot}/code-pushup.config.ts", - "cache": { - "write": true - }, - "persist": { - "skipReports": true, - "outputDir": "{projectRoot}/.code-pushup" - }, - "upload": { - "project": "{projectName}" - } + "args": ["--config={projectRoot}/code-pushup.config.ts", "--cache.write=true", "--persist.skipReports=true", "--persist.outputDir={projectRoot}/.code-pushup", "--upload.project={projectName}"] }, "dependsOn": ["unit-test", "int-test"] }, @@ -75,18 +65,9 @@ export default { "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli", - "config": "{projectRoot}/code-pushup.config.ts", - "cache": { - "read": true - }, - "persist": { - "outputDir": ".code-pushup" - }, - "upload": { - "project": "{projectName}" - } + "args": ["--config={projectRoot}/code-pushup.config.ts", "--cache.read=true", "--persist.outputDir={projectRoot}/.code-pushup", "--upload.project={projectName}"] }, - "dependsOn": ["code-pushup:coverage"] + "dependsOn": ["code-pushup-coverage"] } } } From 005d49e209193dd39dee3730e4ee61f0fdd4a4f1 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:40:48 +0200 Subject: [PATCH 27/33] Update packages/cli/docs/turbo-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/turbo-caching.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index 8b9293b4e..d6229d709 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -13,7 +13,6 @@ export default { plugins: [ await coveragePlugin({ reports: ['coverage/lcov.info'], - coverageTypes: ['function', 'branch', 'line'], }), await jsPackagesPlugin(), ], From ba7f9677c58b6e9a0848db6070a4bfd8df772473 Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:41:00 +0200 Subject: [PATCH 28/33] Update packages/cli/docs/turbo-caching.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/cli/docs/turbo-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index d6229d709..b50d06cf8 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -17,7 +17,7 @@ export default { await jsPackagesPlugin(), ], upload: { - server: 'https://portal.code-pushup.dev/api', + server: 'https://api.code-pushup.example.com/graphql', organization: 'my-org', project: 'lib-a', apiKey: process.env.CP_API_KEY, From 76e3f86ba58801964a487a164baf0ebd4ebdd17a Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:43:06 +0200 Subject: [PATCH 29/33] docs: adjust turbo targets 1 --- packages/cli/docs/turbo-caching.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index b50d06cf8..632927bf7 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -41,11 +41,8 @@ export default { "dependsOn": ["unit-test", "int-test"], "outputs": [".code-pushup/coverage/**"] }, - "code-pushup:js-packages": { - "outputs": [".code-pushup/js-packages/**"] - }, "code-pushup": { - "dependsOn": ["code-pushup:coverage", "code-pushup:js-packages"], + "dependsOn": ["code-pushup-coverage"], "outputs": [".code-pushup/**"] } } @@ -61,7 +58,6 @@ export default { "unit-test": "vitest --config packages/lib-a/vitest.unit.config.ts --coverage", "int-test": "vitest --config packages/lib-a/vitest.int.config.ts --coverage", "code-pushup:coverage": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=coverage", - "code-pushup:js-packages": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=js-packages", "code-pushup": "code-pushup autorun --config packages/lib-a/code-pushup.config.ts --cache.read --persist.outputDir packages/lib-a/.code-pushup" } } @@ -80,7 +76,6 @@ This configuration creates the following task dependency graph: ```mermaid graph TD A[lib-a:code-pushup ⚡] --> B[lib-a:code-pushup:coverage] - A --> E[lib-a:code-pushup:js-packages] B --> C[lib-a:unit-test ⚡] B --> D[lib-a:int-test ⚡] ``` From b80b6598586e4dacec492ac62c1e4ba4a5946f66 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:45:47 +0200 Subject: [PATCH 30/33] docs: add category filter --- packages/cli/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 02f0cc77e..294599b49 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -209,10 +209,12 @@ Each example is fully tested to demonstrate best practices for plugin testing as #### Global Options -| Option | Type | Default | Description | -| ------------------- | ---------- | ------- | --------------------------------------------------------------------------- | -| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | -| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | +| Option | Type | Default | Description | +| ---------------------- | ---------- | ------- | ------------------------------------------------------------------------------ | +| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. | +| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. | +| **`--onlyCategories`** | `string[]` | `[]` | Only run the specified categories. Applicable to all commands except `upload`. | +| **`--skipCategories`** | `string[]` | `[]` | Skip the specified categories. Applicable to all commands except `upload`. | #### Cache Options From 2fede247fbdddc7b33ba28bf63b0464b7d6245b8 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 14:47:31 +0200 Subject: [PATCH 31/33] docs: fix typo --- packages/cli/docs/nx-caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index fc4fe8962..9c10e281d 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -101,6 +101,6 @@ nx affected --target=code-pushup This approach has the following benefits: 1. **Parallel Execution**: Plugins can run in parallel -2. **Finegrained Caching**: Code level cache invalidation enables usage of [affected](https://nx.dev/recipes/affected-tasks) command +2. **Fine-grained Caching**: Code level cache invalidation enables usage of [affected](https://nx.dev/recipes/affected-tasks) command 3. **Dependency Management**: Leverage Nx task dependencies and its caching strategy 4. **Clear Separation**: Each plugin has its own target for better debugging and maintainability From 98dedef5b1b2751fceb082065d7898454887af53 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 15:03:12 +0200 Subject: [PATCH 32/33] docs: fix target outputs --- packages/cli/docs/nx-caching.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/docs/nx-caching.md b/packages/cli/docs/nx-caching.md index 9c10e281d..ce3f95f52 100644 --- a/packages/cli/docs/nx-caching.md +++ b/packages/cli/docs/nx-caching.md @@ -51,7 +51,7 @@ export default { }, "code-pushup-coverage": { "cache": true, - "outputs": ["{options.persist.outputDir}/coverage"], + "outputs": ["{projectRoot}/.code-pushup/coverage"], "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli collect", @@ -61,7 +61,7 @@ export default { }, "code-pushup": { "cache": true, - "outputs": ["{options.persist.outputDir}"], + "outputs": ["{projectRoot}/.code-pushup"], "executor": "nx:run-commands", "options": { "command": "npx @code-pushup/cli", From 612dcb8110ed5c8d004a2b10b2d10aaff2e5ec85 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 12 Aug 2025 16:07:58 +0200 Subject: [PATCH 33/33] docs: fix turbo docs --- packages/cli/docs/turbo-caching.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/docs/turbo-caching.md b/packages/cli/docs/turbo-caching.md index 632927bf7..9c03c3071 100644 --- a/packages/cli/docs/turbo-caching.md +++ b/packages/cli/docs/turbo-caching.md @@ -37,7 +37,7 @@ export default { "int-test": { "outputs": ["coverage/int-test/**"] }, - "code-pushup:coverage": { + "code-pushup-coverage": { "dependsOn": ["unit-test", "int-test"], "outputs": [".code-pushup/coverage/**"] }, @@ -57,7 +57,7 @@ export default { "scripts": { "unit-test": "vitest --config packages/lib-a/vitest.unit.config.ts --coverage", "int-test": "vitest --config packages/lib-a/vitest.int.config.ts --coverage", - "code-pushup:coverage": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=coverage", + "code-pushup-coverage": "code-pushup collect --config packages/lib-a/code-pushup.config.ts --cache.write --persist.skipReports --persist.outputDir packages/lib-a/.code-pushup --onlyPlugins=coverage", "code-pushup": "code-pushup autorun --config packages/lib-a/code-pushup.config.ts --cache.read --persist.outputDir packages/lib-a/.code-pushup" } } @@ -75,7 +75,7 @@ This configuration creates the following task dependency graph: ```mermaid graph TD - A[lib-a:code-pushup ⚡] --> B[lib-a:code-pushup:coverage] + A[lib-a:code-pushup ⚡] --> B[lib-a:code-pushup-coverage] B --> C[lib-a:unit-test ⚡] B --> D[lib-a:int-test ⚡] ```