Skip to content

Commit edc343d

Browse files
authored
Add custom GHA report for package size (microsoft#53241)
1 parent 9dfd5ea commit edc343d

File tree

4 files changed

+241
-55
lines changed

4 files changed

+241
-55
lines changed

.github/workflows/ci.yml

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,38 +89,15 @@ jobs:
8989

9090
smoke:
9191
runs-on: ubuntu-latest
92-
defaults:
93-
run:
94-
working-directory: ./pr
9592

9693
steps:
9794
- uses: actions/checkout@v3
98-
with:
99-
path: pr
100-
101-
- uses: actions/checkout@v3
102-
with:
103-
path: base
104-
ref: ${{ github.base_ref }}
105-
if: github.event_name == 'pull_request'
10695

10796
- uses: actions/setup-node@v3
10897
with:
10998
node-version: "*"
11099
check-latest: true
111100

112-
# Pre-build the base branch so we can check lib folder size changes.
113-
# Note that github.sha points to a merge commit, meaning we're testing
114-
# the base branch versus the base branch with the PR applied.
115-
- name: Build base LKG
116-
if: github.event_name == 'pull_request'
117-
run: |
118-
npm ci
119-
npx hereby lkg
120-
rm -rf $GITHUB_WORKSPACE/pr/lib
121-
mv ./lib $GITHUB_WORKSPACE/pr/
122-
working-directory: ./base
123-
124101
- run: npm ci
125102

126103
- run: npx hereby lkg
@@ -177,6 +154,41 @@ jobs:
177154
node ./smoke.js typescript
178155
node ./smoke.js typescript/lib/tsserverlibrary
179156
157+
package-size:
158+
runs-on: ubuntu-latest
159+
if: github.event_name == 'pull_request'
160+
161+
steps:
162+
- uses: actions/checkout@v3
163+
with:
164+
path: pr
165+
166+
- uses: actions/checkout@v3
167+
with:
168+
path: base
169+
ref: ${{ github.base_ref }}
170+
171+
- uses: actions/setup-node@v3
172+
with:
173+
node-version: "*"
174+
check-latest: true
175+
176+
- run: npm ci
177+
working-directory: ./pr
178+
179+
- run: npm ci
180+
working-directory: ./base
181+
182+
- run: npx hereby lkg
183+
working-directory: ./pr
184+
185+
- run: npx hereby lkg
186+
working-directory: ./base
187+
188+
- run: |
189+
echo "See $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID for more info."
190+
node ./pr/scripts/checkPackageSize.mjs ./base ./pr >> $GITHUB_STEP_SUMMARY
191+
180192
misc:
181193
runs-on: ubuntu-latest
182194

Herebyfile.mjs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { localizationDirectories } from "./scripts/build/localization.mjs";
1515
import cmdLineOptions from "./scripts/build/options.mjs";
1616
import { buildProject, cleanProject, watchProject } from "./scripts/build/projects.mjs";
1717
import { localBaseline, localRwcBaseline, refBaseline, refRwcBaseline, runConsoleTests } from "./scripts/build/tests.mjs";
18-
import { Debouncer, Deferred, exec, getDiffTool, getDirSize, memoize, needsUpdate, readJson } from "./scripts/build/utils.mjs";
18+
import { Debouncer, Deferred, exec, getDiffTool, memoize, needsUpdate, readJson } from "./scripts/build/utils.mjs";
1919

2020
const glob = util.promisify(_glob);
2121

@@ -833,19 +833,7 @@ export const produceLKG = task({
833833
throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n"));
834834
}
835835

836-
/** @type {number | undefined} */
837-
let sizeBefore;
838-
if (fs.existsSync("lib")) {
839-
sizeBefore = getDirSize("lib");
840-
}
841836
await exec(process.execPath, ["scripts/produceLKG.mjs"]);
842-
843-
if (sizeBefore !== undefined) {
844-
const sizeAfter = getDirSize("lib");
845-
if (sizeAfter > (sizeBefore * 1.10)) {
846-
throw new Error("The lib folder increased by 10% or more. This likely indicates a bug.");
847-
}
848-
}
849837
}
850838
});
851839

