Skip to content

Commit bcd23ea

Browse files
authored
feat: rework size calc (#8)
* feat: rework size calc * feat: add a nice table * fix: show ls usage once
1 parent 8a670fe commit bcd23ea

File tree

3 files changed

+174
-44
lines changed

3 files changed

+174
-44
lines changed

main.js

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24170,6 +24170,30 @@ async function fetchPackageMetadata(packageName, version) {
2417024170
return null;
2417124171
}
2417224172
}
24173+
async function calculateTotalDependencySizeIncrease(newVersions) {
24174+
let totalSize = 0;
24175+
const processedPackages = /* @__PURE__ */ new Set();
24176+
const packageSizes = /* @__PURE__ */ new Map();
24177+
for (const dep of newVersions) {
24178+
const packageKey = `${dep.name}@${dep.version}`;
24179+
if (processedPackages.has(packageKey)) {
24180+
continue;
24181+
}
24182+
try {
24183+
const metadata = await fetchPackageMetadata(dep.name, dep.version);
24184+
if (!metadata || metadata.dist?.unpackedSize === void 0) {
24185+
return null;
24186+
}
24187+
totalSize += metadata.dist.unpackedSize;
24188+
packageSizes.set(packageKey, metadata.dist.unpackedSize);
24189+
processedPackages.add(packageKey);
24190+
core2.info(`Added ${metadata.dist.unpackedSize} bytes for ${packageKey}`);
24191+
} catch {
24192+
return null;
24193+
}
24194+
}
24195+
return { totalSize, packageSizes };
24196+
}
2417324197

