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
22 changes: 20 additions & 2 deletions .github/actions/size-limit/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/actions/size-limit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/bun": "latest"
"@types/bun": "^1.3.5",
"@types/node": "^25.0.6"
},
"packageManager": "bun@1.2.22",
"dependencies": {
"arkregex": "0.0.5",
"glob": "^13.0.0",
"typescript": "5.9.3"
}
}
4 changes: 1 addition & 3 deletions .github/actions/size-limit/src/package/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ export const getFilenameFromConfig = (packageName: string): string | null => {
packagePath = `packages/${folder}`;
break;
}
} catch {
continue;
}
} catch {}
}
}

Expand Down
19 changes: 16 additions & 3 deletions .github/actions/size-limit/src/size-limit/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { spawn } from "bun";
import type { SizeLimitResult } from "../types.ts";
import { parseSizeLimitOutput, getPackageNames } from "../utils/parser.ts";
import {
getPackageNames,
parseJsonFiles,
parseSizeLimitOutput,
} from "../utils/parser.ts";

/**
* Runs size-limit for the current branch and returns parsed results.
Expand Down Expand Up @@ -41,8 +45,17 @@ export const runSizeLimit = async (
const exitCode = await proc.exited;
const rawOutput = stdout + stderr;

// Parse results using the captured target packages
const results = parseSizeLimitOutput(rawOutput, targetPackages);
// 1. Try to parse JSON files first
let results = await parseJsonFiles();

// 2. Fallback to stdout parsing if no JSON results were found
// This happens when checking out main where size-limit --json hasn't been added yet
if (results.length === 0) {
process.stdout.write(
"ℹ️ No JSON results found. Falling back to stdout parsing...\n",
);
results = parseSizeLimitOutput(rawOutput, targetPackages);
}

return {
results,
Expand Down
97 changes: 64 additions & 33 deletions .github/actions/size-limit/src/utils/parser.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,86 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { regex } from "arkregex";
import { glob } from "glob";
import type { SizeLimitResult, SizeLimitState } from "../types.ts";
import { formatBytes } from "./size.ts";

/**
* Reads and parses .size-limit.json files from all packages.
*/
export async function parseJsonFiles(): Promise<SizeLimitResult[]> {
const results: SizeLimitResult[] = [];
const files = glob.sync("packages/**/.size-limit.json", {
ignore: ["**/node_modules/**", "**/.turbo/**"],
});

for (const file of files) {
try {
const content = fs.readFileSync(file, "utf-8");
const data = JSON.parse(content);

const dir = path.dirname(file);
let pkgName = path.basename(dir);
try {
const pkgJsonPath = path.join(dir, "package.json");
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
pkgName = pkgJson.name || pkgName;
} catch {
// Fallback to folder name
}

if (Array.isArray(data)) {
for (const item of data) {
// size-limit --json uses 'size' and 'sizeLimit' as numbers
const rawSize = item.size;
const rawLimit = item.sizeLimit ?? item.limit;

const size =
typeof rawSize === "number"
? formatBytes(rawSize)
: rawSize || "0 B";
const limit =
typeof rawLimit === "number"
? formatBytes(rawLimit)
: rawLimit || "—";

results.push({
package: pkgName,
file: item.name || item.path || "index.js",
size,
limit,
status: item.passed !== false ? "✅" : "❌",
});
}
}
} catch (error) {
process.stdout.write(`DEBUG: Failed to parse ${file}: ${error}\n`);
}
}

return results;
}

/**
* Normalizes package names from Turbo/GHA output.
*/
export const normalizePackageName = (name: string): string => {
// Remove GHA timestamps if they exist
let normalized = name.replace(
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z\s*/,
"",
);

// Remove Turbo status characters from the start
normalized = normalized.replace(/^[○●•✔✖ℹ⚠»>\s]+/, "");

// Remove Turbo prefixes like "packages/arkenv (arkenv)" -> "arkenv"
const parenMatch = normalized.match(/\(([^)]+)\)$/);
if (parenMatch) {
normalized = parenMatch[1] ?? normalized;
}

// Handle "package@version task"
const taskMatch = normalized.match(/^([^@\s]+)@\d+\.\d+\.\d+\s+([^:]+)/);
if (taskMatch) {
normalized = taskMatch[1] ?? normalized;
}

