Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
444 changes: 242 additions & 202 deletions build/main.js

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/checks/bundle-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {formatBytes} from '../common.js';
import {comparePackSizes, type PackInfo} from '../packs.js';

export async function scanForBundleSize(
messages: string[],
basePacks: PackInfo[],
sourcePacks: PackInfo[],
threshold: number
): Promise<void> {
if (basePacks.length === 0 && sourcePacks.length === 0) {
return;
}
const comparison = comparePackSizes(basePacks, sourcePacks, threshold);
const packWarnings = comparison.packChanges.filter(
(change) => change.exceedsThreshold && change.sizeChange > 0
);

if (packWarnings.length > 0) {
const packRows = packWarnings
.map((change) => {
const baseSize = change.baseSize ? formatBytes(change.baseSize) : 'New';
const sourceSize = change.sourceSize
? formatBytes(change.sourceSize)
: 'Removed';
const sizeChange = formatBytes(change.sizeChange);
return `| ${change.name} | ${baseSize} | ${sourceSize} | ${sizeChange} |`;
})
.join('\n');

messages.push(
`## ⚠️ Package Size Increase

These packages exceed the size increase threshold of ${formatBytes(threshold)}:

| 📦 Package | 📏 Base Size | 📏 Source Size | 📈 Size Change |
| --- | --- | --- | --- |
${packRows}`
);
}
}
29 changes: 29 additions & 0 deletions src/checks/dependency-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as core from '@actions/core';

export function scanForDependencyCount(
messages: string[],
threshold: number,
currentDeps: Map<string, Set<string>>,
baseDeps: Map<string, Set<string>>
): void {
const currentDepCount = Array.from(currentDeps.values()).reduce(
(sum, versions) => sum + versions.size,
0
);
const baseDepCount = Array.from(baseDeps.values()).reduce(
(sum, versions) => sum + versions.size,
0
);
const depIncrease = currentDepCount - baseDepCount;
core.info(`Base dependency count: ${baseDepCount}`);
core.info(`Current dependency count: ${currentDepCount}`);
core.info(`Dependency count increase: ${depIncrease}`);

if (depIncrease >= threshold) {
messages.push(
`## ⚠️ Dependency Count

This PR adds ${depIncrease} new dependencies (${baseDepCount} → ${currentDepCount}), which exceeds the threshold of ${threshold}.`
);
}
}
35 changes: 35 additions & 0 deletions src/checks/dependency-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as core from '@actions/core';
import {calculateTotalDependencySizeIncrease} from '../npm.js';
import {formatBytes} from '../common.js';

export async function scanForDependencySize(
messages: string[],
threshold: number,
newVersions: Array<{name: string; version: string}>
): Promise<void> {
if (newVersions.length === 0) {
return;
}
try {
const sizeData = await calculateTotalDependencySizeIncrease(newVersions);

if (sizeData !== null && sizeData.totalSize >= threshold) {
const packageRows = Array.from(sizeData.packageSizes.entries())
.sort(([, a], [, b]) => b - a)
.map(([pkg, size]) => `| ${pkg} | ${formatBytes(size)} |`)
.join('\n');

messages.push(
`## ⚠️ Large Dependency Size Increase

This PR adds ${formatBytes(sizeData.totalSize)} of new dependencies, which exceeds the threshold of ${formatBytes(threshold)}.

| 📦 Package | 📏 Size |
| --- | --- |
${packageRows}`
);
}
} catch (err) {
core.info(`Failed to calculate total dependency size increase: ${err}`);
}
}
49 changes: 49 additions & 0 deletions src/checks/duplicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function getLsCommand(
lockfilePath: string,
packageName: string
): string | undefined {
if (lockfilePath.endsWith('package-lock.json')) {
return `npm ls ${packageName}`;
}
if (lockfilePath.endsWith('pnpm-lock.yaml')) {
return `pnpm why ${packageName}`;
}
if (lockfilePath.endsWith('yarn.lock')) {
return `yarn why ${packageName}`;
}
if (lockfilePath.endsWith('bun.lock')) {
return `bun pm ls ${packageName}`;
}
return undefined;
}

export function scanForDuplicates(
messages: string[],
threshold: number,
dependencyMap: Map<string, Set<string>>,
lockfilePath: string
): void {
const duplicateRows: string[] = [];
for (const [packageName, currentVersionSet] of dependencyMap) {
if (currentVersionSet.size > threshold) {
const versions = Array.from(currentVersionSet).sort();
duplicateRows.push(
`| ${packageName} | ${currentVersionSet.size} versions | ${versions.join(', ')} |`
);
}
}

if (duplicateRows.length > 0) {
const exampleCommand = getLsCommand(lockfilePath, 'example-package');
const helpMessage = exampleCommand
? `\n\n💡 To find out what depends on a specific package, run: \`${exampleCommand}\``
: '';
messages.push(
`## ⚠️ Duplicate Dependencies (threshold: ${threshold})

| 📦 Package | 🔢 Version Count | 📋 Versions |
| --- | --- | --- |
${duplicateRows.join('\n')}${helpMessage}`
);
}
}
61 changes: 61 additions & 0 deletions src/checks/provenance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as core from '@actions/core';
import {getMinTrustLevel, getProvenanceForPackageVersions} from '../npm.js';

export async function scanForProvenance(
messages: string[],
currentDeps: Map<string, Set<string>>,
baseDeps: Map<string, Set<string>>
): Promise<void> {
const provenanceRows: string[] = [];

for (const [packageName, currentVersionSet] of currentDeps) {
const baseVersionSet = baseDeps.get(packageName);

if (!baseVersionSet || baseVersionSet.size === 0) {
continue;
}

if (currentVersionSet.isSubsetOf(baseVersionSet)) {
continue;
}

try {
const baseProvenances = await getProvenanceForPackageVersions(
packageName,
baseVersionSet
);
const currentProvenances = await getProvenanceForPackageVersions(
packageName,
currentVersionSet
);

if (baseProvenances.size === 0 || currentProvenances.size === 0) {
continue;
}

const minBaseTrust = getMinTrustLevel(baseProvenances.values());
const minCurrentTrust = getMinTrustLevel(currentProvenances.values());

if (minCurrentTrust.level < minBaseTrust.level) {
provenanceRows.push(
`| ${packageName} | ${minBaseTrust.status} | ${minCurrentTrust.status} |`
);
}
} catch (err) {
core.info(`Failed to check provenance for ${packageName}: ${err}`);
}
}

if (provenanceRows.length > 0) {
messages.push(
`## ⚠️ Package Trust Level Decreased

> [!CAUTION]
> Decreased trust levels may indicate a higher risk of supply chain attacks. Please review these changes carefully.

| 📦 Package | 🔒 Before | 🔓 After |
| --- | --- | --- |
${provenanceRows.join('\n')}`
);
}
}
File renamed without changes.
7 changes: 7 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1000;
const sizes = ['B', 'kB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
Loading