Skip to content

Commit 911422b

Browse files
committed
improve action code & add some more logging
1 parent 2bf9247 commit 911422b

File tree

2 files changed

+158
-156
lines changed

2 files changed

+158
-156
lines changed

dev-packages/size-limit-gh-action/index.mjs

Lines changed: 32 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable max-lines */
21
import { promises as fs } from 'node:fs';
32
import path from 'node:path';
43
import { fileURLToPath } from 'node:url';
@@ -9,13 +8,18 @@ import { exec } from '@actions/exec';
98
import { context, getOctokit } from '@actions/github';
109
import * as glob from '@actions/glob';
1110
import * as io from '@actions/io';
12-
import bytes from 'bytes';
1311
import { markdownTable } from 'markdown-table';
1412

13+
import { SizeLimit } from './utils/size-limit-formatter.mjs';
14+
1515
const SIZE_LIMIT_HEADING = '## size-limit report 📦 ';
1616
const ARTIFACT_NAME = 'size-limit-action';
1717
const RESULTS_FILE = 'size-limit-results.json';
1818

19+
const RESULTS_FILE_PATH = path.resolve(__dirname, RESULTS_FILE);
20+
21+
const { getInput, setFailed } = core;
22+
1923
async function fetchPreviousComment(octokit, repo, pr) {
2024
const { data: commentList } = await octokit.rest.issues.listComments({
2125
...repo,
@@ -26,124 +30,6 @@ async function fetchPreviousComment(octokit, repo, pr) {
2630
return !sizeLimitComment ? null : sizeLimitComment;
2731
}
2832

29-
class SizeLimit {
30-
formatBytes(size) {
31-
return bytes.format(size, { unitSeparator: ' ' });
32-
}
33-
34-
formatSizeLimitResult(size, sizeLimit, passed) {
35-
if (passed) {
36-
return this.formatBytes(size);
37-
}
38-
39-
return `⛔️ ${this.formatBytes(size)} (max: ${this.formatBytes(sizeLimit)})`;
40-
}
41-
42-
formatPercentageChange(base = 0, current = 0) {
43-
if (base === 0) {
44-
return 'added';
45-
}
46-
47-
if (current === 0) {
48-
return 'removed';
49-
}
50-
51-
const value = ((current - base) / base) * 100;
52-
const formatted = (Math.sign(value) * Math.ceil(Math.abs(value) * 100)) / 100;
53-
54-
if (value > 0) {
55-
return `+${formatted}%`;
56-
}
57-
58-
if (value === 0) {
59-
return '-';
60-
}
61-
62-
return `${formatted}%`;
63-
}
64-
65-
formatChange(base = 0, current = 0) {
66-
if (base === 0) {
67-
return 'added';
68-
}
69-
70-
if (current === 0) {
71-
return 'removed';
72-
}
73-
74-
const value = current - base;
75-
const formatted = this.formatBytes(value);
76-
77-
if (value > 0) {
78-
return `+${formatted} 🔺`;
79-
}
80-
81-
if (value === 0) {
82-
return '-';
83-
}
84-
85-
return `${formatted} 🔽`;
86-
}
87-
88-
formatLine(value, change) {
89-
return `${value} (${change})`;
90-
}
91-
92-
formatSizeResult(name, base, current) {
93-
return [
94-
name,
95-
this.formatSizeLimitResult(current.size, current.sizeLimit, current.passed),
96-
this.formatPercentageChange(base.size, current.size),
97-
this.formatChange(base.size, current.size),
98-
];
99-
}
100-
101-
parseResults(output) {
102-
const results = JSON.parse(output);
103-
104-
return results.reduce((current, result) => {
105-
return {
106-
// biome-ignore lint/performance/noAccumulatingSpread: <explanation>
107-
...current,
108-
[result.name]: {
109-
name: result.name,
110-
size: +result.size,
111-
sizeLimit: +result.sizeLimit,
112-
passed: result.passed || false,
113-
},
114-
};
115-
}, {});
116-
}
117-
118-
hasSizeChanges(base, current, threshold = 0) {
119-
const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])];
120-
121-
return !!names.find(name => {
122-
const baseResult = base?.[name] || EmptyResult;
123-
const currentResult = current[name] || EmptyResult;
124-
125-
if (baseResult.size === 0 && currentResult.size === 0) {
126-
return true;
127-
}
128-
129-
return Math.abs((currentResult.size - baseResult.size) / baseResult.size) * 100 > threshold;
130-
});
131-
}
132-
133-
formatResults(base, current) {
134-
const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])];
135-
const header = SIZE_RESULTS_HEADER;
136-
const fields = names.map(name => {
137-
const baseResult = base?.[name] || EmptyResult;
138-
const currentResult = current[name] || EmptyResult;
139-
140-
return this.formatSizeResult(name, baseResult, currentResult);
141-
});
142-
143-
return [header, ...fields];
144-
}
145-
}
146-
14733
async function execSizeLimit() {
14834
let output = '';
14935

@@ -161,16 +47,7 @@ async function execSizeLimit() {
16147
return { status, output };
16248
}
16349

164-
const SIZE_RESULTS_HEADER = ['Path', 'Size', '% Change', 'Change'];
165-
166-
const EmptyResult = {
167-
name: '-',
168-
size: 0,
169-
};
170-
17150
async function run() {
172-
const { getInput, setFailed } = core;
173-
17451
try {
17552
const { payload, repo } = context;
17653
const pr = payload.pull_request;
@@ -185,35 +62,11 @@ async function run() {
18562

18663
const octokit = getOctokit(githubToken);
18764
const limit = new SizeLimit();
188-
const artifactClient = artifact.create();
18965
const __dirname = path.dirname(fileURLToPath(import.meta.url));
190-
const resultsFilePath = path.resolve(__dirname, RESULTS_FILE);
19166

19267
// If we have no comparison branch, we just run size limit & store the result as artifact
19368
if (!comparisonBranch) {
194-
let base;
195-
const { output: baseOutput } = await execSizeLimit();
196-
197-
try {
198-
base = limit.parseResults(baseOutput);
199-
} catch (error) {
200-
core.error('Error parsing size-limit output. The output should be a json.');
201-
throw error;
202-
}
203-
204-
try {
205-
await fs.writeFile(resultsFilePath, JSON.stringify(base), 'utf8');
206-
} catch (err) {
207-
core.error(err);
208-
}
209-
const globber = await glob.create(resultsFilePath, {
210-
followSymbolicLinks: false,
211-
});
212-
const files = await globber.glob();
213-
214-
await artifactClient.uploadArtifact(ARTIFACT_NAME, files, __dirname);
215-
216-
return;
69+
return runSizeLimitOnComparisonBranch();
21770
}
21871

21972
// Else, we run size limit for the current branch, AND fetch it for the comparison branch
@@ -243,7 +96,7 @@ async function run() {
24396
downloadPath: __dirname,
24497
});
24598

246-
base = JSON.parse(await fs.readFile(resultsFilePath, { encoding: 'utf8' }));
99+
base = JSON.parse(await fs.readFile(RESULTS_FILE_PATH, { encoding: 'utf8' }));
247100

248101
if (!artifacts.isLatest) {
249102
baseIsNotLatest = true;
@@ -265,7 +118,6 @@ async function run() {
265118

266119
const thresholdNumber = Number(threshold);
267120

268-
// @ts-ignore
269121
const sizeLimitComment = await fetchPreviousComment(octokit, repo, pr);
270122

271123
const shouldComment =
@@ -293,6 +145,8 @@ async function run() {
293145

294146
const body = bodyParts.join('\r\n');
295147

148+
core.debug(`Posting PR comment: \n\n${body}`);
149+
296150
try {
297151
if (!sizeLimitComment) {
298152
await octokit.rest.issues.createComment({
@@ -323,6 +177,28 @@ async function run() {
323177
}
324178
}
325179

180+
async function runSizeLimitOnComparisonBranch() {
181+
const limit = new SizeLimit();
182+
const artifactClient = artifact.create();
183+
184+
const { output: baseOutput } = await execSizeLimit();
185+
186+
try {
187+
const base = limit.parseResults(baseOutput);
188+
await fs.writeFile(RESULTS_FILE_PATH, JSON.stringify(base), 'utf8');
189+
} catch (error) {
190+
core.error('Error parsing size-limit output. The output should be a json.');
191+
throw error;
192+
}
193+
194+
const globber = await glob.create(RESULTS_FILE_PATH, {
195+
followSymbolicLinks: false,
196+
});
197+
const files = await globber.glob();
198+
199+
await artifactClient.uploadArtifact(ARTIFACT_NAME, files, __dirname);
200+
}
201+
326202
// max pages of workflows to pagination through
327203
const DEFAULT_MAX_PAGES = 50;
328204
// max results per page
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import bytes from 'bytes';
2+
3+
const SIZE_RESULTS_HEADER = ['Path', 'Size', '% Change', 'Change'];
4+
5+
const EmptyResult = {
6+
name: '-',
7+
size: 0,
8+
};
9+
10+
export class SizeLimit {
11+
formatBytes(size) {
12+
return bytes.format(size, { unitSeparator: ' ' });
13+
}
14+
15+
formatSizeLimitResult(size, sizeLimit, passed) {
16+
if (passed) {
17+
return this.formatBytes(size);
18+
}
19+
20+
return `⛔️ ${this.formatBytes(size)} (max: ${this.formatBytes(sizeLimit)})`;
21+
}
22+
23+
formatPercentageChange(base = 0, current = 0) {
24+
if (base === 0) {
25+
return 'added';
26+
}
27+
28+
if (current === 0) {
29+
return 'removed';
30+
}
31+
32+
const value = ((current - base) / base) * 100;
33+
const formatted = (Math.sign(value) * Math.ceil(Math.abs(value) * 100)) / 100;
34+
35+
if (value > 0) {
36+
return `+${formatted}%`;
37+
}
38+
39+
if (value === 0) {
40+
return '-';
41+
}
42+
43+
return `${formatted}%`;
44+
}
45+
46+
formatChange(base = 0, current = 0) {
47+
if (base === 0) {
48+
return 'added';
49+
}
50+
51+
if (current === 0) {
52+
return 'removed';
53+
}
54+
55+
const value = current - base;
56+
const formatted = this.formatBytes(value);
57+
58+
if (value > 0) {
59+
return `+${formatted} 🔺`;
60+
}
61+
62+
if (value === 0) {
63+
return '-';
64+
}
65+
66+
return `${formatted} 🔽`;
67+
}
68+
69+
formatLine(value, change) {
70+
return `${value} (${change})`;
71+
}
72+
73+
formatSizeResult(name, base, current) {
74+
return [
75+
name,
76+
this.formatSizeLimitResult(current.size, current.sizeLimit, current.passed),
77+
this.formatPercentageChange(base.size, current.size),
78+
this.formatChange(base.size, current.size),
79+
];
80+
}
81+
82+
parseResults(output) {
83+
const results = JSON.parse(output);
84+
85+
return results.reduce((current, result) => {
86+
return {
87+
// biome-ignore lint/performance/noAccumulatingSpread: <explanation>
88+
...current,
89+
[result.name]: {
90+
name: result.name,
91+
size: +result.size,
92+
sizeLimit: +result.sizeLimit,
93+
passed: result.passed || false,
94+
},
95+
};
96+
}, {});
97+
}
98+
99+
hasSizeChanges(base, current, threshold = 0) {
100+
const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])];
101+
102+
return !!names.find(name => {
103+
const baseResult = base?.[name] || EmptyResult;
104+
const currentResult = current[name] || EmptyResult;
105+
106+
if (baseResult.size === 0 && currentResult.size === 0) {
107+
return true;
108+
}
109+
110+
return Math.abs((currentResult.size - baseResult.size) / baseResult.size) * 100 > threshold;
111+
});
112+
}
113+
114+
formatResults(base, current) {
115+
const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])];
116+
const header = SIZE_RESULTS_HEADER;
117+
const fields = names.map(name => {
118+
const baseResult = base?.[name] || EmptyResult;
119+
const currentResult = current[name] || EmptyResult;
120+
121+
return this.formatSizeResult(name, baseResult, currentResult);
122+
});
123+
124+
return [header, ...fields];
125+
}
126+
}

0 commit comments

Comments
 (0)