// Handle "package:size" or "package#size"
if (normalized.includes(":") || normalized.includes("#")) {
const parts = normalized.split(/[:#]/);
normalized = (parts[0] ?? normalized).trim();
Expand All @@ -37,7 +90,7 @@ export const normalizePackageName = (name: string): string => {
};

/**
* Gets all package names in the monorepo to help with attribution.
* Gets all package names in the monorepo.
*/
export function getPackageNames(): string[] {
const results: string[] = [];
Expand All @@ -52,34 +105,17 @@ export function getPackageNames(): string[] {
if (fs.existsSync(pkgPath)) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (pkg.name) results.push(pkg.name);
} else {
// Handle scoped packages in subdirectories
const subDir = path.join(packagesDir, dirent.name);
const subDirents = fs.readdirSync(subDir, { withFileTypes: true });
for (const subDirent of subDirents) {
if (subDirent.isDirectory()) {
const subPkgPath = path.join(
subDir,
subDirent.name,
"package.json",
);
if (fs.existsSync(subPkgPath)) {
const subPkg = JSON.parse(fs.readFileSync(subPkgPath, "utf-8"));
if (subPkg.name) results.push(subPkg.name);
}
}
}
}
}
}
} catch (error) {
} catch {
// Silent fail
}
return results;
}

/**
* Parses the raw output of size-limit.
* Parses raw output string for fallback support.
*/
export function parseSizeLimitOutput(
output: string,
Expand Down Expand Up @@ -157,8 +193,8 @@ export function parseSizeLimitOutput(
}
};

const ansiRegex = new RegExp(
"[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]",
const ansiRegex = regex(
"[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]",
"g",
);

Expand All @@ -171,7 +207,6 @@ export function parseSizeLimitOutput(
const strippedLine = lineNoTimestamp.replace(/^[\s|>○●•✔✖ℹ⚠]+/, "").trim();
if (!strippedLine) continue;

// 1. Group Detection (Context is "sticky")
if (strippedLine.includes("##[group]")) {
const groupContent = strippedLine.split("##[group]")[1]?.trim();
if (groupContent) {
Expand All @@ -188,7 +223,6 @@ export function parseSizeLimitOutput(
}
}

// 2. Header Detection
const headerMatch = strippedLine.match(
/([@a-z0-9/._-]+)\s*[:#]\s*size(?:\s*[:#]\s*(.*))?/i,
);
Expand All @@ -209,7 +243,6 @@ export function parseSizeLimitOutput(
continue;
}

// 3. Attribution fallback for replayed logs
const lineAttributed = (() => {
for (const pkg of relevantPackages) {
const unscoped = pkg.startsWith("@") ? (pkg.split("/")[1] ?? pkg) : pkg;
Expand All @@ -232,7 +265,6 @@ export function parseSizeLimitOutput(
})();
if (lineAttributed) continue;

// 4. Final fallback for guessing context
if (!lastPackage && strippedLine.match(/size:|size\s*limit/i)) {
for (const pkg of relevantPackages) {
const unscoped = pkg.startsWith("@") ? (pkg.split("/")[1] ?? pkg) : pkg;
Expand All @@ -246,7 +278,6 @@ export function parseSizeLimitOutput(
}
}

// 5. Apply context
if (lastPackage) {
parseMessageLine(lastPackage, strippedLine);
}
Expand Down
12 changes: 12 additions & 0 deletions .github/actions/size-limit/src/utils/size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@ export const calculateDiff = (
const sign = diff > 0 ? "+" : "";
return `${sign}${diff.toFixed(1)}%`;
};

// Format bytes to human readable string
export const formatBytes = (bytes: number): string => {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "kB", "MB", "GB", "TB"];
const i = Math.min(
Math.floor(Math.log(bytes) / Math.log(k)),
sizes.length - 1,
);
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
};
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pnpm-debug.log*
coverage/

# size-limit
.size-limit.json
esbuild-why*.html

# arktype clone
Expand Down
2 changes: 1 addition & 1 deletion packages/arkenv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"scripts": {
"build": "tsdown",
"size": "size-limit",
"size": "size-limit --json > .size-limit.json",
"test:once": "pnpm test",
"typecheck": "tsc --noEmit",
"clean": "rimraf dist node_modules",
Expand Down
2 changes: 1 addition & 1 deletion packages/bun-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"test": "vitest",
"fix": "pnpm -w run fix",
"changeset": "pnpm -w run changeset",
"size": "size-limit"
"size": "size-limit --json > .size-limit.json"
},
"type": "module",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"test": "vitest",
"fix": "pnpm -w run fix",
"changeset": "pnpm -w run changeset",
"size": "size-limit"
"size": "size-limit --json > .size-limit.json"
},
"type": "module",
"types": "./dist/index.d.ts",
Expand Down
6 changes: 5 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
"cache": false,
"persistent": true
},
"test:e2e:update": {}
"test:e2e:update": {},
"size": {
"dependsOn": ["^build"],
"outputs": [".size-limit.json"]
}
}
}
Loading