scripts/build/utils.mjs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import chalk from "chalk";
44
import { spawn } from "child_process";
55
import fs from "fs";
66
import JSONC from "jsonc-parser";
7-
import path from "path";
87
import which from "which";
98

109
/**
@@ -140,24 +139,6 @@ export function getDiffTool() {
140139
return program;
141140
}
142141

143-
/**
144-
* Find the size of a directory recursively.
145-
* Symbolic links can cause a loop.
146-
* @param {string} root
147-
* @returns {number} bytes
148-
*/
149-
export function getDirSize(root) {
150-
const stats = fs.lstatSync(root);
151-
152-
if (!stats.isDirectory()) {
153-
return stats.size;
154-
}
155-
156-
return fs.readdirSync(root)
157-
.map(file => getDirSize(path.join(root, file)))
158-
.reduce((acc, num) => acc + num, 0);
159-
}
160-
161142
/**
162143
* @template T
163144
*/

scripts/checkPackageSize.mjs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import assert from "assert";
2+
import cp from "child_process";
3+
4+
const baseRepo = process.argv[2];
5+
const headRepo = process.argv[3];
6+
7+
/** @type {Array<{ size: number, unpackedSize: number; files: Array<{ path: string; size: number; }>; }>} */
8+
const [before, after] = JSON.parse(cp.execFileSync("npm", ["pack", "--dry-run", "--json", baseRepo, headRepo], { encoding: "utf8" }));
9+
10+
/** @param {{ path: string; size: number; }[]} files */
11+
function filesToMap(files) {
12+
return new Map(files.map(f => [f.path, f.size]));
13+
}
14+
15+
const beforeFileToSize = filesToMap(before.files);
16+
const afterFileToSize = filesToMap(after.files);
17+
18+
/**
19+
* @param {number} before
20+
* @param {number} after
21+
*/
22+
function failIfTooBig(before, after) {
23+
if (after > (before * 1.1)) {
24+
process.exitCode = 1;
25+
}
26+
}
27+
28+
/**
29+
* @param {number} value
30+
*/
31+
function sign(value) {
32+
return value > 0 ? "+" : "-";
33+
}
34+
35+
const units = ["B", "KiB", "MiB", "GiB"];
36+
/**
37+
* @param {number} size
38+
*/
39+
function prettyPrintSize(size) {
40+
assert(size >= 0);
41+
42+
let i = 0;
43+
while (size > 1024) {
44+
i++;
45+
size /= 1024;
46+
}
47+
48+
return `${size.toFixed(2)} ${units[i]}`;
49+
}
50+
51+
/**
52+
* @param {number} before
53+
* @param {number} after
54+
*/
55+
function prettyPrintSizeDiff(before, after) {
56+
const diff = after - before;
57+
return sign(diff) + prettyPrintSize(Math.abs(diff));
58+
}
59+
60+
/**
61+
* @param {number} before
62+
* @param {number} after
63+
*/
64+
function prettyPercentDiff(before, after) {
65+
const percent = 100 * (after - before) / before;
66+
return `${sign(percent)}${Math.abs(percent).toFixed(2)}%`;
67+
}
68+
69+
/**
70+
* @param {string[]} header
71+
* @param {string[][]} data
72+
*/
73+
function logTable(header, data) {
74+
/** @type {string[]} */
75+
const lines = [];
76+
77+
/**
78+
* @param {string[]} row
79+
*/
80+
function addRow(row) {
81+
lines.push("| " + row.join(" | ") + " |");
82+
}
83+
84+
addRow(header);
85+
addRow(new Array(header.length).fill("-"));
86+
for (const row of data) {
87+
addRow(row);
88+
}
89+
90+
console.log(lines.join("\n"));
91+
}
92+
93+
console.log(`# Package size report`);
94+
console.log();
95+
96+
console.log(`## Overall package size`);
97+
console.log();
98+
99+
if (before.size === after.size && before.unpackedSize === after.unpackedSize) {
100+
console.log("No change.");
101+
}
102+
else {
103+
logTable(
104+
["", "Before", "After", "Diff", "Diff (percent)"],
105+
[
106+
[
107+
"Packed",
108+
prettyPrintSize(before.size),
109+
prettyPrintSize(after.size),
110+
prettyPrintSizeDiff(before.size, after.size),
111+
prettyPercentDiff(before.size, after.size),
112+
],
113+
[
114+
"Unpacked",
115+
prettyPrintSize(before.unpackedSize),
116+
prettyPrintSize(after.unpackedSize),
117+
prettyPrintSizeDiff(before.unpackedSize, after.unpackedSize),
118+
prettyPercentDiff(before.unpackedSize, after.unpackedSize),
119+
],
120+
]
121+
);
122+
}
123+
124+
failIfTooBig(before.size, after.size);
125+
failIfTooBig(before.unpackedSize, after.unpackedSize);
126+
127+
console.log();
128+
129+
130+
/** @type {Map<string, number>} */
131+
const fileCounts = new Map();
132+
const inBefore = -1;
133+
const inAfter = 1;
134+
135+
/**
136+
* @param {Iterable<string>} paths
137+
* @param {-1 | 1} marker
138+
*/
139+
function addFiles(paths, marker) {
140+
for (const p of paths) {
141+
fileCounts.set(p, (fileCounts.get(p) ?? 0) + marker);
142+
}
143+
}
144+
addFiles(beforeFileToSize.keys(), inBefore);
145+
addFiles(afterFileToSize.keys(), inAfter);
146+
147+
const allEntries = [...fileCounts.entries()];
148+
const commonFiles = allEntries.filter(([, count]) => count === 0).map(([path]) => path);
149+
const beforeOnly = allEntries.filter(([, count]) => count === inBefore).map(([path]) => path);
150+
const afterOnly = allEntries.filter(([, count]) => count === inAfter).map(([path]) => path);
151+
152+
const commonData = commonFiles.map(path => {
153+
const beforeSize = beforeFileToSize.get(path) ?? 0;
154+
const afterSize = afterFileToSize.get(path) ?? 0;
155+
return { path, beforeSize, afterSize };
156+
})
157+
.filter(({ beforeSize, afterSize }) => beforeSize !== afterSize)
158+
.map(({ path, beforeSize, afterSize }) => {
159+
return [
160+
"`" + path + "`",
161+
prettyPrintSize(beforeSize),
162+
prettyPrintSize(afterSize),
163+
prettyPrintSizeDiff(beforeSize, afterSize),
164+
prettyPercentDiff(beforeSize, afterSize),
165+
];
166+
});
167+
168+
if (commonData.length > 0) {
169+
console.log(`## Files`);
170+
console.log();
171+
logTable(["", "Before", "After", "Diff", "Diff (percent)"], commonData);
172+
console.log();
173+
}
174+
175+
if (afterOnly.length > 0) {
176+
console.log(`## New files`);
177+
console.log();
178+
logTable(
179+
["", "Size"],
180+
afterOnly.map(path => {
181+
const afterSize = afterFileToSize.get(path) ?? 0;
182+
return { path, afterSize };
183+
})
184+
.map(({ path, afterSize }) => {
185+
return ["`" + path + "`", prettyPrintSize(afterSize)];
186+
}),
187+
);
188+
console.log();
189+
}
190+
191+
if (beforeOnly.length > 0) {
192+
console.log(`## Deleted files`);
193+
console.log();
194+
logTable(
195+
["", "Size"],
196+
beforeOnly.map(path => {
197+
const afterSize = afterFileToSize.get(path) ?? 0;
198+
return { path, afterSize };
199+
})
200+
.map(({ path, afterSize }) => {
201+
return ["`" + path + "`", prettyPrintSize(afterSize)];
202+
}),
203+
);
204+
console.log();
205+
}

0 commit comments

Comments
 (0)