Skip to content

Commit 0b14111

Browse files
committed
feat(ci): add searchCommits option to extend portal cache range
1 parent b7705b3 commit 0b14111

File tree

4 files changed

+92
-29
lines changed

4 files changed

+92
-29
lines changed

packages/ci/README.md

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Optionally, you can override default options for further customization:
109109
| `logger` | `Logger` | `console` | Logger for reporting progress and encountered problems |
110110
| `skipComment` | `boolean` | `false` | Toggles if comparison comment is posted to PR |
111111
| `configPatterns` | `ConfigPatterns \| null` | `null` | Additional configuration which enables [faster CI runs](#faster-ci-runs-with-configpatterns) |
112+
| `searchCommits` | `boolean \| number` | `false` | If base branch has no cached report in portal, [extends search up to 100 recent commits](#search-latest-commits-for-previous-report) |
112113

113114
[^1]: By default, the `code-pushup.config` file is autodetected as described in [`@code-pushup/cli` docs](../cli/README.md#configuration).
114115

@@ -217,32 +218,6 @@ await runInCI(refs, api, {
217218
});
218219
```
219220

220-
### Faster CI runs with `configPatterns`
221-
222-
By default, the `print-config` command is run sequentially for each project in order to reliably detect how `code-pushup` is configured - specifically, where to read output files from (`persist` config) and whether portal may be used as a cache (`upload` config). This allows for each project to be configured in its own way without breaking anything, but for large monorepos these extra `code-pushup print-config` executions can accumulate and significantly slow down CI pipelines.
223-
224-
As a more scalable alternative, `configPatterns` may be provided. A user declares upfront how every project is configured, which allows `print-config` to be skipped. It's the user's responsibility to ensure this configuration holds for every project (it won't be checked). The `configPatterns` support string interpolation, substituting `{projectName}` with each project's name. Other than that, each project's `code-pushup.config` must have exactly the same `persist` and `upload` configurations.
225-
226-
```ts
227-
await runInCI(refs, api, {
228-
monorepo: true,
229-
configPatterns: {
230-
persist: {
231-
outputDir: '.code-pushup/{projectName}',
232-
filename: 'report',
233-
format: ['json', 'md'],
234-
},
235-
// optional: will use portal as cache when comparing reports in PRs
236-
upload: {
237-
server: 'https://api.code-pushup.example.com/graphql',
238-
apiKey: 'cp_...',
239-
organization: 'example',
240-
project: '{projectName}',
241-
},
242-
},
243-
});
244-
```
245-
246221
### Monorepo result
247222

248223
In monorepo mode, the resolved object includes the merged diff at the top-level, as well as a list of projects.
@@ -273,3 +248,48 @@ if (result.mode === 'monorepo') {
273248
}
274249
}
275250
```
251+
252+
## Advanced usage
253+
254+
### Faster CI runs with `configPatterns`
255+
256+
By default, the `print-config` command is run sequentially for each project in order to reliably detect how `code-pushup` is configured - specifically, where to read output files from (`persist` config) and whether portal may be used as a cache (`upload` config). This allows for each project to be configured in its own way without breaking anything, but for large monorepos these extra `code-pushup print-config` executions can accumulate and significantly slow down CI pipelines.
257+
258+
As a more scalable alternative, `configPatterns` may be provided. A user declares upfront how every project is configured, which allows `print-config` to be skipped. It's the user's responsibility to ensure this configuration holds for every project (it won't be checked). The `configPatterns` support string interpolation, substituting `{projectName}` with each project's name. Other than that, each project's `code-pushup.config` must have exactly the same `persist` and `upload` configurations.
259+
260+
```ts
261+
await runInCI(refs, api, {
262+
monorepo: true,
263+
configPatterns: {
264+
persist: {
265+
outputDir: '.code-pushup/{projectName}',
266+
filename: 'report',
267+
format: ['json', 'md'],
268+
},
269+
// optional: will use portal as cache when comparing reports in PRs
270+
upload: {
271+
server: 'https://api.code-pushup.example.com/graphql',
272+
apiKey: 'cp_...',
273+
organization: 'example',
274+
project: '{projectName}',
275+
},
276+
},
277+
});
278+
```
279+
280+
### Search latest commits for previous report
281+
282+
When comparing reports, the report for the base branch can be cached. If a project has an `upload` configuration, then the Portal API is queried for a report matching that commit. If no such report was uploaded, then the report is looked up in CI artifacts (implemented in `downloadReportArtifact` in [`ProviderApiClient`](#provider-api-client)). If there's no report to be found, then the base branch is checked and the previous report is collected.
283+
284+
In some scenarios, there may not be a report for the latest commit in main branch, but some other recent commit may have a usable report - e.g. if `nxProjectsFilter` is used with `--affected` flag. In that case, the `searchCommits` option can be enabled. Then a limited number of recent commits in the main branch will be checked, but.
285+
286+
```ts
287+
await runInCI(refs, api, {
288+
monorepo: 'nx',
289+
nxProjectsFilter: '--with-target=code-pushup --affected',
290+
// checks 10 most recent commits by default
291+
searchCommits: true,
292+
// optionally, number of searched commits may be extended up to 100
293+
// searchCommits: 30
294+
});
295+
```

packages/ci/src/lib/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ export const DEFAULT_SETTINGS: Settings = {
1515
nxProjectsFilter: '--with-target={task}',
1616
skipComment: false,
1717
configPatterns: null,
18+
searchCommits: false,
1819
};
20+
21+
export const MIN_SEARCH_COMMITS = 1;
22+
export const MAX_SEARCH_COMMITS = 100;

packages/ci/src/lib/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type Options = {
2121
logger?: Logger;
2222
skipComment?: boolean;
2323
configPatterns?: ConfigPatterns | null;
24+
searchCommits?: boolean | number;
2425
};
2526

2627
/**

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@ import {
2828
runCompare,
2929
runPrintConfig,
3030
} from './cli/index.js';
31-
import { DEFAULT_SETTINGS } from './constants.js';
31+
import {
32+
DEFAULT_SETTINGS,
33+
MAX_SEARCH_COMMITS,
34+
MIN_SEARCH_COMMITS,
35+
} from './constants.js';
3236
import { listChangedFiles, normalizeGitRef } from './git.js';
3337
import { type SourceFileIssue, filterRelevantIssues } from './issues.js';
3438
import type {
3539
ConfigPatterns,
3640
GitBranch,
3741
GitRefs,
42+
Logger,
3843
Options,
3944
OutputFiles,
4045
ProjectRunResult,
@@ -101,12 +106,40 @@ export async function createRunEnv(
101106
api,
102107
settings: {
103108
...DEFAULT_SETTINGS,
104-
...(options && removeUndefinedAndEmptyProps(options)),
109+
...(options && sanitizeOptions(options)),
105110
},
106111
git,
107112
};
108113
}
109114

115+
function sanitizeOptions(options: Options): Options {
116+
const logger = options.logger ?? DEFAULT_SETTINGS.logger;
117+
118+
return removeUndefinedAndEmptyProps({
119+
...options,
120+
searchCommits: sanitizeSearchCommits(options.searchCommits, logger),
121+
});
122+
}
123+
124+
function sanitizeSearchCommits(
125+
searchCommits: Options['searchCommits'],
126+
logger: Logger,
127+
): Options['searchCommits'] {
128+
if (
129+
typeof searchCommits === 'number' &&
130+
(!Number.isInteger(searchCommits) ||
131+
searchCommits < MIN_SEARCH_COMMITS ||
132+
searchCommits > MAX_SEARCH_COMMITS)
133+
) {
134+
logger.warn(
135+
`The searchCommits option must be a boolean or an integer in range ${MIN_SEARCH_COMMITS} to ${MAX_SEARCH_COMMITS}, ignoring invalid value ${searchCommits}.`,
136+
);
137+
return undefined;
138+
}
139+
140+
return searchCommits;
141+
}
142+
110143
export async function runOnProject(
111144
project: ProjectConfig | null,
112145
env: RunEnv,
@@ -382,8 +415,13 @@ async function loadCachedBaseReportFromPortal(
382415
parameters: {
383416
organization: config.upload.organization,
384417
project: config.upload.project,
385-
commit: base.sha,
386418
withAuditDetails: true,
419+
...(!settings.searchCommits && {
420+
commit: base.sha,
421+
}),
422+
...(typeof settings.searchCommits === 'number' && {
423+
maxCommits: settings.searchCommits,
424+
}),
387425
},
388426
}).catch((error: unknown) => {
389427
logger.warn(

0 commit comments

Comments
 (0)