2417424198
// src/main.ts
2417524199
function formatBytes(bytes) {
@@ -24180,6 +24204,21 @@ function formatBytes(bytes) {
2418024204
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
2418124205
}
2418224206
var COMMENT_TAG = "<!-- dependency-diff-action -->";
24207+
function getLsCommand(lockfilePath, packageName) {
24208+
if (lockfilePath.endsWith("package-lock.json")) {
24209+
return `npm ls ${packageName}`;
24210+
}
24211+
if (lockfilePath.endsWith("pnpm-lock.yaml")) {
24212+
return `pnpm why ${packageName}`;
24213+
}
24214+
if (lockfilePath.endsWith("yarn.lock")) {
24215+
return `yarn why ${packageName}`;
24216+
}
24217+
if (lockfilePath.endsWith("bun.lock")) {
24218+
return `bun pm ls ${packageName}`;
24219+
}
24220+
return void 0;
24221+
}
2418324222
async function run() {
2418424223
try {
2418524224
const workspacePath = process2.env.GITHUB_WORKSPACE || process2.cwd();
@@ -24227,8 +24266,13 @@ async function run() {
2422724266
core3.getInput("size-threshold") || "100000",
2422824267
10
2422924268
);
24269+
const duplicateThreshold = parseInt(
24270+
core3.getInput("duplicate-threshold") || "1",
24271+
10
24272+
);
2423024273
core3.info(`Dependency threshold set to ${dependencyThreshold}`);
2423124274
core3.info(`Size threshold set to ${formatBytes(sizeThreshold)}`);
24275+
core3.info(`Duplicate threshold set to ${duplicateThreshold}`);
2423224276
const messages = [];
2423324277
const currentDepCount = Array.from(currentDeps.values()).reduce(
2423424278
(sum, versions) => sum + versions.size,
@@ -24247,6 +24291,26 @@ async function run() {
2424724291
`\u26A0\uFE0F **Dependency Count Warning**: This PR adds ${depIncrease} new dependencies (${baseDepCount} \u2192 ${currentDepCount}), which exceeds the threshold of ${dependencyThreshold}.`
2424824292
);
2424924293
}
24294+
const duplicateWarnings = [];
24295+
for (const [packageName, currentVersionSet] of currentDeps) {
24296+
if (currentVersionSet.size > duplicateThreshold) {
24297+
const versions = Array.from(currentVersionSet).sort();
24298+
duplicateWarnings.push(
24299+
`\u{1F4E6} **${packageName}**: ${currentVersionSet.size} versions (${versions.join(", ")})`
24300+
);
24301+
}
24302+
}
24303+
if (duplicateWarnings.length > 0) {
24304+
const exampleCommand = getLsCommand(lockfilePath, "example-package");
24305+
const helpMessage = exampleCommand ? `
24306+
24307+
\u{1F4A1} To find out what depends on a specific package, run: \`${exampleCommand}\`` : "";
24308+
messages.push(
24309+
`\u26A0\uFE0F **Duplicate Dependencies Warning** (threshold: ${duplicateThreshold}):
24310+
24311+
${duplicateWarnings.join("\n")}${helpMessage}`
24312+
);
24313+
}
2425024314
const newVersions = [];
2425124315
for (const [packageName, currentVersionSet] of currentDeps) {
2425224316
const baseVersionSet = baseDeps.get(packageName);
@@ -24262,28 +24326,20 @@ async function run() {
2426224326
}
2426324327
core3.info(`Found ${newVersions.length} new package versions`);
2426424328
if (newVersions.length > 0) {
24265-
const sizeWarnings = [];
24266-
for (const dep of newVersions) {
24267-
try {
24268-
const metadata = await fetchPackageMetadata(dep.name, dep.version);
24269-
if (metadata?.dist?.unpackedSize && metadata.dist.unpackedSize >= sizeThreshold) {
24270-
const label = dep.isNewPackage ? "new package" : "new version";
24271-
sizeWarnings.push(
24272-
`\u{1F4E6} **${dep.name}@${dep.version}** (${label}): ${formatBytes(metadata.dist.unpackedSize)}`
24273-
);
24274-
}
24275-
} catch (err) {
24276-
core3.info(
24277-
`Failed to check size for ${dep.name}@${dep.version}: ${err}`
24329+
try {
24330+
const sizeData = await calculateTotalDependencySizeIncrease(newVersions);
24331+
if (sizeData !== null && sizeData.totalSize >= sizeThreshold) {
24332+
const packageRows = Array.from(sizeData.packageSizes.entries()).sort(([, a], [, b]) => b - a).map(([pkg, size]) => `| ${pkg} | ${formatBytes(size)} |`).join("\n");
24333+
messages.push(
24334+
`\u26A0\uFE0F **Large Dependency Size Increase**: This PR adds ${formatBytes(sizeData.totalSize)} of new dependencies, which exceeds the threshold of ${formatBytes(sizeThreshold)}.
24335+
24336+
| Package | Size |
24337+
|---------|------|
24338+
${packageRows}`
2427824339
);
2427924340
}
24280-
}
24281-
if (sizeWarnings.length > 0) {
24282-
messages.push(
24283-
`\u26A0\uFE0F **Large Package Warnings** (threshold: ${formatBytes(sizeThreshold)}):
24284-
24285-
${sizeWarnings.join("\n")}`
24286-
);
24341+
} catch (err) {
24342+
core3.info(`Failed to calculate total dependency size increase: ${err}`);
2428724343
}
2428824344
}
2428924345
if (messages.length === 0) {

src/main.ts

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as core from '@actions/core';
33
import * as github from '@actions/github';
44
import {parseLockfile, detectLockfile} from './lockfile.js';
55
import {getFileFromRef, getBaseRef} from './git.js';
6-
import {fetchPackageMetadata} from './npm.js';
6+
import {calculateTotalDependencySizeIncrease} from './npm.js';
77

88
function formatBytes(bytes: number): string {
99
if (bytes === 0) return '0 B';
@@ -15,6 +15,25 @@ function formatBytes(bytes: number): string {
1515

1616
const COMMENT_TAG = '<!-- dependency-diff-action -->';
1717

18+
function getLsCommand(
19+
lockfilePath: string,
20+
packageName: string
21+
): string | undefined {
22+
if (lockfilePath.endsWith('package-lock.json')) {
23+
return `npm ls ${packageName}`;
24+
}
25+
if (lockfilePath.endsWith('pnpm-lock.yaml')) {
26+
return `pnpm why ${packageName}`;
27+
}
28+
if (lockfilePath.endsWith('yarn.lock')) {
29+
return `yarn why ${packageName}`;
30+
}
31+
if (lockfilePath.endsWith('bun.lock')) {
32+
return `bun pm ls ${packageName}`;
33+
}
34+
return undefined;
35+
}
36+
1837
async function run(): Promise<void> {
1938
try {
2039
const workspacePath = process.env.GITHUB_WORKSPACE || process.cwd();
@@ -68,9 +87,14 @@ async function run(): Promise<void> {
6887
core.getInput('size-threshold') || '100000',
6988
10
7089
);
90+
const duplicateThreshold = parseInt(
91+
core.getInput('duplicate-threshold') || '1',
92+
10
93+
);
7194

7295
core.info(`Dependency threshold set to ${dependencyThreshold}`);
7396
core.info(`Size threshold set to ${formatBytes(sizeThreshold)}`);
97+
core.info(`Duplicate threshold set to ${duplicateThreshold}`);
7498

7599
const messages: string[] = [];
76100

@@ -95,6 +119,26 @@ async function run(): Promise<void> {
95119
);
96120
}
97121

122+
const duplicateWarnings: string[] = [];
123+
for (const [packageName, currentVersionSet] of currentDeps) {
124+
if (currentVersionSet.size > duplicateThreshold) {
125+
const versions = Array.from(currentVersionSet).sort();
126+
duplicateWarnings.push(
127+
`📦 **${packageName}**: ${currentVersionSet.size} versions (${versions.join(', ')})`
128+
);
129+
}
130+
}
131+
132+
if (duplicateWarnings.length > 0) {
133+
const exampleCommand = getLsCommand(lockfilePath, 'example-package');
134+
const helpMessage = exampleCommand
135+
? `\n\n💡 To find out what depends on a specific package, run: \`${exampleCommand}\``
136+
: '';
137+
messages.push(
138+
`⚠️ **Duplicate Dependencies Warning** (threshold: ${duplicateThreshold}):\n\n${duplicateWarnings.join('\n')}${helpMessage}`
139+
);
140+
}
141+
98142
const newVersions: Array<{
99143
name: string;
100144
version: string;
@@ -118,31 +162,22 @@ async function run(): Promise<void> {
118162
core.info(`Found ${newVersions.length} new package versions`);
119163

120164
if (newVersions.length > 0) {
121-
const sizeWarnings: string[] = [];
122-
123-
for (const dep of newVersions) {
124-
try {
125-
const metadata = await fetchPackageMetadata(dep.name, dep.version);
126-
if (
127-
metadata?.dist?.unpackedSize &&
128-
metadata.dist.unpackedSize >= sizeThreshold
129-
) {
130-
const label = dep.isNewPackage ? 'new package' : 'new version';
131-
sizeWarnings.push(
132-
`📦 **${dep.name}@${dep.version}** (${label}): ${formatBytes(metadata.dist.unpackedSize)}`
133-
);
134-
}
135-
} catch (err) {
136-
core.info(
137-
`Failed to check size for ${dep.name}@${dep.version}: ${err}`
165+
try {
166+
const sizeData =
167+
await calculateTotalDependencySizeIncrease(newVersions);
168+
169+
if (sizeData !== null && sizeData.totalSize >= sizeThreshold) {
170+
const packageRows = Array.from(sizeData.packageSizes.entries())
171+
.sort(([, a], [, b]) => b - a)
172+
.map(([pkg, size]) => `| ${pkg} | ${formatBytes(size)} |`)
173+
.join('\n');
174+
175+
messages.push(
176+
`⚠️ **Large Dependency Size Increase**: This PR adds ${formatBytes(sizeData.totalSize)} of new dependencies, which exceeds the threshold of ${formatBytes(sizeThreshold)}.\n\n| Package | Size |\n|---------|------|\n${packageRows}`
138177
);
139178
}
140-
}
141-
142-
if (sizeWarnings.length > 0) {
143-
messages.push(
144-
`⚠️ **Large Package Warnings** (threshold: ${formatBytes(sizeThreshold)}):\n\n${sizeWarnings.join('\n')}`
145-
);
179+
} catch (err) {
180+
core.info(`Failed to calculate total dependency size increase: ${err}`);
146181
}
147182
}
148183

src/npm.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export interface PackageMetadata {
66
dist?: {
77
unpackedSize?: number;
88
};
9+
dependencies?: Record<string, string>;
10+
}
11+
12+
export interface PackageIndex {
13+
versions: Record<string, PackageMetadata>;
914
}
1015

1116
export async function fetchPackageMetadata(
@@ -24,3 +29,37 @@ export async function fetchPackageMetadata(
2429
return null;
2530
}
2631
}
32+
33+
export async function calculateTotalDependencySizeIncrease(
34+
newVersions: Array<{name: string; version: string}>
35+
): Promise<{totalSize: number; packageSizes: Map<string, number>} | null> {
36+
let totalSize = 0;
37+
const processedPackages = new Set<string>();
38+
const packageSizes = new Map<string, number>();
39+
40+
for (const dep of newVersions) {
41+
const packageKey = `${dep.name}@${dep.version}`;
42+
43+
if (processedPackages.has(packageKey)) {
44+
continue;
45+
}
46+
47+
try {
48+
const metadata = await fetchPackageMetadata(dep.name, dep.version);
49+
50+
if (!metadata || metadata.dist?.unpackedSize === undefined) {
51+
return null;
52+
}
53+
54+
totalSize += metadata.dist.unpackedSize;
55+
packageSizes.set(packageKey, metadata.dist.unpackedSize);
56+
processedPackages.add(packageKey);
57+
58+
core.info(`Added ${metadata.dist.unpackedSize} bytes for ${packageKey}`);
59+
} catch {
60+
return null;
61+
}
62+
}
63+
64+
return {totalSize, packageSizes};
65+
}

0 commit comments

Comments
 (0)