Skip to content

Commit 3c7fa08

Browse files
committed
fix(ci): prevent overwriting report artifacts
1 parent 1a3f66f commit 3c7fa08

File tree

11 files changed

+736
-347
lines changed

11 files changed

+736
-347
lines changed

e2e/ci-e2e/tests/basic.e2e.test.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ describe('CI - standalone mode', () => {
4646
).resolves.toEqual({
4747
mode: 'standalone',
4848
files: {
49-
report: {
50-
json: path.join(repo.baseDir, '.code-pushup/report.json'),
51-
md: path.join(repo.baseDir, '.code-pushup/report.md'),
49+
current: {
50+
json: path.join(
51+
repo.baseDir,
52+
'.code-pushup/.ci/.current/report.json',
53+
),
54+
md: path.join(repo.baseDir, '.code-pushup/.ci/.current/report.md'),
5255
},
5356
},
5457
} satisfies RunResult);
5558

5659
const jsonPromise = readFile(
57-
path.join(repo.baseDir, '.code-pushup/report.json'),
60+
path.join(repo.baseDir, '.code-pushup/.ci/.current/report.json'),
5861
'utf8',
5962
);
6063
await expect(jsonPromise).resolves.toBeTruthy();
@@ -103,19 +106,35 @@ describe('CI - standalone mode', () => {
103106
commentId: MOCK_COMMENT.id,
104107
newIssues: [],
105108
files: {
106-
report: {
107-
json: path.join(repo.baseDir, '.code-pushup/report.json'),
108-
md: path.join(repo.baseDir, '.code-pushup/report.md'),
109+
current: {
110+
json: path.join(
111+
repo.baseDir,
112+
'.code-pushup/.ci/.current/report.json',
113+
),
114+
md: path.join(repo.baseDir, '.code-pushup/.ci/.current/report.md'),
109115
},
110-
diff: {
111-
json: path.join(repo.baseDir, '.code-pushup/report-diff.json'),
112-
md: path.join(repo.baseDir, '.code-pushup/report-diff.md'),
116+
previous: {
117+
json: path.join(
118+
repo.baseDir,
119+
'.code-pushup/.ci/.previous/report.json',
120+
),
121+
md: path.join(repo.baseDir, '.code-pushup/.ci/.previous/report.md'),
122+
},
123+
comparison: {
124+
json: path.join(
125+
repo.baseDir,
126+
'.code-pushup/.ci/.comparison/report-diff.json',
127+
),
128+
md: path.join(
129+
repo.baseDir,
130+
'.code-pushup/.ci/.comparison/report-diff.md',
131+
),
113132
},
114133
},
115134
} satisfies RunResult);
116135

117136
const mdPromise = readFile(
118-
path.join(repo.baseDir, '.code-pushup/report-diff.md'),
137+
path.join(repo.baseDir, '.code-pushup/.ci/.comparison/report-diff.md'),
119138
'utf8',
120139
);
121140
await expect(mdPromise).resolves.toBeTruthy();

e2e/ci-e2e/tests/npm-workspaces.e2e.test.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ describe('CI - monorepo mode (npm workspaces)', () => {
5252
{
5353
name: '@example/cli',
5454
files: {
55-
report: {
55+
current: {
5656
json: path.join(
5757
repo.baseDir,
58-
'packages/cli/.code-pushup/report.json',
58+
'.code-pushup/.ci/@example/cli/.current/report.json',
5959
),
6060
md: path.join(
6161
repo.baseDir,
62-
'packages/cli/.code-pushup/report.md',
62+
'.code-pushup/.ci/@example/cli/.current/report.md',
6363
),
6464
},
6565
},
@@ -69,7 +69,10 @@ describe('CI - monorepo mode (npm workspaces)', () => {
6969

7070
await expect(
7171
readJsonFile(
72-
path.join(repo.baseDir, 'packages/cli/.code-pushup/report.json'),
72+
path.join(
73+
repo.baseDir,
74+
'.code-pushup/.ci/@example/cli/.current/report.json',
75+
),
7376
),
7477
).resolves.toEqual(
7578
expect.objectContaining({
@@ -120,29 +123,46 @@ describe('CI - monorepo mode (npm workspaces)', () => {
120123
await expect(runInCI(refs, MOCK_API, options, git)).resolves.toEqual({
121124
mode: 'monorepo',
122125
commentId: MOCK_COMMENT.id,
123-
diffPath: path.join(repo.baseDir, '.code-pushup/merged-report-diff.md'),
126+
files: {
127+
comparison: {
128+
md: path.join(
129+
repo.baseDir,
130+
'.code-pushup/.ci/.comparison/report-diff.md',
131+
),
132+
},
133+
},
124134
projects: expect.arrayContaining<ProjectRunResult>([
125135
{
126136
name: '@example/core',
127137
files: {
128-
report: {
138+
current: {
139+
json: path.join(
140+
repo.baseDir,
141+
'.code-pushup/.ci/@example/core/.current/report.json',
142+
),
143+
md: path.join(
144+
repo.baseDir,
145+
'.code-pushup/.ci/@example/core/.current/report.md',
146+
),
147+
},
148+
previous: {
129149
json: path.join(
130150
repo.baseDir,
131-
'packages/core/.code-pushup/report.json',
151+
'.code-pushup/.ci/@example/core/.previous/report.json',
132152
),
133153
md: path.join(
134154
repo.baseDir,
135-
'packages/core/.code-pushup/report.md',
155+
'.code-pushup/.ci/@example/core/.previous/report.md',
136156
),
137157
},
138-
diff: {
158+
comparison: {
139159
json: path.join(
140160
repo.baseDir,
141-
'packages/core/.code-pushup/report-diff.json',
161+
'.code-pushup/.ci/@example/core/.comparison/report-diff.json',
142162
),
143163
md: path.join(
144164
repo.baseDir,
145-
'packages/core/.code-pushup/report-diff.md',
165+
'.code-pushup/.ci/@example/core/.comparison/report-diff.md',
146166
),
147167
},
148168
},
@@ -152,7 +172,7 @@ describe('CI - monorepo mode (npm workspaces)', () => {
152172
} satisfies RunResult);
153173

154174
const mdPromise = readFile(
155-
path.join(repo.baseDir, '.code-pushup/merged-report-diff.md'),
175+
path.join(repo.baseDir, '.code-pushup/.ci/.comparison/report-diff.md'),
156176
'utf8',
157177
);
158178
await expect(mdPromise).resolves.toBeTruthy();

e2e/ci-e2e/tests/nx-monorepo.e2e.test.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ describe('CI - monorepo mode (Nx)', () => {
5353
{
5454
name: 'api',
5555
files: {
56-
report: {
56+
current: {
5757
json: path.join(
5858
repo.baseDir,
59-
'apps/api/.code-pushup/report.json',
59+
'.code-pushup/.ci/api/.current/report.json',
60+
),
61+
md: path.join(
62+
repo.baseDir,
63+
'.code-pushup/.ci/api/.current/report.md',
6064
),
61-
md: path.join(repo.baseDir, 'apps/api/.code-pushup/report.md'),
6265
},
6366
},
6467
},
@@ -67,7 +70,7 @@ describe('CI - monorepo mode (Nx)', () => {
6770

6871
await expect(
6972
readJsonFile(
70-
path.join(repo.baseDir, 'apps/api/.code-pushup/report.json'),
73+
path.join(repo.baseDir, '.code-pushup/.ci/api/.current/report.json'),
7174
),
7275
).resolves.toEqual(
7376
expect.objectContaining({
@@ -85,7 +88,7 @@ describe('CI - monorepo mode (Nx)', () => {
8588
);
8689
await expect(
8790
readJsonFile(
88-
path.join(repo.baseDir, 'libs/ui/.code-pushup/report.json'),
91+
path.join(repo.baseDir, '.code-pushup/.ci/ui/.current/report.json'),
8992
),
9093
).resolves.toEqual(
9194
expect.objectContaining({
@@ -145,26 +148,46 @@ describe('CI - monorepo mode (Nx)', () => {
145148
await expect(runInCI(refs, MOCK_API, options, git)).resolves.toEqual({
146149
mode: 'monorepo',
147150
commentId: MOCK_COMMENT.id,
148-
diffPath: path.join(repo.baseDir, '.code-pushup/merged-report-diff.md'),
151+
files: {
152+
comparison: {
153+
md: path.join(
154+
repo.baseDir,
155+
'.code-pushup/.ci/.comparison/report-diff.md',
156+
),
157+
},
158+
},
149159
projects: expect.arrayContaining<ProjectRunResult>([
150160
{
151161
name: 'web',
152162
files: {
153-
report: {
163+
current: {
164+
json: path.join(
165+
repo.baseDir,
166+
'.code-pushup/.ci/web/.current/report.json',
167+
),
168+
md: path.join(
169+
repo.baseDir,
170+
'.code-pushup/.ci/web/.current/report.md',
171+
),
172+
},
173+
previous: {
154174
json: path.join(
155175
repo.baseDir,
156-
'apps/web/.code-pushup/report.json',
176+
'.code-pushup/.ci/web/.previous/report.json',
177+
),
178+
md: path.join(
179+
repo.baseDir,
180+
'.code-pushup/.ci/web/.previous/report.md',
157181
),
158-
md: path.join(repo.baseDir, 'apps/web/.code-pushup/report.md'),
159182
},
160-
diff: {
183+
comparison: {
161184
json: path.join(
162185
repo.baseDir,
163-
'apps/web/.code-pushup/report-diff.json',
186+
'.code-pushup/.ci/web/.comparison/report-diff.json',
164187
),
165188
md: path.join(
166189
repo.baseDir,
167-
'apps/web/.code-pushup/report-diff.md',
190+
'.code-pushup/.ci/web/.comparison/report-diff.md',
168191
),
169192
},
170193
},
@@ -182,7 +205,7 @@ describe('CI - monorepo mode (Nx)', () => {
182205
} satisfies RunResult);
183206

184207
const mdPromise = readFile(
185-
path.join(repo.baseDir, '.code-pushup/merged-report-diff.md'),
208+
path.join(repo.baseDir, '.code-pushup/.ci/.comparison/report-diff.md'),
186209
'utf8',
187210
);
188211
await expect(mdPromise).resolves.toBeTruthy();

packages/ci/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ const result = await runInCI(refs, api);
137137
if (result.mode === 'standalone') {
138138
const {
139139
// output files, can be uploaded as job artifact
140-
files: { report, diff },
140+
files: { current, comparison },
141141
// ID of created/updated PR comment
142142
commentId,
143143
// array of source code issues, can be used to annotate changed files in PR
@@ -231,15 +231,15 @@ if (result.mode === 'monorepo') {
231231
// ID of created/updated PR comment
232232
commentId,
233233
// merged report-diff.md used in PR comment, can also be uploaded as job artifact
234-
diffPath,
234+
files: { comparison },
235235
} = result;
236236

237237
for (const project of projects) {
238238
const {
239239
// detected project name (from package.json, project.json or folder name)
240240
name,
241241
// output files, can be uploaded as job artifacts
242-
files: { report, diff },
242+
files: { current, comparison },
243243
// array of source code issues, can be used to annotate changed files in PR
244244
newIssues,
245245
} = project;

packages/ci/src/lib/models.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ export type MonorepoRunResult = {
9494
mode: 'monorepo';
9595
projects: ProjectRunResult[];
9696
commentId?: number;
97-
diffPath?: string;
97+
files?: {
98+
comparison: Pick<OutputFiles, 'md'>;
99+
};
98100
};
99101

100102
/**
@@ -103,8 +105,9 @@ export type MonorepoRunResult = {
103105
export type ProjectRunResult = {
104106
name: string;
105107
files: {
106-
report: OutputFiles;
107-
diff?: OutputFiles;
108+
current: OutputFiles;
109+
previous?: OutputFiles | Pick<OutputFiles, 'json'>;
110+
comparison?: OutputFiles;
108111
};
109112
newIssues?: SourceFileIssue[];
110113
};

packages/ci/src/lib/output-files.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { copyFile, mkdir } from 'node:fs/promises';
2+
import path from 'node:path';
3+
import { DEFAULT_PERSIST_FILENAME, type Format } from '@code-pushup/models';
4+
import { objectFromEntries, objectToKeys } from '@code-pushup/utils';
5+
import type { OutputFiles, Settings } from './models.js';
6+
import type { ProjectConfig } from './monorepo/tools.js';
7+
8+
const BASE_DIR = path.join('.code-pushup', '.ci');
9+
10+
type OutputType = 'current' | 'previous' | 'comparison';
11+
12+
export async function saveOutputFiles<T extends Partial<OutputFiles>>({
13+
project,
14+
type,
15+
files,
16+
settings: { logger, directory },
17+
}: {
18+
project: Pick<ProjectConfig, 'name'> | null;
19+
type: OutputType;
20+
files: T;
21+
settings: Pick<Settings, 'logger' | 'directory'>;
22+
}): Promise<T> {
23+
const baseDir = project ? path.join(BASE_DIR, project.name) : BASE_DIR;
24+
const outputDir = path.join(directory, baseDir, `.${type}`);
25+
const name =
26+
type === 'comparison'
27+
? `${DEFAULT_PERSIST_FILENAME}-diff`
28+
: DEFAULT_PERSIST_FILENAME;
29+
30+
const formats = objectToKeys(files) as Format[];
31+
const outputs = objectFromEntries(
32+
formats.map(format => [
33+
format,
34+
path.join(outputDir, `${name}.${format.toString()}`),
35+
]),
36+
);
37+
38+
if (formats.length > 0) {
39+
await mkdir(outputDir, { recursive: true });
40+
}
41+
42+
await Promise.all(
43+
formats.map(async format => {
44+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
45+
const src = files[format]!;
46+
const dest = outputs[format];
47+
await copyFile(src, dest);
48+
logger.debug(`Copied ${type} report from ${src} to ${dest}`);
49+
}),
50+
);
51+
52+
return outputs as T;
53+
}

0 commit comments

Comments
 (0)