Skip to content

Commit bb5afa3

Browse files
committed
feat: add persist.report output to CLI
1 parent d32a643 commit bb5afa3

File tree

15 files changed

+128
-30
lines changed

15 files changed

+128
-30
lines changed

e2e/cli-e2e/tests/collect.e2e.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
TEST_OUTPUT_DIR,
88
teardownTestFolder,
99
} from '@code-pushup/test-utils';
10-
import { executeProcess, readTextFile } from '@code-pushup/utils';
10+
import { executeProcess, fileExists, readTextFile } from '@code-pushup/utils';
1111

1212
describe('CLI collect', () => {
1313
const dummyPluginTitle = 'Dummy Plugin';
@@ -61,6 +61,28 @@ describe('CLI collect', () => {
6161
expect(md).toContain(dummyAuditTitle);
6262
});
6363

64+
it('should not create reports if --persist.no-report is given', async () => {
65+
const { code } = await executeProcess({
66+
command: 'npx',
67+
args: [
68+
'@code-pushup/cli',
69+
'--no-progress',
70+
'collect',
71+
'--persist.no-report',
72+
],
73+
cwd: dummyDir,
74+
});
75+
76+
expect(code).toBe(0);
77+
78+
await expect(
79+
fileExists(path.join(dummyOutputDir, 'report.md')),
80+
).resolves.toBeFalsy();
81+
await expect(
82+
fileExists(path.join(dummyOutputDir, 'report.json')),
83+
).resolves.toBeFalsy();
84+
});
85+
6486
it('should print report summary to stdout', async () => {
6587
const { code, stdout } = await executeProcess({
6688
command: 'npx',

packages/ci/src/lib/run-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ export function configFromPatterns(
483483
outputDir: interpolate(persist.outputDir, variables),
484484
filename: interpolate(persist.filename, variables),
485485
format: persist.format,
486+
report: persist.report,
486487
},
487488
...(upload && {
488489
upload: {

packages/cli/README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,20 @@ Each example is fully tested to demonstrate best practices for plugin testing as
207207

208208
### Common Command Options
209209

210-
| Option | Type | Default | Description |
211-
| --------------------------- | -------------------- | -------- | --------------------------------------------------------------------------- |
212-
| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. |
213-
| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. |
214-
| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. |
215-
| **`--upload.organization`** | `string` | n/a | Organization slug from portal. |
216-
| **`--upload.project`** | `string` | n/a | Project slug from portal. |
217-
| **`--upload.server`** | `string` | n/a | URL to your portal server. |
218-
| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. |
219-
| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. |
220-
| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. |
210+
| Option | Type | Default | Description |
211+
| --------------------------- | -------------------- | -------- | -------------------------------------------------------------------------------- |
212+
| **`--persist.outputDir`** | `string` | n/a | Directory for the produced reports. |
213+
| **`--persist.filename`** | `string` | `report` | Filename for the produced reports without extension. |
214+
| **`--persist.format`** | `('json' \| 'md')[]` | `json` | Format(s) of the report file. |
215+
| **`--persist.report`** | `boolean` | `true` | Generate the report files for given formats. (useful in combination wit caching) |
216+
| **`--upload.organization`** | `string` | n/a | Organization slug from portal. |
217+
| **`--upload.project`** | `string` | n/a | Project slug from portal. |
218+
| **`--upload.server`** | `string` | n/a | URL to your portal server. |
219+
| **`--upload.apiKey`** | `string` | n/a | API key for the portal server. |
220+
| **`--cache.read`** | `boolean` | `false` | If plugin audit outputs should be read from file system cache. |
221+
| **`--cache.write`** | `boolean` | `false` | If plugin audit outputs should be written to file system cache. |
222+
| **`--onlyPlugins`** | `string[]` | `[]` | Only run the specified plugins. Applicable to all commands except `upload`. |
223+
| **`--skipPlugins`** | `string[]` | `[]` | Skip the specified plugins. Applicable to all commands except `upload`. |
221224

222225
> [!NOTE]
223226
> All common options, except `--onlyPlugins` and `--skipPlugins`, can be specified in the configuration file as well.

packages/cli/src/lib/collect/collect-command.unit.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('collect-command', () => {
3737
expect(collectAndPersistReports).toHaveBeenCalledWith(
3838
expect.objectContaining({
3939
config: '/test/code-pushup.config.ts',
40-
persist: expect.objectContaining<Required<PersistConfig>>({
40+
persist: expect.objectContaining<PersistConfig>({
4141
filename: DEFAULT_PERSIST_FILENAME,
4242
outputDir: DEFAULT_PERSIST_OUTPUT_DIR,
4343
format: DEFAULT_PERSIST_FORMAT,

packages/cli/src/lib/implementation/core-config.middleware.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function coreConfigMiddleware<
2323
tsconfig,
2424
persist: cliPersist,
2525
upload: cliUpload,
26+
cache: cliCache,
2627
...remainingCliOptions
2728
} = processArgs;
2829
// Search for possible configuration file extensions if path is not given
@@ -43,6 +44,11 @@ export async function coreConfigMiddleware<
4344
});
4445
return {
4546
...(config != null && { config }),
47+
cache: {
48+
write: false,
49+
read: false,
50+
...cliCache,
51+
},
4652
persist: {
4753
outputDir:
4854
cliPersist?.outputDir ??
@@ -53,6 +59,7 @@ export async function coreConfigMiddleware<
5359
format: normalizeFormats(
5460
cliPersist?.format ?? rcPersist?.format ?? DEFAULT_PERSIST_FORMAT,
5561
),
62+
report: !('no-report' in (cliPersist ?? {})),
5663
},
5764
...(upload != null && { upload }),
5865
...remainingRcConfig,

packages/cli/src/lib/implementation/core-config.middleware.unit.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,14 @@ describe('coreConfigMiddleware', () => {
7373
'apps/website/tsconfig.json',
7474
);
7575
});
76+
77+
it('should forward normalize --persist.report option', async () => {
78+
await coreConfigMiddleware({
79+
config: 'apps/website/code-pushup.config.ts',
80+
} as GeneralCliOptions & CoreConfigCliOptions & FilterOptions);
81+
expect(readRcByPath).toHaveBeenCalledWith(
82+
'apps/website/code-pushup.config.ts',
83+
'apps/website/tsconfig.json',
84+
);
85+
});
7686
});

packages/cli/src/lib/implementation/core-config.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ export type ConfigCliOptions = {
1919
verbose?: string;
2020
};
2121

22-
export type CoreConfigCliOptions = Pick<CoreConfig, 'persist'> & {
22+
export type CoreConfigCliOptions = Pick<CoreConfig, 'persist' | 'cache'> & {
2323
upload?: Partial<Omit<UploadConfig, 'timeout'>>;
2424
};

packages/core/src/lib/collect-and-persist.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
logStdoutSummary,
99
scoreReport,
1010
sortReport,
11+
ui,
1112
} from '@code-pushup/utils';
1213
import { collect } from './implementation/collect.js';
1314
import {
@@ -18,30 +19,42 @@ import type { GlobalOptions } from './types.js';
1819

1920
export type CollectAndPersistReportsOptions = Pick<
2021
CoreConfig,
21-
'plugins' | 'categories'
22-
> & { persist: Required<PersistConfig> } & Partial<GlobalOptions>;
22+
'plugins' | 'categories' | 'cache'
23+
> & {
24+
persist: Required<Omit<PersistConfig, 'report'>> &
25+
Pick<PersistConfig, 'report'>;
26+
} & Partial<GlobalOptions>;
2327

2428
export async function collectAndPersistReports(
2529
options: CollectAndPersistReportsOptions,
2630
): Promise<void> {
27-
const report = await collect(options);
28-
const sortedScoredReport = sortReport(scoreReport(report));
31+
const logger = ui().logger;
32+
const reportResult = await collect(options);
33+
const sortedScoredReport = sortReport(scoreReport(reportResult));
2934

30-
const persistResults = await persistReport(
31-
report,
32-
sortedScoredReport,
33-
options.persist,
34-
);
35+
const { persist } = options;
36+
const { report: shouldGenerateReport = true, ...persistOptions } =
37+
persist ?? {};
3538

36-
// terminal output
37-
logStdoutSummary(sortedScoredReport);
39+
if (shouldGenerateReport === true) {
40+
const persistResults = await persistReport(
41+
reportResult,
42+
sortedScoredReport,
43+
persistOptions,
44+
);
3845

39-
if (isVerbose()) {
40-
logPersistedResults(persistResults);
46+
if (isVerbose()) {
47+
logPersistedResults(persistResults);
48+
}
49+
} else {
50+
logger.info('Skipping saving reports as `persist.report` is false');
4151
}
4252

53+
// terminal output
54+
logStdoutSummary(sortedScoredReport);
55+
4356
// validate report and throw if invalid
44-
report.plugins.forEach(plugin => {
57+
reportResult.plugins.forEach(plugin => {
4558
// Running checks after persisting helps while debugging as you can check the invalid output after the error is thrown
4659
pluginReportSchema.parse(plugin);
4760
});

packages/core/src/lib/collect-and-persist.unit.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,31 @@ describe('collectAndPersistReports', () => {
115115
expect(logPersistedResults).toHaveBeenCalled();
116116
});
117117

118+
it('should call collect and not persistReport if report options is false in verbose mode', async () => {
119+
const sortedScoredReport = sortReport(scoreReport(MINIMAL_REPORT_MOCK));
120+
121+
vi.stubEnv('CP_VERBOSE', 'true');
122+
123+
const verboseConfig: CollectAndPersistReportsOptions = {
124+
...MINIMAL_CONFIG_MOCK,
125+
persist: {
126+
outputDir: 'output',
127+
filename: 'report',
128+
format: ['md'],
129+
report: false,
130+
},
131+
progress: false,
132+
};
133+
await collectAndPersistReports(verboseConfig);
134+
135+
expect(collect).toHaveBeenCalledWith(verboseConfig);
136+
137+
expect(persistReport).not.toHaveBeenCalled();
138+
expect(logPersistedResults).not.toHaveBeenCalled();
139+
140+
expect(logStdoutSummary).toHaveBeenCalledWith(sortedScoredReport);
141+
});
142+
118143
it('should print a summary to stdout', async () => {
119144
await collectAndPersistReports(
120145
MINIMAL_CONFIG_MOCK as CollectAndPersistReportsOptions,

packages/core/src/lib/implementation/persist.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class PersistError extends Error {
2525
export async function persistReport(
2626
report: Report,
2727
sortedScoredReport: ScoredReport,
28-
options: Required<PersistConfig>,
28+
options: Required<Omit<PersistConfig, 'report'>>,
2929
): Promise<MultipleFileResults> {
3030
const { outputDir, filename, format } = options;
3131

0 commit comments

Comments
 (0)