From 7b41930eed5d2d326d926831e4e0ec31d49a20e8 Mon Sep 17 00:00:00 2001 From: rahim Date: Fri, 13 Feb 2026 21:15:34 +1100 Subject: [PATCH] chore(ci): add turbo caching and replace size-limit (#524) --- .github/scripts/bundle-size-report.js | 376 ++++++++++++++++++++++++++ .github/scripts/bundle-size.js | 177 ++++++++++++ .github/workflows/bundle-size.yml | 247 ++++++----------- .github/workflows/cd.yml | 9 + .github/workflows/ci.yml | 8 + .github/workflows/website-tests.yml | 8 + .size-limit.json | 87 ------ package.json | 6 +- pnpm-lock.yaml | 352 ++++++++++++++++++------ 9 files changed, 929 insertions(+), 341 deletions(-) create mode 100644 .github/scripts/bundle-size-report.js create mode 100644 .github/scripts/bundle-size.js delete mode 100644 .size-limit.json diff --git a/.github/scripts/bundle-size-report.js b/.github/scripts/bundle-size-report.js new file mode 100644 index 00000000..d349c1c1 --- /dev/null +++ b/.github/scripts/bundle-size-report.js @@ -0,0 +1,376 @@ +/** + * Generates a markdown bundle size report from measurement JSON data. + * + * Usage: + * node bundle-size-report.js --pr pr-size.json [--base base-size.json] + * + * When --base is omitted, generates a report showing current sizes only (no diff). + * When --base is provided, generates a comparison report with diffs and status icons. + * + * Reads JSON arrays of { name, size, type } entries produced by bundle-size.js. + */ + +import { readFileSync } from 'node:fs'; + +const ESC = '\x1b['; +const ansi = { + bold: (s) => `${ESC}1m${s}${ESC}22m`, + dim: (s) => `${ESC}2m${s}${ESC}22m`, + cyan: (s) => `${ESC}36m${s}${ESC}39m`, + yellow: (s) => `${ESC}33m${s}${ESC}39m`, + white: (s) => `${ESC}37m${s}${ESC}39m`, + green: (s) => `${ESC}32m${s}${ESC}39m`, +}; + +function formatBytes(bytes) { + if (bytes < 1024) return `${bytes} B`; + return `${(bytes / 1024).toFixed(2)} kB`; +} + +function formatDelta(current, previous) { + if (previous === undefined) return { bytes: 'โ€”', pct: '' }; + const diff = current - previous; + if (diff === 0) return { bytes: '0 B', pct: '0%' }; + const sign = diff > 0 ? '+' : '-'; + const pct = Math.abs((diff / previous) * 100).toFixed(1); + return { + bytes: `${sign}${formatBytes(Math.abs(diff))}`, + pct: `${sign}${pct}%`, + }; +} + +function statusIcon(current, previous) { + if (previous === undefined) return '๐Ÿ†•'; + const diff = current - previous; + if (diff === 0) return 'โœ…'; + if (diff < 0) return '๐Ÿ”ฝ'; + const pct = (diff / previous) * 100; + return pct > 10 ? '๐Ÿ”ด' : '๐Ÿ”บ'; +} + +function deltaBar(current, previous, maxAbsPct) { + const width = 8; + if (previous === undefined || maxAbsPct === 0) return 'โ–‘'.repeat(width); + const pct = Math.abs(((current - previous) / previous) * 100); + const filled = Math.round((pct / maxAbsPct) * width); + return 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(width - filled); +} + +/** Group entries by package: @videojs/utils/* -> utils, @videojs/store/* -> store */ +function groupByPackage(entries) { + const groups = new Map(); + for (const entry of entries) { + const match = entry.name.match(/^@videojs\/([^/]+)/); + const pkg = match ? match[1] : 'other'; + if (!groups.has(pkg)) groups.set(pkg, []); + groups.get(pkg).push(entry); + } + return groups; +} + +function computePackageData(groups, baseMap) { + const pkgData = []; + let grandTotalCurrent = 0; + let grandTotalBase = 0; + + for (const [pkg, entries] of groups) { + const rootEntries = entries.filter((e) => e.type === 'root'); + const subEntries = entries.filter((e) => e.type === 'subpath'); + + const pkgTotalCurrent = + rootEntries.reduce((s, e) => s + e.size, 0) + + subEntries.reduce((s, e) => s + e.size, 0); + + const pkgTotalBase = + rootEntries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0) + + subEntries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0); + + const hasBase = entries.some((e) => baseMap[e.name] !== undefined); + grandTotalCurrent += pkgTotalCurrent; + grandTotalBase += pkgTotalBase; + + pkgData.push({ + pkg, + entries, + rootEntries, + subEntries, + pkgTotalCurrent, + pkgTotalBase, + hasBase, + }); + } + + return { pkgData, grandTotalCurrent, grandTotalBase }; +} + +/** Generate a comparison report (PR vs base). */ +function generateComparisonReport(current, base) { + const baseMap = Object.fromEntries(base.map((e) => [e.name, e.size])); + const groups = groupByPackage(current); + const { pkgData, grandTotalCurrent, grandTotalBase } = computePackageData( + groups, + baseMap, + ); + + const maxAbsPct = Math.max( + ...pkgData.map((p) => { + if (!p.hasBase || p.pkgTotalBase === 0) return 0; + return Math.abs( + ((p.pkgTotalCurrent - p.pkgTotalBase) / p.pkgTotalBase) * 100, + ); + }), + ); + + // Overview table + const overview = []; + overview.push('| Package | Size | Diff | | % | |'); + overview.push('|---|--:|--:|---|--:|:-:|'); + + for (const p of pkgData) { + const d = formatDelta( + p.pkgTotalCurrent, + p.hasBase ? p.pkgTotalBase : undefined, + ); + const icon = p.hasBase + ? statusIcon(p.pkgTotalCurrent, p.pkgTotalBase) + : ''; + const bar = `\`${deltaBar(p.pkgTotalCurrent, p.hasBase ? p.pkgTotalBase : undefined, maxAbsPct)}\``; + overview.push( + `| **@videojs/${p.pkg}** | **${formatBytes(p.pkgTotalCurrent)}** | ${p.hasBase ? d.bytes : 'โ€”'} | ${bar} | ${p.hasBase ? d.pct : ''} | ${icon} |`, + ); + } + + // Detail sections โ€” only for packages with multiple entries + const details = []; + const pkgsWithSubs = pkgData.filter((p) => p.entries.length > 1); + + if (pkgsWithSubs.length > 0) { + details.push('#### Entry Breakdown'); + details.push(''); + details.push( + 'Subpath sizes are the additional bytes on top of the root entry point, measured by bundling root + subpath together and subtracting the root-only size.', + ); + details.push(''); + + for (const p of pkgsWithSubs) { + const { + pkg, + rootEntries, + subEntries, + pkgTotalCurrent, + pkgTotalBase, + hasBase, + } = p; + + details.push('
'); + details.push(`@videojs/${pkg}`); + details.push(''); + details.push('| Entry | Base | PR | Diff | % | |'); + details.push('|---|--:|--:|--:|--:|:-:|'); + + for (const entry of [...rootEntries, ...subEntries]) { + const displayName = + entry.name.replace(`@videojs/${pkg}`, '') || '.'; + const label = displayName === '.' ? displayName : `.${displayName}`; + const prev = baseMap[entry.name]; + const d = formatDelta(entry.size, prev); + details.push( + `| \`${label}\` | ${prev !== undefined ? formatBytes(prev) : 'โ€”'} | **${formatBytes(entry.size)}** | ${d.bytes} | ${d.pct} | ${statusIcon(entry.size, prev)} |`, + ); + } + + if (rootEntries.length + subEntries.length > 1) { + const d = formatDelta( + pkgTotalCurrent, + hasBase ? pkgTotalBase : undefined, + ); + details.push( + `| **total** | **${hasBase ? formatBytes(pkgTotalBase) : 'โ€”'}** | **${formatBytes(pkgTotalCurrent)}** | **${hasBase ? d.bytes : 'โ€”'}** | **${hasBase ? d.pct : ''}** | |`, + ); + } + + details.push(''); + details.push('
'); + details.push(''); + } + } + + const grandDelta = formatDelta(grandTotalCurrent, grandTotalBase); + + const marker = ''; + return [ + marker, + '### ๐Ÿ“ฆ Bundle Size Report', + '', + ...overview, + '', + `**Total: ${formatBytes(grandTotalCurrent)}**${grandTotalBase ? ` ยท ${grandDelta.bytes} ยท ${grandDelta.pct}` : ''}`, + '', + '---', + '', + ...details, + '---', + '', + '
', + 'โ„น๏ธ How to interpret', + '', + 'Sizes are minified + brotli, measured with esbuild.', + 'Package totals are computed as root size + marginal subpath costs.', + 'Subpath marginal cost = (root + subpath bundled together) โˆ’ root alone.', + '', + '| Icon | Meaning |', + '|---|---|', + '| โœ… | No change |', + '| ๐Ÿ”บ | Increased โ‰ค 10% |', + '| ๐Ÿ”ด | Increased > 10% |', + '| ๐Ÿ”ฝ | Decreased |', + '| ๐Ÿ†• | New (no baseline) |', + '', + 'Run `pnpm size` locally to check current sizes.', + '
', + ].join('\n'); +} + +/** + * Render rows as a padded, aligned table for terminal output. + * + * Each row is an array of cell values. Cells can be plain strings or + * `{ text, style }` objects where `style` is a ansi chain (e.g. ansi.bold). + * Alignment and padding use the plain text width; ANSI codes are ignored. + * + * The first row is treated as a dim header. + */ +function printTable(rows) { + if (rows.length === 0) return ''; + + const text = (cell) => (typeof cell === 'string' ? cell : cell.text); + const style = (cell) => + typeof cell === 'string' ? (s) => s : cell.style ?? ((s) => s); + + const cols = rows[0].length; + const widths = Array.from({ length: cols }, () => 0); + for (const row of rows) { + for (let i = 0; i < cols; i++) { + widths[i] = Math.max(widths[i], text(row[i]).length); + } + } + + const sep = ansi.dim( + `โ”€${widths.map((w) => 'โ”€'.repeat(w)).join('โ”€โ”ผโ”€')}โ”€`, + ); + const lines = []; + + for (let r = 0; r < rows.length; r++) { + const cells = rows[r].map((cell, i) => { + const t = text(cell); + const padded = + i === cols - 1 ? t.padStart(widths[i]) : t.padEnd(widths[i]); + // Header row is dim, data rows use their own style + return r === 0 ? ansi.dim(padded) : style(cell)(padded); + }); + lines.push(` ${cells.join(ansi.dim(' โ”‚ '))} `); + if (r === 0) lines.push(sep); + } + + return lines.join('\n'); +} + +/** Color a size value based on magnitude. */ +function colorSize(bytes) { + const text = formatBytes(bytes); + if (bytes >= 5 * 1024) return { text, style: ansi.yellow }; + if (bytes >= 1024) return { text, style: ansi.white }; + return { text, style: ansi.green }; +} + +/** Generate a local-only report (no base comparison). */ +function generateLocalReport(current) { + const groups = groupByPackage(current); + + const overviewRows = [['Package', 'Size']]; + let grandTotal = 0; + const pkgsWithSubs = []; + + for (const [pkg, entries] of groups) { + const rootEntries = entries.filter((e) => e.type === 'root'); + const subEntries = entries.filter((e) => e.type === 'subpath'); + + const pkgTotal = + rootEntries.reduce((s, e) => s + e.size, 0) + + subEntries.reduce((s, e) => s + e.size, 0); + + grandTotal += pkgTotal; + overviewRows.push([ + { text: `@videojs/${pkg}`, style: ansi.bold }, + colorSize(pkgTotal), + ]); + + if (entries.length > 1) { + pkgsWithSubs.push({ pkg, rootEntries, subEntries, pkgTotal }); + } + } + + const lines = []; + lines.push(''); + lines.push(printTable(overviewRows)); + lines.push(''); + lines.push(ansi.bold(`Total: ${formatBytes(grandTotal)}`)); + + if (pkgsWithSubs.length > 0) { + for (const { pkg, rootEntries, subEntries, pkgTotal } of pkgsWithSubs) { + lines.push(''); + lines.push(ansi.bold(`@videojs/${pkg}`)); + + const rows = [['Entry', 'Size']]; + for (const entry of [...rootEntries, ...subEntries]) { + const displayName = + entry.name.replace(`@videojs/${pkg}`, '') || '.'; + const label = displayName === '.' ? displayName : `.${displayName}`; + rows.push([ + { text: label, style: ansi.cyan }, + colorSize(entry.size), + ]); + } + rows.push([ + { text: 'total', style: ansi.bold }, + { text: formatBytes(pkgTotal), style: ansi.bold }, + ]); + + lines.push(printTable(rows)); + } + } + + lines.push(''); + lines.push(ansi.dim('Sizes are minified + brotli.')); + lines.push(''); + + return lines.join('\n'); +} + +function main() { + const args = process.argv.slice(2); + + const prIndex = args.indexOf('--pr'); + const baseIndex = args.indexOf('--base'); + + if (prIndex === -1) { + // Read from stdin (piped from bundle-size.js) + const input = readFileSync('/dev/stdin', 'utf8'); + const current = JSON.parse(input); + console.log(generateLocalReport(current)); + return; + } + + const prPath = args[prIndex + 1]; + const current = JSON.parse(readFileSync(prPath, 'utf8')); + + if (baseIndex !== -1) { + const basePath = args[baseIndex + 1]; + const base = JSON.parse(readFileSync(basePath, 'utf8')); + console.log(generateComparisonReport(current, base)); + } else { + console.log(generateLocalReport(current)); + } +} + +main(); diff --git a/.github/scripts/bundle-size.js b/.github/scripts/bundle-size.js new file mode 100644 index 00000000..f5c45d5f --- /dev/null +++ b/.github/scripts/bundle-size.js @@ -0,0 +1,177 @@ +/** + * Measures bundle sizes for all packages, computing marginal subpath costs. + * + * Auto-discovers packages from `packages/`, reads their `exports` field to find + * entry points, and externalizes `peerDependencies`. + * + * For packages with subpaths, each subpath is bundled together with the root + * entry point. The marginal cost is: (root + subpath) - root. This captures + * shared minification and compression, avoiding the inflated totals you get + * from summing independently-measured subpaths. + * + * Usage: node .github/scripts/bundle-size.js [--json output.json] + */ + +import { build } from 'esbuild'; +import { brotliCompressSync, constants } from 'node:zlib'; +import { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs'; +import { resolve, dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, '../..'); +const PACKAGES_DIR = join(ROOT, 'packages'); + +const SKIP_PACKAGES = new Set(['sandbox', '__tech-preview__', 'react-native']); + +/** + * @typedef {object} SizeEntry + * @property {string} name + * @property {number} size - Root: brotli size. Subpath: marginal cost over root. + * @property {'root' | 'subpath'} type + */ + +/** Bundle entry points with esbuild and return the minified + brotli size. */ +async function measure(entryPoints, external = []) { + const result = await build({ + entryPoints, + bundle: true, + minify: true, + treeShaking: true, + format: 'esm', + write: false, + metafile: true, + outdir: '/tmp/bundle-size-out', + external, + logLevel: 'silent', + }); + + const code = result.outputFiles.map((f) => f.text).join(''); + const compressed = brotliCompressSync(Buffer.from(code), { + params: { + [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, + }, + }); + + return compressed.length; +} + +/** + * Resolve the `default` condition from an export value. + * Handles both `{ default: "./dist/..." }` objects and plain string values. + */ +function resolveDefault(exportValue) { + if (typeof exportValue === 'string') return exportValue; + if (typeof exportValue === 'object' && exportValue !== null) { + return exportValue.default ?? null; + } + return null; +} + +/** Discover packages and their entry points from the filesystem. */ +function discoverPackages() { + const packages = []; + + for (const dirName of readdirSync(PACKAGES_DIR).sort()) { + if (SKIP_PACKAGES.has(dirName)) continue; + + const pkgJsonPath = join(PACKAGES_DIR, dirName, 'package.json'); + if (!existsSync(pkgJsonPath)) continue; + + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + if (!pkgJson.exports) continue; + + const pkgName = pkgJson.name; + const pkgDir = join(PACKAGES_DIR, dirName); + const external = pkgJson.peerDependencies + ? Object.keys(pkgJson.peerDependencies) + : []; + + let rootPath = null; + const subpaths = []; + + for (const [key, value] of Object.entries(pkgJson.exports)) { + // Skip wildcard exports (side-effect registration files) + if (key.includes('*')) continue; + + const defaultPath = resolveDefault(value); + if (!defaultPath) continue; + + // Skip types-only exports (no runtime code) + if (defaultPath.endsWith('.d.ts')) continue; + + const absolutePath = resolve(pkgDir, defaultPath); + if (!existsSync(absolutePath)) continue; + + // Skip empty files (types-only exports with no runtime code) + if (readFileSync(absolutePath, 'utf8').trim().length === 0) continue; + + if (key === '.') { + rootPath = absolutePath; + } else { + subpaths.push({ + name: `${pkgName}${key.slice(1)}`, + path: absolutePath, + }); + } + } + + if (rootPath) { + // Package with a root export (and optional subpaths) + packages.push({ name: pkgName, rootPath, external, subpaths }); + } else if (subpaths.length > 0) { + // Package with only subpath exports (e.g., @videojs/utils) + // Each subpath is measured as an independent root + for (const sub of subpaths) { + packages.push({ + name: sub.name, + rootPath: sub.path, + external, + subpaths: [], + }); + } + } + } + + return packages; +} + +async function main() { + const packages = discoverPackages(); + + /** @type {SizeEntry[]} */ + const results = []; + + for (const pkg of packages) { + const rootSize = await measure([pkg.rootPath], pkg.external); + results.push({ name: pkg.name, size: rootSize, type: 'root' }); + + for (const sub of pkg.subpaths) { + const combinedSize = await measure( + [pkg.rootPath, sub.path], + pkg.external, + ); + const marginal = combinedSize - rootSize; + + results.push({ name: sub.name, size: marginal, type: 'subpath' }); + } + } + + // Parse --json flag + const jsonIndex = process.argv.indexOf('--json'); + const outputPath = jsonIndex !== -1 ? process.argv[jsonIndex + 1] : null; + + const output = JSON.stringify(results, null, 2); + + if (outputPath) { + writeFileSync(outputPath, output); + console.log(`Written to ${outputPath}`); + } else { + console.log(output); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index b56c0876..434ca8fd 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -9,7 +9,7 @@ permissions: pull-requests: write jobs: - size: + pr-size: runs-on: ubuntu-latest steps: @@ -25,191 +25,96 @@ jobs: node-version: 22 cache: pnpm - - name: Install and build PR + - name: Cache turbo build setup + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - name: Install and build run: pnpm install && pnpm build:packages - - name: Measure PR bundle size - run: pnpm -w exec size-limit --json > /tmp/pr-size.json + - name: Measure bundle size + run: node .github/scripts/bundle-size.js --json pr-size.json - - name: Measure base bundle size + - name: Upload size data + uses: actions/upload-artifact@v4 + with: + name: pr-size + path: pr-size.json + + base-size: + runs-on: ubuntu-latest + + steps: + - name: Checkout base + uses: actions/checkout@v5 + with: + ref: ${{ github.base_ref }} + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: pnpm + + - name: Cache turbo build setup + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.event.pull_request.base.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - name: Install and build run: | - git fetch origin ${{ github.base_ref }} --depth=1 - git checkout -f FETCH_HEAD - pnpm install - if [ -f .size-limit.json ]; then + if [ -f .github/scripts/bundle-size.js ]; then pnpm build:packages - pnpm -w exec size-limit --json > /tmp/base-size.json + node .github/scripts/bundle-size.js --json base-size.json else - echo '[]' > /tmp/base-size.json + echo '[]' > base-size.json fi - - name: Report + - name: Upload size data + uses: actions/upload-artifact@v4 + with: + name: base-size + path: base-size.json + + report: + runs-on: ubuntu-latest + needs: [pr-size, base-size] + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Download size data + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Generate report + run: > + node .github/scripts/bundle-size-report.js + --pr artifacts/pr-size/pr-size.json + --base artifacts/base-size/base-size.json + > report.md + + - name: Post comment uses: actions/github-script@v7 with: script: | const fs = require('fs'); - - const pr = JSON.parse(fs.readFileSync('/tmp/pr-size.json', 'utf8')); - const base = JSON.parse(fs.readFileSync('/tmp/base-size.json', 'utf8')); - - const baseMap = Object.fromEntries(base.map(e => [e.name, e.size])); - - function formatBytes(bytes) { - if (bytes < 1024) return `${bytes} B`; - return `${(bytes / 1024).toFixed(2)} kB`; - } - - function formatDelta(current, previous) { - if (previous === undefined) return { bytes: 'โ€”', pct: '' }; - const diff = current - previous; - if (diff === 0) return { bytes: '0 B', pct: '0%' }; - const sign = diff > 0 ? '+' : '-'; - const pct = Math.abs((diff / previous) * 100).toFixed(1); - return { bytes: `${sign}${formatBytes(Math.abs(diff))}`, pct: `${sign}${pct}%` }; - } - - function statusIcon(current, previous) { - if (previous === undefined) return '๐Ÿ†•'; - const diff = current - previous; - if (diff === 0) return 'โœ…'; - if (diff < 0) return '๐Ÿ”ฝ'; - const pct = (diff / previous) * 100; - return pct > 10 ? '๐Ÿ”ด' : '๐Ÿ”บ'; - } - - // Delta bar: visualizes % change relative to the largest change - function deltaBar(current, previous, maxAbsPct) { - const width = 8; - if (previous === undefined || maxAbsPct === 0) return 'โ–‘'.repeat(width); - const pct = Math.abs(((current - previous) / previous) * 100); - const filled = Math.round((pct / maxAbsPct) * width); - return 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(width - filled); - } - - // Group entries by package: @videojs/utils/* -> utils, @videojs/store/* -> store, etc. - const groups = new Map(); - for (const entry of pr) { - const match = entry.name.match(/^@videojs\/([^/]+)/); - const pkg = match ? match[1] : 'other'; - if (!groups.has(pkg)) groups.set(pkg, []); - groups.get(pkg).push(entry); - } - - // First pass: compute per-package totals - const pkgData = []; - let grandTotalPr = 0; - let grandTotalBase = 0; - - for (const [pkg, entries] of groups) { - const pkgTotalPr = entries.reduce((s, e) => s + e.size, 0); - const pkgTotalBase = entries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0); - const hasBase = entries.some(e => baseMap[e.name] !== undefined); - grandTotalPr += pkgTotalPr; - grandTotalBase += pkgTotalBase; - - const pkgPrev = entries.length > 1 ? pkgTotalBase : baseMap[entries[0].name]; - const pkgCur = entries.length > 1 ? pkgTotalPr : entries[0].size; - const changed = hasBase && pkgCur !== pkgPrev; - - pkgData.push({ pkg, entries, pkgTotalPr, pkgTotalBase, hasBase, pkgPrev, pkgCur, changed }); - } - - const maxAbsPct = Math.max( - ...pkgData.map(p => { - if (!p.hasBase || p.pkgPrev === 0) return 0; - return Math.abs(((p.pkgCur - p.pkgPrev) / p.pkgPrev) * 100); - }) - ); - - // Overview table - const overview = []; - overview.push('| Package | Size | Diff | | % | |'); - overview.push('|---|--:|--:|---|--:|:-:|'); - - for (const p of pkgData) { - const d = formatDelta(p.pkgCur, p.pkgPrev); - const icon = p.hasBase ? statusIcon(p.pkgCur, p.pkgPrev) : ''; - const bar = `\`${deltaBar(p.pkgCur, p.pkgPrev, maxAbsPct)}\``; - overview.push( - `| **@videojs/${p.pkg}** | **${formatBytes(p.pkgCur)}** | ${p.hasBase ? d.bytes : 'โ€”'} | ${bar} | ${p.hasBase ? d.pct : ''} | ${icon} |` - ); - } - - // Detail sections - const details = []; - details.push('#### Subpath Breakdown'); - details.push(''); - - for (const p of pkgData) { - const { pkg, entries, pkgTotalPr, pkgTotalBase, hasBase } = p; - - // Subpath display: @videojs/utils/dom -> ./dom, @videojs/store -> . - const displayName = (name) => { - const sub = name.replace(`@videojs/${pkg}`, ''); - return sub ? `.${sub}` : '.'; - }; - - details.push(`
`); - details.push(`@videojs/${pkg}`); - details.push(''); - details.push('| Subpath | Base | PR | Diff | % | |'); - details.push('|---|--:|--:|--:|--:|:-:|'); - - for (const entry of entries) { - const prev = baseMap[entry.name]; - const d = formatDelta(entry.size, prev); - details.push( - `| \`${displayName(entry.name)}\` | ${prev !== undefined ? formatBytes(prev) : 'โ€”'} | **${formatBytes(entry.size)}** | ${d.bytes} | ${d.pct} | ${statusIcon(entry.size, prev)} |` - ); - } - - if (entries.length > 1) { - const d = formatDelta(pkgTotalPr, pkgTotalBase); - details.push( - `| **total** | **${hasBase ? formatBytes(pkgTotalBase) : 'โ€”'}** | **${formatBytes(pkgTotalPr)}** | **${hasBase ? d.bytes : 'โ€”'}** | **${hasBase ? d.pct : ''}** | |` - ); - } - - details.push(''); - details.push('
'); - details.push(''); - } - - const grandDelta = formatDelta(grandTotalPr, grandTotalBase); - + const body = fs.readFileSync('report.md', 'utf8'); const marker = ''; - const body = [ - marker, - '### ๐Ÿ“ฆ Bundle Size Report', - '', - ...overview, - '', - `**Total: ${formatBytes(grandTotalPr)}**${grandTotalBase ? ` ยท ${grandDelta.bytes} ยท ${grandDelta.pct}` : ''}`, - '', - '---', - '', - ...details, - '---', - '', - '
', - 'โ„น๏ธ How to interpret', - '', - 'Each package shows its own code size with workspace and peer dependencies externalized.', - 'Sizes are minified + brotli, measured via [size-limit](https://github.com/ai/size-limit) with esbuild.', - '', - '| Icon | Meaning |', - '|---|---|', - '| โœ… | No change |', - '| ๐Ÿ”บ | Increased โ‰ค 10% |', - '| ๐Ÿ”ด | Increased > 10% |', - '| ๐Ÿ”ฝ | Decreased |', - '| ๐Ÿ†• | New (no baseline) |', - '', - 'Run `pnpm size` locally to check current sizes.', - '
', - ].join('\n'); const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5dee1da3..3af1a0a1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -55,6 +55,15 @@ jobs: if: ${{ steps.release.outputs.releases_created == 'true' }} run: pnpm i --frozen-lockfile + - name: Cache turbo build setup + if: ${{ steps.release.outputs.releases_created == 'true' }} + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + - name: Clean if: ${{ steps.release.outputs.releases_created == 'true' }} run: pnpm clean diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2daff849..77b634e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,5 +28,13 @@ jobs: - name: Install dependencies run: pnpm install + - name: Cache turbo build setup + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + - name: Build run: pnpm build:packages diff --git a/.github/workflows/website-tests.yml b/.github/workflows/website-tests.yml index bb300fa7..c6600c43 100644 --- a/.github/workflows/website-tests.yml +++ b/.github/workflows/website-tests.yml @@ -30,5 +30,13 @@ jobs: - name: Install dependencies run: pnpm install + - name: Cache turbo build setup + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + - name: Run website tests run: pnpm --filter site test diff --git a/.size-limit.json b/.size-limit.json deleted file mode 100644 index 2f505b5e..00000000 --- a/.size-limit.json +++ /dev/null @@ -1,87 +0,0 @@ -[ - { - "name": "@videojs/element", - "path": "packages/element/dist/default/index.js", - "import": "*" - }, - { - "name": "@videojs/element/context", - "path": "packages/element/dist/default/context.js", - "import": "*" - }, - { - "name": "@videojs/store", - "path": "packages/store/dist/default/index.js", - "import": "*" - }, - { - "name": "@videojs/store/html", - "path": "packages/store/dist/default/html.js", - "import": "*", - "ignore": ["@videojs/utils", "@videojs/element"] - }, - { - "name": "@videojs/store/react", - "path": "packages/store/dist/default/react.js", - "import": "*", - "ignore": ["@videojs/utils", "react"] - }, - { - "name": "@videojs/core", - "path": "packages/core/dist/default/index.js", - "import": "*", - "ignore": ["@videojs/utils", "@videojs/store"] - }, - { - "name": "@videojs/core/dom", - "path": "packages/core/dist/default/dom.js", - "import": "*", - "ignore": ["@videojs/utils", "@videojs/store"] - }, - { - "name": "@videojs/html", - "path": "packages/html/dist/default/index.js", - "import": "*" - }, - { - "name": "@videojs/react", - "path": "packages/react/dist/default/index.js", - "import": "*", - "ignore": ["react"] - }, - { - "name": "@videojs/utils/array", - "path": "packages/utils/dist/array.js", - "import": "*" - }, - { - "name": "@videojs/utils/dom", - "path": "packages/utils/dist/dom.js", - "import": "*" - }, - { - "name": "@videojs/utils/events", - "path": "packages/utils/dist/events.js", - "import": "*" - }, - { - "name": "@videojs/utils/function", - "path": "packages/utils/dist/function.js", - "import": "*" - }, - { - "name": "@videojs/utils/object", - "path": "packages/utils/dist/object.js", - "import": "*" - }, - { - "name": "@videojs/utils/predicate", - "path": "packages/utils/dist/predicate.js", - "import": "*" - }, - { - "name": "@videojs/utils/time", - "path": "packages/utils/dist/time.js", - "import": "*" - } -] diff --git a/package.json b/package.json index 6e4ea38d..e84bdc71 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "lint:fix:file": "biome check --write", "link:opencode": "[ -e .opencode ] || ln -s .claude .opencode", "link:agents": "[ -e AGENTS.md ] || ln -s CLAUDE.md AGENTS.md; [ -e agents ] || ln -s .claude agents", - "size": "size-limit", + "size": "node .github/scripts/bundle-size.js | node .github/scripts/bundle-size-report.js", "test": "turbo run test", "typecheck": "tsc --build" }, @@ -39,15 +39,13 @@ "@commitlint/cli": "^20.1.0", "@commitlint/config-conventional": "^20.0.0", "@commitlint/format": "^20.0.0", - "@size-limit/esbuild": "^12.0.0", - "@size-limit/file": "^12.0.0", "@types/node": "^22.18.6", + "esbuild": "^0.27.3", "lint-staged": "^16.2.3", "react": "^18.0.0", "react-compiler-runtime": "^1.0.0", "react-dom": "^18.0.0", "simple-git-hooks": "^2.13.1", - "size-limit": "^12.0.0", "tsx": "^4.21.0", "turbo": "^2.5.8", "typescript": "^5.9.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55f36b26..cd627781 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,15 +20,12 @@ importers: '@commitlint/format': specifier: ^20.0.0 version: 20.2.0 - '@size-limit/esbuild': - specifier: ^12.0.0 - version: 12.0.0(size-limit@12.0.0(jiti@2.6.1)) - '@size-limit/file': - specifier: ^12.0.0 - version: 12.0.0(size-limit@12.0.0(jiti@2.6.1)) '@types/node': specifier: ^22.18.6 version: 22.19.3 + esbuild: + specifier: ^0.27.3 + version: 0.27.3 lint-staged: specifier: ^16.2.3 version: 16.2.7 @@ -44,9 +41,6 @@ importers: simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 - size-limit: - specifier: ^12.0.0 - version: 12.0.0(jiti@2.6.1) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -1009,6 +1003,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -1021,6 +1021,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -1033,6 +1039,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -1045,6 +1057,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -1057,6 +1075,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -1069,6 +1093,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -1081,6 +1111,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -1093,6 +1129,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -1105,6 +1147,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -1117,6 +1165,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -1129,6 +1183,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -1141,6 +1201,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -1153,6 +1219,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -1165,6 +1237,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -1177,6 +1255,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -1189,6 +1273,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -1201,6 +1291,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -1213,6 +1309,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -1225,6 +1327,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -1237,6 +1345,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -1249,6 +1363,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -1261,6 +1381,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -1273,6 +1399,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -1285,6 +1417,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -1297,6 +1435,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -1309,6 +1453,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} @@ -2522,18 +2672,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@size-limit/esbuild@12.0.0': - resolution: {integrity: sha512-r9i+HrtunIu7wAPtqD3t4DqfYin3kxPoMAv8cidkzlCS69IYCe3EG2UbQa10AdvQyaHTEK+MPkr9ifUd3W29og==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - size-limit: 12.0.0 - - '@size-limit/file@12.0.0': - resolution: {integrity: sha512-OzKYpDzWJ2jo6cAIzVsaPuvzZTmMLDoVCViEvsctmImxpXzwJZcuBEpPohFKKdgVdZuNTU8WstmvywPq55Njdw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - size-limit: 12.0.0 - '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -3308,10 +3446,6 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bytes-iec@3.1.1: - resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} - engines: {node: '>= 0.8'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -3928,6 +4062,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -4744,10 +4883,6 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5172,14 +5307,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} - engines: {node: ^18 || >=20} - hasBin: true - - nanospinner@1.2.2: - resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} - nanostores@1.1.0: resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} engines: {node: ^20.0.0 || >=22.0.0} @@ -5929,16 +6056,6 @@ packages: engines: {node: '>=14.0.0', npm: '>=6.0.0'} hasBin: true - size-limit@12.0.0: - resolution: {integrity: sha512-JBG8dioIs0m2kHOhs9jD6E/tZKD08vmbf2bfqj/rJyNWqJxk/ZcakixjhYtsqdbi+AKVbfPkt3g2RRZiKaizYA==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - jiti: ^2.0.0 - peerDependenciesMeta: - jiti: - optional: true - slashes@3.0.12: resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} @@ -7626,156 +7743,234 @@ snapshots: '@esbuild/aix-ppc64@0.27.2': optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm64@0.27.2': optional: true + '@esbuild/android-arm64@0.27.3': + optional: true + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-arm@0.27.2': optional: true + '@esbuild/android-arm@0.27.3': + optional: true + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/android-x64@0.27.2': optional: true + '@esbuild/android-x64@0.27.3': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true + '@esbuild/darwin-arm64@0.27.3': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/darwin-x64@0.27.2': optional: true + '@esbuild/darwin-x64@0.27.3': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true + '@esbuild/freebsd-arm64@0.27.3': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true + '@esbuild/freebsd-x64@0.27.3': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm64@0.27.2': optional: true + '@esbuild/linux-arm64@0.27.3': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-arm@0.27.2': optional: true + '@esbuild/linux-arm@0.27.3': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-ia32@0.27.2': optional: true + '@esbuild/linux-ia32@0.27.3': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-loong64@0.27.2': optional: true + '@esbuild/linux-loong64@0.27.3': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true + '@esbuild/linux-mips64el@0.27.3': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true + '@esbuild/linux-ppc64@0.27.3': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true + '@esbuild/linux-riscv64@0.27.3': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.2': optional: true + '@esbuild/linux-s390x@0.27.3': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/linux-x64@0.27.2': optional: true + '@esbuild/linux-x64@0.27.3': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true '@esbuild/netbsd-arm64@0.27.2': optional: true + '@esbuild/netbsd-arm64@0.27.3': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true '@esbuild/netbsd-x64@0.27.2': optional: true + '@esbuild/netbsd-x64@0.27.3': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true '@esbuild/openbsd-arm64@0.27.2': optional: true + '@esbuild/openbsd-arm64@0.27.3': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true '@esbuild/openbsd-x64@0.27.2': optional: true + '@esbuild/openbsd-x64@0.27.3': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true '@esbuild/openharmony-arm64@0.27.2': optional: true + '@esbuild/openharmony-arm64@0.27.3': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/sunos-x64@0.27.2': optional: true + '@esbuild/sunos-x64@0.27.3': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-arm64@0.27.2': optional: true + '@esbuild/win32-arm64@0.27.3': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-ia32@0.27.2': optional: true + '@esbuild/win32-ia32@0.27.3': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true '@esbuild/win32-x64@0.27.2': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@fastify/accept-negotiator@2.0.1': {} '@fastify/busboy@3.2.0': {} @@ -9135,16 +9330,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@size-limit/esbuild@12.0.0(size-limit@12.0.0(jiti@2.6.1))': - dependencies: - esbuild: 0.27.2 - nanoid: 5.1.6 - size-limit: 12.0.0(jiti@2.6.1) - - '@size-limit/file@12.0.0(size-limit@12.0.0(jiti@2.6.1))': - dependencies: - size-limit: 12.0.0(jiti@2.6.1) - '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -9714,7 +9899,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) '@vitest/utils@3.2.4': dependencies: @@ -10165,8 +10350,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bytes-iec@3.1.1: {} - cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -10768,6 +10951,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} escape-string-regexp@5.0.0: {} @@ -11688,8 +11900,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 - lilconfig@3.1.3: {} - lines-and-columns@1.2.4: {} lint-staged@16.2.7: @@ -12371,12 +12581,6 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.6: {} - - nanospinner@1.2.2: - dependencies: - picocolors: 1.1.1 - nanostores@1.1.0: {} neotraverse@0.6.18: {} @@ -13243,16 +13447,6 @@ snapshots: arg: 5.0.2 sax: 1.4.3 - size-limit@12.0.0(jiti@2.6.1): - dependencies: - bytes-iec: 3.1.1 - lilconfig: 3.1.3 - nanospinner: 1.2.2 - picocolors: 1.1.1 - tinyglobby: 0.2.15 - optionalDependencies: - jiti: 2.6.1 - slashes@3.0.12: {} slice-ansi@7.1.2: @@ -13600,7 +13794,7 @@ snapshots: tsx@4.21.0: dependencies: - esbuild: 0.27.2 + esbuild: 0.27.3 get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3